sqlstack 2.0.0 → 3.1.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/codegen/generateManifest.d.ts +12 -0
- package/dist/cjs/codegen/generateManifest.d.ts.map +1 -1
- package/dist/cjs/codegen/generateManifest.js +21 -11
- package/dist/cjs/codegen/generateManifest.js.map +1 -1
- package/dist/cjs/core/resolver.d.ts +25 -12
- package/dist/cjs/core/resolver.d.ts.map +1 -1
- package/dist/cjs/core/resolver.js +66 -20
- package/dist/cjs/core/resolver.js.map +1 -1
- package/dist/cjs/core/sourceScanResolver.d.ts +25 -0
- package/dist/cjs/core/sourceScanResolver.d.ts.map +1 -0
- package/dist/cjs/core/sourceScanResolver.js +56 -0
- package/dist/cjs/core/sourceScanResolver.js.map +1 -0
- package/dist/cjs/decorators/query.d.ts.map +1 -1
- package/dist/cjs/decorators/query.js +276 -245
- package/dist/cjs/decorators/query.js.map +1 -1
- package/dist/cjs/families/sqliteFamily.d.ts +26 -0
- package/dist/cjs/families/sqliteFamily.d.ts.map +1 -0
- package/dist/cjs/families/sqliteFamily.js +187 -0
- package/dist/cjs/families/sqliteFamily.js.map +1 -0
- package/dist/cjs/family.d.ts +69 -0
- package/dist/cjs/family.d.ts.map +1 -0
- package/dist/cjs/family.js +206 -0
- package/dist/cjs/family.js.map +1 -0
- package/dist/cjs/familyBinding.d.ts +22 -0
- package/dist/cjs/familyBinding.d.ts.map +1 -0
- package/dist/cjs/familyBinding.js +22 -0
- package/dist/cjs/familyBinding.js.map +1 -0
- package/dist/cjs/index.d.ts +10 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +17 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/ioc.d.ts +22 -0
- package/dist/cjs/ioc.d.ts.map +1 -0
- package/dist/cjs/ioc.js +35 -0
- package/dist/cjs/ioc.js.map +1 -0
- package/dist/cjs/run.d.ts +29 -0
- package/dist/cjs/run.d.ts.map +1 -0
- package/dist/cjs/run.js +38 -0
- package/dist/cjs/run.js.map +1 -0
- package/dist/cjs/runtime.d.ts +25 -0
- package/dist/cjs/runtime.d.ts.map +1 -0
- package/dist/cjs/runtime.js +133 -0
- package/dist/cjs/runtime.js.map +1 -0
- package/dist/cjs/stack.d.ts +101 -0
- package/dist/cjs/stack.d.ts.map +1 -0
- package/dist/cjs/stack.js +288 -0
- package/dist/cjs/stack.js.map +1 -0
- package/dist/cjs/testing/builder.d.ts +29 -0
- package/dist/cjs/testing/builder.d.ts.map +1 -0
- package/dist/cjs/testing/builder.js +131 -0
- package/dist/cjs/testing/builder.js.map +1 -0
- package/dist/cjs/testing/index.d.ts +14 -0
- package/dist/cjs/testing/index.d.ts.map +1 -0
- package/dist/cjs/testing/index.js +22 -0
- package/dist/cjs/testing/index.js.map +1 -0
- package/dist/cjs/testing/postgres/allocator.d.ts +17 -0
- package/dist/cjs/testing/postgres/allocator.d.ts.map +1 -0
- package/dist/cjs/testing/postgres/allocator.js +54 -0
- package/dist/cjs/testing/postgres/allocator.js.map +1 -0
- package/dist/cjs/testing/postgres/database.d.ts +21 -0
- package/dist/cjs/testing/postgres/database.d.ts.map +1 -0
- package/dist/cjs/testing/postgres/database.js +39 -0
- package/dist/cjs/testing/postgres/database.js.map +1 -0
- package/dist/cjs/testing/postgres/loadData.d.ts +31 -0
- package/dist/cjs/testing/postgres/loadData.d.ts.map +1 -0
- package/dist/cjs/testing/postgres/loadData.js +184 -0
- package/dist/cjs/testing/postgres/loadData.js.map +1 -0
- package/dist/cjs/testing/postgres/server.d.ts +19 -0
- package/dist/cjs/testing/postgres/server.d.ts.map +1 -0
- package/dist/cjs/testing/postgres/server.js +78 -0
- package/dist/cjs/testing/postgres/server.js.map +1 -0
- package/dist/cjs/testing/schema/ddl.d.ts +5 -0
- package/dist/cjs/testing/schema/ddl.d.ts.map +1 -0
- package/dist/cjs/testing/schema/ddl.js +70 -0
- package/dist/cjs/testing/schema/ddl.js.map +1 -0
- package/dist/cjs/testing/schema/normalize.d.ts +7 -0
- package/dist/cjs/testing/schema/normalize.d.ts.map +1 -0
- package/dist/cjs/testing/schema/normalize.js +202 -0
- package/dist/cjs/testing/schema/normalize.js.map +1 -0
- package/dist/cjs/testing/schema/parser.d.ts +9 -0
- package/dist/cjs/testing/schema/parser.d.ts.map +1 -0
- package/dist/cjs/testing/schema/parser.js +80 -0
- package/dist/cjs/testing/schema/parser.js.map +1 -0
- package/dist/cjs/testing/schema/types.d.ts +63 -0
- package/dist/cjs/testing/schema/types.d.ts.map +1 -0
- package/dist/cjs/testing/schema/types.js +6 -0
- package/dist/cjs/testing/schema/types.js.map +1 -0
- package/dist/cjs/testing/sqlite/builder.d.ts +24 -0
- package/dist/cjs/testing/sqlite/builder.d.ts.map +1 -0
- package/dist/cjs/testing/sqlite/builder.js +134 -0
- package/dist/cjs/testing/sqlite/builder.js.map +1 -0
- package/dist/cjs/testing/sqlite/database.d.ts +18 -0
- package/dist/cjs/testing/sqlite/database.d.ts.map +1 -0
- package/dist/cjs/testing/sqlite/database.js +123 -0
- package/dist/cjs/testing/sqlite/database.js.map +1 -0
- package/dist/cjs/testing/sqlite/loadData.d.ts +7 -0
- package/dist/cjs/testing/sqlite/loadData.d.ts.map +1 -0
- package/dist/cjs/testing/sqlite/loadData.js +55 -0
- package/dist/cjs/testing/sqlite/loadData.js.map +1 -0
- package/dist/cjs/transactions.d.ts +14 -5
- package/dist/cjs/transactions.d.ts.map +1 -1
- package/dist/cjs/transactions.js +40 -24
- package/dist/cjs/transactions.js.map +1 -1
- package/dist/esm/codegen/generateManifest.js +20 -11
- package/dist/esm/codegen/generateManifest.js.map +1 -1
- package/dist/esm/core/resolver.js +59 -19
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/core/sourceScanResolver.js +51 -0
- package/dist/esm/core/sourceScanResolver.js.map +1 -0
- package/dist/esm/decorators/query.js +278 -247
- package/dist/esm/decorators/query.js.map +1 -1
- package/dist/esm/families/sqliteFamily.js +151 -0
- package/dist/esm/families/sqliteFamily.js.map +1 -0
- package/dist/esm/family.js +202 -0
- package/dist/esm/family.js.map +1 -0
- package/dist/esm/familyBinding.js +18 -0
- package/dist/esm/familyBinding.js.map +1 -0
- package/dist/esm/index.js +10 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/ioc.js +32 -0
- package/dist/esm/ioc.js.map +1 -0
- package/dist/esm/run.js +34 -0
- package/dist/esm/run.js.map +1 -0
- package/dist/esm/runtime.js +94 -0
- package/dist/esm/runtime.js.map +1 -0
- package/dist/esm/stack.js +251 -0
- package/dist/esm/stack.js.map +1 -0
- package/dist/esm/testing/builder.js +95 -0
- package/dist/esm/testing/builder.js.map +1 -0
- package/dist/esm/testing/index.js +9 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/testing/postgres/allocator.js +49 -0
- package/dist/esm/testing/postgres/allocator.js.map +1 -0
- package/dist/esm/testing/postgres/database.js +36 -0
- package/dist/esm/testing/postgres/database.js.map +1 -0
- package/dist/esm/testing/postgres/loadData.js +176 -0
- package/dist/esm/testing/postgres/loadData.js.map +1 -0
- package/dist/esm/testing/postgres/server.js +69 -0
- package/dist/esm/testing/postgres/server.js.map +1 -0
- package/dist/esm/testing/schema/ddl.js +65 -0
- package/dist/esm/testing/schema/ddl.js.map +1 -0
- package/dist/esm/testing/schema/normalize.js +164 -0
- package/dist/esm/testing/schema/normalize.js.map +1 -0
- package/dist/esm/testing/schema/parser.js +43 -0
- package/dist/esm/testing/schema/parser.js.map +1 -0
- package/dist/esm/testing/schema/types.js +5 -0
- package/dist/esm/testing/schema/types.js.map +1 -0
- package/dist/esm/testing/sqlite/builder.js +98 -0
- package/dist/esm/testing/sqlite/builder.js.map +1 -0
- package/dist/esm/testing/sqlite/database.js +85 -0
- package/dist/esm/testing/sqlite/database.js.map +1 -0
- package/dist/esm/testing/sqlite/loadData.js +48 -0
- package/dist/esm/testing/sqlite/loadData.js.map +1 -0
- package/dist/esm/transactions.js +39 -24
- package/dist/esm/transactions.js.map +1 -1
- package/package.json +19 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../../src/testing/schema/normalize.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\nimport { SqlStackError } from '../../core/errors';\nimport type {\n NormalizedColumn,\n NormalizedReference,\n NormalizedTable,\n NormalizedTestSchema,\n ParseTestSchemaOptions,\n RawSqlEntry,\n} from './types';\n\nexport class TestSchemaError extends SqlStackError {\n constructor(message: string, source?: string) {\n super(source ? `${message} (schema source: ${source})` : message);\n this.name = 'TestSchemaError';\n }\n}\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction assertIdentifier(kind: string, name: string, source: string): void {\n if (!IDENT.test(name)) {\n throw new TestSchemaError(\n `Invalid ${kind} name ${JSON.stringify(name)}: names must match /^[A-Za-z_][A-Za-z0-9_]*$/`,\n source\n );\n }\n}\n\nfunction parseReference(raw: unknown, where: string, source: string): NormalizedReference {\n if (typeof raw !== 'string') {\n throw new TestSchemaError(`Invalid \"references\" at ${where}: expected a string like \"table.column\"`, source);\n }\n // Accept \"table.column\" or \"table(column)\".\n let m = /^([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(raw);\n if (!m) m = /^([A-Za-z_][A-Za-z0-9_]*)\\(([A-Za-z_][A-Za-z0-9_]*)\\)$/.exec(raw);\n if (!m) {\n throw new TestSchemaError(\n `Invalid \"references\" syntax ${JSON.stringify(raw)} at ${where}: use \"table.column\" or \"table(column)\"`,\n source\n );\n }\n return { table: m[1]!, column: m[2]! };\n}\n\nconst COLUMN_KEYS = new Set(['type', 'nullable', 'primary', 'unique', 'default', 'references']);\n\nfunction normalizeColumn(tableName: string, name: string, decl: unknown, source: string): NormalizedColumn {\n const where = `${tableName}.${name}`;\n assertIdentifier('column', name, source);\n let obj: Record<string, unknown>;\n if (typeof decl === 'string') {\n obj = { type: decl };\n } else if (decl && typeof decl === 'object' && !Array.isArray(decl)) {\n obj = decl as Record<string, unknown>;\n } else {\n throw new TestSchemaError(\n `Malformed column declaration at ${where}: expected a type string or an object`,\n source\n );\n }\n for (const key of Object.keys(obj)) {\n if (!COLUMN_KEYS.has(key)) {\n throw new TestSchemaError(\n `Unknown column property ${JSON.stringify(key)} at ${where}: supported properties are ${[...COLUMN_KEYS].join(', ')}`,\n source\n );\n }\n }\n const type = obj.type;\n if (typeof type !== 'string' || type.trim() === '') {\n throw new TestSchemaError(`Missing or invalid \"type\" at ${where}: a non-empty string is required`, source);\n }\n for (const flag of ['nullable', 'primary', 'unique'] as const) {\n if (obj[flag] !== undefined && typeof obj[flag] !== 'boolean') {\n throw new TestSchemaError(`Invalid \"${flag}\" at ${where}: expected a boolean`, source);\n }\n }\n const def = obj.default;\n if (def !== undefined && !['string', 'number', 'boolean'].includes(typeof def)) {\n throw new TestSchemaError(`Invalid \"default\" at ${where}: expected a string, number, or boolean`, source);\n }\n const primary = obj.primary === true;\n return {\n name,\n type: type.trim(),\n nullable: obj.nullable === undefined ? !primary : (obj.nullable as boolean),\n primary,\n unique: obj.unique === true,\n hasDefault: def !== undefined,\n default: def as string | number | boolean | undefined,\n references: obj.references === undefined ? undefined : parseReference(obj.references, where, source),\n };\n}\n\nfunction normalizeTable(name: string, decl: unknown, source: string): NormalizedTable {\n assertIdentifier('table', name, source);\n if (!decl || typeof decl !== 'object' || Array.isArray(decl)) {\n throw new TestSchemaError(`Malformed table declaration \"${name}\": expected an object with \"columns\"`, source);\n }\n const columnsDecl = (decl as Record<string, unknown>).columns;\n if (!columnsDecl || typeof columnsDecl !== 'object' || Array.isArray(columnsDecl)) {\n throw new TestSchemaError(`Table \"${name}\" is missing a \"columns\" map`, source);\n }\n const columns = Object.entries(columnsDecl as Record<string, unknown>).map(([colName, colDecl]) =>\n normalizeColumn(name, colName, colDecl, source)\n );\n if (columns.length === 0) {\n throw new TestSchemaError(`Table \"${name}\" declares no columns`, source);\n }\n const primaryKey = columns.filter((c) => c.primary).map((c) => c.name);\n return { name, columns: Object.freeze(columns), primaryKey: Object.freeze(primaryKey) };\n}\n\nfunction normalizeSqlEntry(\n raw: unknown,\n index: number,\n options: ParseTestSchemaOptions,\n source: string\n): RawSqlEntry {\n if (typeof raw !== 'string' || raw.trim() === '') {\n throw new TestSchemaError(`Invalid sql entry #${index}: expected a non-empty string`, source);\n }\n const looksLikeFile = !raw.includes('\\n') && /\\.sql$/i.test(raw.trim());\n if (!looksLikeFile) {\n return { kind: 'inline', index, sql: raw };\n }\n const authored = raw.trim();\n if (!options.sourceDir) {\n throw new TestSchemaError(\n `sql entry #${index} references file \"${authored}\" but no sourceDir is available. ` +\n `Pass the schema as a file path, or provide { sourceDir } so relative .sql paths can be resolved ` +\n `(paths are never resolved against process.cwd() implicitly).`,\n source\n );\n }\n const resolvedPath = path.resolve(options.sourceDir, authored);\n if (!fs.existsSync(resolvedPath)) {\n throw new TestSchemaError(\n `sql entry #${index}: file not found: ${authored} (resolved to ${resolvedPath})`,\n source\n );\n }\n return { kind: 'file', index, sourcePath: authored, resolvedPath };\n}\n\nexport function normalizeTestSchema(\n doc: unknown,\n options: ParseTestSchemaOptions = {}\n): NormalizedTestSchema {\n const source = options.sourceLabel ?? '<inline schema>';\n if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {\n throw new TestSchemaError('Test schema must be a YAML mapping / object', source);\n }\n const root = doc as Record<string, unknown>;\n if (root.version !== 1) {\n throw new TestSchemaError(\n `Unsupported test schema version: ${JSON.stringify(root.version)} (expected: version: 1)`,\n source\n );\n }\n if (root.dialect !== 'postgres' && root.dialect !== 'sqlite') {\n throw new TestSchemaError(\n `Unsupported test schema dialect: ${JSON.stringify(root.dialect)} (expected: dialect: postgres or sqlite)`,\n source\n );\n }\n for (const key of Object.keys(root)) {\n if (!['version', 'dialect', 'tables', 'sql'].includes(key)) {\n throw new TestSchemaError(`Unknown top-level key ${JSON.stringify(key)} in test schema`, source);\n }\n }\n\n let tables: NormalizedTable[] = [];\n if (root.tables !== undefined) {\n if (!root.tables || typeof root.tables !== 'object' || Array.isArray(root.tables)) {\n throw new TestSchemaError('\"tables\" must be a mapping of table name to declaration', source);\n }\n tables = Object.entries(root.tables as Record<string, unknown>).map(([name, decl]) =>\n normalizeTable(name, decl, source)\n );\n }\n\n // Validate references that point at structured tables.\n const byName = new Map(tables.map((t) => [t.name, t]));\n for (const t of tables) {\n for (const c of t.columns) {\n const ref = c.references;\n if (ref && byName.has(ref.table)) {\n const target = byName.get(ref.table)!;\n if (!target.columns.some((tc) => tc.name === ref.column)) {\n throw new TestSchemaError(\n `Column ${t.name}.${c.name} references ${ref.table}.${ref.column}, but table \"${ref.table}\" declares no column \"${ref.column}\"`,\n source\n );\n }\n }\n }\n }\n\n let sql: RawSqlEntry[] = [];\n if (root.sql !== undefined) {\n if (!Array.isArray(root.sql)) {\n throw new TestSchemaError('\"sql\" must be a list of inline SQL strings or .sql file paths', source);\n }\n sql = root.sql.map((entry, i) => normalizeSqlEntry(entry, i, options, source));\n }\n\n if (tables.length === 0 && sql.length === 0) {\n throw new TestSchemaError('Test schema declares no tables and no sql entries', source);\n }\n\n return Object.freeze({\n version: 1 as const,\n dialect: root.dialect as 'postgres' | 'sqlite',\n tables: Object.freeze(tables),\n sql: Object.freeze(sql),\n source,\n });\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { normalizeTestSchema, TestSchemaError } from './normalize.js';
|
|
5
|
+
/**
|
|
6
|
+
* Parse a test schema from a YAML string, a plain object, or a .yml/.yaml
|
|
7
|
+
* file path. When given a file path, relative raw .sql entries resolve
|
|
8
|
+
* against the file's own directory unless options.sourceDir overrides it.
|
|
9
|
+
*/
|
|
10
|
+
export function parseTestSchema(input, options = {}) {
|
|
11
|
+
if (typeof input === 'object') {
|
|
12
|
+
return normalizeTestSchema(input, options);
|
|
13
|
+
}
|
|
14
|
+
if (isSchemaFilePath(input)) {
|
|
15
|
+
const filePath = path.resolve(input);
|
|
16
|
+
let text;
|
|
17
|
+
try {
|
|
18
|
+
text = fs.readFileSync(filePath, 'utf8');
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
throw new TestSchemaError(`Cannot read test schema file: ${filePath} (${err.message})`);
|
|
22
|
+
}
|
|
23
|
+
return parseYamlText(text, {
|
|
24
|
+
sourceDir: options.sourceDir ?? path.dirname(filePath),
|
|
25
|
+
sourceLabel: options.sourceLabel ?? filePath,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return parseYamlText(input, options);
|
|
29
|
+
}
|
|
30
|
+
export function isSchemaFilePath(input) {
|
|
31
|
+
return !input.includes('\n') && /\.ya?ml$/i.test(input.trim());
|
|
32
|
+
}
|
|
33
|
+
function parseYamlText(text, options) {
|
|
34
|
+
let doc;
|
|
35
|
+
try {
|
|
36
|
+
doc = parseYaml(text);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
throw new TestSchemaError(`Failed to parse test schema YAML: ${err.message}`, options.sourceLabel);
|
|
40
|
+
}
|
|
41
|
+
return normalizeTestSchema(doc, options);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../../src/testing/schema/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnE;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAsB,EACtB,UAAkC,EAAE;IAEpC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,eAAe,CACvB,iCAAiC,QAAQ,KAAM,GAAa,CAAC,OAAO,GAAG,CACxE,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC,IAAI,EAAE;YACzB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;YACtD,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,QAAQ;SAC7C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,OAA+B;IAClE,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,qCAAsC,GAAa,CAAC,OAAO,EAAE,EAC7D,OAAO,CAAC,WAAW,CACpB,CAAC;IACJ,CAAC;IACD,OAAO,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\nimport { parse as parseYaml } from 'yaml';\nimport { normalizeTestSchema, TestSchemaError } from './normalize';\nimport type { NormalizedTestSchema, ParseTestSchemaOptions } from './types';\n\n/**\n * Parse a test schema from a YAML string, a plain object, or a .yml/.yaml\n * file path. When given a file path, relative raw .sql entries resolve\n * against the file's own directory unless options.sourceDir overrides it.\n */\nexport function parseTestSchema(\n input: string | object,\n options: ParseTestSchemaOptions = {}\n): NormalizedTestSchema {\n if (typeof input === 'object') {\n return normalizeTestSchema(input, options);\n }\n if (isSchemaFilePath(input)) {\n const filePath = path.resolve(input);\n let text: string;\n try {\n text = fs.readFileSync(filePath, 'utf8');\n } catch (err) {\n throw new TestSchemaError(\n `Cannot read test schema file: ${filePath} (${(err as Error).message})`\n );\n }\n return parseYamlText(text, {\n sourceDir: options.sourceDir ?? path.dirname(filePath),\n sourceLabel: options.sourceLabel ?? filePath,\n });\n }\n return parseYamlText(input, options);\n}\n\nexport function isSchemaFilePath(input: string): boolean {\n return !input.includes('\\n') && /\\.ya?ml$/i.test(input.trim());\n}\n\nfunction parseYamlText(text: string, options: ParseTestSchemaOptions): NormalizedTestSchema {\n let doc: unknown;\n try {\n doc = parseYaml(text);\n } catch (err) {\n throw new TestSchemaError(\n `Failed to parse test schema YAML: ${(err as Error).message}`,\n options.sourceLabel\n );\n }\n return normalizeTestSchema(doc, options);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/testing/schema/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Types for the sqlstack/testing versioned YAML test-schema.\n */\n\nexport interface TestColumnInput {\n type: string;\n nullable?: boolean;\n primary?: boolean;\n unique?: boolean;\n /** Raw SQL default expression (emitted verbatim into DDL). */\n default?: string | number | boolean;\n /** Reference target: \"table.column\" or \"table(column)\". */\n references?: string;\n}\n\nexport interface TestTableInput {\n columns: Record<string, string | TestColumnInput>;\n}\n\nexport interface TestSchemaInput {\n version: number;\n dialect: string;\n tables?: Record<string, TestTableInput>;\n sql?: string[];\n}\n\nexport interface NormalizedReference {\n readonly table: string;\n readonly column: string;\n}\n\nexport interface NormalizedColumn {\n readonly name: string;\n readonly type: string;\n readonly nullable: boolean;\n readonly primary: boolean;\n readonly unique: boolean;\n readonly hasDefault: boolean;\n readonly default?: string | number | boolean;\n readonly references?: NormalizedReference;\n}\n\nexport interface NormalizedTable {\n readonly name: string;\n readonly columns: readonly NormalizedColumn[];\n readonly primaryKey: readonly string[];\n}\n\nexport type RawSqlEntry =\n | { readonly kind: 'inline'; readonly index: number; readonly sql: string }\n | {\n readonly kind: 'file';\n readonly index: number;\n readonly sourcePath: string;\n readonly resolvedPath: string;\n };\n\nexport interface NormalizedTestSchema {\n readonly version: 1;\n readonly dialect: 'postgres' | 'sqlite';\n readonly tables: readonly NormalizedTable[];\n readonly sql: readonly RawSqlEntry[];\n readonly source: string;\n}\n\nexport interface ParseTestSchemaOptions {\n sourceDir?: string;\n sourceLabel?: string;\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { generateDdl } from '../schema/ddl.js';
|
|
3
|
+
import { parseTestSchema } from '../schema/parser.js';
|
|
4
|
+
import { createDatabaseHandle, TestSqliteError } from './database.js';
|
|
5
|
+
import { composeRows, insertRows, toOperations } from './loadData.js';
|
|
6
|
+
function readRawSql(schema) {
|
|
7
|
+
return schema.sql.map((entry) => {
|
|
8
|
+
if (entry.kind === 'inline') {
|
|
9
|
+
return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return {
|
|
13
|
+
label: `sql entry #${entry.index} (${entry.sourcePath})`,
|
|
14
|
+
sql: fs.readFileSync(entry.resolvedPath, 'utf8'),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
throw new TestSqliteError(`Cannot read raw SQL file for sql entry #${entry.index}: ${entry.resolvedPath} ` +
|
|
19
|
+
`(authored as "${entry.sourcePath}" in ${schema.source}): ${err.message}`);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function executeRaw(handle, sql) {
|
|
24
|
+
const conn = handle.conn;
|
|
25
|
+
if (typeof conn?.exec === 'function') {
|
|
26
|
+
await conn.exec(sql);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await handle.db.query(sql, []);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function makeBuilder(state) {
|
|
33
|
+
return {
|
|
34
|
+
schema: state.schema,
|
|
35
|
+
ops: state.ops,
|
|
36
|
+
data(rows) {
|
|
37
|
+
return makeBuilder({
|
|
38
|
+
...state,
|
|
39
|
+
ops: [...state.ops, ...toOperations(state.schema, rows)],
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
async build() {
|
|
43
|
+
const rawSql = readRawSql(state.schema);
|
|
44
|
+
const ddl = generateDdl(state.schema);
|
|
45
|
+
const effective = composeRows(state.schema, state.ops);
|
|
46
|
+
const fileBacked = state.options.mode
|
|
47
|
+
? state.options.mode === 'file'
|
|
48
|
+
: state.options.fileBacked !== false && state.options.tempFile !== false;
|
|
49
|
+
const handle = await createDatabaseHandle(fileBacked);
|
|
50
|
+
try {
|
|
51
|
+
for (const statement of ddl) {
|
|
52
|
+
try {
|
|
53
|
+
await handle.db.query(statement, []);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new TestSqliteError(`Failed to apply structured DDL from ${state.schema.source}: ${err.message}\n` +
|
|
57
|
+
`Statement:\n${statement}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const entry of rawSql) {
|
|
61
|
+
try {
|
|
62
|
+
await executeRaw(handle, entry.sql);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new TestSqliteError(`Failed to apply raw ${entry.label} from ${state.schema.source}: ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await insertRows((sql, params) => handle.db.query(sql, params), state.schema, effective);
|
|
69
|
+
return handle;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
await handle.dispose();
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function testSqlite(schema, options = {}) {
|
|
79
|
+
const normalized = isNormalizedSchema(schema)
|
|
80
|
+
? schema
|
|
81
|
+
: parseTestSchema(schema, options);
|
|
82
|
+
if (normalized.dialect !== 'sqlite') {
|
|
83
|
+
throw new TestSqliteError(`testSqlite requires a schema with dialect: sqlite (received: ${normalized.dialect})`);
|
|
84
|
+
}
|
|
85
|
+
return makeBuilder({
|
|
86
|
+
schema: normalized,
|
|
87
|
+
ops: [],
|
|
88
|
+
options: Object.freeze({ ...options }),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function isNormalizedSchema(input) {
|
|
92
|
+
return (!!input &&
|
|
93
|
+
typeof input === 'object' &&
|
|
94
|
+
Array.isArray(input.tables) &&
|
|
95
|
+
Array.isArray(input.sql) &&
|
|
96
|
+
typeof input.source === 'string');
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport { generateDdl } from '../schema/ddl';\nimport { parseTestSchema } from '../schema/parser';\nimport type { NormalizedTestSchema, ParseTestSchemaOptions } from '../schema/types';\nimport { createDatabaseHandle, TestSqliteDatabase, TestSqliteError } from './database';\nimport { composeRows, DataOperation, insertRows, Row, toOperations } from './loadData';\n\nexport interface TestSqliteOptions extends ParseTestSchemaOptions {\n /**\n * Storage for the test database. Defaults to `'file'`: an owned temporary\n * file, removed on `dispose()`. Pass `'memory'` to opt back into an isolated\n * `:memory:` database — only worth it for a test that opens many databases\n * and never touches restart behavior.\n */\n mode?: 'memory' | 'file';\n /** Legacy alias for `mode`. `false` selects `:memory:`. */\n fileBacked?: boolean;\n /** Legacy alias for `mode`. `false` selects `:memory:`. */\n tempFile?: boolean;\n}\n\nexport interface TestSqliteBuilder {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n data(rows: Record<string, Row[]>): TestSqliteBuilder;\n build(): Promise<TestSqliteDatabase>;\n}\n\ninterface BuilderState {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n readonly options: Readonly<TestSqliteOptions>;\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 return {\n label: `sql entry #${entry.index} (${entry.sourcePath})`,\n sql: fs.readFileSync(entry.resolvedPath, 'utf8'),\n };\n } catch (err) {\n throw new TestSqliteError(\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\nasync function executeRaw(handle: TestSqliteDatabase, sql: string): Promise<void> {\n const conn = handle.conn as { exec?: (sql: string) => unknown };\n if (typeof conn?.exec === 'function') {\n await conn.exec(sql);\n } else {\n await handle.db.query(sql, []);\n }\n}\n\nfunction makeBuilder(state: BuilderState): TestSqliteBuilder {\n return {\n schema: state.schema,\n ops: state.ops,\n data(rows: Record<string, Row[]>): TestSqliteBuilder {\n return makeBuilder({\n ...state,\n ops: [...state.ops, ...toOperations(state.schema, rows)],\n });\n },\n async build(): Promise<TestSqliteDatabase> {\n const rawSql = readRawSql(state.schema);\n const ddl = generateDdl(state.schema);\n const effective = composeRows(state.schema, state.ops);\n const fileBacked = state.options.mode\n ? state.options.mode === 'file'\n : state.options.fileBacked !== false && state.options.tempFile !== false;\n const handle = await createDatabaseHandle(fileBacked);\n try {\n for (const statement of ddl) {\n try {\n await handle.db.query(statement, []);\n } catch (err) {\n throw new TestSqliteError(\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 await executeRaw(handle, entry.sql);\n } catch (err) {\n throw new TestSqliteError(\n `Failed to apply raw ${entry.label} from ${state.schema.source}: ${(err as Error).message}`\n );\n }\n }\n await insertRows((sql, params) => handle.db.query(sql, params), state.schema, effective);\n return handle;\n } catch (err) {\n await handle.dispose();\n throw err;\n }\n },\n };\n}\n\nexport function testSqlite(\n schema: string | object,\n options: TestSqliteOptions = {}\n): TestSqliteBuilder {\n const normalized = isNormalizedSchema(schema)\n ? schema\n : parseTestSchema(schema as string | object, options);\n if (normalized.dialect !== 'sqlite') {\n throw new TestSqliteError(\n `testSqlite requires a schema with dialect: sqlite (received: ${normalized.dialect})`\n );\n }\n return makeBuilder({\n schema: normalized,\n ops: [],\n options: Object.freeze({ ...options }),\n });\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,85 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { createSqliteDb } from '../../adapters/sqlite.js';
|
|
5
|
+
import { SqlStackError } from '../../core/errors.js';
|
|
6
|
+
export class TestSqliteError extends SqlStackError {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'TestSqliteError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Temp directories for file-backed handles that were never disposed. Tests are
|
|
14
|
+
* expected to call `dispose()`, but a failed assertion can skip it; this keeps
|
|
15
|
+
* an abandoned directory from outliving the worker process.
|
|
16
|
+
*/
|
|
17
|
+
const abandonedDirectories = new Set();
|
|
18
|
+
let exitCleanupRegistered = false;
|
|
19
|
+
function trackDirectory(directory) {
|
|
20
|
+
abandonedDirectories.add(directory);
|
|
21
|
+
if (exitCleanupRegistered)
|
|
22
|
+
return;
|
|
23
|
+
exitCleanupRegistered = true;
|
|
24
|
+
process.once('exit', () => {
|
|
25
|
+
for (const dir of abandonedDirectories) {
|
|
26
|
+
try {
|
|
27
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* best-effort cleanup */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function createDatabaseHandle(fileBacked) {
|
|
36
|
+
let directory;
|
|
37
|
+
let filename = ':memory:';
|
|
38
|
+
if (fileBacked) {
|
|
39
|
+
directory = fs.mkdtempSync(path.join(os.tmpdir(), 'sqlstack-test-'));
|
|
40
|
+
filename = path.join(directory, 'database.sqlite');
|
|
41
|
+
trackDirectory(directory);
|
|
42
|
+
}
|
|
43
|
+
let db;
|
|
44
|
+
try {
|
|
45
|
+
db = await createSqliteDb(filename);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
if (directory) {
|
|
49
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
50
|
+
abandonedDirectories.delete(directory);
|
|
51
|
+
}
|
|
52
|
+
const message = err.message;
|
|
53
|
+
if (message.includes('No SQLite driver available')) {
|
|
54
|
+
throw new TestSqliteError('testSqlite requires the optional peer "better-sqlite3".');
|
|
55
|
+
}
|
|
56
|
+
throw new TestSqliteError(`Failed to create SQLite test database: ${message}`);
|
|
57
|
+
}
|
|
58
|
+
let disposed = false;
|
|
59
|
+
return {
|
|
60
|
+
db,
|
|
61
|
+
conn: db.conn,
|
|
62
|
+
path: fileBacked ? filename : undefined,
|
|
63
|
+
async query(sql, params = []) {
|
|
64
|
+
if (disposed)
|
|
65
|
+
throw new TestSqliteError('Test SQLite database has been disposed.');
|
|
66
|
+
return db.query(sql, [...params]);
|
|
67
|
+
},
|
|
68
|
+
async dispose() {
|
|
69
|
+
if (disposed)
|
|
70
|
+
return;
|
|
71
|
+
disposed = true;
|
|
72
|
+
try {
|
|
73
|
+
await db.close();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* connection may already be closed */
|
|
77
|
+
}
|
|
78
|
+
if (directory) {
|
|
79
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
80
|
+
abandonedDirectories.delete(directory);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/database.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { createSqliteDb } from '../../adapters/sqlite';\nimport { SqlStackError } from '../../core/errors';\nimport type { Database } from '../../registry';\n\nexport class TestSqliteError extends SqlStackError {\n constructor(message: string) {\n super(message);\n this.name = 'TestSqliteError';\n }\n}\n\nexport interface TestSqliteDatabase {\n /** The live sqlstack SQLite adapter, suitable for direct injection. */\n readonly db: Database;\n /** The underlying better-sqlite3 (or sqlite3) connection. */\n readonly conn: unknown;\n /** Absolute database path in file mode; undefined for :memory:. */\n readonly path?: string;\n query(sql: string, params?: readonly unknown[]): Promise<unknown>;\n /** Close the connection and remove owned temporary files. Idempotent. */\n dispose(): Promise<void>;\n}\n\n/**\n * Temp directories for file-backed handles that were never disposed. Tests are\n * expected to call `dispose()`, but a failed assertion can skip it; this keeps\n * an abandoned directory from outliving the worker process.\n */\nconst abandonedDirectories = new Set<string>();\nlet exitCleanupRegistered = false;\n\nfunction trackDirectory(directory: string): void {\n abandonedDirectories.add(directory);\n if (exitCleanupRegistered) return;\n exitCleanupRegistered = true;\n process.once('exit', () => {\n for (const dir of abandonedDirectories) {\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n } catch {\n /* best-effort cleanup */\n }\n }\n });\n}\n\nexport async function createDatabaseHandle(\n fileBacked: boolean\n): Promise<TestSqliteDatabase> {\n let directory: string | undefined;\n let filename = ':memory:';\n if (fileBacked) {\n directory = fs.mkdtempSync(path.join(os.tmpdir(), 'sqlstack-test-'));\n filename = path.join(directory, 'database.sqlite');\n trackDirectory(directory);\n }\n\n let db: Database;\n try {\n db = await createSqliteDb(filename);\n } catch (err) {\n if (directory) {\n fs.rmSync(directory, { recursive: true, force: true });\n abandonedDirectories.delete(directory);\n }\n const message = (err as Error).message;\n if (message.includes('No SQLite driver available')) {\n throw new TestSqliteError('testSqlite requires the optional peer \"better-sqlite3\".');\n }\n throw new TestSqliteError(`Failed to create SQLite test database: ${message}`);\n }\n\n let disposed = false;\n return {\n db,\n conn: db.conn,\n path: fileBacked ? filename : undefined,\n async query(sql: string, params: readonly unknown[] = []) {\n if (disposed) throw new TestSqliteError('Test SQLite database has been disposed.');\n return db.query(sql, [...params]);\n },\n async dispose() {\n if (disposed) return;\n disposed = true;\n try {\n await db.close();\n } catch {\n /* connection may already be closed */\n }\n if (directory) {\n fs.rmSync(directory, { recursive: true, force: true });\n abandonedDirectories.delete(directory);\n }\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { quoteIdent } from '../schema/ddl.js';
|
|
2
|
+
import { TestDataError, composeRows, planInsertOrder, toOperations, } from '../postgres/loadData.js';
|
|
3
|
+
export { TestDataError, composeRows, planInsertOrder, toOperations };
|
|
4
|
+
export async function insertRows(query, schema, effective) {
|
|
5
|
+
const order = planInsertOrder(schema, [...effective.keys()]);
|
|
6
|
+
const declared = new Map(schema.tables.map((table) => [
|
|
7
|
+
table.name,
|
|
8
|
+
table.columns.map((column) => column.name),
|
|
9
|
+
]));
|
|
10
|
+
for (const tableName of order) {
|
|
11
|
+
const rows = effective.get(tableName);
|
|
12
|
+
const columnOrder = declared.get(tableName);
|
|
13
|
+
const batches = new Map();
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
const columns = columnOrder.filter((column) => row[column] !== undefined);
|
|
16
|
+
const signature = columns.join(',');
|
|
17
|
+
const batch = batches.get(signature) ?? { columns, rows: [] };
|
|
18
|
+
batch.rows.push(row);
|
|
19
|
+
batches.set(signature, batch);
|
|
20
|
+
}
|
|
21
|
+
for (const { columns, rows: batchRows } of batches.values()) {
|
|
22
|
+
if (columns.length === 0) {
|
|
23
|
+
for (let i = 0; i < batchRows.length; i++) {
|
|
24
|
+
await run(query, tableName, `INSERT INTO ${quoteIdent(tableName)} DEFAULT VALUES`, []);
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const params = [];
|
|
29
|
+
const tuples = batchRows.map((row) => {
|
|
30
|
+
const placeholders = columns.map((column) => {
|
|
31
|
+
params.push(row[column]);
|
|
32
|
+
return '?';
|
|
33
|
+
});
|
|
34
|
+
return `(${placeholders.join(', ')})`;
|
|
35
|
+
});
|
|
36
|
+
await run(query, tableName, `INSERT INTO ${quoteIdent(tableName)} (${columns.map(quoteIdent).join(', ')}) VALUES ${tuples.join(', ')}`, params);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function run(query, tableName, sql, params) {
|
|
41
|
+
try {
|
|
42
|
+
await query(sql, params);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new TestDataError(`Failed to insert scenario rows into table "${tableName}": ${err.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=loadData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadData.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/loadData.ts"],"names":[],"mappings":"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","sourcesContent":["import { quoteIdent } from '../schema/ddl';\nimport type { NormalizedTestSchema } from '../schema/types';\nimport {\n TestDataError,\n composeRows,\n planInsertOrder,\n toOperations,\n} from '../postgres/loadData';\nimport type { DataOperation, Row } from '../postgres/loadData';\n\nexport { TestDataError, composeRows, planInsertOrder, toOperations };\nexport type { DataOperation, Row };\n\nexport async function insertRows(\n query: (sql: string, params: unknown[]) => Promise<unknown>,\n schema: NormalizedTestSchema,\n effective: Map<string, Row[]>\n): Promise<void> {\n const order = planInsertOrder(schema, [...effective.keys()]);\n const declared = new Map(schema.tables.map((table) => [\n table.name,\n table.columns.map((column) => column.name),\n ]));\n for (const tableName of order) {\n const rows = effective.get(tableName)!;\n const columnOrder = declared.get(tableName)!;\n const batches = new Map<string, { columns: string[]; rows: Row[] }>();\n for (const row of rows) {\n const columns = columnOrder.filter((column) => row[column] !== undefined);\n const signature = columns.join(',');\n const batch = batches.get(signature) ?? { columns, rows: [] };\n batch.rows.push(row);\n batches.set(signature, batch);\n }\n for (const { columns, rows: batchRows } of batches.values()) {\n if (columns.length === 0) {\n for (let i = 0; i < batchRows.length; i++) {\n await run(query, tableName, `INSERT INTO ${quoteIdent(tableName)} DEFAULT VALUES`, []);\n }\n continue;\n }\n const params: unknown[] = [];\n const tuples = batchRows.map((row) => {\n const placeholders = columns.map((column) => {\n params.push(row[column]);\n return '?';\n });\n return `(${placeholders.join(', ')})`;\n });\n await run(\n query,\n tableName,\n `INSERT INTO ${quoteIdent(tableName)} (${columns.map(quoteIdent).join(', ')}) VALUES ${tuples.join(', ')}`,\n params\n );\n }\n }\n}\n\nasync function run(\n query: (sql: string, params: unknown[]) => Promise<unknown>,\n tableName: string,\n sql: string,\n params: unknown[]\n): Promise<void> {\n try {\n await query(sql, params);\n } catch (err) {\n throw new TestDataError(\n `Failed to insert scenario rows into table \"${tableName}\": ${(err as Error).message}`\n );\n }\n}\n"]}
|
package/dist/esm/transactions.js
CHANGED
|
@@ -11,9 +11,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
11
11
|
};
|
|
12
12
|
var _TransactionHandle_state, _TransactionHandle_owned, _TransactionHandle_finished;
|
|
13
13
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
14
|
-
import { SqlStackDB } from './registry.js';
|
|
15
14
|
import { createPgDb, createMysqlDb } from './adapters/index.js';
|
|
16
15
|
import { RollbackTransactionError, SqlStackError } from './core/errors.js';
|
|
16
|
+
import { resolveExecution, legacyEntry } from './runtime.js';
|
|
17
17
|
const als = new AsyncLocalStorage();
|
|
18
18
|
function getStore() {
|
|
19
19
|
return als.getStore();
|
|
@@ -31,14 +31,17 @@ function unwrapRollbackCause(err) {
|
|
|
31
31
|
return typeof err.cause === 'undefined' ? err : err.cause;
|
|
32
32
|
}
|
|
33
33
|
export class TransactionHandle {
|
|
34
|
-
constructor(
|
|
34
|
+
constructor(entry, state, owned) {
|
|
35
35
|
_TransactionHandle_state.set(this, void 0);
|
|
36
36
|
_TransactionHandle_owned.set(this, void 0);
|
|
37
37
|
_TransactionHandle_finished.set(this, false);
|
|
38
|
-
this.
|
|
38
|
+
this.entry = entry;
|
|
39
39
|
__classPrivateFieldSet(this, _TransactionHandle_state, state, "f");
|
|
40
40
|
__classPrivateFieldSet(this, _TransactionHandle_owned, owned, "f");
|
|
41
41
|
}
|
|
42
|
+
get dbName() {
|
|
43
|
+
return this.entry.name;
|
|
44
|
+
}
|
|
42
45
|
async ensureStarted() {
|
|
43
46
|
await __classPrivateFieldGet(this, _TransactionHandle_state, "f").ensureStarted();
|
|
44
47
|
}
|
|
@@ -78,13 +81,13 @@ export class TransactionHandle {
|
|
|
78
81
|
}
|
|
79
82
|
_TransactionHandle_state = new WeakMap(), _TransactionHandle_owned = new WeakMap(), _TransactionHandle_finished = new WeakMap();
|
|
80
83
|
class TxState {
|
|
81
|
-
constructor(
|
|
84
|
+
constructor(entry, options) {
|
|
82
85
|
this.depth = 1;
|
|
83
86
|
this.started = false;
|
|
84
87
|
this.completed = false;
|
|
85
88
|
this.rollbackOnly = false;
|
|
86
|
-
this.
|
|
87
|
-
this.db = db;
|
|
89
|
+
this.entry = entry;
|
|
90
|
+
this.db = entry.db;
|
|
88
91
|
this.options = {
|
|
89
92
|
lazy: options?.lazy ?? true,
|
|
90
93
|
nested: options?.nested ?? 'join',
|
|
@@ -192,40 +195,52 @@ class TxState {
|
|
|
192
195
|
throw err;
|
|
193
196
|
}
|
|
194
197
|
}
|
|
195
|
-
function enterTx(
|
|
198
|
+
function enterTx(entry, options) {
|
|
196
199
|
const { store, isNew } = getOrCreateStore();
|
|
197
|
-
const existing = store.txByDb.get(
|
|
200
|
+
const existing = store.txByDb.get(entry);
|
|
198
201
|
if (existing) {
|
|
199
202
|
existing.depth++;
|
|
200
|
-
return { handle: new TransactionHandle(
|
|
203
|
+
return { handle: new TransactionHandle(entry, existing, false), store, isNewStore: isNew };
|
|
201
204
|
}
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return { handle: new TransactionHandle(dbName, state, true), store, isNewStore: isNew };
|
|
205
|
+
const state = new TxState(entry, options);
|
|
206
|
+
store.txByDb.set(entry, state);
|
|
207
|
+
return { handle: new TransactionHandle(entry, state, true), store, isNewStore: isNew };
|
|
206
208
|
}
|
|
207
|
-
async function exitTx(
|
|
208
|
-
const state = store.txByDb.get(
|
|
209
|
+
async function exitTx(entry, store) {
|
|
210
|
+
const state = store.txByDb.get(entry);
|
|
209
211
|
if (!state)
|
|
210
212
|
return;
|
|
211
213
|
state.depth = Math.max(0, state.depth - 1);
|
|
212
214
|
if (state.depth === 0) {
|
|
213
|
-
store.txByDb.delete(
|
|
215
|
+
store.txByDb.delete(entry);
|
|
214
216
|
}
|
|
215
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* @deprecated Synchronous lookup against the legacy global registry only.
|
|
220
|
+
* Under an active IoC root, use `currentTransactionFor(entry)` with an entry
|
|
221
|
+
* from the root's SqlStack (`stack.getEntry(name)`).
|
|
222
|
+
*/
|
|
216
223
|
export function currentTransaction(db) {
|
|
217
224
|
const store = getStore();
|
|
218
225
|
if (!store)
|
|
219
226
|
return undefined;
|
|
220
|
-
const
|
|
221
|
-
const state = store.txByDb.get(
|
|
227
|
+
const entry = legacyEntry(db);
|
|
228
|
+
const state = store.txByDb.get(entry);
|
|
229
|
+
if (!state)
|
|
230
|
+
return undefined;
|
|
231
|
+
return new TransactionHandle(entry, state, false);
|
|
232
|
+
}
|
|
233
|
+
/** Active transaction handle for a concrete database entry, if any. */
|
|
234
|
+
export function currentTransactionFor(entry) {
|
|
235
|
+
const store = getStore();
|
|
236
|
+
const state = store?.txByDb.get(entry);
|
|
222
237
|
if (!state)
|
|
223
238
|
return undefined;
|
|
224
|
-
return new TransactionHandle(
|
|
239
|
+
return new TransactionHandle(entry, state, false);
|
|
225
240
|
}
|
|
226
241
|
export async function withTransaction(fn, options) {
|
|
227
|
-
const
|
|
228
|
-
const { handle, store, isNewStore } = enterTx(
|
|
242
|
+
const { entry } = await resolveExecution(options?.db);
|
|
243
|
+
const { handle, store, isNewStore } = enterTx(entry, options);
|
|
229
244
|
const run = async () => {
|
|
230
245
|
try {
|
|
231
246
|
const out = await fn();
|
|
@@ -237,7 +252,7 @@ export async function withTransaction(fn, options) {
|
|
|
237
252
|
throw err;
|
|
238
253
|
}
|
|
239
254
|
finally {
|
|
240
|
-
await exitTx(
|
|
255
|
+
await exitTx(entry, store);
|
|
241
256
|
}
|
|
242
257
|
};
|
|
243
258
|
return isNewStore ? await als.run(store, run) : await run();
|
|
@@ -261,8 +276,8 @@ function wrapMethod(_target, _prop, desc, options) {
|
|
|
261
276
|
return await withTransaction(() => original.apply(this, callArgs), options);
|
|
262
277
|
};
|
|
263
278
|
}
|
|
264
|
-
export function getActiveTransaction(
|
|
279
|
+
export function getActiveTransaction(entry) {
|
|
265
280
|
const store = getStore();
|
|
266
|
-
return store?.txByDb.get(
|
|
281
|
+
return store?.txByDb.get(entry);
|
|
267
282
|
}
|
|
268
283
|
//# sourceMappingURL=transactions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactions.js","sourceRoot":"","sources":["../../src/transactions.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAYxE,MAAM,GAAG,GAAG,IAAI,iBAAiB,EAAW,CAAC;AAE7C,SAAS,QAAQ;IACf,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,QAAQ,GAAG,QAAQ,EAAE,CAAC;IAC5B,IAAI,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACvD,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACvD,CAAC;AAED,SAAS,0BAA0B,CAAC,GAAY;IAC9C,OAAO,GAAG,YAAY,wBAAwB,CAAC;AACjD,CAAC;AAED,SAAS,mBAAmB,CAAC,GAA6B;IACxD,OAAO,OAAO,GAAG,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;AAC5D,CAAC;AAED,MAAM,OAAO,iBAAiB;IAM5B,YAAY,MAAc,EAAE,KAAc,EAAE,KAAc;QAJ1D,2CAAgB;QAChB,2CAAgB;QAChB,sCAAY,KAAK,EAAC;QAGhB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,uBAAA,IAAI,4BAAU,KAAK,MAAA,CAAC;QACpB,uBAAA,IAAI,4BAAU,KAAK,MAAA,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,uBAAA,IAAI,gCAAO,CAAC,aAAa,EAAE,CAAC;IACpC,CAAC;IAED,YAAY,CAAC,YAAsB;QACjC,uBAAA,IAAI,gCAAO,CAAC,YAAY,GAAG,IAAI,CAAC;QAChC,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;YACxC,uBAAA,IAAI,gCAAO,CAAC,aAAa,GAAG,YAAY,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,uBAAA,IAAI,gCAAO;YAAE,OAAO;QACzB,MAAM,uBAAA,IAAI,gCAAO,CAAC,eAAe,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,uBAAA,IAAI,gCAAO;YAAE,OAAO;QACzB,MAAM,uBAAA,IAAI,gCAAO,CAAC,iBAAiB,EAAE,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,uBAAA,IAAI,gCAAO;YAAE,OAAO;QACzB,MAAM,uBAAA,IAAI,gCAAO,CAAC,OAAO,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,uBAAA,IAAI,gCAAO,IAAI,uBAAA,IAAI,mCAAU;YAAE,OAAO;QAC3C,uBAAA,IAAI,+BAAa,IAAI,MAAA,CAAC;QACtB,MAAM,uBAAA,IAAI,gCAAO,CAAC,eAAe,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,GAAY;QAC9B,IAAI,CAAC,uBAAA,IAAI,gCAAO,IAAI,uBAAA,IAAI,mCAAU;YAAE,OAAO;QAC3C,uBAAA,IAAI,+BAAa,IAAI,MAAA,CAAC;QACtB,MAAM,uBAAA,IAAI,gCAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;CACF;;AAED,MAAM,OAAO;IAaX,YAAY,MAAc,EAAE,EAAY,EAAE,OAAuC;QATjF,UAAK,GAAG,CAAC,CAAC;QACV,YAAO,GAAG,KAAK,CAAC;QAChB,cAAS,GAAG,KAAK,CAAC;QAClB,iBAAY,GAAG,KAAK,CAAC;QAOnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,OAAO,GAAG;YACb,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,IAAI;YAC3B,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,MAAM;SAClC,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,QAAQ,CAAC;QAC5C,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAQ,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;YAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBAChD,MAAM,IAAI,aAAa,CAAC,0EAA0E,CAAC,CAAC;YACtG,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;YAC/B,IAAI,CAAC,SAAS,GAAG,KAAK,IAAI,EAAE;gBAC1B,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU;oBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YAC7D,CAAC,CAAC;YACF,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YACxB,MAAM,IAAI,GAAQ,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;YAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;gBACtD,MAAM,IAAI,aAAa,CAAC,iFAAiF,CAAC,CAAC;YAC7G,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,CAAC,SAAS,GAAG,KAAK,IAAI,EAAE;gBAC1B,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU;oBAAE,IAAI,CAAC,OAAO,EAAE,CAAC;YACzD,CAAC,CAAC;YACF,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;YAC/C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,OAAO;QACT,CAAC;QAED,+CAA+C;QAC/C,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,MAAiB;QACxC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,OAAO;QACT,CAAC;QACD,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,OAAO;QACT,CAAC;QACD,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,SAAS;gBAAE,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7C,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;YACtB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACtB,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC/B,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,WAAW;oBAAE,MAAM,IAAI,CAAC,aAAa,CAAC;gBACxE,MAAM,IAAI,wBAAwB,EAAE,CAAC;YACvC,CAAC;YACD,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,GAAY;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACjC,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QACD,IAAI,0BAA0B,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;CACF;AAED,SAAS,OAAO,CAAC,MAAc,EAAE,OAA4B;IAC3D,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,gBAAgB,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,IAAI,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAC9F,CAAC;IACD,MAAM,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAC/C,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,EAAE,IAAI,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAC1F,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,MAAc,EAAE,KAAc;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,EAAW;IAC5C,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,OAAO,IAAI,iBAAiB,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAI,EAAoB,EAAE,OAA4B;IACzF,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC;IACvD,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAEjE,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,EAAE,EAAE,CAAC;YACvB,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;YAC/B,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAChC,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,UAAU,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;AAC9D,CAAC;AAOD,MAAM,CAAC,MAAM,WAAW,GAAyB,UAAU,GAAG,IAAW;IACvE,eAAe;IACf,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,UAAU,EAAE,CAAC;QAC9D,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,IAAkD,CAAC;QAChF,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IACD,0CAA0C;IAC1C,MAAM,OAAO,GAAmC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtF,OAAO,UAAU,MAAW,EAAE,IAAqB,EAAE,IAAwB;QAC3E,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC;AACJ,CAAQ,CAAC;AAET,SAAS,UAAU,CAAC,OAAY,EAAE,KAAsB,EAAE,IAAwB,EAAE,OAA4B;IAC9G,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;IAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,WAAW,GAAG,QAAe;QAC7C,OAAO,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC9E,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,OAAO,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Database, QueryResult } from './registry';\nimport { SqlStackDB } from './registry';\nimport { createPgDb, createMysqlDb } from './adapters';\nimport { RollbackTransactionError, SqlStackError } from './core/errors';\n\nexport type TransactionOptions = {\n db?: string;\n lazy?: boolean;\n nested?: 'join';\n};\n\ntype TxStore = {\n txByDb: Map<string, TxState>;\n};\n\nconst als = new AsyncLocalStorage<TxStore>();\n\nfunction getStore(): TxStore | undefined {\n return als.getStore();\n}\n\nfunction getOrCreateStore(): { store: TxStore; isNew: boolean } {\n const existing = getStore();\n if (existing) return { store: existing, isNew: false };\n return { store: { txByDb: new Map() }, isNew: true };\n}\n\nfunction isRollbackTransactionError(err: unknown): err is RollbackTransactionError {\n return err instanceof RollbackTransactionError;\n}\n\nfunction unwrapRollbackCause(err: RollbackTransactionError): unknown {\n return typeof err.cause === 'undefined' ? err : err.cause;\n}\n\nexport class TransactionHandle {\n readonly dbName: string;\n #state: TxState;\n #owned: boolean;\n #finished = false;\n\n constructor(dbName: string, state: TxState, owned: boolean) {\n this.dbName = dbName;\n this.#state = state;\n this.#owned = owned;\n }\n\n async ensureStarted() {\n await this.#state.ensureStarted();\n }\n\n rollbackOnly(errorToThrow?: unknown) {\n this.#state.rollbackOnly = true;\n if (typeof errorToThrow !== 'undefined') {\n this.#state.rollbackError = errorToThrow;\n }\n }\n\n async commit() {\n if (!this.#owned) return;\n await this.#state.commitIfStarted();\n }\n\n async rollback() {\n if (!this.#owned) return;\n await this.#state.rollbackIfStarted();\n }\n\n async release() {\n if (!this.#owned) return;\n await this.#state.release();\n }\n\n async finishOnSuccess() {\n if (!this.#owned || this.#finished) return;\n this.#finished = true;\n await this.#state.finishOnSuccess();\n }\n\n async finishOnError(err: unknown) {\n if (!this.#owned || this.#finished) return;\n this.#finished = true;\n await this.#state.finishOnError(err);\n }\n}\n\nclass TxState {\n readonly dbName: string;\n readonly db: Database;\n readonly options: Required<Pick<TransactionOptions, 'lazy'>> & Pick<TransactionOptions, 'nested'>;\n depth = 1;\n started = false;\n completed = false;\n rollbackOnly = false;\n rollbackError: unknown | undefined;\n\n private txDb: Database | undefined;\n private releaseFn: (() => Promise<void>) | undefined;\n\n constructor(dbName: string, db: Database, options: TransactionOptions | undefined) {\n this.dbName = dbName;\n this.db = db;\n this.options = {\n lazy: options?.lazy ?? true,\n nested: options?.nested ?? 'join',\n };\n }\n\n get activeDb(): Database {\n return this.txDb ?? this.db;\n }\n\n async ensureStarted() {\n if (this.started) return;\n\n const dialect = this.db.dialect ?? 'sqlite';\n if (dialect === 'postgres') {\n const pool: any = this.db.conn;\n if (!pool || typeof pool.connect !== 'function') {\n throw new SqlStackError('Postgres transactions require a pg Pool with .connect() as Database.conn');\n }\n const client = await pool.connect();\n this.txDb = createPgDb(client);\n this.releaseFn = async () => {\n if (typeof client.release === 'function') client.release();\n };\n await this.txDb.query('BEGIN', []);\n this.started = true;\n return;\n }\n\n if (dialect === 'mysql') {\n const pool: any = this.db.conn;\n if (!pool || typeof pool.getConnection !== 'function') {\n throw new SqlStackError('MySQL transactions require a mysql2 Pool with .getConnection() as Database.conn');\n }\n const conn = await pool.getConnection();\n this.txDb = createMysqlDb(conn);\n this.releaseFn = async () => {\n if (typeof conn.release === 'function') conn.release();\n };\n await this.txDb.query('START TRANSACTION', []);\n this.started = true;\n return;\n }\n\n // sqlite: existing connection is already bound\n await this.db.query('BEGIN', []);\n this.started = true;\n }\n\n async query(sql: string, params: unknown[]): Promise<QueryResult> {\n await this.ensureStarted();\n return await this.activeDb.query(sql, params);\n }\n\n async commitIfStarted() {\n if (this.completed) return;\n if (!this.started) {\n this.completed = true;\n return;\n }\n await this.activeDb.query('COMMIT', []);\n this.completed = true;\n }\n\n async rollbackIfStarted() {\n if (this.completed) return;\n if (!this.started) {\n this.completed = true;\n return;\n }\n await this.activeDb.query('ROLLBACK', []);\n this.completed = true;\n }\n\n async release() {\n try {\n if (this.releaseFn) await this.releaseFn();\n } finally {\n this.txDb = undefined;\n this.releaseFn = undefined;\n }\n }\n\n async finishOnSuccess() {\n try {\n if (this.rollbackOnly) {\n await this.rollbackIfStarted();\n if (typeof this.rollbackError !== 'undefined') throw this.rollbackError;\n throw new RollbackTransactionError();\n }\n await this.commitIfStarted();\n } finally {\n await this.release();\n }\n }\n\n async finishOnError(err: unknown) {\n try {\n await this.rollbackIfStarted();\n } finally {\n await this.release();\n }\n if (isRollbackTransactionError(err)) {\n throw unwrapRollbackCause(err);\n }\n throw err;\n }\n}\n\nfunction enterTx(dbName: string, options?: TransactionOptions): { handle: TransactionHandle; store: TxStore; isNewStore: boolean } {\n const { store, isNew } = getOrCreateStore();\n const existing = store.txByDb.get(dbName);\n if (existing) {\n existing.depth++;\n return { handle: new TransactionHandle(dbName, existing, false), store, isNewStore: isNew };\n }\n const { db } = SqlStackDB.getEntry(dbName);\n const state = new TxState(dbName, db, options);\n store.txByDb.set(dbName, state);\n return { handle: new TransactionHandle(dbName, state, true), store, isNewStore: isNew };\n}\n\nasync function exitTx(dbName: string, store: TxStore) {\n const state = store.txByDb.get(dbName);\n if (!state) return;\n state.depth = Math.max(0, state.depth - 1);\n if (state.depth === 0) {\n store.txByDb.delete(dbName);\n }\n}\n\nexport function currentTransaction(db?: string): TransactionHandle | undefined {\n const store = getStore();\n if (!store) return undefined;\n const resolved = SqlStackDB.getEntry(db).name;\n const state = store.txByDb.get(resolved);\n if (!state) return undefined;\n return new TransactionHandle(resolved, state, false);\n}\n\nexport async function withTransaction<T>(fn: () => Promise<T>, options?: TransactionOptions): Promise<T> {\n const resolved = SqlStackDB.getEntry(options?.db).name;\n const { handle, store, isNewStore } = enterTx(resolved, options);\n\n const run = async () => {\n try {\n const out = await fn();\n await handle.finishOnSuccess();\n return out;\n } catch (err) {\n await handle.finishOnError(err);\n throw err;\n } finally {\n await exitTx(resolved, store);\n }\n };\n\n return isNewStore ? await als.run(store, run) : await run();\n}\n\nexport type TransactionDecorator = {\n (target: any, prop: string | symbol, desc: PropertyDescriptor): void;\n (options?: TransactionOptions): (target: any, prop: string | symbol, desc: PropertyDescriptor) => void;\n};\n\nexport const transaction: TransactionDecorator = function (...args: any[]) {\n // @transaction\n if (args.length === 3 && typeof args[2]?.value === 'function') {\n const [target, prop, desc] = args as [any, string | symbol, PropertyDescriptor];\n wrapMethod(target, prop, desc, undefined);\n return;\n }\n // @transaction() or @transaction({ ... })\n const options: TransactionOptions | undefined = args.length > 0 ? args[0] : undefined;\n return function (target: any, prop: string | symbol, desc: PropertyDescriptor) {\n wrapMethod(target, prop, desc, options);\n };\n} as any;\n\nfunction wrapMethod(_target: any, _prop: string | symbol, desc: PropertyDescriptor, options?: TransactionOptions) {\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n return await withTransaction(() => original.apply(this, callArgs), options);\n };\n}\n\nexport type ActiveTransaction = {\n query(sql: string, params: unknown[]): Promise<QueryResult>;\n};\n\nexport function getActiveTransaction(dbName: string): ActiveTransaction | undefined {\n const store = getStore();\n return store?.txByDb.get(dbName);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transactions.js","sourceRoot":"","sources":["../../src/transactions.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Database, QueryResult } from './registry';\nimport { createPgDb, createMysqlDb } from './adapters';\nimport { RollbackTransactionError, SqlStackError } from './core/errors';\nimport type { DatabaseEntry } from './stack';\nimport { resolveExecution, legacyEntry } from './runtime';\n\nexport type TransactionOptions = {\n db?: string;\n lazy?: boolean;\n nested?: 'join';\n};\n\n// Keyed by concrete DatabaseEntry identity (owning stack + registration), so\n// two roots transacting on the same name (\"primary\") can never collide\n// (spec 16 §20, spec 17 §14).\ntype TxStore = {\n txByDb: Map<DatabaseEntry, TxState>;\n};\n\nconst als = new AsyncLocalStorage<TxStore>();\n\nfunction getStore(): TxStore | undefined {\n return als.getStore();\n}\n\nfunction getOrCreateStore(): { store: TxStore; isNew: boolean } {\n const existing = getStore();\n if (existing) return { store: existing, isNew: false };\n return { store: { txByDb: new Map() }, isNew: true };\n}\n\nfunction isRollbackTransactionError(err: unknown): err is RollbackTransactionError {\n return err instanceof RollbackTransactionError;\n}\n\nfunction unwrapRollbackCause(err: RollbackTransactionError): unknown {\n return typeof err.cause === 'undefined' ? err : err.cause;\n}\n\nexport class TransactionHandle {\n readonly entry: DatabaseEntry;\n #state: TxState;\n #owned: boolean;\n #finished = false;\n\n constructor(entry: DatabaseEntry, state: TxState, owned: boolean) {\n this.entry = entry;\n this.#state = state;\n this.#owned = owned;\n }\n\n get dbName(): string {\n return this.entry.name;\n }\n\n async ensureStarted() {\n await this.#state.ensureStarted();\n }\n\n rollbackOnly(errorToThrow?: unknown) {\n this.#state.rollbackOnly = true;\n if (typeof errorToThrow !== 'undefined') {\n this.#state.rollbackError = errorToThrow;\n }\n }\n\n async commit() {\n if (!this.#owned) return;\n await this.#state.commitIfStarted();\n }\n\n async rollback() {\n if (!this.#owned) return;\n await this.#state.rollbackIfStarted();\n }\n\n async release() {\n if (!this.#owned) return;\n await this.#state.release();\n }\n\n async finishOnSuccess() {\n if (!this.#owned || this.#finished) return;\n this.#finished = true;\n await this.#state.finishOnSuccess();\n }\n\n async finishOnError(err: unknown) {\n if (!this.#owned || this.#finished) return;\n this.#finished = true;\n await this.#state.finishOnError(err);\n }\n}\n\nclass TxState {\n readonly entry: DatabaseEntry;\n readonly db: Database;\n readonly options: Required<Pick<TransactionOptions, 'lazy'>> & Pick<TransactionOptions, 'nested'>;\n depth = 1;\n started = false;\n completed = false;\n rollbackOnly = false;\n rollbackError: unknown | undefined;\n\n private txDb: Database | undefined;\n private releaseFn: (() => Promise<void>) | undefined;\n\n constructor(entry: DatabaseEntry, options: TransactionOptions | undefined) {\n this.entry = entry;\n this.db = entry.db;\n this.options = {\n lazy: options?.lazy ?? true,\n nested: options?.nested ?? 'join',\n };\n }\n\n get activeDb(): Database {\n return this.txDb ?? this.db;\n }\n\n async ensureStarted() {\n if (this.started) return;\n\n const dialect = this.db.dialect ?? 'sqlite';\n if (dialect === 'postgres') {\n const pool: any = this.db.conn;\n if (!pool || typeof pool.connect !== 'function') {\n throw new SqlStackError('Postgres transactions require a pg Pool with .connect() as Database.conn');\n }\n const client = await pool.connect();\n this.txDb = createPgDb(client);\n this.releaseFn = async () => {\n if (typeof client.release === 'function') client.release();\n };\n await this.txDb.query('BEGIN', []);\n this.started = true;\n return;\n }\n\n if (dialect === 'mysql') {\n const pool: any = this.db.conn;\n if (!pool || typeof pool.getConnection !== 'function') {\n throw new SqlStackError('MySQL transactions require a mysql2 Pool with .getConnection() as Database.conn');\n }\n const conn = await pool.getConnection();\n this.txDb = createMysqlDb(conn);\n this.releaseFn = async () => {\n if (typeof conn.release === 'function') conn.release();\n };\n await this.txDb.query('START TRANSACTION', []);\n this.started = true;\n return;\n }\n\n // sqlite: existing connection is already bound\n await this.db.query('BEGIN', []);\n this.started = true;\n }\n\n async query(sql: string, params: unknown[]): Promise<QueryResult> {\n await this.ensureStarted();\n return await this.activeDb.query(sql, params);\n }\n\n async commitIfStarted() {\n if (this.completed) return;\n if (!this.started) {\n this.completed = true;\n return;\n }\n await this.activeDb.query('COMMIT', []);\n this.completed = true;\n }\n\n async rollbackIfStarted() {\n if (this.completed) return;\n if (!this.started) {\n this.completed = true;\n return;\n }\n await this.activeDb.query('ROLLBACK', []);\n this.completed = true;\n }\n\n async release() {\n try {\n if (this.releaseFn) await this.releaseFn();\n } finally {\n this.txDb = undefined;\n this.releaseFn = undefined;\n }\n }\n\n async finishOnSuccess() {\n try {\n if (this.rollbackOnly) {\n await this.rollbackIfStarted();\n if (typeof this.rollbackError !== 'undefined') throw this.rollbackError;\n throw new RollbackTransactionError();\n }\n await this.commitIfStarted();\n } finally {\n await this.release();\n }\n }\n\n async finishOnError(err: unknown) {\n try {\n await this.rollbackIfStarted();\n } finally {\n await this.release();\n }\n if (isRollbackTransactionError(err)) {\n throw unwrapRollbackCause(err);\n }\n throw err;\n }\n}\n\nfunction enterTx(entry: DatabaseEntry, options?: TransactionOptions): { handle: TransactionHandle; store: TxStore; isNewStore: boolean } {\n const { store, isNew } = getOrCreateStore();\n const existing = store.txByDb.get(entry);\n if (existing) {\n existing.depth++;\n return { handle: new TransactionHandle(entry, existing, false), store, isNewStore: isNew };\n }\n const state = new TxState(entry, options);\n store.txByDb.set(entry, state);\n return { handle: new TransactionHandle(entry, state, true), store, isNewStore: isNew };\n}\n\nasync function exitTx(entry: DatabaseEntry, store: TxStore) {\n const state = store.txByDb.get(entry);\n if (!state) return;\n state.depth = Math.max(0, state.depth - 1);\n if (state.depth === 0) {\n store.txByDb.delete(entry);\n }\n}\n\n/**\n * @deprecated Synchronous lookup against the legacy global registry only.\n * Under an active IoC root, use `currentTransactionFor(entry)` with an entry\n * from the root's SqlStack (`stack.getEntry(name)`).\n */\nexport function currentTransaction(db?: string): TransactionHandle | undefined {\n const store = getStore();\n if (!store) return undefined;\n const entry = legacyEntry(db);\n const state = store.txByDb.get(entry);\n if (!state) return undefined;\n return new TransactionHandle(entry, state, false);\n}\n\n/** Active transaction handle for a concrete database entry, if any. */\nexport function currentTransactionFor(entry: DatabaseEntry): TransactionHandle | undefined {\n const store = getStore();\n const state = store?.txByDb.get(entry);\n if (!state) return undefined;\n return new TransactionHandle(entry, state, false);\n}\n\nexport async function withTransaction<T>(fn: () => Promise<T>, options?: TransactionOptions): Promise<T> {\n const { entry } = await resolveExecution(options?.db);\n const { handle, store, isNewStore } = enterTx(entry, options);\n\n const run = async () => {\n try {\n const out = await fn();\n await handle.finishOnSuccess();\n return out;\n } catch (err) {\n await handle.finishOnError(err);\n throw err;\n } finally {\n await exitTx(entry, store);\n }\n };\n\n return isNewStore ? await als.run(store, run) : await run();\n}\n\nexport type TransactionDecorator = {\n (target: any, prop: string | symbol, desc: PropertyDescriptor): void;\n (options?: TransactionOptions): (target: any, prop: string | symbol, desc: PropertyDescriptor) => void;\n};\n\nexport const transaction: TransactionDecorator = function (...args: any[]) {\n // @transaction\n if (args.length === 3 && typeof args[2]?.value === 'function') {\n const [target, prop, desc] = args as [any, string | symbol, PropertyDescriptor];\n wrapMethod(target, prop, desc, undefined);\n return;\n }\n // @transaction() or @transaction({ ... })\n const options: TransactionOptions | undefined = args.length > 0 ? args[0] : undefined;\n return function (target: any, prop: string | symbol, desc: PropertyDescriptor) {\n wrapMethod(target, prop, desc, options);\n };\n} as any;\n\nfunction wrapMethod(_target: any, _prop: string | symbol, desc: PropertyDescriptor, options?: TransactionOptions) {\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n return await withTransaction(() => original.apply(this, callArgs), options);\n };\n}\n\nexport type ActiveTransaction = {\n query(sql: string, params: unknown[]): Promise<QueryResult>;\n};\n\nexport function getActiveTransaction(entry: DatabaseEntry): ActiveTransaction | undefined {\n const store = getStore();\n return store?.txByDb.get(entry);\n}\n"]}
|