sqlstack 3.3.0 → 3.5.0
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/dist/cjs/adapters/sqlite.d.ts.map +1 -1
- package/dist/cjs/adapters/sqlite.js +4 -1
- package/dist/cjs/adapters/sqlite.js.map +1 -1
- package/dist/cjs/core/errors.d.ts +9 -0
- package/dist/cjs/core/errors.d.ts.map +1 -1
- package/dist/cjs/core/errors.js +10 -0
- package/dist/cjs/core/errors.js.map +1 -1
- package/dist/cjs/decorators/query.d.ts +14 -0
- package/dist/cjs/decorators/query.d.ts.map +1 -1
- package/dist/cjs/decorators/query.js +124 -17
- package/dist/cjs/decorators/query.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/run.d.ts +140 -6
- package/dist/cjs/run.d.ts.map +1 -1
- package/dist/cjs/run.js +127 -17
- package/dist/cjs/run.js.map +1 -1
- package/dist/cjs/runtime.d.ts +8 -0
- package/dist/cjs/runtime.d.ts.map +1 -1
- package/dist/cjs/runtime.js +12 -0
- package/dist/cjs/runtime.js.map +1 -1
- package/dist/cjs/sql.d.ts +19 -0
- package/dist/cjs/sql.d.ts.map +1 -1
- package/dist/cjs/sql.js +66 -28
- package/dist/cjs/sql.js.map +1 -1
- package/dist/cjs/stack.d.ts +2 -0
- package/dist/cjs/stack.d.ts.map +1 -1
- package/dist/cjs/stack.js +40 -27
- package/dist/cjs/stack.js.map +1 -1
- package/dist/cjs/testing/builder.d.ts.map +1 -1
- package/dist/cjs/testing/builder.js +14 -5
- package/dist/cjs/testing/builder.js.map +1 -1
- package/dist/cjs/testing/fixture-provenance.d.ts +3 -0
- package/dist/cjs/testing/fixture-provenance.d.ts.map +1 -0
- package/dist/cjs/testing/fixture-provenance.js +21 -0
- package/dist/cjs/testing/fixture-provenance.js.map +1 -0
- package/dist/cjs/testing/index.d.ts +2 -0
- package/dist/cjs/testing/index.d.ts.map +1 -1
- package/dist/cjs/testing/index.js +5 -1
- package/dist/cjs/testing/index.js.map +1 -1
- package/dist/cjs/testing/postgres/database.d.ts +9 -1
- package/dist/cjs/testing/postgres/database.d.ts.map +1 -1
- package/dist/cjs/testing/postgres/database.js +17 -13
- package/dist/cjs/testing/postgres/database.js.map +1 -1
- package/dist/cjs/testing/resource.d.ts +76 -0
- package/dist/cjs/testing/resource.d.ts.map +1 -0
- package/dist/cjs/testing/resource.js +117 -0
- package/dist/cjs/testing/resource.js.map +1 -0
- package/dist/cjs/testing/sqlite/builder.d.ts.map +1 -1
- package/dist/cjs/testing/sqlite/builder.js +10 -2
- package/dist/cjs/testing/sqlite/builder.js.map +1 -1
- package/dist/cjs/testing/sqlite/database.d.ts +11 -1
- package/dist/cjs/testing/sqlite/database.d.ts.map +1 -1
- package/dist/cjs/testing/sqlite/database.js +32 -18
- package/dist/cjs/testing/sqlite/database.js.map +1 -1
- package/dist/cjs/transactions.d.ts +14 -0
- package/dist/cjs/transactions.d.ts.map +1 -1
- package/dist/cjs/transactions.js +27 -0
- package/dist/cjs/transactions.js.map +1 -1
- package/dist/esm/adapters/sqlite.js +4 -1
- package/dist/esm/adapters/sqlite.js.map +1 -1
- package/dist/esm/core/errors.js +10 -0
- package/dist/esm/core/errors.js.map +1 -1
- package/dist/esm/decorators/query.js +127 -20
- package/dist/esm/decorators/query.js.map +1 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/run.js +123 -18
- package/dist/esm/run.js.map +1 -1
- package/dist/esm/runtime.js +11 -0
- package/dist/esm/runtime.js.map +1 -1
- package/dist/esm/sql.js +63 -27
- package/dist/esm/sql.js.map +1 -1
- package/dist/esm/stack.js +40 -27
- package/dist/esm/stack.js.map +1 -1
- package/dist/esm/testing/builder.js +14 -5
- package/dist/esm/testing/builder.js.map +1 -1
- package/dist/esm/testing/fixture-provenance.js +15 -0
- package/dist/esm/testing/fixture-provenance.js.map +1 -0
- package/dist/esm/testing/index.js +1 -0
- package/dist/esm/testing/index.js.map +1 -1
- package/dist/esm/testing/postgres/database.js +17 -13
- package/dist/esm/testing/postgres/database.js.map +1 -1
- package/dist/esm/testing/resource.js +106 -0
- package/dist/esm/testing/resource.js.map +1 -0
- package/dist/esm/testing/sqlite/builder.js +10 -2
- package/dist/esm/testing/sqlite/builder.js.map +1 -1
- package/dist/esm/testing/sqlite/database.js +32 -18
- package/dist/esm/testing/sqlite/database.js.map +1 -1
- package/dist/esm/transactions.js +25 -0
- package/dist/esm/transactions.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +128 -39
package/dist/esm/sql.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tagged template literal for SQL queries with `?` placeholders.\n *\n * Usage:\n * sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`\n * sql`INSERT INTO users :insert(${data})`\n * sql`UPDATE users SET :update(${data}) WHERE id = ${id}`\n * sql`SELECT * FROM users WHERE :filter(${criteria})`\n * sql`INSERT INTO users :batch_insert(${rows})`\n */\nconst MACRO_OPEN = /:(insert|update|filter|batch_insert)\\s*\\(\\s*$/i;\nconst MACRO_CLOSE = /^\\s*\\)/;\n\nexport class SqlFragment {\n constructor(\n public readonly strings: readonly string[],\n public readonly values: readonly unknown[],\n ) {}\n\n toSQL(): { sql: string; params: unknown[] } {\n const segments = [...this.strings];\n const sqlParts: string[] = [];\n const params: unknown[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n if (i < this.values.length) {\n const openMatch = segments[i].match(MACRO_OPEN);\n const closeMatch = segments[i + 1]?.match(MACRO_CLOSE);\n\n if (openMatch && closeMatch) {\n // Binder macro: trim the macro syntax and expand\n sqlParts.push(segments[i].slice(0, -openMatch[0].length));\n segments[i + 1] = segments[i + 1].slice(closeMatch[0].length);\n\n const { text, params: expanded } = expandMacro(openMatch[1].toLowerCase(), this.values[i]);\n sqlParts.push(text);\n params.push(...expanded);\n } else {\n sqlParts.push(segments[i]);\n sqlParts.push('?');\n params.push(this.values[i]);\n }\n } else {\n sqlParts.push(segments[i]);\n }\n }\n\n return { sql: sqlParts.join(''), params };\n }\n}\n\nfunction expandMacro(type: string, value: unknown): { text: string; params: unknown[] } {\n switch (type) {\n case 'insert': {\n assertPlainObject(value, ':insert()');\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length === 0) throw new Error(':insert() requires at least one column');\n const cols = keys.join(', ');\n const placeholders = keys.map(() => '?').join(', ');\n return { text: `(${cols}) VALUES (${placeholders})`, params: keys.map(k => obj[k]) };\n }\n case 'update': {\n assertPlainObject(value, ':update()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':update() requires at least one defined column');\n return {\n text: entries.map(([k]) => `${k} = ?`).join(', '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'filter': {\n assertPlainObject(value, ':filter()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':filter() requires at least one defined condition');\n return {\n text: entries.map(([k]) => `${k} = ?`).join(' AND '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'batch_insert': {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(':batch_insert() requires a non-empty array of objects');\n }\n const keys = Object.keys(value[0]);\n if (keys.length === 0) throw new Error(':batch_insert() requires at least one column');\n const cols = keys.join(', ');\n const rowPlaceholder = `(${keys.map(() => '?').join(', ')})`;\n const allPlaceholders = value.map(() => rowPlaceholder).join(', ');\n return {\n text: `(${cols}) VALUES ${allPlaceholders}`,\n params: value.flatMap(row => keys.map(k => (row as Record<string, unknown>)[k])),\n };\n }\n default:\n throw new Error(`Unknown binder macro :${type}()`);\n }\n}\n\nfunction assertPlainObject(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} in sql\\`\\` template requires a plain object`);\n }\n}\n\nexport function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment {\n return new SqlFragment(strings, values);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dialect } from './registry';\n\n/**\n * Tagged template literal for SQL queries with `?` placeholders.\n *\n * Usage:\n * sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`\n * sql`INSERT INTO users :insert(${data})`\n * sql`UPDATE users SET :update(${data}) WHERE id = ${id}`\n * sql`SELECT * FROM users WHERE :filter(${criteria})`\n * sql`INSERT INTO users :batch_insert(${rows})`\n *\n * Execute a fragment with `run(db, fragment)` (or `run(fragment)` inside a\n * @Query method): run binds it for the target dialect (`?` for SQLite/MySQL,\n * `$1..` for Postgres) and wraps failures in DatabaseError.\n */\nconst MACRO_OPEN = /:(insert|update|filter|batch_insert)\\s*\\(\\s*$/i;\nconst MACRO_CLOSE = /^\\s*\\)/;\n\n// Cross-copy identity for fragments: duplicated module instances (npm link,\n// dual ESM/CJS loads) each define their own SqlFragment class, so `instanceof`\n// alone would misclassify a fragment built by another copy.\nconst FRAGMENT_BRAND = Symbol.for('noego.sqlstack.SqlFragment');\n\nexport class SqlFragment {\n constructor(\n public readonly strings: readonly string[],\n public readonly values: readonly unknown[],\n ) {}\n\n /**\n * Compile to SQL with `?` placeholders and the matching parameter list.\n *\n * @throws {Error} When a binder macro (`:insert`, `:update`, `:filter`,\n * `:batch_insert`) receives invalid input. `run(...)` wraps this in\n * DatabaseError with the original error in `.cause`.\n */\n toSQL(): { sql: string; params: unknown[] } {\n return compileParts(this.strings, this.values, () => '?');\n }\n}\n\nObject.defineProperty(SqlFragment.prototype, FRAGMENT_BRAND, { value: true });\n\n/** @internal True for a SqlFragment from this or any other sqlstack copy. */\nexport function isSqlFragment(value: unknown): value is SqlFragment {\n if (value instanceof SqlFragment) return true;\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [FRAGMENT_BRAND]?: unknown })[FRAGMENT_BRAND] === true\n );\n}\n\n/**\n * @internal Compile a fragment for a dialect: `$1, $2, ...` for Postgres,\n * `?` for SQLite and MySQL. Throws plain Error on invalid macro input\n * (callers in run() wrap it).\n */\nexport function compileFragment(fragment: SqlFragment, dialect: Dialect): { sql: string; params: unknown[] } {\n let next = 0;\n const placeholder = dialect === 'postgres' ? () => `$${++next}` : () => '?';\n return compileParts(fragment.strings, fragment.values, placeholder);\n}\n\nfunction compileParts(\n strings: readonly string[],\n values: readonly unknown[],\n placeholder: () => string,\n): { sql: string; params: unknown[] } {\n const segments = [...strings];\n const sqlParts: string[] = [];\n const params: unknown[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n if (i < values.length) {\n const openMatch = segments[i].match(MACRO_OPEN);\n const closeMatch = segments[i + 1]?.match(MACRO_CLOSE);\n\n if (openMatch && closeMatch) {\n // Binder macro: trim the macro syntax and expand\n sqlParts.push(segments[i].slice(0, -openMatch[0].length));\n segments[i + 1] = segments[i + 1].slice(closeMatch[0].length);\n\n const { text, params: expanded } = expandMacro(openMatch[1].toLowerCase(), values[i], placeholder);\n sqlParts.push(text);\n params.push(...expanded);\n } else {\n sqlParts.push(segments[i]);\n sqlParts.push(placeholder());\n params.push(values[i]);\n }\n } else {\n sqlParts.push(segments[i]);\n }\n }\n\n return { sql: sqlParts.join(''), params };\n}\n\nfunction expandMacro(type: string, value: unknown, placeholder: () => string): { text: string; params: unknown[] } {\n switch (type) {\n case 'insert': {\n assertPlainObject(value, ':insert()');\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length === 0) throw new Error(':insert() requires at least one column');\n const cols = keys.join(', ');\n const placeholders = keys.map(() => placeholder()).join(', ');\n return { text: `(${cols}) VALUES (${placeholders})`, params: keys.map(k => obj[k]) };\n }\n case 'update': {\n assertPlainObject(value, ':update()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':update() requires at least one defined column');\n return {\n text: entries.map(([k]) => `${k} = ${placeholder()}`).join(', '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'filter': {\n assertPlainObject(value, ':filter()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':filter() requires at least one defined condition');\n return {\n text: entries.map(([k]) => `${k} = ${placeholder()}`).join(' AND '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'batch_insert': {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(':batch_insert() requires a non-empty array of objects');\n }\n const keys = Object.keys(value[0]);\n if (keys.length === 0) throw new Error(':batch_insert() requires at least one column');\n const cols = keys.join(', ');\n const allPlaceholders = value.map(() => `(${keys.map(() => placeholder()).join(', ')})`).join(', ');\n return {\n text: `(${cols}) VALUES ${allPlaceholders}`,\n params: value.flatMap(row => keys.map(k => (row as Record<string, unknown>)[k])),\n };\n }\n default:\n throw new Error(`Unknown binder macro :${type}()`);\n }\n}\n\nfunction assertPlainObject(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} in sql\\`\\` template requires a plain object`);\n }\n}\n\nexport function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment {\n return new SqlFragment(strings, values);\n}\n"]}
|
package/dist/esm/stack.js
CHANGED
|
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
10
10
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
11
|
};
|
|
12
|
-
var _SqlStack_instances, _SqlStack_entries, _SqlStack_families, _SqlStack_defaultName, _SqlStack_resolver, _SqlStack_closed, _SqlStack_assertOpen, _SqlStack_registeredNames;
|
|
12
|
+
var _SqlStack_instances, _SqlStack_entries, _SqlStack_families, _SqlStack_defaultName, _SqlStack_resolver, _SqlStack_closed, _SqlStack_closing, _SqlStack_assertOpen, _SqlStack_registeredNames;
|
|
13
13
|
import { setGlobalResolver, clearGlobalResolver, ManifestResolver, assertManifestCoverage, assertManifestCoversClasses, } from './core/resolver.js';
|
|
14
14
|
import { SqlStackError } from './core/errors.js';
|
|
15
15
|
import { FamilyRuntime } from './family.js';
|
|
@@ -42,6 +42,7 @@ export class SqlStack {
|
|
|
42
42
|
_SqlStack_defaultName.set(this, void 0);
|
|
43
43
|
_SqlStack_resolver.set(this, void 0);
|
|
44
44
|
_SqlStack_closed.set(this, false);
|
|
45
|
+
_SqlStack_closing.set(this, void 0);
|
|
45
46
|
if (config?.databases) {
|
|
46
47
|
for (const [name, reg] of Object.entries(config.databases)) {
|
|
47
48
|
if (typeof reg === 'object' && reg !== null && 'db' in reg) {
|
|
@@ -206,38 +207,50 @@ export class SqlStack {
|
|
|
206
207
|
* Externally supplied databases (owned: false) are not touched. Failures are
|
|
207
208
|
* collected and reported with database identity rather than swallowed.
|
|
208
209
|
*/
|
|
209
|
-
|
|
210
|
-
if (__classPrivateFieldGet(this,
|
|
211
|
-
return;
|
|
210
|
+
close() {
|
|
211
|
+
if (__classPrivateFieldGet(this, _SqlStack_closing, "f"))
|
|
212
|
+
return __classPrivateFieldGet(this, _SqlStack_closing, "f");
|
|
212
213
|
__classPrivateFieldSet(this, _SqlStack_closed, true, "f");
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
214
|
+
__classPrivateFieldSet(this, _SqlStack_closing, Promise.resolve().then(async () => {
|
|
215
|
+
const failures = [];
|
|
216
|
+
const errors = [];
|
|
217
|
+
const closed = new Set();
|
|
218
|
+
for (const entry of __classPrivateFieldGet(this, _SqlStack_entries, "f").values()) {
|
|
219
|
+
if (!entry.owned || closed.has(entry.db))
|
|
220
|
+
continue;
|
|
221
|
+
closed.add(entry.db);
|
|
222
|
+
try {
|
|
223
|
+
await entry.db.close();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
errors.push(error);
|
|
227
|
+
failures.push(`"${entry.name}": ${error?.message ?? String(error)}`);
|
|
228
|
+
}
|
|
222
229
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
for (const fam of __classPrivateFieldGet(this, _SqlStack_families, "f").values()) {
|
|
231
|
+
try {
|
|
232
|
+
await fam.closeAll();
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
errors.push(error);
|
|
236
|
+
failures.push(error?.message ?? String(error));
|
|
237
|
+
}
|
|
227
238
|
}
|
|
228
|
-
|
|
229
|
-
|
|
239
|
+
__classPrivateFieldGet(this, _SqlStack_entries, "f").clear();
|
|
240
|
+
__classPrivateFieldGet(this, _SqlStack_families, "f").clear();
|
|
241
|
+
__classPrivateFieldSet(this, _SqlStack_resolver, undefined, "f");
|
|
242
|
+
if (failures.length) {
|
|
243
|
+
throw Object.assign(new SqlStackError(`SqlStack close failed for ${failures.join(', ')}`), {
|
|
244
|
+
errors: Object.freeze(errors), cause: errors[0],
|
|
245
|
+
});
|
|
230
246
|
}
|
|
231
|
-
}
|
|
232
|
-
__classPrivateFieldGet(this,
|
|
233
|
-
__classPrivateFieldGet(this, _SqlStack_families, "f").clear();
|
|
234
|
-
__classPrivateFieldSet(this, _SqlStack_resolver, undefined, "f");
|
|
235
|
-
if (failures.length) {
|
|
236
|
-
throw new SqlStackError(`SqlStack close failed for ${failures.join(', ')}`);
|
|
237
|
-
}
|
|
247
|
+
}), "f");
|
|
248
|
+
return __classPrivateFieldGet(this, _SqlStack_closing, "f");
|
|
238
249
|
}
|
|
250
|
+
/** Native IoC resource hook; manually calling close remains supported. */
|
|
251
|
+
dispose() { return this.close(); }
|
|
239
252
|
}
|
|
240
|
-
_SqlStack_entries = new WeakMap(), _SqlStack_families = new WeakMap(), _SqlStack_defaultName = new WeakMap(), _SqlStack_resolver = new WeakMap(), _SqlStack_closed = new WeakMap(), _SqlStack_instances = new WeakSet(), _SqlStack_assertOpen = function _SqlStack_assertOpen() {
|
|
253
|
+
_SqlStack_entries = new WeakMap(), _SqlStack_families = new WeakMap(), _SqlStack_defaultName = new WeakMap(), _SqlStack_resolver = new WeakMap(), _SqlStack_closed = new WeakMap(), _SqlStack_closing = new WeakMap(), _SqlStack_instances = new WeakSet(), _SqlStack_assertOpen = function _SqlStack_assertOpen() {
|
|
241
254
|
if (__classPrivateFieldGet(this, _SqlStack_closed, "f")) {
|
|
242
255
|
throw new SqlStackError('SqlStack has been closed; its owning root was disposed.');
|
|
243
256
|
}
|
package/dist/esm/stack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../src/stack.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Database } from './registry';\nimport type { SqlResolver } from './core/resolver';\nimport {\n setGlobalResolver,\n clearGlobalResolver,\n ManifestResolver,\n assertManifestCoverage,\n assertManifestCoversClasses,\n} from './core/resolver';\nimport { SqlStackError } from './core/errors';\nimport { FamilyRuntime } from './family';\nimport type { FamilyConfig, FamilyMemberEntry, DatabaseLease } from './family';\nimport { currentBinding, runWithBinding, BindingFrame } from './familyBinding';\n\n/**\n * Concrete database identity inside one SqlStack instance.\n *\n * The entry OBJECT is the identity: two roots that both register a database\n * under the name \"primary\" produce two distinct DatabaseEntry instances, so\n * transaction lookup, caches, and diagnostics keyed by entry can never collide\n * across roots merely because the registration text matches.\n *\n * Dynamic database families (spec 17) extend this identity with a normalized\n * key; a static database is the no-key case.\n */\nexport interface DatabaseEntry {\n readonly name: string;\n readonly db: Database;\n /** Close() closes owned databases; externally supplied ones are left open. */\n readonly owned: boolean;\n /** The stack this entry belongs to, for diagnostics and mismatch checks. */\n readonly stack: SqlStack;\n}\n\nexport type DatabaseRegistration =\n | Database\n | {\n db: Database | (() => Database);\n /** Default false for direct Database values, true for factories. */\n owned?: boolean;\n };\n\nexport interface SqlStackConfig {\n databases?: Record<string, DatabaseRegistration>;\n /** Name of the default database. Required when more than one is registered. */\n default?: string;\n resolver?: SqlResolver;\n}\n\n/**\n * Environment-owned SQLStack runtime (spec 16 §12-13, repo spec 19 §3-5).\n *\n * One instance per owning IoC root; child scopes resolve the root's Singleton.\n * Native @Query/transaction/resolver execution goes through an instance —\n * never through the process-global SqlStackDB registry.\n */\nexport class SqlStack {\n /**\n * @deprecated Legacy v2 process-global resolver registration\n * (`SqlStack.useResolver(...)`). Forwards to the deprecated global state,\n * which is only consulted when no IoC environment is active. Use the\n * per-instance `useResolver` / SqlStackConfig.resolver instead.\n */\n static useResolver(resolver: SqlResolver): void {\n setGlobalResolver(resolver);\n }\n\n /** @deprecated See static useResolver. */\n static clearResolver(): void {\n clearGlobalResolver();\n }\n\n #entries = new Map<string, DatabaseEntry>();\n #families = new Map<string, FamilyRuntime<any>>();\n #defaultName: string | undefined;\n #resolver: SqlResolver | undefined;\n #closed = false;\n\n constructor(config?: SqlStackConfig) {\n if (config?.databases) {\n for (const [name, reg] of Object.entries(config.databases)) {\n if (typeof reg === 'object' && reg !== null && 'db' in reg) {\n const db = typeof reg.db === 'function' ? reg.db() : reg.db;\n this.register(name, db, { owned: reg.owned ?? typeof reg.db === 'function' });\n } else {\n this.register(name, reg as Database);\n }\n }\n }\n if (config?.default) this.setDefault(config.default);\n if (config?.resolver) this.useResolver(config.resolver);\n }\n\n register(name: string, db: Database, options?: { owned?: boolean; default?: boolean }): this {\n this.#assertOpen();\n const entry: DatabaseEntry = { name, db, owned: options?.owned ?? false, stack: this };\n this.#entries.set(name, entry);\n if (options?.default || this.#entries.size === 1) {\n this.#defaultName ??= name;\n if (options?.default) this.#defaultName = name;\n }\n return this;\n }\n\n setDefault(name: string): this {\n this.#assertOpen();\n if (!this.#entries.has(name)) {\n throw new SqlStackError(\n `SqlStack has no database registered under \"${name}\". Registered: ${this.#registeredNames()}`\n );\n }\n this.#defaultName = name;\n return this;\n }\n\n getEntry(name?: string): DatabaseEntry {\n this.#assertOpen();\n const key = name ?? this.#defaultName;\n if (!key) {\n throw new SqlStackError(\n 'SqlStack has no default database. Register one with { default: true } or call setDefault(name).'\n );\n }\n const entry = this.#entries.get(key);\n if (!entry) {\n throw new SqlStackError(\n `SqlStack has no database registered under \"${key}\". Registered: ${this.#registeredNames()}`\n );\n }\n return entry;\n }\n\n database(name?: string): Database {\n return this.getEntry(name).db;\n }\n\n has(name: string): boolean {\n return this.#entries.has(name) || this.#families.has(name);\n }\n\n /**\n * Register a dynamic database family (spec 17): one registration whose\n * concrete member is selected per execution flow via withDatabase(...).\n */\n registerFamily<K = string>(name: string, config: FamilyConfig<K>): this {\n this.#assertOpen();\n if (this.#entries.has(name)) {\n throw new SqlStackError(`\"${name}\" is already registered as a static database.`);\n }\n this.#families.set(name, new FamilyRuntime(this, name, config));\n return this;\n }\n\n family<K = string>(name: string): FamilyRuntime<K> {\n const fam = this.#families.get(name);\n if (!fam) {\n throw new SqlStackError(\n `SqlStack has no database family registered under \"${name}\". Families: ${\n [...this.#families.keys()].map((n) => `\"${n}\"`).join(', ') || '(none)'\n }`\n );\n }\n return fam;\n }\n\n /**\n * Bind a family registration to a concrete member for the duration of fn\n * (spec 17 §10). The binding is flow-local to this SqlStack; the member's\n * connection is leased lazily on first database resolution and released\n * when fn settles. Rebinding a family to a different key while it has an\n * active transaction fails deterministically (spec 17 §29).\n */\n async withDatabase<T>(family: string, key: unknown, fn: () => Promise<T>): Promise<T> {\n this.#assertOpen();\n const fam = this.family(family);\n const { keyId } = fam.normalize(key);\n\n const outer = currentBinding(this, family);\n if (outer && outer.keyId !== keyId && outer.lease) {\n const { getActiveTransaction } = await import('./transactions');\n if (getActiveTransaction(outer.lease.entry)) {\n throw new SqlStackError(\n `Cannot rebind database family \"${family}\" from \"${outer.keyId}\" to \"${keyId}\": ` +\n 'a transaction is active for the current member. Finish the transaction first.'\n );\n }\n }\n\n const frame: BindingFrame = { stack: this, family, keyInput: key, keyId };\n try {\n return await runWithBinding(frame, fn);\n } finally {\n await frame.lease?.release();\n }\n }\n\n /**\n * Async database resolution for query/transaction execution. Static\n * registrations resolve directly; family registrations resolve through the\n * active flow-local binding, opening/leasing the member on first use.\n */\n async resolveEntry(name?: string): Promise<DatabaseEntry> {\n this.#assertOpen();\n const key =\n name ??\n this.#defaultName ??\n // A stack whose only registration is one family treats it as default.\n (this.#entries.size === 0 && this.#families.size === 1\n ? [...this.#families.keys()][0]\n : undefined);\n if (key !== undefined) {\n const entry = this.#entries.get(key);\n if (entry) return entry;\n const fam = this.#families.get(key);\n if (fam) {\n const frame = currentBinding(this, key);\n if (!frame) {\n throw new SqlStackError(\n `SQLStack database family \"${key}\" requires a concrete database key. ` +\n 'No active binding exists for this execution. Establish the database at the ' +\n `orchestration boundary with sqlStack.withDatabase(\"${key}\", key, ...) ` +\n 'or acquire a member explicitly via sqlStack.family(...).with(...).'\n );\n }\n if (!frame.lease) {\n frame.lease = await fam.acquire(frame.keyInput);\n }\n return frame.lease.entry;\n }\n }\n return this.getEntry(name);\n }\n\n /**\n * Set this stack's SQL resolver. When `options.classes` supplies the\n * repository catalog, manifest coverage is validated from those classes'\n * immutable metadata (environment-local, unpollutable by unrelated\n * imports). Without a catalog, coverage falls back to the deprecated\n * process-global decorated list.\n */\n useResolver(\n resolver: SqlResolver,\n options?: { classes?: ReadonlyArray<abstract new (...args: any[]) => any> }\n ): this {\n this.#assertOpen();\n if (resolver instanceof ManifestResolver && resolver.options.assert !== false) {\n if (options?.classes) {\n assertManifestCoversClasses(resolver, options.classes);\n } else {\n assertManifestCoverage(resolver);\n }\n }\n this.#resolver = resolver;\n return this;\n }\n\n resolver(): SqlResolver | undefined {\n return this.#resolver;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n /**\n * Close owned database adapters exactly once and release runtime resources.\n * Externally supplied databases (owned: false) are not touched. Failures are\n * collected and reported with database identity rather than swallowed.\n */\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n const failures: string[] = [];\n for (const entry of this.#entries.values()) {\n if (!entry.owned) continue;\n try {\n await entry.db.close();\n } catch (err: any) {\n failures.push(`\"${entry.name}\": ${err?.message ?? String(err)}`);\n }\n }\n for (const fam of this.#families.values()) {\n try {\n await fam.closeAll();\n } catch (err: any) {\n failures.push(err?.message ?? String(err));\n }\n }\n this.#entries.clear();\n this.#families.clear();\n this.#resolver = undefined;\n if (failures.length) {\n throw new SqlStackError(`SqlStack close failed for ${failures.join(', ')}`);\n }\n }\n\n #assertOpen(): void {\n if (this.#closed) {\n throw new SqlStackError('SqlStack has been closed; its owning root was disposed.');\n }\n }\n\n #registeredNames(): string {\n const names = [\n ...[...this.#entries.keys()].map((n) => `\"${n}\"`),\n ...[...this.#families.keys()].map((n) => `\"${n}\" (family)`),\n ];\n return names.length ? names.join(', ') : '(none)';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../src/stack.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAgB,MAAM,iBAAiB,CAAC;AAqC/E;;;;;;GAMG;AACH,MAAM,OAAO,QAAQ;IACnB;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,QAAqB;QACtC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAED,0CAA0C;IAC1C,MAAM,CAAC,aAAa;QAClB,mBAAmB,EAAE,CAAC;IACxB,CAAC;IASD,YAAY,MAAuB;;QAPnC,4BAAW,IAAI,GAAG,EAAyB,EAAC;QAC5C,6BAAY,IAAI,GAAG,EAA8B,EAAC;QAClD,wCAAiC;QACjC,qCAAmC;QACnC,2BAAU,KAAK,EAAC;QAChB,oCAAoC;QAGlC,IAAI,MAAM,EAAE,SAAS,EAAE,CAAC;YACtB,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3D,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;oBAC3D,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5D,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC,CAAC;gBAChF,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAe,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,MAAM,EAAE,OAAO;YAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,MAAM,EAAE,QAAQ;YAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1D,CAAC;IAED,QAAQ,CAAC,IAAY,EAAE,EAAY,EAAE,OAAgD;QACnF,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,MAAM,KAAK,GAAkB,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACvF,uBAAA,IAAI,yBAAS,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,IAAI,OAAO,EAAE,OAAO,IAAI,uBAAA,IAAI,yBAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACjD,gHAAsB,IAAI,MAAA,CAAC;YAC3B,IAAI,OAAO,EAAE,OAAO;gBAAE,uBAAA,IAAI,yBAAgB,IAAI,MAAA,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU,CAAC,IAAY;QACrB,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,IAAI,CAAC,uBAAA,IAAI,yBAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,aAAa,CACrB,8CAA8C,IAAI,kBAAkB,uBAAA,IAAI,sDAAiB,MAArB,IAAI,CAAmB,EAAE,CAC9F,CAAC;QACJ,CAAC;QACD,uBAAA,IAAI,yBAAgB,IAAI,MAAA,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,IAAa;QACpB,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,MAAM,GAAG,GAAG,IAAI,IAAI,uBAAA,IAAI,6BAAa,CAAC;QACtC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,aAAa,CACrB,iGAAiG,CAClG,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,uBAAA,IAAI,yBAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,aAAa,CACrB,8CAA8C,GAAG,kBAAkB,uBAAA,IAAI,sDAAiB,MAArB,IAAI,CAAmB,EAAE,CAC7F,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ,CAAC,IAAa;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;IAChC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,uBAAA,IAAI,yBAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,uBAAA,IAAI,0BAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED;;;OAGG;IACH,cAAc,CAAa,IAAY,EAAE,MAAuB;QAC9D,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,IAAI,uBAAA,IAAI,yBAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,IAAI,IAAI,+CAA+C,CAAC,CAAC;QACnF,CAAC;QACD,uBAAA,IAAI,0BAAU,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAa,IAAY;QAC7B,MAAM,GAAG,GAAG,uBAAA,IAAI,0BAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,aAAa,CACrB,qDAAqD,IAAI,gBACvD,CAAC,GAAG,uBAAA,IAAI,0BAAU,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAChE,EAAE,CACH,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAI,MAAc,EAAE,GAAY,EAAE,EAAoB;QACtE,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAErC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAClD,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAChE,IAAI,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5C,MAAM,IAAI,aAAa,CACrB,kCAAkC,MAAM,WAAW,KAAK,CAAC,KAAK,SAAS,KAAK,KAAK;oBAC/E,+EAA+E,CAClF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAiB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QAC1E,IAAI,CAAC;YACH,OAAO,MAAM,cAAc,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzC,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,IAAa;QAC9B,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,MAAM,GAAG,GACP,IAAI;YACJ,uBAAA,IAAI,6BAAa;YACjB,sEAAsE;YACtE,CAAC,uBAAA,IAAI,yBAAS,CAAC,IAAI,KAAK,CAAC,IAAI,uBAAA,IAAI,0BAAU,CAAC,IAAI,KAAK,CAAC;gBACpD,CAAC,CAAC,CAAC,GAAG,uBAAA,IAAI,0BAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC/B,CAAC,CAAC,SAAS,CAAC,CAAC;QACjB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,uBAAA,IAAI,yBAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;YACxB,MAAM,GAAG,GAAG,uBAAA,IAAI,0BAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBACxC,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,MAAM,IAAI,aAAa,CACrB,6BAA6B,GAAG,sCAAsC;wBACpE,6EAA6E;wBAC7E,sDAAsD,GAAG,eAAe;wBACxE,oEAAoE,CACvE,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACjB,KAAK,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAClD,CAAC;gBACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACH,WAAW,CACT,QAAqB,EACrB,OAA2E;QAE3E,uBAAA,IAAI,iDAAY,MAAhB,IAAI,CAAc,CAAC;QACnB,IAAI,QAAQ,YAAY,gBAAgB,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC9E,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;gBACrB,2BAA2B,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,sBAAsB,CAAC,QAAQ,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QACD,uBAAA,IAAI,sBAAa,QAAQ,MAAA,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ;QACN,OAAO,uBAAA,IAAI,0BAAU,CAAC;IACxB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,uBAAA,IAAI,wBAAQ,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,KAAK;QACH,IAAI,uBAAA,IAAI,yBAAS;YAAE,OAAO,uBAAA,IAAI,yBAAS,CAAC;QACxC,uBAAA,IAAI,oBAAW,IAAI,MAAA,CAAC;QACpB,uBAAA,IAAI,qBAAY,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YAChD,MAAM,QAAQ,GAAa,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAc,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAY,CAAC;YACnC,KAAK,MAAM,KAAK,IAAI,uBAAA,IAAI,yBAAS,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC3C,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAAE,SAAS;gBACnD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACrB,IAAI,CAAC;oBAAC,MAAM,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBAAC,CAAC;gBAC/B,OAAO,KAAU,EAAE,CAAC;oBAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBACnB,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,uBAAA,IAAI,0BAAU,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1C,IAAI,CAAC;oBAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC;gBAAC,CAAC;gBAC7B,OAAO,KAAU,EAAE,CAAC;oBAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAAC,CAAC;YAC5F,CAAC;YACD,uBAAA,IAAI,yBAAS,CAAC,KAAK,EAAE,CAAC;YACtB,uBAAA,IAAI,0BAAU,CAAC,KAAK,EAAE,CAAC;YACvB,uBAAA,IAAI,sBAAa,SAAS,MAAA,CAAC;YAC3B,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,6BAA6B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;oBACzF,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;iBAChD,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,MAAA,CAAC;QACH,OAAO,uBAAA,IAAI,yBAAS,CAAC;IACvB,CAAC;IAED,0EAA0E;IAC1E,OAAO,KAAoB,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;CAelD;;IAZG,IAAI,uBAAA,IAAI,wBAAQ,EAAE,CAAC;QACjB,MAAM,IAAI,aAAa,CAAC,yDAAyD,CAAC,CAAC;IACrF,CAAC;AACH,CAAC;IAGC,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,GAAG,uBAAA,IAAI,yBAAS,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;QACjD,GAAG,CAAC,GAAG,uBAAA,IAAI,0BAAU,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC;KAC5D,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpD,CAAC","sourcesContent":["import type { Database } from './registry';\nimport type { SqlResolver } from './core/resolver';\nimport {\n setGlobalResolver,\n clearGlobalResolver,\n ManifestResolver,\n assertManifestCoverage,\n assertManifestCoversClasses,\n} from './core/resolver';\nimport { SqlStackError } from './core/errors';\nimport { FamilyRuntime } from './family';\nimport type { FamilyConfig, FamilyMemberEntry, DatabaseLease } from './family';\nimport { currentBinding, runWithBinding, BindingFrame } from './familyBinding';\n\n/**\n * Concrete database identity inside one SqlStack instance.\n *\n * The entry OBJECT is the identity: two roots that both register a database\n * under the name \"primary\" produce two distinct DatabaseEntry instances, so\n * transaction lookup, caches, and diagnostics keyed by entry can never collide\n * across roots merely because the registration text matches.\n *\n * Dynamic database families (spec 17) extend this identity with a normalized\n * key; a static database is the no-key case.\n */\nexport interface DatabaseEntry {\n readonly name: string;\n readonly db: Database;\n /** Close() closes owned databases; externally supplied ones are left open. */\n readonly owned: boolean;\n /** The stack this entry belongs to, for diagnostics and mismatch checks. */\n readonly stack: SqlStack;\n}\n\nexport type DatabaseRegistration =\n | Database\n | {\n db: Database | (() => Database);\n /** Default false for direct Database values, true for factories. */\n owned?: boolean;\n };\n\nexport interface SqlStackConfig {\n databases?: Record<string, DatabaseRegistration>;\n /** Name of the default database. Required when more than one is registered. */\n default?: string;\n resolver?: SqlResolver;\n}\n\n/**\n * Environment-owned SQLStack runtime (spec 16 §12-13, repo spec 19 §3-5).\n *\n * One instance per owning IoC root; child scopes resolve the root's Singleton.\n * Native @Query/transaction/resolver execution goes through an instance —\n * never through the process-global SqlStackDB registry.\n */\nexport class SqlStack {\n /**\n * @deprecated Legacy v2 process-global resolver registration\n * (`SqlStack.useResolver(...)`). Forwards to the deprecated global state,\n * which is only consulted when no IoC environment is active. Use the\n * per-instance `useResolver` / SqlStackConfig.resolver instead.\n */\n static useResolver(resolver: SqlResolver): void {\n setGlobalResolver(resolver);\n }\n\n /** @deprecated See static useResolver. */\n static clearResolver(): void {\n clearGlobalResolver();\n }\n\n #entries = new Map<string, DatabaseEntry>();\n #families = new Map<string, FamilyRuntime<any>>();\n #defaultName: string | undefined;\n #resolver: SqlResolver | undefined;\n #closed = false;\n #closing: Promise<void> | undefined;\n\n constructor(config?: SqlStackConfig) {\n if (config?.databases) {\n for (const [name, reg] of Object.entries(config.databases)) {\n if (typeof reg === 'object' && reg !== null && 'db' in reg) {\n const db = typeof reg.db === 'function' ? reg.db() : reg.db;\n this.register(name, db, { owned: reg.owned ?? typeof reg.db === 'function' });\n } else {\n this.register(name, reg as Database);\n }\n }\n }\n if (config?.default) this.setDefault(config.default);\n if (config?.resolver) this.useResolver(config.resolver);\n }\n\n register(name: string, db: Database, options?: { owned?: boolean; default?: boolean }): this {\n this.#assertOpen();\n const entry: DatabaseEntry = { name, db, owned: options?.owned ?? false, stack: this };\n this.#entries.set(name, entry);\n if (options?.default || this.#entries.size === 1) {\n this.#defaultName ??= name;\n if (options?.default) this.#defaultName = name;\n }\n return this;\n }\n\n setDefault(name: string): this {\n this.#assertOpen();\n if (!this.#entries.has(name)) {\n throw new SqlStackError(\n `SqlStack has no database registered under \"${name}\". Registered: ${this.#registeredNames()}`\n );\n }\n this.#defaultName = name;\n return this;\n }\n\n getEntry(name?: string): DatabaseEntry {\n this.#assertOpen();\n const key = name ?? this.#defaultName;\n if (!key) {\n throw new SqlStackError(\n 'SqlStack has no default database. Register one with { default: true } or call setDefault(name).'\n );\n }\n const entry = this.#entries.get(key);\n if (!entry) {\n throw new SqlStackError(\n `SqlStack has no database registered under \"${key}\". Registered: ${this.#registeredNames()}`\n );\n }\n return entry;\n }\n\n database(name?: string): Database {\n return this.getEntry(name).db;\n }\n\n has(name: string): boolean {\n return this.#entries.has(name) || this.#families.has(name);\n }\n\n /**\n * Register a dynamic database family (spec 17): one registration whose\n * concrete member is selected per execution flow via withDatabase(...).\n */\n registerFamily<K = string>(name: string, config: FamilyConfig<K>): this {\n this.#assertOpen();\n if (this.#entries.has(name)) {\n throw new SqlStackError(`\"${name}\" is already registered as a static database.`);\n }\n this.#families.set(name, new FamilyRuntime(this, name, config));\n return this;\n }\n\n family<K = string>(name: string): FamilyRuntime<K> {\n const fam = this.#families.get(name);\n if (!fam) {\n throw new SqlStackError(\n `SqlStack has no database family registered under \"${name}\". Families: ${\n [...this.#families.keys()].map((n) => `\"${n}\"`).join(', ') || '(none)'\n }`\n );\n }\n return fam;\n }\n\n /**\n * Bind a family registration to a concrete member for the duration of fn\n * (spec 17 §10). The binding is flow-local to this SqlStack; the member's\n * connection is leased lazily on first database resolution and released\n * when fn settles. Rebinding a family to a different key while it has an\n * active transaction fails deterministically (spec 17 §29).\n */\n async withDatabase<T>(family: string, key: unknown, fn: () => Promise<T>): Promise<T> {\n this.#assertOpen();\n const fam = this.family(family);\n const { keyId } = fam.normalize(key);\n\n const outer = currentBinding(this, family);\n if (outer && outer.keyId !== keyId && outer.lease) {\n const { getActiveTransaction } = await import('./transactions');\n if (getActiveTransaction(outer.lease.entry)) {\n throw new SqlStackError(\n `Cannot rebind database family \"${family}\" from \"${outer.keyId}\" to \"${keyId}\": ` +\n 'a transaction is active for the current member. Finish the transaction first.'\n );\n }\n }\n\n const frame: BindingFrame = { stack: this, family, keyInput: key, keyId };\n try {\n return await runWithBinding(frame, fn);\n } finally {\n await frame.lease?.release();\n }\n }\n\n /**\n * Async database resolution for query/transaction execution. Static\n * registrations resolve directly; family registrations resolve through the\n * active flow-local binding, opening/leasing the member on first use.\n */\n async resolveEntry(name?: string): Promise<DatabaseEntry> {\n this.#assertOpen();\n const key =\n name ??\n this.#defaultName ??\n // A stack whose only registration is one family treats it as default.\n (this.#entries.size === 0 && this.#families.size === 1\n ? [...this.#families.keys()][0]\n : undefined);\n if (key !== undefined) {\n const entry = this.#entries.get(key);\n if (entry) return entry;\n const fam = this.#families.get(key);\n if (fam) {\n const frame = currentBinding(this, key);\n if (!frame) {\n throw new SqlStackError(\n `SQLStack database family \"${key}\" requires a concrete database key. ` +\n 'No active binding exists for this execution. Establish the database at the ' +\n `orchestration boundary with sqlStack.withDatabase(\"${key}\", key, ...) ` +\n 'or acquire a member explicitly via sqlStack.family(...).with(...).'\n );\n }\n if (!frame.lease) {\n frame.lease = await fam.acquire(frame.keyInput);\n }\n return frame.lease.entry;\n }\n }\n return this.getEntry(name);\n }\n\n /**\n * Set this stack's SQL resolver. When `options.classes` supplies the\n * repository catalog, manifest coverage is validated from those classes'\n * immutable metadata (environment-local, unpollutable by unrelated\n * imports). Without a catalog, coverage falls back to the deprecated\n * process-global decorated list.\n */\n useResolver(\n resolver: SqlResolver,\n options?: { classes?: ReadonlyArray<abstract new (...args: any[]) => any> }\n ): this {\n this.#assertOpen();\n if (resolver instanceof ManifestResolver && resolver.options.assert !== false) {\n if (options?.classes) {\n assertManifestCoversClasses(resolver, options.classes);\n } else {\n assertManifestCoverage(resolver);\n }\n }\n this.#resolver = resolver;\n return this;\n }\n\n resolver(): SqlResolver | undefined {\n return this.#resolver;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n /**\n * Close owned database adapters exactly once and release runtime resources.\n * Externally supplied databases (owned: false) are not touched. Failures are\n * collected and reported with database identity rather than swallowed.\n */\n close(): Promise<void> {\n if (this.#closing) return this.#closing;\n this.#closed = true;\n this.#closing = Promise.resolve().then(async () => {\n const failures: string[] = [];\n const errors: unknown[] = [];\n const closed = new Set<Database>();\n for (const entry of this.#entries.values()) {\n if (!entry.owned || closed.has(entry.db)) continue;\n closed.add(entry.db);\n try { await entry.db.close(); }\n catch (error: any) {\n errors.push(error);\n failures.push(`\"${entry.name}\": ${error?.message ?? String(error)}`);\n }\n }\n for (const fam of this.#families.values()) {\n try { await fam.closeAll(); }\n catch (error: any) { errors.push(error); failures.push(error?.message ?? String(error)); }\n }\n this.#entries.clear();\n this.#families.clear();\n this.#resolver = undefined;\n if (failures.length) {\n throw Object.assign(new SqlStackError(`SqlStack close failed for ${failures.join(', ')}`), {\n errors: Object.freeze(errors), cause: errors[0],\n });\n }\n });\n return this.#closing;\n }\n\n /** Native IoC resource hook; manually calling close remains supported. */\n dispose(): Promise<void> { return this.close(); }\n\n #assertOpen(): void {\n if (this.#closed) {\n throw new SqlStackError('SqlStack has been closed; its owning root was disposed.');\n }\n }\n\n #registeredNames(): string {\n const names = [\n ...[...this.#entries.keys()].map((n) => `\"${n}\"`),\n ...[...this.#families.keys()].map((n) => `\"${n}\" (family)`),\n ];\n return names.length ? names.join(', ') : '(none)';\n }\n}\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import { adoptFixtureResource, TestDatabaseBuildError } from './resource.js';
|
|
3
|
+
import { assertFixtureSqlFile } from './fixture-provenance.js';
|
|
2
4
|
import { parseTestSchema } from './schema/parser.js';
|
|
3
5
|
import { generateDdl } from './schema/ddl.js';
|
|
4
6
|
import { allocateDatabase } from './postgres/allocator.js';
|
|
@@ -12,6 +14,7 @@ function readRawSql(schema) {
|
|
|
12
14
|
return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };
|
|
13
15
|
}
|
|
14
16
|
try {
|
|
17
|
+
assertFixtureSqlFile(entry.resolvedPath);
|
|
15
18
|
return {
|
|
16
19
|
label: `sql entry #${entry.index} (${entry.sourcePath})`,
|
|
17
20
|
sql: fs.readFileSync(entry.resolvedPath, 'utf8'),
|
|
@@ -61,14 +64,20 @@ function makeBuilder(state) {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
await insertRows((sql, params) => handle._pool.query(sql, params), state.schema, effective);
|
|
64
|
-
return handle;
|
|
67
|
+
return adoptFixtureResource(handle);
|
|
65
68
|
}
|
|
66
69
|
catch (err) {
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
// Use the handle's all-steps disposer and retire its provenance even
|
|
71
|
+
// after failed DDL/data. Preserve allocation and every cleanup failure.
|
|
72
|
+
try {
|
|
73
|
+
if (handle)
|
|
74
|
+
await handle.dispose();
|
|
75
|
+
else
|
|
76
|
+
await dropDatabase(allocated.name, state.options);
|
|
77
|
+
}
|
|
78
|
+
catch (cleanup) {
|
|
79
|
+
throw new TestDatabaseBuildError('PostgreSQL fixture build and cleanup failed', err, cleanup);
|
|
70
80
|
}
|
|
71
|
-
await dropDatabase(allocated.name, state.options).catch(() => undefined);
|
|
72
81
|
throw err;
|
|
73
82
|
}
|
|
74
83
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../../src/testing/builder.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../../src/testing/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport { adoptFixtureResource, TestDatabaseBuildError } from './resource';\nimport { assertFixtureSqlFile } from './fixture-provenance';\nimport { parseTestSchema } from './schema/parser';\nimport { generateDdl } from './schema/ddl';\nimport type { NormalizedTestSchema, ParseTestSchemaOptions } from './schema/types';\nimport { allocateDatabase } from './postgres/allocator';\nimport { createDatabaseHandle, TestPostgresDatabase } from './postgres/database';\nimport { dropDatabase } from './postgres/allocator';\nimport { TestPostgresServerError, TestServerOptions } from './postgres/server';\nimport { composeRows, DataOperation, insertRows, Row, toOperations } from './postgres/loadData';\n\nexport interface TestPostgresOptions extends TestServerOptions, ParseTestSchemaOptions {}\n\nexport interface TestPostgresBuilder {\n /** The normalized immutable schema (useful for assertions/diagnostics). */\n readonly schema: NormalizedTestSchema;\n /** @internal Accumulated row operations (exposed for pure tests). */\n readonly ops: readonly DataOperation[];\n /** Returns a NEW derived builder; this builder is unchanged. */\n data(rows: Record<string, Row[]>): TestPostgresBuilder;\n /**\n * Create a fresh isolated PostgreSQL database, apply DDL + raw SQL,\n * insert effective scenario rows, and return the handle. Does not\n * consume the builder: building twice yields two independent databases.\n */\n build(): Promise<TestPostgresDatabase>;\n}\n\ninterface BuilderState {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n readonly options: Readonly<TestPostgresOptions>;\n}\n\nfunction readRawSql(schema: NormalizedTestSchema): { label: string; sql: string }[] {\n return schema.sql.map((entry) => {\n if (entry.kind === 'inline') {\n return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };\n }\n try {\n assertFixtureSqlFile(entry.resolvedPath);\n return {\n label: `sql entry #${entry.index} (${entry.sourcePath})`,\n sql: fs.readFileSync(entry.resolvedPath, 'utf8'),\n };\n } catch (err) {\n throw new TestPostgresServerError(\n `Cannot read raw SQL file for sql entry #${entry.index}: ${entry.resolvedPath} ` +\n `(authored as \"${entry.sourcePath}\" in ${schema.source}): ${(err as Error).message}`\n );\n }\n });\n}\n\nfunction makeBuilder(state: BuilderState): TestPostgresBuilder {\n return {\n schema: state.schema,\n ops: state.ops,\n data(rows: Record<string, Row[]>): TestPostgresBuilder {\n const newOps = toOperations(state.schema, rows);\n return makeBuilder({ ...state, ops: [...state.ops, ...newOps] });\n },\n async build(): Promise<TestPostgresDatabase> {\n // Fail on pure planning errors (cycles, raw SQL file readability)\n // before touching the server where possible.\n const rawSql = readRawSql(state.schema);\n const ddl = generateDdl(state.schema);\n const effective = composeRows(state.schema, state.ops);\n\n const allocated = await allocateDatabase(state.options);\n let handle: (TestPostgresDatabase & { _pool: any }) | undefined;\n try {\n handle = await createDatabaseHandle(allocated, state.options);\n for (const statement of ddl) {\n try {\n await handle._pool.query(statement);\n } catch (err) {\n throw new TestPostgresServerError(\n `Failed to apply structured DDL from ${state.schema.source}: ${(err as Error).message}\\n` +\n `Statement:\\n${statement}`\n );\n }\n }\n for (const entry of rawSql) {\n try {\n // Simple-query protocol: the pg driver executes multi-statement\n // scripts directly when no parameters are passed.\n await handle._pool.query(entry.sql);\n } catch (err) {\n throw new TestPostgresServerError(\n `Failed to apply raw ${entry.label} from ${state.schema.source}: ${(err as Error).message}`\n );\n }\n }\n await insertRows((sql, params) => handle!._pool.query(sql, params), state.schema, effective);\n return adoptFixtureResource(handle);\n } catch (err) {\n // Use the handle's all-steps disposer and retire its provenance even\n // after failed DDL/data. Preserve allocation and every cleanup failure.\n try {\n if (handle) await handle.dispose();\n else await dropDatabase(allocated.name, state.options);\n } catch (cleanup) {\n throw new TestDatabaseBuildError('PostgreSQL fixture build and cleanup failed', err, cleanup);\n }\n throw err;\n }\n },\n };\n}\n\n/**\n * Persistent immutable test database builder.\n *\n * `schema` may be a parsed schema object, a YAML string, or a path to a\n * .yml/.yaml file (raw .sql entries then resolve relative to that file's\n * directory unless options.sourceDir overrides it).\n */\nexport function testPostgres(\n schema: string | object,\n options: TestPostgresOptions = {}\n): TestPostgresBuilder {\n const normalized =\n isNormalizedSchema(schema) ? schema : parseTestSchema(schema as string | object, options);\n return makeBuilder({ schema: normalized, ops: [], options: Object.freeze({ ...options }) });\n}\n\nfunction isNormalizedSchema(input: unknown): input is NormalizedTestSchema {\n return (\n !!input &&\n typeof input === 'object' &&\n Array.isArray((input as NormalizedTestSchema).tables) &&\n Array.isArray((input as NormalizedTestSchema).sql) &&\n typeof (input as NormalizedTestSchema).source === 'string'\n );\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { SqlStackError } from '../core/errors.js';
|
|
4
|
+
/** File provenance guard; inline SQL still requires architecture/source review. */
|
|
5
|
+
export function assertFixtureSqlFile(file) {
|
|
6
|
+
const resolved = realpathSync(file);
|
|
7
|
+
for (const candidate of [file, resolved]) {
|
|
8
|
+
const segments = candidate.split(/[\\/]/);
|
|
9
|
+
if (segments.some(segment => /^migrations?$/i.test(segment))
|
|
10
|
+
|| /\.(up|down)\.(sql|[cm]?js|ts)$/i.test(path.basename(candidate))) {
|
|
11
|
+
throw new SqlStackError(`Application fixtures cannot read migration file ${file}; use explicit schema/plain data and keep migration transitions in @noego/proper/testing.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=fixture-provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture-provenance.js","sourceRoot":"","sources":["../../../src/testing/fixture-provenance.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,mFAAmF;AACnF,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,KAAK,MAAM,SAAS,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;eACvD,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,aAAa,CACrB,mDAAmD,IAAI,2FAA2F,CACnJ,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import path from 'node:path';\nimport { realpathSync } from 'node:fs';\nimport { SqlStackError } from '../core/errors';\n\n/** File provenance guard; inline SQL still requires architecture/source review. */\nexport function assertFixtureSqlFile(file: string): void {\n const resolved = realpathSync(file);\n for (const candidate of [file, resolved]) {\n const segments = candidate.split(/[\\\\/]/);\n if (segments.some(segment => /^migrations?$/i.test(segment))\n || /\\.(up|down)\\.(sql|[cm]?js|ts)$/i.test(path.basename(candidate))) {\n throw new SqlStackError(\n `Application fixtures cannot read migration file ${file}; use explicit schema/plain data and keep migration transitions in @noego/proper/testing.`,\n );\n }\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { testPostgres } from './builder.js';
|
|
2
2
|
export { testSqlite } from './sqlite/builder.js';
|
|
3
3
|
export { TestSqliteError } from './sqlite/database.js';
|
|
4
|
+
export { isDisposableTestDatabase, TestDatabaseDisposeError, TestDatabaseBuildError } from './resource.js';
|
|
4
5
|
export { parseTestSchema } from './schema/parser.js';
|
|
5
6
|
export { normalizeTestSchema, TestSchemaError } from './schema/normalize.js';
|
|
6
7
|
export { generateDdl } from './schema/ddl.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export { testPostgres } from './builder';\nexport type { TestPostgresBuilder, TestPostgresOptions } from './builder';\nexport type { TestPostgresDatabase } from './postgres/database';\nexport { testSqlite } from './sqlite/builder';\nexport type { TestSqliteBuilder, TestSqliteOptions } from './sqlite/builder';\nexport { TestSqliteError } from './sqlite/database';\nexport type { TestSqliteDatabase } from './sqlite/database';\nexport { parseTestSchema } from './schema/parser';\nexport { normalizeTestSchema, TestSchemaError } from './schema/normalize';\nexport { generateDdl } from './schema/ddl';\nexport { TestDataError } from './postgres/loadData';\nexport { TestPostgresServerError, DEFAULT_ADMIN_URL } from './postgres/server';\nexport type {\n NormalizedTestSchema,\n NormalizedTable,\n NormalizedColumn,\n RawSqlEntry,\n ParseTestSchemaOptions,\n} from './schema/types';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAExG,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export { testPostgres } from './builder';\nexport type { TestPostgresBuilder, TestPostgresOptions } from './builder';\nexport type { TestPostgresDatabase } from './postgres/database';\nexport { testSqlite } from './sqlite/builder';\nexport type { TestSqliteBuilder, TestSqliteOptions } from './sqlite/builder';\nexport { TestSqliteError } from './sqlite/database';\nexport type { TestSqliteDatabase } from './sqlite/database';\nexport { isDisposableTestDatabase, TestDatabaseDisposeError, TestDatabaseBuildError } from './resource';\nexport type { DisposableTestDatabase } from './resource';\nexport { parseTestSchema } from './schema/parser';\nexport { normalizeTestSchema, TestSchemaError } from './schema/normalize';\nexport { generateDdl } from './schema/ddl';\nexport { TestDataError } from './postgres/loadData';\nexport { TestPostgresServerError, DEFAULT_ADMIN_URL } from './postgres/server';\nexport type {\n NormalizedTestSchema,\n NormalizedTable,\n NormalizedColumn,\n RawSqlEntry,\n ParseTestSchemaOptions,\n} from './schema/types';\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dropDatabase } from './allocator.js';
|
|
2
2
|
import { loadPg, TestPostgresServerError } from './server.js';
|
|
3
|
+
import { mintResourceId, registerResource, retireResource, runCleanupSteps } from '../resource.js';
|
|
3
4
|
/**
|
|
4
5
|
* Connect a helper pool to an allocated database and produce the handle.
|
|
5
6
|
* The handle owns exactly this database and drops it on dispose.
|
|
@@ -8,29 +9,32 @@ export async function createDatabaseHandle(owned, options = {}) {
|
|
|
8
9
|
const pg = await loadPg();
|
|
9
10
|
const Pool = pg.Pool ?? pg.default?.Pool;
|
|
10
11
|
const pool = new Pool({ connectionString: owned.url, max: 4 });
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const resourceId = mintResourceId('postgres');
|
|
13
|
+
let disposal;
|
|
14
|
+
const handle = {
|
|
15
|
+
engine: 'postgres',
|
|
16
|
+
resourceId,
|
|
13
17
|
url: owned.url,
|
|
14
18
|
_pool: pool,
|
|
15
19
|
async query(sql, params = []) {
|
|
16
|
-
if (
|
|
20
|
+
if (disposal) {
|
|
17
21
|
throw new TestPostgresServerError(`Test database "${owned.name}" has been disposed; no further queries are possible.`);
|
|
18
22
|
}
|
|
19
23
|
const res = await pool.query(sql, params);
|
|
20
24
|
return res.rows;
|
|
21
25
|
},
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
dispose() {
|
|
27
|
+
if (!disposal) {
|
|
28
|
+
retireResource(handle);
|
|
29
|
+
disposal = Promise.resolve().then(() => runCleanupSteps(`Test database "${owned.name}"`, [
|
|
30
|
+
() => pool.end(),
|
|
31
|
+
() => dropDatabase(owned.name, options),
|
|
32
|
+
]));
|
|
28
33
|
}
|
|
29
|
-
|
|
30
|
-
/* pool may already be closed */
|
|
31
|
-
}
|
|
32
|
-
await dropDatabase(owned.name, options);
|
|
34
|
+
return disposal;
|
|
33
35
|
},
|
|
34
36
|
};
|
|
37
|
+
registerResource(handle, { engine: 'postgres', resourceId });
|
|
38
|
+
return handle;
|
|
35
39
|
}
|
|
36
40
|
//# sourceMappingURL=database.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/postgres/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAqB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/postgres/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAqB,MAAM,UAAU,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAwBhG;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAoB,EACpB,UAA6B,EAAE;IAE/B,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,QAAmC,CAAC;IAExC,MAAM,MAAM,GAA0C;QACpD,MAAM,EAAE,UAAU;QAClB,UAAU;QACV,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,KAAK,EAAE,IAAI;QACX,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,SAA6B,EAAE;YACtD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,uBAAuB,CAC/B,kBAAkB,KAAK,CAAC,IAAI,uDAAuD,CACpF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAmB,CAAC,CAAC;YACvD,OAAO,GAAG,CAAC,IAAI,CAAC;QAClB,CAAC;QACD,OAAO;YACL,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,cAAc,CAAC,MAAM,CAAC,CAAC;gBACvB,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,KAAK,CAAC,IAAI,GAAG,EAAE;oBACvF,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;oBAChB,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC;iBACxC,CAAC,CAAC,CAAC;YACN,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;IACF,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7D,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { dropDatabase } from './allocator';\nimport { loadPg, TestPostgresServerError, TestServerOptions } from './server';\nimport { mintResourceId, registerResource, retireResource, runCleanupSteps } from '../resource';\n\n/** The built test database handle. */\nexport interface TestPostgresDatabase {\n /** Engine discriminator for SQLStack-minted disposable test databases. */\n readonly engine: 'postgres';\n /** Fresh per-allocation identifier; never reused within a process. */\n readonly resourceId: string;\n /** Normal PostgreSQL connection string identifying only this database. */\n readonly url: string;\n query(sql: string, params?: readonly unknown[]): Promise<unknown>;\n /**\n * Close helper connections and drop the owned database. Idempotent:\n * concurrent and repeated calls share one outcome. Every cleanup step is\n * attempted; failures are aggregated into a `TestDatabaseDisposeError`.\n */\n dispose(): Promise<void>;\n}\n\nexport interface OwnedDatabase {\n readonly name: string;\n readonly url: string;\n}\n\n/**\n * Connect a helper pool to an allocated database and produce the handle.\n * The handle owns exactly this database and drops it on dispose.\n */\nexport async function createDatabaseHandle(\n owned: OwnedDatabase,\n options: TestServerOptions = {}\n): Promise<TestPostgresDatabase & { _pool: any }> {\n const pg = await loadPg();\n const Pool = pg.Pool ?? pg.default?.Pool;\n const pool = new Pool({ connectionString: owned.url, max: 4 });\n const resourceId = mintResourceId('postgres');\n let disposal: Promise<void> | undefined;\n\n const handle: TestPostgresDatabase & { _pool: any } = {\n engine: 'postgres',\n resourceId,\n url: owned.url,\n _pool: pool,\n async query(sql: string, params: readonly unknown[] = []) {\n if (disposal) {\n throw new TestPostgresServerError(\n `Test database \"${owned.name}\" has been disposed; no further queries are possible.`\n );\n }\n const res = await pool.query(sql, params as unknown[]);\n return res.rows;\n },\n dispose() {\n if (!disposal) {\n retireResource(handle);\n disposal = Promise.resolve().then(() => runCleanupSteps(`Test database \"${owned.name}\"`, [\n () => pool.end(),\n () => dropDatabase(owned.name, options),\n ]));\n }\n return disposal;\n },\n };\n registerResource(handle, { engine: 'postgres', resourceId });\n return handle;\n}\n"]}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { SqlStackError } from '../core/errors.js';
|
|
3
|
+
/** Handles whose owned resources are currently allocated (not yet disposed). */
|
|
4
|
+
const active = new WeakSet();
|
|
5
|
+
/** Provenance recorded at mint time, kept for the handle's whole lifetime. */
|
|
6
|
+
const provenance = new WeakMap();
|
|
7
|
+
let counter = 0;
|
|
8
|
+
/** Fresh, process-unique identifier for one allocation. */
|
|
9
|
+
export function mintResourceId(engine) {
|
|
10
|
+
counter += 1;
|
|
11
|
+
return `${engine}-${randomBytes(6).toString('hex')}-${counter}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Register a freshly allocated handle as an active SQLStack resource.
|
|
15
|
+
* Only fixture allocation code paths call this; it is never exported from
|
|
16
|
+
* `sqlstack/testing`.
|
|
17
|
+
*/
|
|
18
|
+
export function registerResource(handle, record) {
|
|
19
|
+
const locator = handle;
|
|
20
|
+
provenance.set(handle, Object.freeze({ ...record, url: locator.url, db: locator.db,
|
|
21
|
+
conn: locator.conn, path: locator.path, query: locator.query, dispose: locator.dispose }));
|
|
22
|
+
active.add(handle);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Optional shared testing resource-scope ABI. SQLStack remains independently
|
|
26
|
+
* usable; it never creates a scope or imports a test runner.
|
|
27
|
+
* Called within the builder's rollback boundary before returning a fixture.
|
|
28
|
+
*/
|
|
29
|
+
export function adoptFixtureResource(handle) {
|
|
30
|
+
const channel = globalThis[Symbol.for('@noego/testing.resource-scope.v1')];
|
|
31
|
+
if (channel) {
|
|
32
|
+
if (typeof channel.getStore !== 'function')
|
|
33
|
+
throw new Error('Incompatible NoEgo resource-scope v1 channel');
|
|
34
|
+
channel.getStore()?.own(handle, 'sqlstack fixture');
|
|
35
|
+
}
|
|
36
|
+
return handle;
|
|
37
|
+
}
|
|
38
|
+
/** Mark a handle's owned resources as released. Verification fails from now on. */
|
|
39
|
+
export function retireResource(handle) {
|
|
40
|
+
active.delete(handle);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* True only for a handle object that was minted by a SQLStack fixture
|
|
44
|
+
* allocation in this process and has not been disposed. Field checks are a
|
|
45
|
+
* consistency guard on top of identity membership, never a substitute for it.
|
|
46
|
+
*/
|
|
47
|
+
export function isDisposableTestDatabase(value) {
|
|
48
|
+
if (value === null || (typeof value !== 'object' && typeof value !== 'function'))
|
|
49
|
+
return false;
|
|
50
|
+
const handle = value;
|
|
51
|
+
if (!active.has(handle))
|
|
52
|
+
return false;
|
|
53
|
+
const record = provenance.get(handle);
|
|
54
|
+
if (!record)
|
|
55
|
+
return false;
|
|
56
|
+
const candidate = handle;
|
|
57
|
+
if (candidate.engine !== record.engine || candidate.resourceId !== record.resourceId)
|
|
58
|
+
return false;
|
|
59
|
+
if (record.engine === 'sqlite' && candidate.storage !== record.storage)
|
|
60
|
+
return false;
|
|
61
|
+
const locator = handle;
|
|
62
|
+
for (const key of ['url', 'db', 'conn', 'path', 'query', 'dispose']) {
|
|
63
|
+
if (locator[key] !== record[key])
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/** Failed fixture construction plus failed rollback, preserving both causes. */
|
|
69
|
+
export class TestDatabaseBuildError extends SqlStackError {
|
|
70
|
+
constructor(message, cause, cleanup) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.cause = cause;
|
|
73
|
+
this.name = 'TestDatabaseBuildError';
|
|
74
|
+
this.errors = Object.freeze([cause, cleanup]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Raised by `dispose()` when one or more owned cleanup steps failed. */
|
|
78
|
+
export class TestDatabaseDisposeError extends SqlStackError {
|
|
79
|
+
constructor(message, errors) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = 'TestDatabaseDisposeError';
|
|
82
|
+
this.errors = Object.freeze([...errors]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run every cleanup step, retaining each failure, then either resolve or
|
|
87
|
+
* reject once with all retained errors.
|
|
88
|
+
*/
|
|
89
|
+
export async function runCleanupSteps(label, steps) {
|
|
90
|
+
const errors = [];
|
|
91
|
+
for (const step of steps) {
|
|
92
|
+
try {
|
|
93
|
+
await step();
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
errors.push(err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (errors.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
const detail = errors
|
|
102
|
+
.map((err, index) => ` [${index + 1}] ${err instanceof Error ? err.message : String(err)}`)
|
|
103
|
+
.join('\n');
|
|
104
|
+
throw new TestDatabaseDisposeError(`${label}: ${errors.length} cleanup step(s) failed while disposing:\n${detail}`, errors);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../../src/testing/resource.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { SqlStackError } from '../core/errors';\nimport type { TestPostgresDatabase } from './postgres/database';\nimport type { TestSqliteDatabase } from './sqlite/database';\n\n/**\n * Internal provenance for SQLStack-minted disposable test databases.\n *\n * Every handle allocated by a SQLStack fixture builder is registered here at\n * allocation time and retired on dispose. Membership is module-private:\n * nothing outside this package can mint, register, or extend a handle's\n * lifetime, and structural copies (spread, `{ ...handle }`, hand-built\n * lookalikes) never validate because verification is by object identity.\n */\n\n/** A disposable test database minted by a SQLStack fixture allocation. */\nexport type DisposableTestDatabase =\n | (TestPostgresDatabase & { readonly engine: 'postgres'; readonly resourceId: string })\n | (TestSqliteDatabase & {\n readonly engine: 'sqlite';\n readonly resourceId: string;\n readonly storage: 'memory' | 'file';\n });\n\nexport type DisposableEngine = DisposableTestDatabase['engine'];\n\ninterface ResourceRecord {\n readonly engine: DisposableEngine;\n readonly resourceId: string;\n readonly storage?: 'memory' | 'file';\n readonly url?: unknown;\n readonly db?: unknown;\n readonly conn?: unknown;\n readonly path?: unknown;\n readonly query?: unknown;\n readonly dispose?: unknown;\n}\n\n/** Handles whose owned resources are currently allocated (not yet disposed). */\nconst active = new WeakSet<object>();\n/** Provenance recorded at mint time, kept for the handle's whole lifetime. */\nconst provenance = new WeakMap<object, ResourceRecord>();\n\nlet counter = 0;\n\n/** Fresh, process-unique identifier for one allocation. */\nexport function mintResourceId(engine: DisposableEngine): string {\n counter += 1;\n return `${engine}-${randomBytes(6).toString('hex')}-${counter}`;\n}\n\n/**\n * Register a freshly allocated handle as an active SQLStack resource.\n * Only fixture allocation code paths call this; it is never exported from\n * `sqlstack/testing`.\n */\nexport function registerResource(handle: object, record: ResourceRecord): void {\n const locator = handle as Record<string, unknown>;\n provenance.set(handle, Object.freeze({ ...record, url: locator.url, db: locator.db,\n conn: locator.conn, path: locator.path, query: locator.query, dispose: locator.dispose }));\n active.add(handle);\n}\n\n/**\n * Optional shared testing resource-scope ABI. SQLStack remains independently\n * usable; it never creates a scope or imports a test runner.\n * Called within the builder's rollback boundary before returning a fixture.\n */\nexport function adoptFixtureResource<T extends { dispose(): unknown }>(handle: T): T {\n const channel = (globalThis as Record<PropertyKey, any>)[Symbol.for('@noego/testing.resource-scope.v1')];\n if (channel) {\n if (typeof channel.getStore !== 'function') throw new Error('Incompatible NoEgo resource-scope v1 channel');\n channel.getStore()?.own(handle, 'sqlstack fixture');\n }\n return handle;\n}\n\n/** Mark a handle's owned resources as released. Verification fails from now on. */\nexport function retireResource(handle: object): void {\n active.delete(handle);\n}\n\n/**\n * True only for a handle object that was minted by a SQLStack fixture\n * allocation in this process and has not been disposed. Field checks are a\n * consistency guard on top of identity membership, never a substitute for it.\n */\nexport function isDisposableTestDatabase(value: unknown): value is DisposableTestDatabase {\n if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return false;\n const handle = value as object;\n if (!active.has(handle)) return false;\n const record = provenance.get(handle);\n if (!record) return false;\n const candidate = handle as { engine?: unknown; resourceId?: unknown; storage?: unknown };\n if (candidate.engine !== record.engine || candidate.resourceId !== record.resourceId) return false;\n if (record.engine === 'sqlite' && candidate.storage !== record.storage) return false;\n const locator = handle as Record<string, unknown>;\n for (const key of ['url', 'db', 'conn', 'path', 'query', 'dispose'] as const) {\n if (locator[key] !== record[key]) return false;\n }\n return true;\n}\n\n/** Failed fixture construction plus failed rollback, preserving both causes. */\nexport class TestDatabaseBuildError extends SqlStackError {\n readonly errors: readonly unknown[];\n constructor(message: string, readonly cause: unknown, cleanup: unknown) {\n super(message);\n this.name = 'TestDatabaseBuildError';\n this.errors = Object.freeze([cause, cleanup]);\n }\n}\n\n/** Raised by `dispose()` when one or more owned cleanup steps failed. */\nexport class TestDatabaseDisposeError extends SqlStackError {\n /** Every cleanup failure, in attempt order. Nothing is swallowed. */\n readonly errors: readonly unknown[];\n\n constructor(message: string, errors: readonly unknown[]) {\n super(message);\n this.name = 'TestDatabaseDisposeError';\n this.errors = Object.freeze([...errors]);\n }\n}\n\n/**\n * Run every cleanup step, retaining each failure, then either resolve or\n * reject once with all retained errors.\n */\nexport async function runCleanupSteps(\n label: string,\n steps: readonly (() => Promise<void> | void)[]\n): Promise<void> {\n const errors: unknown[] = [];\n for (const step of steps) {\n try {\n await step();\n } catch (err) {\n errors.push(err);\n }\n }\n if (errors.length === 0) return;\n const detail = errors\n .map((err, index) => ` [${index + 1}] ${err instanceof Error ? err.message : String(err)}`)\n .join('\\n');\n throw new TestDatabaseDisposeError(\n `${label}: ${errors.length} cleanup step(s) failed while disposing:\\n${detail}`,\n errors\n );\n}\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
|
+
import { adoptFixtureResource, TestDatabaseBuildError } from '../resource.js';
|
|
3
|
+
import { assertFixtureSqlFile } from '../fixture-provenance.js';
|
|
2
4
|
import { generateDdl } from '../schema/ddl.js';
|
|
3
5
|
import { parseTestSchema } from '../schema/parser.js';
|
|
4
6
|
import { createDatabaseHandle, TestSqliteError } from './database.js';
|
|
@@ -9,6 +11,7 @@ function readRawSql(schema) {
|
|
|
9
11
|
return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };
|
|
10
12
|
}
|
|
11
13
|
try {
|
|
14
|
+
assertFixtureSqlFile(entry.resolvedPath);
|
|
12
15
|
return {
|
|
13
16
|
label: `sql entry #${entry.index} (${entry.sourcePath})`,
|
|
14
17
|
sql: fs.readFileSync(entry.resolvedPath, 'utf8'),
|
|
@@ -66,10 +69,15 @@ function makeBuilder(state) {
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
await insertRows((sql, params) => handle.db.query(sql, params), state.schema, effective);
|
|
69
|
-
return handle;
|
|
72
|
+
return adoptFixtureResource(handle);
|
|
70
73
|
}
|
|
71
74
|
catch (err) {
|
|
72
|
-
|
|
75
|
+
try {
|
|
76
|
+
await handle.dispose();
|
|
77
|
+
}
|
|
78
|
+
catch (cleanup) {
|
|
79
|
+
throw new TestDatabaseBuildError('SQLite fixture build and cleanup failed', err, cleanup);
|
|
80
|
+
}
|
|
73
81
|
throw err;
|
|
74
82
|
}
|
|
75
83
|
},
|