diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 5f4e42023..63baba850 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -47,10 +47,12 @@ "@cloudflare/workers-types": ">=3", "@libsql/client": "*", "@neondatabase/serverless": ">=0.1", + "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", + "@vercel/postgres": "*", "better-sqlite3": ">=7", "bun-types": "*", "knex": "*", @@ -59,8 +61,7 @@ "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", - "sqlite3": ">=5", - "@vercel/postgres": "*" + "sqlite3": ">=5" }, "peerDependenciesMeta": { "mysql2": { @@ -116,6 +117,9 @@ }, "@libsql/client": { "optional": true + }, + "@opentelemetry/api": { + "optional": true } }, "devDependencies": { @@ -123,9 +127,11 @@ "@cloudflare/workers-types": "^4.20230321.0", "@libsql/client": "^0.1.1", "@neondatabase/serverless": "^0.2.9", + "@opentelemetry/api": "^1.4.1", "@originjs/vite-plugin-commonjs": "^1.0.3", "@planetscale/database": "^1.5.0", "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-typescript": "^11.1.0", "@types/better-sqlite3": "^7.6.2", "@types/node": "^18.15.11", @@ -134,6 +140,7 @@ "@vercel/postgres": "^0.1.2", "better-sqlite3": "^8.0.0", "bun-types": "^0.5.8", + "concurrently": "^8.0.1", "knex": "^2.4.2", "kysely": "^0.24.2", "mysql2": "^3.2.0", diff --git a/drizzle-orm/rollup.config.ts b/drizzle-orm/rollup.cjs.config.ts similarity index 58% rename from drizzle-orm/rollup.config.ts rename to drizzle-orm/rollup.cjs.config.ts index 2b35c2432..1e5c4c8a8 100644 --- a/drizzle-orm/rollup.config.ts +++ b/drizzle-orm/rollup.cjs.config.ts @@ -1,4 +1,5 @@ import json from '@rollup/plugin-json'; +import replace from '@rollup/plugin-replace'; import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; import { entries, external } from './rollup.common'; @@ -11,29 +12,25 @@ export default defineConfig([ acc[to] = from; return acc; }, {}), - output: [ - { - format: 'esm', - dir: 'dist.new', - entryFileNames: '[name].mjs', - chunkFileNames: '[name]-[hash].mjs', - sourcemap: true, - }, - { - format: 'cjs', - dir: 'dist.new', - entryFileNames: '[name].cjs', - chunkFileNames: '[name]-[hash].cjs', - sourcemap: true, - }, - ], + output: { + format: 'cjs', + dir: 'dist.new', + entryFileNames: '[name].cjs', + chunkFileNames: '[name]-[hash].cjs', + sourcemap: true, + }, external, plugins: [ + replace({ + 'await import': 'require', + preventAssignment: true, + }), json({ preferConst: true, }), typescript({ - tsconfig: 'tsconfig.build.json', + tsconfig: 'tsconfig.cjs.json', + outputToFilesystem: true, }), ], }, diff --git a/drizzle-orm/rollup.common.ts b/drizzle-orm/rollup.common.ts index 24493c265..5b11aaf40 100644 --- a/drizzle-orm/rollup.common.ts +++ b/drizzle-orm/rollup.common.ts @@ -51,4 +51,6 @@ export const external = [ 'postgres', 'sqlite3', 'bun:sqlite', + '@opentelemetry/api', + '@vercel/postgres', ]; diff --git a/drizzle-orm/rollup.esm.config.ts b/drizzle-orm/rollup.esm.config.ts new file mode 100644 index 000000000..a14dc78ed --- /dev/null +++ b/drizzle-orm/rollup.esm.config.ts @@ -0,0 +1,32 @@ +import json from '@rollup/plugin-json'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; +import { entries, external } from './rollup.common'; + +export default defineConfig([ + { + input: entries.reduce>((acc, entry) => { + const from = 'src/' + entry + '.ts'; + const to = entry; + acc[to] = from; + return acc; + }, {}), + output: { + format: 'esm', + dir: 'dist.new', + entryFileNames: '[name].mjs', + chunkFileNames: '[name]-[hash].mjs', + sourcemap: true, + }, + external, + plugins: [ + json({ + preferConst: true, + }), + typescript({ + tsconfig: 'tsconfig.esm.json', + outputToFilesystem: true, + }), + ], + }, +]); diff --git a/drizzle-orm/scripts/build.ts b/drizzle-orm/scripts/build.ts index 67b559668..cb53feec3 100755 --- a/drizzle-orm/scripts/build.ts +++ b/drizzle-orm/scripts/build.ts @@ -1,5 +1,6 @@ #!/usr/bin/env -S pnpm tsx import 'zx/globals'; +import concurrently from 'concurrently'; import { entries } from '../rollup.common'; function updateAndCopyPackageJson() { @@ -11,7 +12,12 @@ function updateAndCopyPackageJson() { const importEntry = `./${entry}.mjs`; const requireEntry = `./${entry}.cjs`; const typesEntry = `./${entry}.d.ts`; - acc[exportsEntry] = { import: importEntry, require: requireEntry, default: importEntry, types: typesEntry }; + acc[exportsEntry] = { + types: typesEntry, + import: importEntry, + require: requireEntry, + default: importEntry, + }; return acc; }, {}, @@ -20,11 +26,23 @@ function updateAndCopyPackageJson() { fs.writeJSONSync('dist.new/package.json', pkg, { spaces: 2 }); } -await $`rollup --config rollup.config.ts --configPlugin typescript`; -fs.copySync('../README.md', 'dist/README.md'); +await concurrently([ + { + command: 'rollup --config rollup.cjs.config.ts --configPlugin typescript', + name: 'cjs', + }, + { + command: 'rollup --config rollup.esm.config.ts --configPlugin typescript', + name: 'esm', + }, + { + command: `tsc -p tsconfig.esm.json --declaration --outDir dist-dts --emitDeclarationOnly && +resolve-tspaths --out dist-dts && +rollup --config rollup.dts.config.ts --configPlugin typescript`, + name: 'dts', + }, +]).result; +fs.copySync('../README.md', 'dist.new/README.md'); updateAndCopyPackageJson(); -await $`tsc -p tsconfig.build.json --declaration --outDir dist-dts --emitDeclarationOnly`; -await $`resolve-tspaths --out dist-dts`; -await $`rollup --config rollup.dts.config.ts --configPlugin typescript`; fs.removeSync('dist'); fs.renameSync('dist.new', 'dist'); diff --git a/drizzle-orm/src/node-postgres/session.ts b/drizzle-orm/src/node-postgres/session.ts index a116926dc..0a5f1cc34 100644 --- a/drizzle-orm/src/node-postgres/session.ts +++ b/drizzle-orm/src/node-postgres/session.ts @@ -8,6 +8,7 @@ import type { PgTransactionConfig, PreparedQueryConfig, QueryResultHKT } from '~ import { PgSession, PreparedQuery } from '~/pg-core/session'; import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations'; import { fillPlaceholders, type Query, sql } from '~/sql'; +import { tracer } from '~/tracing'; import { type Assume, mapResultRow } from '~/utils'; const { Pool } = pg; @@ -40,32 +41,53 @@ export class NodePgPreparedQuery extends Prepared } async execute(placeholderValues: Record | undefined = {}): Promise { - const params = fillPlaceholders(this.params, placeholderValues); - - this.logger.logQuery(this.rawQuery.text, params); - - const { fields, rawQuery, client, query, joinsNotNullableMap, customResultMapper } = this; - if (!fields && !customResultMapper) { - return client.query(rawQuery, params); - } - - const result = await client.query(query, params); + return tracer.startActiveSpan('drizzle.execute', async () => { + const params = fillPlaceholders(this.params, placeholderValues); + + this.logger.logQuery(this.rawQuery.text, params); + + const { fields, rawQuery, client, query, joinsNotNullableMap, customResultMapper } = this; + if (!fields && !customResultMapper) { + return tracer.startActiveSpan('drizzle.driver.execute', async (span) => { + span?.setAttributes({ + 'drizzle.query.name': rawQuery.name, + 'drizzle.query.text': rawQuery.text, + 'drizzle.query.params': JSON.stringify(params), + }); + return client.query(rawQuery, params); + }); + } - return customResultMapper - ? customResultMapper(result.rows) - : result.rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + const result = await tracer.startActiveSpan('drizzle.driver.execute', (span) => { + span?.setAttributes({ + 'drizzle.query.name': query.name, + 'drizzle.query.text': query.text, + 'drizzle.query.params': JSON.stringify(params), + }); + return client.query(query, params); + }); + + return tracer.startActiveSpan('drizzle.mapResponse', () => { + return customResultMapper + ? customResultMapper(result.rows) + : result.rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + }); + }); } all(placeholderValues: Record | undefined = {}): Promise { - const params = fillPlaceholders(this.params, placeholderValues); - this.logger.logQuery(this.rawQuery.text, params); - return this.client.query(this.rawQuery, params).then((result) => result.rows); - } - - values(placeholderValues: Record | undefined = {}): Promise { - const params = fillPlaceholders(this.params, placeholderValues); - this.logger.logQuery(this.rawQuery.text, params); - return this.client.query(this.query, params).then((result) => result.rows); + return tracer.startActiveSpan('drizzle.execute', () => { + const params = fillPlaceholders(this.params, placeholderValues); + this.logger.logQuery(this.rawQuery.text, params); + return tracer.startActiveSpan('drizzle.driver.execute', (span) => { + span?.setAttributes({ + 'drizzle.query.name': this.rawQuery.name, + 'drizzle.query.text': this.rawQuery.text, + 'drizzle.query.params': JSON.stringify(params), + }); + return this.client.query(this.rawQuery, params).then((result) => result.rows); + }); + }); } } @@ -98,23 +120,6 @@ export class NodePgSession< return new NodePgPreparedQuery(this.client, query.sql, query.params, this.logger, fields, name, customResultMapper); } - async query(query: string, params: unknown[]): Promise { - this.logger.logQuery(query, params); - const result = await this.client.query({ - rowMode: 'array', - text: query, - values: params, - }); - return result; - } - - async queryObjects( - query: string, - params: unknown[], - ): Promise> { - return this.client.query(query, params); - } - override async transaction( transaction: (tx: NodePgTransaction) => Promise, config?: PgTransactionConfig | undefined, diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index e88e7290d..6403c8a39 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -5,6 +5,7 @@ import type { SelectResultFields } from '~/query-builders/select.types'; import { QueryPromise } from '~/query-promise'; import type { Query, SQL, SQLWrapper } from '~/sql'; import { type InferModel, Table } from '~/table'; +import { tracer } from '~/tracing'; import { orderSelectedFields, type Simplify } from '~/utils'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types'; @@ -69,7 +70,13 @@ export class PgDelete< execute: TReturning extends undefined ? QueryResultKind : TReturning[]; } > { - return this.session.prepareQuery(this.dialect.sqlToQuery(this.getSQL()), this.config.returning, name); + return tracer.startActiveSpan('drizzle.prepareQuery', () => { + return this.session.prepareQuery< + PreparedQueryConfig & { + execute: TReturning extends undefined ? QueryResultKind : TReturning[]; + } + >(this.dialect.sqlToQuery(this.getSQL()), this.config.returning, name); + }); } prepare(name: string): PreparedQuery< @@ -81,6 +88,8 @@ export class PgDelete< } override execute: ReturnType['execute'] = (placeholderValues) => { - return this._prepare().execute(placeholderValues); + return tracer.startActiveSpan('drizzle.operation', () => { + return this._prepare().execute(placeholderValues); + }); }; } diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index a03510d44..eef5f6465 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -7,6 +7,7 @@ import { QueryPromise } from '~/query-promise'; import type { Placeholder, Query, SQLWrapper } from '~/sql'; import { Param, SQL, sql } from '~/sql'; import { type InferModel, Table } from '~/table'; +import { tracer } from '~/tracing'; import type { Simplify } from '~/utils'; import { mapUpdateSet, orderSelectedFields } from '~/utils'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types'; @@ -143,7 +144,13 @@ export class PgInsert< execute: TReturning extends undefined ? QueryResultKind : TReturning[]; } > { - return this.session.prepareQuery(this.dialect.sqlToQuery(this.getSQL()), this.config.returning, name); + return tracer.startActiveSpan('drizzle.prepareQuery', () => { + return this.session.prepareQuery< + PreparedQueryConfig & { + execute: TReturning extends undefined ? QueryResultKind : TReturning[]; + } + >(this.dialect.sqlToQuery(this.getSQL()), this.config.returning, name); + }); } prepare(name: string): PreparedQuery< @@ -155,6 +162,8 @@ export class PgInsert< } override execute: ReturnType['execute'] = (placeholderValues) => { - return this._prepare().execute(placeholderValues); + return tracer.startActiveSpan('drizzle.operation', () => { + return this._prepare().execute(placeholderValues); + }); }; } diff --git a/drizzle-orm/src/pg-core/query-builders/query.ts b/drizzle-orm/src/pg-core/query-builders/query.ts index 7d018b500..12663d68d 100644 --- a/drizzle-orm/src/pg-core/query-builders/query.ts +++ b/drizzle-orm/src/pg-core/query-builders/query.ts @@ -7,6 +7,7 @@ import { type TablesRelationalConfig, } from '~/relations'; import { type SQL } from '~/sql'; +import { tracer } from '~/tracing'; import { type KnownKeysOnly } from '~/utils'; import { type PgDialect } from '../dialect'; import { type PgSession, type PreparedQuery, type PreparedQueryConfig } from '../session'; @@ -74,31 +75,33 @@ export class PgRelationalQuery extends QueryPromise { } private _prepare(name?: string): PreparedQuery { - const query = this.dialect.buildRelationalQuery( - this.fullSchema, - this.schema, - this.tableNamesMap, - this.table, - this.tableConfig, - this.config, - this.tableConfig.tsName, - [], - true, - ); + return tracer.startActiveSpan('drizzle.prepareQuery', () => { + const query = this.dialect.buildRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.config, + this.tableConfig.tsName, + [], + true, + ); - const builtQuery = this.dialect.sqlToQuery(query.sql as SQL); - return this.session.prepareQuery( - builtQuery, - undefined, - name, - (rawRows) => { - const rows = rawRows.map((row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection)); - if (this.mode === 'first') { - return rows[0] as TResult; - } - return rows as TResult; - }, - ); + const builtQuery = this.dialect.sqlToQuery(query.sql as SQL); + return this.session.prepareQuery( + builtQuery, + undefined, + name, + (rawRows) => { + const rows = rawRows.map((row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection)); + if (this.mode === 'first') { + return rows[0] as TResult; + } + return rows as TResult; + }, + ); + }); } prepare(name: string): PreparedQuery { @@ -106,6 +109,8 @@ export class PgRelationalQuery extends QueryPromise { } override execute(): Promise { - return this._prepare().execute(); + return tracer.startActiveSpan('drizzle.operation', () => { + return this._prepare().execute(); + }); } } diff --git a/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts b/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts index 6078ad2df..030c30704 100644 --- a/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts +++ b/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts @@ -3,6 +3,7 @@ import type { PgSession, PreparedQuery, PreparedQueryConfig, QueryResultHKT, Que import type { PgMaterializedView } from '~/pg-core/view'; import { QueryPromise } from '~/query-promise'; import type { Query, SQL } from '~/sql'; +import { tracer } from '~/tracing'; import type { Simplify } from '~/utils'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -59,7 +60,9 @@ export class PgRefreshMaterializedView execute: QueryResultKind; } > { - return this.session.prepareQuery(this.dialect.sqlToQuery(this.getSQL()), undefined, name); + return tracer.startActiveSpan('drizzle.prepareQuery', () => { + return this.session.prepareQuery(this.dialect.sqlToQuery(this.getSQL()), undefined, name); + }); } prepare(name: string): PreparedQuery< @@ -71,6 +74,8 @@ export class PgRefreshMaterializedView } execute: ReturnType['execute'] = (placeholderValues) => { - return this._prepare().execute(placeholderValues); + return tracer.startActiveSpan('drizzle.operation', () => { + return this._prepare().execute(placeholderValues); + }); }; } diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index a884296d2..efc8bf5fe 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -18,6 +18,7 @@ import { QueryPromise } from '~/query-promise'; import { type Placeholder, type Query, SQL } from '~/sql'; import { SelectionProxyHandler, Subquery, SubqueryConfig } from '~/subquery'; import { Table } from '~/table'; +import { tracer } from '~/tracing'; import { applyMixins, getTableColumns, getTableLikeName, type Simplify, type ValueOrArray } from '~/utils'; import { orderSelectedFields } from '~/utils'; import { type ColumnsSelection, View, ViewBaseConfig } from '~/view'; @@ -451,15 +452,18 @@ export class PgSelect< execute: SelectResult[]; } > { - if (!this.session) { + const { session, config, dialect, joinsNotNullableMap } = this; + if (!session) { throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } - const fieldsList = orderSelectedFields(this.config.fields); - const query = this.session.prepareQuery< - PreparedQueryConfig & { execute: SelectResult[] } - >(this.dialect.sqlToQuery(this.getSQL()), fieldsList, name); - query.joinsNotNullableMap = this.joinsNotNullableMap; - return query; + return tracer.startActiveSpan('drizzle.prepareQuery', () => { + const fieldsList = orderSelectedFields(config.fields); + const query = session.prepareQuery< + PreparedQueryConfig & { execute: SelectResult[] } + >(dialect.sqlToQuery(this.getSQL()), fieldsList, name); + query.joinsNotNullableMap = joinsNotNullableMap; + return query; + }); } /** @@ -478,7 +482,9 @@ export class PgSelect< } execute: ReturnType['execute'] = (placeholderValues) => { - return this._prepare().execute(placeholderValues); + return tracer.startActiveSpan('drizzle.operation', () => { + return this._prepare().execute(placeholderValues); + }); }; } diff --git a/drizzle-orm/src/pg-core/session.ts b/drizzle-orm/src/pg-core/session.ts index 6a194b936..78fc69f11 100644 --- a/drizzle-orm/src/pg-core/session.ts +++ b/drizzle-orm/src/pg-core/session.ts @@ -1,6 +1,7 @@ import { TransactionRollbackError } from '~/errors'; import { type TablesRelationalConfig } from '~/relations'; import { type Query, type SQL, sql } from '~/sql'; +import { tracer } from '~/tracing'; import { PgDatabase } from './db'; import type { PgDialect } from './dialect'; import type { SelectedFieldsOrdered } from './query-builders/select.types'; @@ -19,9 +20,6 @@ export abstract class PreparedQuery { /** @internal */ abstract all(placeholderValues?: Record): Promise; - - /** @internal */ - abstract values(placeholderValues?: Record): Promise; } export interface PgTransactionConfig { @@ -45,11 +43,17 @@ export abstract class PgSession< ): PreparedQuery; execute(query: SQL): Promise { - return this.prepareQuery( - this.dialect.sqlToQuery(query), - undefined, - undefined, - ).execute(); + return tracer.startActiveSpan('drizzle.operation', () => { + const prepared = tracer.startActiveSpan('drizzle.prepareQuery', () => { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + undefined, + ); + }); + + return prepared.execute(); + }); } all(query: SQL): Promise { @@ -60,14 +64,6 @@ export abstract class PgSession< ).all(); } - values(query: SQL): Promise { - return this.prepareQuery( - this.dialect.sqlToQuery(query), - undefined, - undefined, - ).values(); - } - abstract transaction( transaction: (tx: PgTransaction) => Promise, config?: PgTransactionConfig, diff --git a/drizzle-orm/src/postgres-js/session.ts b/drizzle-orm/src/postgres-js/session.ts index 67293df18..362f0e020 100644 --- a/drizzle-orm/src/postgres-js/session.ts +++ b/drizzle-orm/src/postgres-js/session.ts @@ -8,6 +8,7 @@ import type { PgTransactionConfig, PreparedQueryConfig, QueryResultHKT } from '~ import { PgSession, PreparedQuery } from '~/pg-core/session'; import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations'; import { fillPlaceholders, type Query } from '~/sql'; +import { tracer } from '~/tracing'; import { type Assume, mapResultRow } from '~/utils'; export class PostgresJsPreparedQuery extends PreparedQuery { @@ -23,32 +24,56 @@ export class PostgresJsPreparedQuery extends Prep } async execute(placeholderValues: Record | undefined = {}): Promise { - const params = fillPlaceholders(this.params, placeholderValues); + return tracer.startActiveSpan('drizzle.execute', async (span) => { + const params = fillPlaceholders(this.params, placeholderValues); - this.logger.logQuery(this.query, params); + span?.setAttributes({ + 'drizzle.query.text': this.query, + 'drizzle.query.params': JSON.stringify(params), + }); - const { fields, query, client, joinsNotNullableMap, customResultMapper } = this; - if (!fields && !customResultMapper) { - return client.unsafe(query, params as any[]); - } + this.logger.logQuery(this.query, params); - const rows = await client.unsafe(query, params as any[]).values(); + const { fields, query, client, joinsNotNullableMap, customResultMapper } = this; + if (!fields && !customResultMapper) { + return tracer.startActiveSpan('drizzle.driver.execute', () => { + return client.unsafe(query, params as any[]); + }); + } - return customResultMapper - ? customResultMapper(rows) - : rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + const rows = await tracer.startActiveSpan('drizzle.driver.execute', () => { + span?.setAttributes({ + 'drizzle.query.text': query, + 'drizzle.query.params': JSON.stringify(params), + }); + + return client.unsafe(query, params as any[]).values(); + }); + + return tracer.startActiveSpan('drizzle.mapResponse', () => { + return customResultMapper + ? customResultMapper(rows) + : rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + }); + }); } all(placeholderValues: Record | undefined = {}): Promise { - const params = fillPlaceholders(this.params, placeholderValues); - this.logger.logQuery(this.query, params); - return this.client.unsafe(this.query, params as any[]); - } - - values(placeholderValues: Record | undefined = {}): Promise { - const params = fillPlaceholders(this.params, placeholderValues); - this.logger.logQuery(this.query, params); - return this.client.unsafe(this.query, params as any[]).values(); + return tracer.startActiveSpan('drizzle.execute', async (span) => { + const params = fillPlaceholders(this.params, placeholderValues); + span?.setAttributes({ + 'drizzle.query.text': this.query, + 'drizzle.query.params': JSON.stringify(params), + }); + this.logger.logQuery(this.query, params); + return tracer.startActiveSpan('drizzle.driver.execute', () => { + span?.setAttributes({ + 'drizzle.query.text': this.query, + 'drizzle.query.params': JSON.stringify(params), + }); + return this.client.unsafe(this.query, params as any[]); + }); + }); } } diff --git a/drizzle-orm/src/sql/index.ts b/drizzle-orm/src/sql/index.ts index 3533ca3f0..a2e7a8aa5 100644 --- a/drizzle-orm/src/sql/index.ts +++ b/drizzle-orm/src/sql/index.ts @@ -1,4 +1,5 @@ import { Subquery, SubqueryConfig } from '~/subquery'; +import { tracer } from '~/tracing'; import { View, ViewBaseConfig } from '~/view'; import { Relation } from '..'; import type { AnyColumn } from '../column'; @@ -88,7 +89,14 @@ export class SQL implements SQLWrapper { } toQuery(config: BuildQueryConfig): Query { - return this.buildQueryFromSourceParams(this.queryChunks, config); + return tracer.startActiveSpan('drizzle.buildSQL', (span) => { + const query = this.buildQueryFromSourceParams(this.queryChunks, config); + span?.setAttributes({ + 'drizzle.query.text': query.sql, + 'drizzle.query.params': JSON.stringify(query.params), + }); + return query; + }); } buildQueryFromSourceParams(chunks: SQLChunk[], _config: BuildQueryConfig): Query { diff --git a/drizzle-orm/src/tracing.ts b/drizzle-orm/src/tracing.ts new file mode 100644 index 000000000..875f03a86 --- /dev/null +++ b/drizzle-orm/src/tracing.ts @@ -0,0 +1,56 @@ +import { type Span, type Tracer } from '@opentelemetry/api'; +import { iife } from '~/utils'; +import { npmVersion } from '~/version'; + +let otel: typeof import('@opentelemetry/api') | undefined; +let rawTracer: Tracer | undefined; +try { + otel = await import('@opentelemetry/api'); +} catch (err: any) { + if (err.code !== 'MODULE_NOT_FOUND' && err.code !== 'ERR_MODULE_NOT_FOUND') { + throw err; + } +} + +type SpanName = + | 'drizzle.operation' + | 'drizzle.prepareQuery' + | 'drizzle.buildSQL' + | 'drizzle.execute' + | 'drizzle.driver.execute' + | 'drizzle.mapResponse'; + +/** @internal */ +export const tracer = { + startActiveSpan unknown>(name: SpanName, fn: F): ReturnType { + if (!otel) { + return fn() as ReturnType; + } + + if (!rawTracer) { + rawTracer = otel.trace.getTracer('drizzle-orm', npmVersion); + } + + return iife( + (otel, rawTracer) => + rawTracer.startActiveSpan( + name, + ((span: Span) => { + try { + return fn(span); + } catch (e) { + span.setStatus({ + code: otel.SpanStatusCode.ERROR, + message: e instanceof Error ? e.message : 'Unknown error', + }); + throw e; + } finally { + span.end(); + } + }) as F, + ), + otel, + rawTracer, + ); + }, +}; diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index 163b0bdbd..805b14d41 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -277,3 +277,7 @@ export interface DrizzleConfig = Record< export type KnownKeysOnly = { [K in keyof T]: K extends keyof U ? T[K] : never; }; + +export function iife(fn: (...args: T) => U, ...args: T): U { + return fn(...args); +} diff --git a/drizzle-orm/tsconfig.cjs.json b/drizzle-orm/tsconfig.cjs.json new file mode 100644 index 000000000..3986a7737 --- /dev/null +++ b/drizzle-orm/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "rootDir": "src", + "incremental": true + }, + "include": ["src"] +} diff --git a/drizzle-orm/tsconfig.build.json b/drizzle-orm/tsconfig.esm.json similarity index 100% rename from drizzle-orm/tsconfig.build.json rename to drizzle-orm/tsconfig.esm.json diff --git a/drizzle-orm/tsconfig.pg.json b/drizzle-orm/tsconfig.pg.json deleted file mode 100644 index 7331ff34f..000000000 --- a/drizzle-orm/tsconfig.pg.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.build.json", - "compilerOptions": { - "rootDir": ".", - "noEmit": true - }, - "include": ["src/pg-core", "tests/pg"] -} diff --git a/drizzle-orm/tsconfig.sqlite.json b/drizzle-orm/tsconfig.sqlite.json deleted file mode 100644 index 68d9b6676..000000000 --- a/drizzle-orm/tsconfig.sqlite.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.build.json", - "compilerOptions": { - "rootDir": ".", - "noEmit": true - }, - "include": ["src/sqlite-core", "tests/sqlite"] -} diff --git a/drizzle-zod/package.json b/drizzle-zod/package.json index 66f039afb..9310b43b5 100644 --- a/drizzle-zod/package.json +++ b/drizzle-zod/package.json @@ -13,10 +13,10 @@ }, "exports": { ".": { + "types": "./index.d.ts", "import": "./index.mjs", "require": "./index.cjs", - "default": "./index.mjs", - "types": "./index.d.ts" + "default": "./index.mjs" } }, "main": "./index.cjs", diff --git a/integration-tests/package.json b/integration-tests/package.json index 97b286537..12b1c8859 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -55,7 +55,7 @@ "better-sqlite3": "^8.0.0", "dockerode": "^3.3.4", "dotenv": "^16.0.3", - "drizzle-orm": "workspace:../drizzle-orm/dist", + "drizzle-orm": "file:../drizzle-orm/package.tgz", "drizzle-zod": "workspace:../drizzle-zod/dist", "express": "^4.18.2", "get-port": "^6.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 332aed279..572b556ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@neondatabase/serverless': specifier: ^0.2.9 version: 0.2.9 + '@opentelemetry/api': + specifier: ^1.4.1 + version: 1.4.1 '@originjs/vite-plugin-commonjs': specifier: ^1.0.3 version: 1.0.3 @@ -75,6 +78,9 @@ importers: '@rollup/plugin-json': specifier: ^6.0.0 version: 6.0.0(rollup@3.20.7) + '@rollup/plugin-replace': + specifier: ^5.0.2 + version: 5.0.2(rollup@3.20.7) '@rollup/plugin-typescript': specifier: ^11.1.0 version: 11.1.0(rollup@3.20.7)(tslib@2.5.0)(typescript@5.0.3) @@ -99,6 +105,9 @@ importers: bun-types: specifier: ^0.5.8 version: 0.5.8 + concurrently: + specifier: ^8.0.1 + version: 8.0.1 knex: specifier: ^2.4.2 version: 2.4.2(better-sqlite3@8.2.0)(mysql2@3.2.0)(pg@8.10.0)(sqlite3@5.1.6) @@ -202,8 +211,8 @@ importers: specifier: ^16.0.3 version: 16.0.3 drizzle-orm: - specifier: workspace:../drizzle-orm/dist - version: link:../drizzle-orm/dist + specifier: file:../drizzle-orm/package.tgz + version: file:drizzle-orm/package.tgz(@aws-sdk/client-rds-data@3.303.0)(@libsql/client@0.1.1)(@planetscale/database@1.6.0)(@types/better-sqlite3@7.6.3)(@types/pg@8.6.6)(@types/sql.js@1.4.4)(@vercel/postgres@0.1.2)(better-sqlite3@8.2.0)(bun-types@0.5.8)(mysql2@3.2.0)(pg@8.10.0)(postgres@3.3.4)(sql.js@1.8.0)(sqlite3@5.1.6) drizzle-zod: specifier: workspace:../drizzle-zod/dist version: link:../drizzle-zod/dist @@ -1568,6 +1577,11 @@ packages: rimraf: 3.0.2 optional: true + /@opentelemetry/api@1.4.1: + resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==} + engines: {node: '>=8.0.0'} + dev: true + /@originjs/vite-plugin-commonjs@1.0.3: resolution: {integrity: sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==} dependencies: @@ -1598,6 +1612,20 @@ packages: rollup: 3.20.7 dev: true + /@rollup/plugin-replace@5.0.2(rollup@3.20.7): + resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2(rollup@3.20.7) + magic-string: 0.27.0 + rollup: 3.20.7 + dev: true + /@rollup/plugin-terser@0.4.1(rollup@3.20.7): resolution: {integrity: sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==} engines: {node: '>=14.0.0'} @@ -1686,7 +1714,6 @@ packages: resolution: {integrity: sha512-YS64N9SNDT/NAvou3QNdzAu3E2om/W/0dhORimtPGLef+zSK5l1vDzfsWb4xgXOgfhtOI5ZDTRxnvRPb22AIVQ==} dependencies: '@types/node': 18.15.11 - dev: true /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} @@ -1725,7 +1752,6 @@ packages: /@types/emscripten@1.39.6: resolution: {integrity: sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==} - dev: true /@types/estree@1.0.1: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} @@ -1829,7 +1855,6 @@ packages: dependencies: '@types/emscripten': 1.39.6 '@types/node': 18.15.11 - dev: true /@types/ssh2@1.11.8: resolution: {integrity: sha512-BsD9yrKmD8avjbR+N5tvv0jxYHzizcrC156YkPbNjqbu81tCm4ZdS7D6KtXbZfz+CFHgFrTC7j046Lr39W5eig==} @@ -2419,7 +2444,6 @@ packages: /bun-types@0.5.8: resolution: {integrity: sha512-VHwD0MAHo3wraYAeqTWH2NDmXOdGfC3wWWOnZvK93ytI6yq/LkgsCjDudWNmN7MlfPvJb2zoLMnkzhjxNwLsLw==} - dev: true /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -4977,6 +5001,13 @@ packages: es5-ext: 0.10.62 dev: true + /magic-string@0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /magic-string@0.30.0: resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} engines: {node: '>=12'} @@ -7161,3 +7192,84 @@ packages: which: 3.0.0 yaml: 2.2.1 dev: true + + file:drizzle-orm/package.tgz(@aws-sdk/client-rds-data@3.303.0)(@libsql/client@0.1.1)(@planetscale/database@1.6.0)(@types/better-sqlite3@7.6.3)(@types/pg@8.6.6)(@types/sql.js@1.4.4)(@vercel/postgres@0.1.2)(better-sqlite3@8.2.0)(bun-types@0.5.8)(mysql2@3.2.0)(pg@8.10.0)(postgres@3.3.4)(sql.js@1.8.0)(sqlite3@5.1.6): + resolution: {integrity: sha512-tmmSxOB106dXiY2KvXv278gmydiSX6esrqX9J/vSol/5E5L46NncdAaGLC4dMFdaK5wA9P06jPTlzpMI3UzBPQ==, tarball: file:drizzle-orm/package.tgz} + id: file:drizzle-orm/package.tgz + name: drizzle-orm + version: 0.26.1 + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@vercel/postgres': '*' + better-sqlite3: '>=7' + bun-types: '*' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dependencies: + '@aws-sdk/client-rds-data': 3.303.0 + '@libsql/client': 0.1.1 + '@planetscale/database': 1.6.0 + '@types/better-sqlite3': 7.6.3 + '@types/pg': 8.6.6 + '@types/sql.js': 1.4.4 + '@vercel/postgres': 0.1.2 + better-sqlite3: 8.2.0 + bun-types: 0.5.8 + mysql2: 3.2.0 + pg: 8.10.0 + postgres: 3.3.4 + sql.js: 1.8.0 + sqlite3: 5.1.6 + dev: false diff --git a/tsconfig.json b/tsconfig.json index e744465ed..433128324 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "isolatedModules": true, "composite": false, - "target": "es2020", - "module": "es2020", + "target": "esnext", + "module": "esnext", "moduleResolution": "node", "lib": ["es2020", "es2018", "es2017", "es7", "es6", "es5"], "declaration": false /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,