@molecule/api-database-duckdb 0.0.0-stage → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +772 -2
  3. package/dist/browser-guard.d.ts +2 -0
  4. package/dist/browser-guard.d.ts.map +1 -0
  5. package/dist/browser-guard.js +19 -0
  6. package/dist/browser-guard.js.map +1 -0
  7. package/dist/capabilities.d.ts +30 -0
  8. package/dist/capabilities.d.ts.map +1 -0
  9. package/dist/capabilities.js +48 -0
  10. package/dist/capabilities.js.map +1 -0
  11. package/dist/index.d.ts +115 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +169 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/instances.d.ts +73 -0
  16. package/dist/instances.d.ts.map +1 -0
  17. package/dist/instances.js +117 -0
  18. package/dist/instances.js.map +1 -0
  19. package/dist/lifecycle.d.ts +22 -0
  20. package/dist/lifecycle.d.ts.map +1 -0
  21. package/dist/lifecycle.js +45 -0
  22. package/dist/lifecycle.js.map +1 -0
  23. package/dist/migrator.d.ts +74 -0
  24. package/dist/migrator.d.ts.map +1 -0
  25. package/dist/migrator.js +172 -0
  26. package/dist/migrator.js.map +1 -0
  27. package/dist/pool.d.ts +40 -0
  28. package/dist/pool.d.ts.map +1 -0
  29. package/dist/pool.js +273 -0
  30. package/dist/pool.js.map +1 -0
  31. package/dist/queue.d.ts +48 -0
  32. package/dist/queue.d.ts.map +1 -0
  33. package/dist/queue.js +98 -0
  34. package/dist/queue.js.map +1 -0
  35. package/dist/secrets.d.ts +16 -0
  36. package/dist/secrets.d.ts.map +1 -0
  37. package/dist/secrets.js +25 -0
  38. package/dist/secrets.js.map +1 -0
  39. package/dist/sql-dialect.d.ts +92 -0
  40. package/dist/sql-dialect.d.ts.map +1 -0
  41. package/dist/sql-dialect.js +309 -0
  42. package/dist/sql-dialect.js.map +1 -0
  43. package/dist/store.d.ts +18 -0
  44. package/dist/store.d.ts.map +1 -0
  45. package/dist/store.js +335 -0
  46. package/dist/store.js.map +1 -0
  47. package/dist/types.d.ts +57 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +7 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/utilities.d.ts +119 -0
  52. package/dist/utilities.d.ts.map +1 -0
  53. package/dist/utilities.js +246 -0
  54. package/dist/utilities.js.map +1 -0
  55. package/package.json +87 -4
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Process-lifecycle integration for the DuckDB bond.
3
+ *
4
+ * Optional and dynamic — a project without `@molecule/api-process-lifecycle`
5
+ * imports this bond unchanged. With the package present, `/health/ready` runs
6
+ * a CRITICAL `SELECT 1` (a DuckDB file can be locked, removed, or its disk
7
+ * full after boot) and the ordered shutdown closes the pool.
8
+ *
9
+ * @module
10
+ */
11
+ /** Runtime import specifier kept in a variable so the package stays optional. */
12
+ const PROCESS_LIFECYCLE = '@molecule/api-process-lifecycle';
13
+ /**
14
+ * Register the database readiness check and shutdown hook with the bonded
15
+ * process-lifecycle provider.
16
+ *
17
+ * @param resources - The ping and close functions this bond owns.
18
+ */
19
+ export function registerLifecycleIntegration(resources) {
20
+ void import(PROCESS_LIFECYCLE)
21
+ .then((module) => {
22
+ const lifecycle = module;
23
+ lifecycle.registerReadinessCheck({
24
+ name: 'database',
25
+ critical: true,
26
+ timeoutMs: 2_000,
27
+ description: 'DuckDB accepts a query',
28
+ check: resources.ping,
29
+ });
30
+ lifecycle.registerShutdownHook({
31
+ name: 'database-pool',
32
+ // Last: other hooks may still need the database while it closes.
33
+ order: 40,
34
+ timeoutMs: 10_000,
35
+ run: async () => {
36
+ if (resources.created())
37
+ await resources.close();
38
+ },
39
+ });
40
+ })
41
+ .catch((_error) => {
42
+ // @molecule/api-process-lifecycle is not installed — nothing to wire.
43
+ });
44
+ }
45
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iFAAiF;AACjF,MAAM,iBAAiB,GAAG,iCAAiC,CAAA;AAmB3D;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAAC,SAI5C;IACC,KAAK,MAAM,CAAC,iBAAiB,CAAC;SAC3B,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,MAAM,SAAS,GAAG,MAAoC,CAAA;QACtD,SAAS,CAAC,sBAAsB,CAAC;YAC/B,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE,IAAI;YACd,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,wBAAwB;YACrC,KAAK,EAAE,SAAS,CAAC,IAAI;SACtB,CAAC,CAAA;QACF,SAAS,CAAC,oBAAoB,CAAC;YAC7B,IAAI,EAAE,eAAe;YACrB,iEAAiE;YACjE,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,MAAM;YACjB,GAAG,EAAE,KAAK,IAAI,EAAE;gBACd,IAAI,SAAS,CAAC,OAAO,EAAE;oBAAE,MAAM,SAAS,CAAC,KAAK,EAAE,CAAA;YAClD,CAAC;SACF,CAAC,CAAA;IACJ,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE;QAChB,sEAAsE;IACxE,CAAC,CAAC,CAAA;AACN,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * DuckDB migration runner.
3
+ *
4
+ * Same contract as the sqlite/pglite bonds' migrators: every `*.sql` file in a
5
+ * directory runs in lexical order; "already exists" errors from a re-run are
6
+ * warnings; any other error fails the run with every broken file named. There
7
+ * is no `CREATE DATABASE` step — DuckDB creates the file when it opens. Each
8
+ * file runs ONCE, inside one transaction, recorded in the
9
+ * `_molecule_migrations` ledger.
10
+ *
11
+ * DuckDB natively supports `ADD COLUMN IF NOT EXISTS`, `DROP COLUMN IF
12
+ * EXISTS`, `gen_random_uuid()`, `now()` and `CREATE TABLE IF NOT EXISTS`, so
13
+ * the sqlite bond's column-reconciliation machinery is not needed here. What the
14
+ * translation does — and refuses — is documented on `translateDdlToDuckdb`:
15
+ * `jsonb`, index `USING <method>` and partial indexes are translated without
16
+ * changing behaviour, while an `ON DELETE`/`ON UPDATE` referential action throws
17
+ * `UnsupportedSqlFeatureError` (DuckDB cannot reproduce it).
18
+ *
19
+ * @module
20
+ */
21
+ import './secrets.js';
22
+ import type { DatabasePool, DatabaseTransaction } from '@molecule/api-database';
23
+ /** The table that records which migration files have run. */
24
+ export declare const MIGRATION_LEDGER = "_molecule_migrations";
25
+ /**
26
+ * A pool that can run migrations — `DatabasePool` plus the interactive
27
+ * transaction every bond in the `database` category implements (the interface
28
+ * marks it optional; no bond ships without it, and the runner needs it).
29
+ */
30
+ export type MigratablePool = DatabasePool & {
31
+ /** Begins a transaction that holds the pool's connection until it ends. */
32
+ transaction(): Promise<DatabaseTransaction>;
33
+ };
34
+ /**
35
+ * Returns true when `err` looks like an "already exists" / duplicate-object
36
+ * idempotency error — the class of error an `IF NOT EXISTS` migration is
37
+ * EXPECTED to hit on a re-run. DuckDB errors carry no code, so this matches
38
+ * the message text (the same approach the sqlite bond takes).
39
+ *
40
+ * @param err - The error thrown while applying a migration.
41
+ * @returns True if it is safe to warn and continue.
42
+ */
43
+ export declare function isIdempotencyError(err: unknown): boolean;
44
+ /** Options for the migration runner. */
45
+ export interface MigrationOptions {
46
+ /** Prefix for ledger keys when one database applies SQL from more than one directory. */
47
+ namespace?: string;
48
+ }
49
+ /**
50
+ * Apply every migration file in `migrationsDir` to `pool`, in lexical order.
51
+ * Exported so a sibling bond (motherduck) can run the same ledger logic on
52
+ * its own pool.
53
+ *
54
+ * @param pool - The pool to migrate (its database is created on first open).
55
+ * @param migrationsDir - Absolute path to the directory of ordered `*.sql` files.
56
+ * @param options - Ledger namespace for multi-directory apps.
57
+ * @returns Resolves when every file is applied or recorded.
58
+ * @throws {Error} When any file fails with a non-idempotency error — every
59
+ * broken file is named in the one error, so a single boot log reports all.
60
+ */
61
+ export declare function applyMigrations(pool: MigratablePool, migrationsDir: string, options?: MigrationOptions): Promise<void>;
62
+ /**
63
+ * Returns a `runMigrations()` bound to a migrations directory.
64
+ *
65
+ * @param migrationsDir - Absolute path to the directory of ordered `*.sql`
66
+ * migration files, resolved from the API package directory.
67
+ * @param options - A stable namespace for a second SQL directory.
68
+ * @returns A no-arg `runMigrations()` that opens the DuckDB file (creating it
69
+ * and its parent directory if missing) and applies every migration file in
70
+ * lexical order. Reads the file path from the `DUCKDB_PATH` env var
71
+ * (default `./data/app.duckdb`), matching the pool.
72
+ */
73
+ export declare function createMigrator(migrationsDir: string, options?: MigrationOptions): () => Promise<void>;
74
+ //# sourceMappingURL=migrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrator.d.ts","sourceRoot":"","sources":["../src/migrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAK/E,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AAEtD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG;IAC1C,2EAA2E;IAC3E,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAA;CAC5C,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAOxD;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsGf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,gBAAqB,GAC7B,MAAM,OAAO,CAAC,IAAI,CAAC,CASrB"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * DuckDB migration runner.
3
+ *
4
+ * Same contract as the sqlite/pglite bonds' migrators: every `*.sql` file in a
5
+ * directory runs in lexical order; "already exists" errors from a re-run are
6
+ * warnings; any other error fails the run with every broken file named. There
7
+ * is no `CREATE DATABASE` step — DuckDB creates the file when it opens. Each
8
+ * file runs ONCE, inside one transaction, recorded in the
9
+ * `_molecule_migrations` ledger.
10
+ *
11
+ * DuckDB natively supports `ADD COLUMN IF NOT EXISTS`, `DROP COLUMN IF
12
+ * EXISTS`, `gen_random_uuid()`, `now()` and `CREATE TABLE IF NOT EXISTS`, so
13
+ * the sqlite bond's column-reconciliation machinery is not needed here. What the
14
+ * translation does — and refuses — is documented on `translateDdlToDuckdb`:
15
+ * `jsonb`, index `USING <method>` and partial indexes are translated without
16
+ * changing behaviour, while an `ON DELETE`/`ON UPDATE` referential action throws
17
+ * `UnsupportedSqlFeatureError` (DuckDB cannot reproduce it).
18
+ *
19
+ * @module
20
+ */
21
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ // Side-effect import: registers this bond's secret definitions so the
24
+ // runtime registry is populated even when migrator.js is imported directly
25
+ // (not through the package barrel).
26
+ import './secrets.js';
27
+ import { createPool } from './pool.js';
28
+ import { translateDdlToDuckdb } from './utilities.js';
29
+ /** The table that records which migration files have run. */
30
+ export const MIGRATION_LEDGER = '_molecule_migrations';
31
+ /**
32
+ * Returns true when `err` looks like an "already exists" / duplicate-object
33
+ * idempotency error — the class of error an `IF NOT EXISTS` migration is
34
+ * EXPECTED to hit on a re-run. DuckDB errors carry no code, so this matches
35
+ * the message text (the same approach the sqlite bond takes).
36
+ *
37
+ * @param err - The error thrown while applying a migration.
38
+ * @returns True if it is safe to warn and continue.
39
+ */
40
+ export function isIdempotencyError(err) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ return (/already exists/i.test(msg) ||
43
+ /duplicate (column|key|object) name/i.test(msg) ||
44
+ /multiple primary keys for table/i.test(msg));
45
+ }
46
+ /**
47
+ * Apply every migration file in `migrationsDir` to `pool`, in lexical order.
48
+ * Exported so a sibling bond (motherduck) can run the same ledger logic on
49
+ * its own pool.
50
+ *
51
+ * @param pool - The pool to migrate (its database is created on first open).
52
+ * @param migrationsDir - Absolute path to the directory of ordered `*.sql` files.
53
+ * @param options - Ledger namespace for multi-directory apps.
54
+ * @returns Resolves when every file is applied or recorded.
55
+ * @throws {Error} When any file fails with a non-idempotency error — every
56
+ * broken file is named in the one error, so a single boot log reports all.
57
+ */
58
+ export async function applyMigrations(pool, migrationsDir, options = {}) {
59
+ const namespace = options.namespace ?? '';
60
+ const sqlFiles = existsSync(migrationsDir)
61
+ ? readdirSync(migrationsDir)
62
+ .filter((file) => file.endsWith('.sql'))
63
+ .sort()
64
+ : [];
65
+ if (sqlFiles.length === 0) {
66
+ console.log('No migration files found.');
67
+ return;
68
+ }
69
+ await pool.query(`CREATE TABLE IF NOT EXISTS "${MIGRATION_LEDGER}" ("file" VARCHAR PRIMARY KEY, "applied_at" TIMESTAMPTZ NOT NULL DEFAULT now(), "namespace" VARCHAR)`);
70
+ const applied = new Set((await pool.query(`SELECT "file" FROM "${MIGRATION_LEDGER}"`)).rows.map((row) => String(row.file)));
71
+ const failures = [];
72
+ let skipped = 0;
73
+ for (const file of sqlFiles) {
74
+ const key = namespace ? `${namespace}/${file}` : file;
75
+ // Resource/template setup migrations are authored in Postgres dialect.
76
+ // translateDdlToDuckdb rewrites jsonb, index USING and partial indexes, and
77
+ // throws for a referential action DuckDB cannot reproduce (naming this file
78
+ // via options.source, so one boot log points at the resource). No-op on
79
+ // already-DuckDB DDL.
80
+ const sql = translateDdlToDuckdb(readFileSync(join(migrationsDir, file), 'utf-8'), {
81
+ source: file,
82
+ });
83
+ if (applied.has(key)) {
84
+ // A recorded file never runs again (a DROP or a backfill in it would
85
+ // repeat on every boot).
86
+ skipped++;
87
+ continue;
88
+ }
89
+ // In a transaction, so a file that fails applies none of its statements.
90
+ // `pool.transaction()` issues the BEGIN; COMMIT/ROLLBACK come from the
91
+ // transaction object. A file that manages its own BEGIN/COMMIT runs
92
+ // OUTSIDE ours — DuckDB refuses to nest transactions ("cannot start a
93
+ // transaction within a transaction"), the same case the sqlite migrator
94
+ // detects.
95
+ const selfManaged = /^\s*(?:BEGIN|START\s+TRANSACTION)\b/im.test(sql);
96
+ try {
97
+ if (selfManaged) {
98
+ await pool.query(sql);
99
+ await pool.query(`INSERT INTO "${MIGRATION_LEDGER}" ("file", "namespace") VALUES (?, ?)`, [
100
+ key,
101
+ namespace,
102
+ ]);
103
+ }
104
+ else {
105
+ const tx = await pool.transaction();
106
+ try {
107
+ // No values: the client runs a multi-statement string in full.
108
+ await tx.query(sql);
109
+ await tx.query(`INSERT INTO "${MIGRATION_LEDGER}" ("file", "namespace") VALUES (?, ?)`, [
110
+ key,
111
+ namespace,
112
+ ]);
113
+ await tx.commit();
114
+ }
115
+ catch (err) {
116
+ await tx.rollback().catch((_error) => {
117
+ // The transaction already aborted in the engine; the rollback is
118
+ // bookkeeping. The original error is the one to report.
119
+ });
120
+ throw err;
121
+ }
122
+ }
123
+ console.log(`✓ ${file}`);
124
+ }
125
+ catch (err) {
126
+ const msg = err instanceof Error ? err.message : String(err);
127
+ if (isIdempotencyError(err)) {
128
+ console.warn(`⚠ ${file}: ${msg} — nothing in this file was applied; an object it creates ` +
129
+ 'already exists, so remove that statement or move the rest to a new migration file');
130
+ }
131
+ else {
132
+ // A genuinely broken migration must NOT be swallowed as if it were
133
+ // idempotent — that boots the app with a missing/partial schema and
134
+ // the failure resurfaces later, far from the real cause. Collect it
135
+ // and keep going so ONE boot log reports every broken file at once.
136
+ console.error(`✗ ${file}: ${msg}`);
137
+ failures.push({ file, message: msg });
138
+ }
139
+ }
140
+ }
141
+ if (failures.length > 0) {
142
+ throw new Error(`Migrations failed — ${failures.length} file(s) had non-idempotency errors; ` +
143
+ `the app would boot with a missing/partial schema:\n` +
144
+ failures.map((f) => ` - ${f.file}: ${f.message}`).join('\n'));
145
+ }
146
+ if (skipped > 0)
147
+ console.log(`· ${skipped} migration file(s) already applied`);
148
+ console.log('Migrations complete.');
149
+ }
150
+ /**
151
+ * Returns a `runMigrations()` bound to a migrations directory.
152
+ *
153
+ * @param migrationsDir - Absolute path to the directory of ordered `*.sql`
154
+ * migration files, resolved from the API package directory.
155
+ * @param options - A stable namespace for a second SQL directory.
156
+ * @returns A no-arg `runMigrations()` that opens the DuckDB file (creating it
157
+ * and its parent directory if missing) and applies every migration file in
158
+ * lexical order. Reads the file path from the `DUCKDB_PATH` env var
159
+ * (default `./data/app.duckdb`), matching the pool.
160
+ */
161
+ export function createMigrator(migrationsDir, options = {}) {
162
+ return async function runMigrations() {
163
+ const pool = createPool();
164
+ try {
165
+ await applyMigrations(pool, migrationsDir, options);
166
+ }
167
+ finally {
168
+ await pool.end();
169
+ }
170
+ };
171
+ }
172
+ //# sourceMappingURL=migrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrator.js","sourceRoot":"","sources":["../src/migrator.ts"],"names":[],"mappings":"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"}
package/dist/pool.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * DuckDB `DatabasePool` implementation using `@duckdb/node-api`.
3
+ *
4
+ * @module
5
+ */
6
+ import './secrets.js';
7
+ import type { DuckDBConnection, DuckDBValue } from '@duckdb/node-api';
8
+ import type { DatabasePool, QueryResult } from '@molecule/api-database';
9
+ import type { DuckdbConfig } from './types.js';
10
+ /**
11
+ * Runs one statement on a connection and normalizes its result into the core
12
+ * `QueryResult` shape. `$1`-style and `?` placeholders pass through to
13
+ * DuckDB untranslated — both are native. With VALUES the client prepares the
14
+ * statement (one statement only); without values a multi-statement string
15
+ * runs in full and the LAST statement's result is returned (the pglite bond's
16
+ * simple-query behavior).
17
+ *
18
+ * `rowCount` follows node-postgres: rows returned for a statement that
19
+ * produced rows (a SELECT, or `INSERT/UPDATE/DELETE … RETURNING` — DuckDB
20
+ * reports 0 changed rows for a RETURNING statement, so the returned rows ARE
21
+ * the affected count), `rowsChanged` for plain DML, `null` for DDL.
22
+ *
23
+ * @param connection - The connection to run on.
24
+ * @param text - SQL with `$1, $2, …` or `?` placeholders.
25
+ * @param values - Positional parameter values (already coerced).
26
+ * @returns The normalized result.
27
+ */
28
+ export declare function runDuckdbQuery<T = Record<string, unknown>>(connection: DuckDBConnection, text: string, values?: DuckDBValue[]): Promise<QueryResult<T>>;
29
+ /**
30
+ * Creates a `DatabasePool` backed by DuckDB.
31
+ *
32
+ * The database opens on first use, not here, so creating a pool is free and
33
+ * synchronous. See {@link DuckdbConfig} for where the file lives and the
34
+ * resource defaults the instance gets.
35
+ *
36
+ * @param config - Database path, resource limits and wait timeout.
37
+ * @returns A pool over one DuckDB connection.
38
+ */
39
+ export declare const createPool: (config?: DuckdbConfig) => DatabasePool;
40
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAIrE,OAAO,KAAK,EAEV,YAAY,EAEZ,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAI/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAmC9C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,UAAU,EAAE,gBAAgB,EAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,EAAE,GACrB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAmBzB;AAkBD;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,YAAY,KAAG,YAoKlD,CAAA"}
package/dist/pool.js ADDED
@@ -0,0 +1,273 @@
1
+ /**
2
+ * DuckDB `DatabasePool` implementation using `@duckdb/node-api`.
3
+ *
4
+ * @module
5
+ */
6
+ // Side-effect import: registers this bond's secret definitions so the
7
+ // runtime registry is populated even when pool.js is imported directly
8
+ // (not through the package barrel).
9
+ import './secrets.js';
10
+ import { ResultReturnType } from '@duckdb/node-api';
11
+ import { getLogger } from '@molecule/api-bond';
12
+ import { getActiveTransaction } from '@molecule/api-database';
13
+ import { acquireInstance, releaseInstance } from './instances.js';
14
+ import { coerceDuckdbParam, normalizeDuckdbRows } from './utilities.js';
15
+ /** Default wait for the single connection. */
16
+ const DEFAULT_ACQUIRE_TIMEOUT_MS = 30_000;
17
+ /** How much of a failing statement the error message carries (whitespace-collapsed). */
18
+ const STATEMENT_IN_MESSAGE_CAP = 200;
19
+ /**
20
+ * Runs `statement` and, when the client throws, names the statement that failed.
21
+ *
22
+ * DuckDB's errors carry no SQL at all (`Catalog Error: Table with name x does
23
+ * not exist!`, `Binder Error: …`) — the same blind spot the sqlite bond patches
24
+ * with `withStatementContext`. The error keeps its class and message; its
25
+ * message gains the statement on one line, and `sql` carries the statement
26
+ * whole. The bound VALUES are never part of either.
27
+ *
28
+ * @param sql - The statement as the driver received it.
29
+ * @param run - Runs the statement and returns its result.
30
+ * @returns Whatever `run` returned.
31
+ */
32
+ async function withStatementContext(sql, run) {
33
+ try {
34
+ return await run();
35
+ }
36
+ catch (error) {
37
+ if (error instanceof Error && !error.sql) {
38
+ const oneLine = sql.replace(/\s+/g, ' ').trim().slice(0, STATEMENT_IN_MESSAGE_CAP);
39
+ error.sql = sql;
40
+ error.message = `${error.message} — in: ${oneLine}`;
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ /**
46
+ * Runs one statement on a connection and normalizes its result into the core
47
+ * `QueryResult` shape. `$1`-style and `?` placeholders pass through to
48
+ * DuckDB untranslated — both are native. With VALUES the client prepares the
49
+ * statement (one statement only); without values a multi-statement string
50
+ * runs in full and the LAST statement's result is returned (the pglite bond's
51
+ * simple-query behavior).
52
+ *
53
+ * `rowCount` follows node-postgres: rows returned for a statement that
54
+ * produced rows (a SELECT, or `INSERT/UPDATE/DELETE … RETURNING` — DuckDB
55
+ * reports 0 changed rows for a RETURNING statement, so the returned rows ARE
56
+ * the affected count), `rowsChanged` for plain DML, `null` for DDL.
57
+ *
58
+ * @param connection - The connection to run on.
59
+ * @param text - SQL with `$1, $2, …` or `?` placeholders.
60
+ * @param values - Positional parameter values (already coerced).
61
+ * @returns The normalized result.
62
+ */
63
+ export async function runDuckdbQuery(connection, text, values) {
64
+ return withStatementContext(text, async () => {
65
+ const result = values && values.length > 0 ? await connection.run(text, values) : await connection.run(text);
66
+ try {
67
+ if (result.returnType !== ResultReturnType.QUERY_RESULT) {
68
+ // Plain INSERT/UPDATE/DELETE/DDL: no rows to read; rowsChanged is the
69
+ // affected count (0 for DDL).
70
+ return { rows: [], rowCount: result.rowsChanged };
71
+ }
72
+ const rows = normalizeDuckdbRows(await result.getRowObjectsJS());
73
+ return { rows, rowCount: rows.length };
74
+ }
75
+ finally {
76
+ // On DuckDB 2.0 an unread streaming result blocks its connection from
77
+ // starting another statement until closed; a materialized result we have
78
+ // fully read can be released eagerly to hand the buffers back.
79
+ result.closeSync();
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Runs one query with caller values, coercing them on the way in.
85
+ *
86
+ * @param connection - The connection to run on.
87
+ * @param text - SQL with `$1, $2, …` or `?` placeholders.
88
+ * @param values - Parameter values as the caller passed them.
89
+ * @returns The normalized result.
90
+ */
91
+ async function runWithValues(connection, text, values) {
92
+ return runDuckdbQuery(connection, text, values?.map(coerceDuckdbParam));
93
+ }
94
+ /**
95
+ * Creates a `DatabasePool` backed by DuckDB.
96
+ *
97
+ * The database opens on first use, not here, so creating a pool is free and
98
+ * synchronous. See {@link DuckdbConfig} for where the file lives and the
99
+ * resource defaults the instance gets.
100
+ *
101
+ * @param config - Database path, resource limits and wait timeout.
102
+ * @returns A pool over one DuckDB connection.
103
+ */
104
+ export const createPool = (config) => {
105
+ const timeout = config?.acquireTimeoutMillis ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
106
+ let opening = null;
107
+ let ended = false;
108
+ /**
109
+ * Opens the instance and its pooled connection once, on first use.
110
+ *
111
+ * @returns The instance handle and its connection.
112
+ */
113
+ const open = () => {
114
+ if (!opening) {
115
+ opening = (async () => {
116
+ const handle = await acquireInstance(config);
117
+ const connection = await handle.instance.connect();
118
+ getLogger().debug('DuckDB database opened', { path: handle.path });
119
+ return { handle, connection };
120
+ })();
121
+ // A failed open can be retried by the next call.
122
+ opening.catch((_error) => {
123
+ // The same rejection reaches the caller awaiting `opening`; resetting
124
+ // here only lets a later call try again.
125
+ opening = null;
126
+ });
127
+ }
128
+ return opening;
129
+ };
130
+ /**
131
+ * Rejects after `end()` — the connection is gone.
132
+ *
133
+ * @returns A rejected promise.
134
+ */
135
+ const refuseEnded = () => Promise.reject(new Error('DuckDB: this pool has been ended'));
136
+ // Transactions this pool opened: a `query()` made inside `withTransaction()`
137
+ // runs on the active one when it is ours.
138
+ const ownTransactions = new WeakSet();
139
+ const pool = {
140
+ async query(text, values) {
141
+ if (ended)
142
+ return refuseEnded();
143
+ const active = getActiveTransaction();
144
+ if (active && ownTransactions.has(active))
145
+ return active.query(text, values);
146
+ const { handle, connection } = await open();
147
+ const release = await handle.lock.acquire('query', timeout);
148
+ try {
149
+ return await runWithValues(connection, text, values);
150
+ }
151
+ finally {
152
+ release();
153
+ }
154
+ },
155
+ async connect() {
156
+ if (ended)
157
+ return refuseEnded();
158
+ const { handle, connection } = await open();
159
+ const release = await handle.lock.acquire('connect()', timeout);
160
+ let released = false;
161
+ return {
162
+ async query(text, values) {
163
+ if (released)
164
+ throw new Error('DuckDB: query on a released connection');
165
+ return runWithValues(connection, text, values);
166
+ },
167
+ release() {
168
+ released = true;
169
+ release();
170
+ },
171
+ };
172
+ },
173
+ async transaction() {
174
+ if (ended)
175
+ return refuseEnded();
176
+ const { handle, connection } = await open();
177
+ // Hold the lock for the WHOLE transaction lifetime (BEGIN → … →
178
+ // COMMIT/ROLLBACK) — a concurrent transaction() or plain query() call
179
+ // queues behind this one instead of racing BEGIN or conflicting with it.
180
+ const release = await handle.lock.acquire('transaction()', timeout);
181
+ let done = false;
182
+ const finish = () => {
183
+ done = true;
184
+ release();
185
+ };
186
+ try {
187
+ await runDuckdbQuery(connection, 'BEGIN');
188
+ }
189
+ catch (error) {
190
+ finish();
191
+ throw error;
192
+ }
193
+ const tx = {
194
+ async query(text, values) {
195
+ if (done)
196
+ throw new Error('DuckDB: query on a finished transaction');
197
+ // Already holds the lock — run directly, do NOT re-acquire.
198
+ return runWithValues(connection, text, values);
199
+ },
200
+ release() {
201
+ // Matches the pglite bond: returns the connection WITHOUT ending the
202
+ // transaction. Roll back first so the connection is clean for the
203
+ // next caller.
204
+ if (done)
205
+ return;
206
+ done = true;
207
+ runDuckdbQuery(connection, 'ROLLBACK')
208
+ .catch((error) => {
209
+ // Nothing awaits release(); surface the failure instead of hiding it.
210
+ getLogger().error('DuckDB: rollback on transaction release() failed', { error });
211
+ })
212
+ .finally(release);
213
+ },
214
+ async commit() {
215
+ if (done)
216
+ return;
217
+ try {
218
+ await runDuckdbQuery(connection, 'COMMIT');
219
+ }
220
+ finally {
221
+ finish();
222
+ }
223
+ },
224
+ async rollback() {
225
+ if (done)
226
+ return;
227
+ try {
228
+ await runDuckdbQuery(connection, 'ROLLBACK');
229
+ }
230
+ finally {
231
+ finish();
232
+ }
233
+ },
234
+ };
235
+ ownTransactions.add(tx);
236
+ return tx;
237
+ },
238
+ async end() {
239
+ if (ended)
240
+ return;
241
+ ended = true;
242
+ if (!opening)
243
+ return;
244
+ const { handle, connection } = await opening;
245
+ // Let queued and open work finish before closing — but do not hang
246
+ // shutdown forever on a connection somebody never released.
247
+ let release;
248
+ try {
249
+ release = await handle.lock.acquire('end()', timeout);
250
+ }
251
+ catch (error) {
252
+ getLogger().warn('DuckDB: closing the pool while its connection is still held', { error });
253
+ }
254
+ try {
255
+ connection.disconnectSync();
256
+ releaseInstance(handle);
257
+ }
258
+ finally {
259
+ release?.();
260
+ }
261
+ getLogger().debug('DuckDB database closed', { path: handle.path });
262
+ },
263
+ stats() {
264
+ // This pool owns exactly one connection. Before first use the lock does
265
+ // not exist yet, and forcing a connection just to answer stats() would
266
+ // be wrong — report the idle pool; once open, waiting queries surface
267
+ // through the lock's timeout error instead.
268
+ return { total: 1, idle: 1, waiting: 0 };
269
+ },
270
+ };
271
+ return pool;
272
+ };
273
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"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"}