@molecule/api-database-duckdb 0.0.0-stage → 0.0.1-stage
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.
- package/LICENSE +202 -0
- package/README.md +772 -2
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/capabilities.d.ts +30 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +48 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/dist/index.js.map +1 -0
- package/dist/instances.d.ts +73 -0
- package/dist/instances.d.ts.map +1 -0
- package/dist/instances.js +117 -0
- package/dist/instances.js.map +1 -0
- package/dist/lifecycle.d.ts +22 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +45 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/migrator.d.ts +74 -0
- package/dist/migrator.d.ts.map +1 -0
- package/dist/migrator.js +172 -0
- package/dist/migrator.js.map +1 -0
- package/dist/pool.d.ts +40 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +273 -0
- package/dist/pool.js.map +1 -0
- package/dist/queue.d.ts +48 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +98 -0
- package/dist/queue.js.map +1 -0
- package/dist/secrets.d.ts +16 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +25 -0
- package/dist/secrets.js.map +1 -0
- package/dist/sql-dialect.d.ts +92 -0
- package/dist/sql-dialect.d.ts.map +1 -0
- package/dist/sql-dialect.js +309 -0
- package/dist/sql-dialect.js.map +1 -0
- package/dist/store.d.ts +18 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +335 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities.d.ts +119 -0
- package/dist/utilities.d.ts.map +1 -0
- package/dist/utilities.js +246 -0
- package/dist/utilities.js.map +1 -0
- 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"}
|
package/dist/migrator.js
ADDED
|
@@ -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
|
package/dist/pool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"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"}
|