sqlstack 1.0.25 → 3.0.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/mysql.d.ts.map +1 -1
- package/dist/cjs/adapters/mysql.js +3 -3
- package/dist/cjs/adapters/mysql.js.map +1 -1
- package/dist/cjs/adapters/pg.d.ts.map +1 -1
- package/dist/cjs/adapters/pg.js +3 -3
- package/dist/cjs/adapters/pg.js.map +1 -1
- package/dist/cjs/codegen/cli.d.ts +3 -0
- package/dist/cjs/codegen/cli.d.ts.map +1 -0
- package/dist/cjs/codegen/cli.js +50 -0
- package/dist/cjs/codegen/cli.js.map +1 -0
- package/dist/cjs/codegen/generateManifest.d.ts +72 -0
- package/dist/cjs/codegen/generateManifest.d.ts.map +1 -0
- package/dist/cjs/codegen/generateManifest.js +191 -0
- package/dist/cjs/codegen/generateManifest.js.map +1 -0
- package/dist/cjs/core/loadNodeModule.d.ts +17 -0
- package/dist/cjs/core/loadNodeModule.d.ts.map +1 -0
- package/dist/cjs/core/loadNodeModule.js +36 -0
- package/dist/cjs/core/loadNodeModule.js.map +1 -0
- package/dist/cjs/core/resolver.d.ts +94 -0
- package/dist/cjs/core/resolver.d.ts.map +1 -0
- package/dist/cjs/core/resolver.js +145 -0
- package/dist/cjs/core/resolver.js.map +1 -0
- 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 +42 -14
- 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 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -1
- 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/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 +10 -0
- package/dist/cjs/testing/index.d.ts.map +1 -0
- package/dist/cjs/testing/index.js +18 -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 +14 -0
- package/dist/cjs/testing/schema/ddl.d.ts.map +1 -0
- package/dist/cjs/testing/schema/ddl.js +54 -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 +82 -0
- package/dist/cjs/testing/schema/types.d.ts.map +1 -0
- package/dist/cjs/testing/schema/types.js +10 -0
- package/dist/cjs/testing/schema/types.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/adapters/mysql.js +3 -3
- package/dist/esm/adapters/mysql.js.map +1 -1
- package/dist/esm/adapters/pg.js +3 -3
- package/dist/esm/adapters/pg.js.map +1 -1
- package/dist/esm/codegen/cli.js +48 -0
- package/dist/esm/codegen/cli.js.map +1 -0
- package/dist/esm/codegen/generateManifest.js +183 -0
- package/dist/esm/codegen/generateManifest.js.map +1 -0
- package/dist/esm/core/loadNodeModule.js +33 -0
- package/dist/esm/core/loadNodeModule.js.map +1 -0
- package/dist/esm/core/resolver.js +132 -0
- package/dist/esm/core/resolver.js.map +1 -0
- package/dist/esm/core/sourceScanResolver.js +51 -0
- package/dist/esm/core/sourceScanResolver.js.map +1 -0
- package/dist/esm/decorators/query.js +43 -15
- 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 +8 -0
- 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/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 +7 -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 +49 -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 +9 -0
- package/dist/esm/testing/schema/types.js.map +1 -0
- package/dist/esm/transactions.js +39 -24
- package/dist/esm/transactions.js.map +1 -1
- package/package.json +29 -4
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { resolveSqlFromFs } from './fileResolution.js';
|
|
2
|
+
/**
|
|
3
|
+
* Dialect → manifest key suffix. Mirrors the on-disk file convention
|
|
4
|
+
* (`findByEmail.pg.sql` → key `UsersRepo.findByEmail.pg`).
|
|
5
|
+
*/
|
|
6
|
+
export const MANIFEST_DIALECT_EXT = {
|
|
7
|
+
postgres: 'pg',
|
|
8
|
+
mysql: 'mysql',
|
|
9
|
+
sqlite: 'sqlite',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Node filesystem resolution — the v1 behavior, unchanged: dialect-specific
|
|
13
|
+
* `<method>.<ext>.sql` first, then generic `<method>.sql`, from the rootDir
|
|
14
|
+
* the decorator captured (explicit argument or stack-based directory guess).
|
|
15
|
+
*/
|
|
16
|
+
export class FsResolver {
|
|
17
|
+
resolve({ rootDir, methodName, dialect }) {
|
|
18
|
+
if (!rootDir)
|
|
19
|
+
return undefined;
|
|
20
|
+
return resolveSqlFromFs(rootDir, methodName, dialect).sql;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build-time manifest resolution — bundling-proof (Cloudflare Workers).
|
|
25
|
+
* Lookup order per Contract A:
|
|
26
|
+
* 1. `${className}.${methodName}.${dialectExt}`
|
|
27
|
+
* 2. `${className}.${methodName}` (generic fallback)
|
|
28
|
+
*/
|
|
29
|
+
export class ManifestResolver {
|
|
30
|
+
constructor(manifest, options = {}) {
|
|
31
|
+
this.manifest = manifest;
|
|
32
|
+
this.options = options;
|
|
33
|
+
}
|
|
34
|
+
resolve({ className, methodName, dialect }) {
|
|
35
|
+
const ext = MANIFEST_DIALECT_EXT[dialect];
|
|
36
|
+
return (this.manifest[`${className}.${methodName}.${ext}`] ??
|
|
37
|
+
this.manifest[`${className}.${methodName}`]);
|
|
38
|
+
}
|
|
39
|
+
/** True when the manifest has a generic or any dialect-specific entry. */
|
|
40
|
+
hasEntryFor(className, methodName) {
|
|
41
|
+
const generic = `${className}.${methodName}`;
|
|
42
|
+
if (generic in this.manifest)
|
|
43
|
+
return true;
|
|
44
|
+
return Object.values(MANIFEST_DIALECT_EXT).some((ext) => `${generic}.${ext}` in this.manifest);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Immutable per-class record of @Query-decorated methods, written at
|
|
48
|
+
// class-definition time onto the prototype. Unlike the process-global list
|
|
49
|
+
// below, this cannot be polluted by unrelated imports/tests: coverage checks
|
|
50
|
+
// derive from the classes explicitly supplied to them (repo spec 19 §10).
|
|
51
|
+
const QUERY_METHODS = Symbol.for('sqlstack:queryMethods');
|
|
52
|
+
/** @internal Called by the @Query decorator at class-definition time. */
|
|
53
|
+
export function recordQueryMethodOnClass(proto, entry) {
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(proto, QUERY_METHODS)) {
|
|
55
|
+
Object.defineProperty(proto, QUERY_METHODS, { value: [], enumerable: false });
|
|
56
|
+
}
|
|
57
|
+
proto[QUERY_METHODS].push(entry);
|
|
58
|
+
}
|
|
59
|
+
/** @Query-decorated methods declared by the given classes (own + inherited). */
|
|
60
|
+
export function queryMethodsOf(classes) {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const cls of classes) {
|
|
63
|
+
let proto = cls.prototype;
|
|
64
|
+
while (proto && proto !== Object.prototype) {
|
|
65
|
+
if (Object.prototype.hasOwnProperty.call(proto, QUERY_METHODS)) {
|
|
66
|
+
out.push(...proto[QUERY_METHODS]);
|
|
67
|
+
}
|
|
68
|
+
proto = Object.getPrototypeOf(proto);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
// Single shared state across module copies (same pattern as SqlStackDB).
|
|
74
|
+
const g = globalThis;
|
|
75
|
+
if (!g.__SQLSTACK_RESOLVER_STATE__) {
|
|
76
|
+
g.__SQLSTACK_RESOLVER_STATE__ = { resolver: undefined, decorated: [] };
|
|
77
|
+
}
|
|
78
|
+
const state = g.__SQLSTACK_RESOLVER_STATE__;
|
|
79
|
+
/** @internal Called by the @Query decorator at class-definition time. */
|
|
80
|
+
export function registerDecoratedMethod(entry) {
|
|
81
|
+
state.decorated.push(entry);
|
|
82
|
+
}
|
|
83
|
+
/** @internal Read by the @Query call path. */
|
|
84
|
+
export function getActiveResolver() {
|
|
85
|
+
return state.resolver;
|
|
86
|
+
}
|
|
87
|
+
function assertMissing(resolver, decorated) {
|
|
88
|
+
const missing = decorated
|
|
89
|
+
.filter((m) => !m.inline)
|
|
90
|
+
.filter((m) => !resolver.hasEntryFor(m.className, m.methodName))
|
|
91
|
+
.map((m) => `${m.className}.${m.methodName}`);
|
|
92
|
+
if (missing.length) {
|
|
93
|
+
throw new Error('sqlstack: ManifestResolver has no SQL for these @Query methods:\n' +
|
|
94
|
+
missing.map((k) => ` - ${k}`).join('\n') +
|
|
95
|
+
'\nRegenerate the manifest (see docs/codegen.md), or pass ' +
|
|
96
|
+
'{ assert: false } if these methods return a SqlFragment from their body.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Environment-local manifest coverage check: validates only the explicitly
|
|
101
|
+
* supplied classes, from their immutable class metadata. Deterministic
|
|
102
|
+
* regardless of what else the process happened to import.
|
|
103
|
+
*/
|
|
104
|
+
export function assertManifestCoversClasses(resolver, classes) {
|
|
105
|
+
assertMissing(resolver, queryMethodsOf(classes));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* @deprecated Process-global resolver registration (legacy v2 surface,
|
|
109
|
+
* previously `SqlStack.useResolver(...)`). The canonical path is a per-root
|
|
110
|
+
* resolver on the SqlStack instance (see src/stack.ts); this global is only
|
|
111
|
+
* consulted when no IoC environment is active. Reached via the deprecated
|
|
112
|
+
* static `SqlStack.useResolver(...)` forwards during migration.
|
|
113
|
+
*/
|
|
114
|
+
export function setGlobalResolver(resolver) {
|
|
115
|
+
if (resolver instanceof ManifestResolver && resolver.options.assert !== false) {
|
|
116
|
+
assertMissing(resolver, state.decorated);
|
|
117
|
+
}
|
|
118
|
+
state.resolver = resolver;
|
|
119
|
+
}
|
|
120
|
+
/** @deprecated See setGlobalResolver. */
|
|
121
|
+
export function clearGlobalResolver() {
|
|
122
|
+
state.resolver = undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* @internal
|
|
126
|
+
* @deprecated Coverage check against the process-global decorated list; the
|
|
127
|
+
* canonical path is assertManifestCoversClasses with an explicit catalog.
|
|
128
|
+
*/
|
|
129
|
+
export function assertManifestCoverage(resolver) {
|
|
130
|
+
assertMissing(resolver, state.decorated);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../../src/core/resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dialect } from '../registry';\nimport { resolveSqlFromFs } from './fileResolution';\n\n/**\n * Dialect → manifest key suffix. Mirrors the on-disk file convention\n * (`findByEmail.pg.sql` → key `UsersRepo.findByEmail.pg`).\n */\nexport const MANIFEST_DIALECT_EXT: Record<Dialect, string> = {\n postgres: 'pg',\n mysql: 'mysql',\n sqlite: 'sqlite',\n};\n\n/**\n * Generated SQL manifest shape (see docs/resolver_architecture.md, Contract A):\n * { '<ClassName>.<methodName>.<dialectExt>': '<sql>', '<ClassName>.<methodName>': '<sql>' }\n */\nexport type SqlManifest = Record<string, string>;\n\nexport type SqlResolutionRequest = {\n className: string;\n methodName: string;\n dialect: Dialect;\n /** Present when the @Query decorator resolved a filesystem root (Node only). */\n rootDir?: string;\n};\n\n/**\n * Strategy for turning a decorated @Query method into a SQL string.\n * The public @Query API is identical under every resolver.\n */\nexport interface SqlResolver {\n /** Return the SQL for the request, or undefined when this resolver has none. */\n resolve(request: SqlResolutionRequest): string | undefined;\n}\n\n/**\n * Node filesystem resolution — the v1 behavior, unchanged: dialect-specific\n * `<method>.<ext>.sql` first, then generic `<method>.sql`, from the rootDir\n * the decorator captured (explicit argument or stack-based directory guess).\n */\nexport class FsResolver implements SqlResolver {\n resolve({ rootDir, methodName, dialect }: SqlResolutionRequest): string | undefined {\n if (!rootDir) return undefined;\n return resolveSqlFromFs(rootDir, methodName, dialect).sql;\n }\n}\n\nexport type ManifestResolverOptions = {\n /**\n * Validate at registration time that every decorated @Query method has a\n * manifest entry (fail fast, earlier than v1's lazy fs lookup).\n * Default true. Set false when some @Query methods build their SQL by\n * returning a SqlFragment from the method body — those never consult the\n * manifest and cannot be distinguished statically.\n */\n assert?: boolean;\n};\n\n/**\n * Build-time manifest resolution — bundling-proof (Cloudflare Workers).\n * Lookup order per Contract A:\n * 1. `${className}.${methodName}.${dialectExt}`\n * 2. `${className}.${methodName}` (generic fallback)\n */\nexport class ManifestResolver implements SqlResolver {\n constructor(\n private readonly manifest: SqlManifest,\n readonly options: ManifestResolverOptions = {}\n ) {}\n\n resolve({ className, methodName, dialect }: SqlResolutionRequest): string | undefined {\n const ext = MANIFEST_DIALECT_EXT[dialect];\n return (\n this.manifest[`${className}.${methodName}.${ext}`] ??\n this.manifest[`${className}.${methodName}`]\n );\n }\n\n /** True when the manifest has a generic or any dialect-specific entry. */\n hasEntryFor(className: string, methodName: string): boolean {\n const generic = `${className}.${methodName}`;\n if (generic in this.manifest) return true;\n return Object.values(MANIFEST_DIALECT_EXT).some((ext) => `${generic}.${ext}` in this.manifest);\n }\n}\n\nexport type DecoratedMethod = { className: string; methodName: string; inline: boolean };\n\n// Immutable per-class record of @Query-decorated methods, written at\n// class-definition time onto the prototype. Unlike the process-global list\n// below, this cannot be polluted by unrelated imports/tests: coverage checks\n// derive from the classes explicitly supplied to them (repo spec 19 §10).\nconst QUERY_METHODS = Symbol.for('sqlstack:queryMethods');\n\n/** @internal Called by the @Query decorator at class-definition time. */\nexport function recordQueryMethodOnClass(proto: any, entry: DecoratedMethod): void {\n if (!Object.prototype.hasOwnProperty.call(proto, QUERY_METHODS)) {\n Object.defineProperty(proto, QUERY_METHODS, { value: [], enumerable: false });\n }\n proto[QUERY_METHODS].push(entry);\n}\n\n/** @Query-decorated methods declared by the given classes (own + inherited). */\nexport function queryMethodsOf(classes: ReadonlyArray<abstract new (...args: any[]) => any>): DecoratedMethod[] {\n const out: DecoratedMethod[] = [];\n for (const cls of classes) {\n let proto = cls.prototype;\n while (proto && proto !== Object.prototype) {\n if (Object.prototype.hasOwnProperty.call(proto, QUERY_METHODS)) {\n out.push(...proto[QUERY_METHODS]);\n }\n proto = Object.getPrototypeOf(proto);\n }\n }\n return out;\n}\n\ntype ResolverState = {\n resolver: SqlResolver | undefined;\n decorated: DecoratedMethod[];\n};\n\n// Single shared state across module copies (same pattern as SqlStackDB).\nconst g = globalThis as any;\nif (!g.__SQLSTACK_RESOLVER_STATE__) {\n g.__SQLSTACK_RESOLVER_STATE__ = { resolver: undefined, decorated: [] } as ResolverState;\n}\nconst state: ResolverState = g.__SQLSTACK_RESOLVER_STATE__;\n\n/** @internal Called by the @Query decorator at class-definition time. */\nexport function registerDecoratedMethod(entry: DecoratedMethod): void {\n state.decorated.push(entry);\n}\n\n/** @internal Read by the @Query call path. */\nexport function getActiveResolver(): SqlResolver | undefined {\n return state.resolver;\n}\n\nfunction assertMissing(resolver: ManifestResolver, decorated: DecoratedMethod[]): void {\n const missing = decorated\n .filter((m) => !m.inline)\n .filter((m) => !resolver.hasEntryFor(m.className, m.methodName))\n .map((m) => `${m.className}.${m.methodName}`);\n if (missing.length) {\n throw new Error(\n 'sqlstack: ManifestResolver has no SQL for these @Query methods:\\n' +\n missing.map((k) => ` - ${k}`).join('\\n') +\n '\\nRegenerate the manifest (see docs/codegen.md), or pass ' +\n '{ assert: false } if these methods return a SqlFragment from their body.'\n );\n }\n}\n\n/**\n * Environment-local manifest coverage check: validates only the explicitly\n * supplied classes, from their immutable class metadata. Deterministic\n * regardless of what else the process happened to import.\n */\nexport function assertManifestCoversClasses(\n resolver: ManifestResolver,\n classes: ReadonlyArray<abstract new (...args: any[]) => any>\n): void {\n assertMissing(resolver, queryMethodsOf(classes));\n}\n\n/**\n * @deprecated Process-global resolver registration (legacy v2 surface,\n * previously `SqlStack.useResolver(...)`). The canonical path is a per-root\n * resolver on the SqlStack instance (see src/stack.ts); this global is only\n * consulted when no IoC environment is active. Reached via the deprecated\n * static `SqlStack.useResolver(...)` forwards during migration.\n */\nexport function setGlobalResolver(resolver: SqlResolver): void {\n if (resolver instanceof ManifestResolver && resolver.options.assert !== false) {\n assertMissing(resolver, state.decorated);\n }\n state.resolver = resolver;\n}\n\n/** @deprecated See setGlobalResolver. */\nexport function clearGlobalResolver(): void {\n state.resolver = undefined;\n}\n\n/**\n * @internal\n * @deprecated Coverage check against the process-global decorated list; the\n * canonical path is assertManifestCoversClasses with an explicit catalog.\n */\nexport function assertManifestCoverage(resolver: ManifestResolver): void {\n assertMissing(resolver, state.decorated);\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
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
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
|
+
};
|
|
12
|
+
var _SourceScanResolver_inner;
|
|
13
|
+
import { ManifestResolver } from './resolver.js';
|
|
14
|
+
import { scanSqlManifestSync } from '../codegen/generateManifest.js';
|
|
15
|
+
/**
|
|
16
|
+
* Zero-config Node resolution for bare @Query(): lazily scans the project
|
|
17
|
+
* tree (process.cwd(), or an explicit rootDir) for @Query classes and their
|
|
18
|
+
* sibling .sql files, then answers Contract A lookups from the in-memory
|
|
19
|
+
* manifest. The scan runs once, on the first bare-@Query execution.
|
|
20
|
+
*
|
|
21
|
+
* This is the DEFAULT resolver — installed resolvers (per-root SqlStackConfig
|
|
22
|
+
* or the deprecated global) always take precedence, and bundled runtimes
|
|
23
|
+
* (Cloudflare Workers) never reach it because their entrypoints install a
|
|
24
|
+
* ManifestResolver.
|
|
25
|
+
*/
|
|
26
|
+
export class SourceScanResolver {
|
|
27
|
+
constructor(rootDir) {
|
|
28
|
+
this.rootDir = rootDir;
|
|
29
|
+
_SourceScanResolver_inner.set(this, void 0);
|
|
30
|
+
}
|
|
31
|
+
resolve(request) {
|
|
32
|
+
if (!__classPrivateFieldGet(this, _SourceScanResolver_inner, "f")) {
|
|
33
|
+
const { manifest } = scanSqlManifestSync(this.rootDir ?? process.cwd());
|
|
34
|
+
__classPrivateFieldSet(this, _SourceScanResolver_inner, new ManifestResolver(manifest, { assert: false }), "f");
|
|
35
|
+
}
|
|
36
|
+
return __classPrivateFieldGet(this, _SourceScanResolver_inner, "f").resolve(request);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
_SourceScanResolver_inner = new WeakMap();
|
|
40
|
+
let defaultInstance;
|
|
41
|
+
/**
|
|
42
|
+
* @internal Process-wide default used by the @Query call path when no
|
|
43
|
+
* resolver is installed and the decorator captured no filesystem root.
|
|
44
|
+
* Node-only: returns undefined where the filesystem is unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export function defaultSourceScanResolver() {
|
|
47
|
+
if (typeof process === 'undefined' || typeof process.cwd !== 'function')
|
|
48
|
+
return undefined;
|
|
49
|
+
return (defaultInstance ?? (defaultInstance = new SourceScanResolver()));
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=sourceScanResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sourceScanResolver.js","sourceRoot":"","sources":["../../../src/core/sourceScanResolver.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;;;;GAUG;AACH,MAAM,OAAO,kBAAkB;IAG7B,YAA6B,OAAgB;QAAhB,YAAO,GAAP,OAAO,CAAS;QAF7C,4CAAqC;IAEW,CAAC;IAEjD,OAAO,CAAC,OAA6B;QACnC,IAAI,CAAC,uBAAA,IAAI,iCAAO,EAAE,CAAC;YACjB,MAAM,EAAE,QAAQ,EAAE,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACxE,uBAAA,IAAI,6BAAU,IAAI,gBAAgB,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,MAAA,CAAC;QAClE,CAAC;QACD,OAAO,uBAAA,IAAI,iCAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;CACF;;AAED,IAAI,eAA+C,CAAC;AAEpD;;;;GAIG;AACH,MAAM,UAAU,yBAAyB;IACvC,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAC1F,OAAO,CAAC,eAAe,KAAf,eAAe,GAAK,IAAI,kBAAkB,EAAE,EAAC,CAAC;AACxD,CAAC","sourcesContent":["import type { SqlResolver, SqlResolutionRequest } from './resolver';\nimport { ManifestResolver } from './resolver';\nimport { scanSqlManifestSync } from '../codegen/generateManifest';\n\n/**\n * Zero-config Node resolution for bare @Query(): lazily scans the project\n * tree (process.cwd(), or an explicit rootDir) for @Query classes and their\n * sibling .sql files, then answers Contract A lookups from the in-memory\n * manifest. The scan runs once, on the first bare-@Query execution.\n *\n * This is the DEFAULT resolver — installed resolvers (per-root SqlStackConfig\n * or the deprecated global) always take precedence, and bundled runtimes\n * (Cloudflare Workers) never reach it because their entrypoints install a\n * ManifestResolver.\n */\nexport class SourceScanResolver implements SqlResolver {\n #inner: ManifestResolver | undefined;\n\n constructor(private readonly rootDir?: string) {}\n\n resolve(request: SqlResolutionRequest): string | undefined {\n if (!this.#inner) {\n const { manifest } = scanSqlManifestSync(this.rootDir ?? process.cwd());\n this.#inner = new ManifestResolver(manifest, { assert: false });\n }\n return this.#inner.resolve(request);\n }\n}\n\nlet defaultInstance: SourceScanResolver | undefined;\n\n/**\n * @internal Process-wide default used by the @Query call path when no\n * resolver is installed and the decorator captured no filesystem root.\n * Node-only: returns undefined where the filesystem is unavailable.\n */\nexport function defaultSourceScanResolver(): SourceScanResolver | undefined {\n if (typeof process === 'undefined' || typeof process.cwd !== 'function') return undefined;\n return (defaultInstance ??= new SourceScanResolver());\n}\n"]}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getClassLabels, getMethodLabels, setMethodLabels } from '../core/metadata.js';
|
|
2
2
|
import { SqlStackError, ExactlyOneRowError, NoRowsError, RowsExistError, DatabaseError } from '../core/errors.js';
|
|
3
3
|
import { isValidationDisabled, validateOutput } from '../core/validator.js';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveExecution } from '../runtime.js';
|
|
5
5
|
import { createSqliteBinder, createMysqlBinder, createPostgresBinder } from '../binders/sqlBinder.js';
|
|
6
|
-
import { coerceRootPath,
|
|
6
|
+
import { coerceRootPath, resolveSqlFromFs } from '../core/fileResolution.js';
|
|
7
|
+
import { registerDecoratedMethod, recordQueryMethodOnClass } from '../core/resolver.js';
|
|
8
|
+
import { defaultSourceScanResolver } from '../core/sourceScanResolver.js';
|
|
7
9
|
import { getActiveTransaction } from '../transactions.js';
|
|
8
10
|
import { SqlFragment } from '../sql.js';
|
|
9
11
|
export function Query(arg) {
|
|
@@ -19,13 +21,25 @@ export function Query(arg) {
|
|
|
19
21
|
setMethodLabels(_target, prop, { source: { kind: 'inline', sql: options.sql } });
|
|
20
22
|
}
|
|
21
23
|
else {
|
|
22
|
-
// FS-based resolution
|
|
24
|
+
// FS-based resolution: only an EXPLICIT path argument pins a directory.
|
|
25
|
+
// Bare @Query() resolves through the active resolver (or the default
|
|
26
|
+
// SourceScanResolver) — no stack-based directory guessing, which breaks
|
|
27
|
+
// under bundlers and test transforms.
|
|
23
28
|
const { rootDir } = coerceRootPath(rootArg);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
setMethodLabels(_target, prop, { source: { kind: 'fs', rootDir: dir } });
|
|
29
|
+
if (rootDir) {
|
|
30
|
+
setMethodLabels(_target, prop, { source: { kind: 'fs', rootDir } });
|
|
27
31
|
}
|
|
28
32
|
}
|
|
33
|
+
// Record for resolver coverage checks: immutable class metadata (the
|
|
34
|
+
// canonical, environment-local source) plus the deprecated global list
|
|
35
|
+
// consulted by the legacy global-resolver path.
|
|
36
|
+
const decoratedEntry = {
|
|
37
|
+
className: _target?.constructor?.name ?? '<anonymous>',
|
|
38
|
+
methodName: String(prop),
|
|
39
|
+
inline: !!options?.sql,
|
|
40
|
+
};
|
|
41
|
+
recordQueryMethodOnClass(_target, decoratedEntry);
|
|
42
|
+
registerDecoratedMethod(decoratedEntry);
|
|
29
43
|
const original = desc.value;
|
|
30
44
|
desc.value = async function (...callArgs) {
|
|
31
45
|
const labels = getMethodLabels(this, prop);
|
|
@@ -42,10 +56,11 @@ export function Query(arg) {
|
|
|
42
56
|
if (fragmentResult instanceof SqlFragment) {
|
|
43
57
|
const { sql: boundSql, params } = fragmentResult.toSQL();
|
|
44
58
|
const dbName = labels.db ?? options?.db ?? classLabels?.db;
|
|
45
|
-
const {
|
|
59
|
+
const { entry } = await resolveExecution(dbName);
|
|
60
|
+
const db = entry.db;
|
|
46
61
|
let out;
|
|
47
62
|
try {
|
|
48
|
-
const tx = getActiveTransaction(
|
|
63
|
+
const tx = getActiveTransaction(entry);
|
|
49
64
|
out = tx ? await tx.query(boundSql, params) : await db.query(boundSql, params);
|
|
50
65
|
}
|
|
51
66
|
catch (dbError) {
|
|
@@ -69,16 +84,29 @@ export function Query(arg) {
|
|
|
69
84
|
}
|
|
70
85
|
// 1) prepare args with defaults
|
|
71
86
|
const rawInput = callArgs.length === 1 ? callArgs[0] : callArgs; // object or positional array
|
|
72
|
-
// 2) choose database and dialect
|
|
87
|
+
// 2) choose database and dialect via the active environment
|
|
73
88
|
const dbName = labels.db ?? options?.db ?? classLabels?.db;
|
|
74
|
-
const {
|
|
89
|
+
const { entry, resolver: activeResolver } = await resolveExecution(dbName);
|
|
90
|
+
const db = entry.db;
|
|
75
91
|
const dialect = options?.dialect ?? db.dialect ?? 'sqlite';
|
|
76
|
-
// 3) load SQL source (inline
|
|
92
|
+
// 3) load SQL source (inline, environment resolver, v1 fs fallback,
|
|
93
|
+
// or the zero-config source scan — bare @Query() with nothing installed)
|
|
77
94
|
const sql = labels?.source?.kind === 'inline'
|
|
78
95
|
? labels.source.sql
|
|
79
|
-
:
|
|
80
|
-
?
|
|
81
|
-
|
|
96
|
+
: activeResolver
|
|
97
|
+
? activeResolver.resolve({
|
|
98
|
+
className: this?.constructor?.name ?? '<anonymous>',
|
|
99
|
+
methodName: String(prop),
|
|
100
|
+
dialect,
|
|
101
|
+
rootDir: labels?.source?.kind === 'fs' ? labels.source.rootDir : undefined,
|
|
102
|
+
})
|
|
103
|
+
: labels?.source?.kind === 'fs'
|
|
104
|
+
? resolveSqlFromFs(labels.source.rootDir, String(prop), dialect).sql
|
|
105
|
+
: defaultSourceScanResolver()?.resolve({
|
|
106
|
+
className: this?.constructor?.name ?? '<anonymous>',
|
|
107
|
+
methodName: String(prop),
|
|
108
|
+
dialect,
|
|
109
|
+
});
|
|
82
110
|
if (!sql) {
|
|
83
111
|
throw new SqlStackError(`No SQL source provided for method ${String(prop)}. Use @Query() with a file/folder path or inline sql.`);
|
|
84
112
|
}
|
|
@@ -266,7 +294,7 @@ export function Query(arg) {
|
|
|
266
294
|
// 5) execute query via registry DB
|
|
267
295
|
let out;
|
|
268
296
|
try {
|
|
269
|
-
const tx = getActiveTransaction(
|
|
297
|
+
const tx = getActiveTransaction(entry);
|
|
270
298
|
out = tx ? await tx.query(boundSql, params) : await db.query(boundSql, params);
|
|
271
299
|
}
|
|
272
300
|
catch (dbError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/decorators/query.ts"],"names":[],"mappings":"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","sourcesContent":["import { getClassLabels, getMethodLabels, setMethodLabels } from '../core/metadata';\nimport { SqlStackError, ExactlyOneRowError, NoRowsError, RowsExistError, DatabaseError } from '../core/errors';\nimport { isValidationDisabled, validateOutput } from '../core/validator';\nimport { SqlStackDB } from '../registry';\nimport { createSqliteBinder, createMysqlBinder, createPostgresBinder } from '../binders/sqlBinder';\nimport { coerceRootPath, guessDecoratorDir, resolveSqlFromFs } from '../core/fileResolution';\nimport { getActiveTransaction } from '../transactions';\nimport { SqlFragment } from '../sql';\n\ntype QueryOptions = {\n sql?: string; // optional inline SQL for testing; otherwise, future: load from file\n db?: string; // optional override DB name\n dialect?: 'sqlite' | 'mysql' | 'postgres';\n};\n\nexport function Query(arg?: string | QueryOptions) {\n return function (_target: any, prop: string | symbol, desc: PropertyDescriptor) {\n const options: QueryOptions | undefined = typeof arg === 'string' ? undefined : arg;\n const rootArg: string | undefined = typeof arg === 'string' ? arg : undefined;\n // Store method-level db override if provided\n if (options?.db) {\n setMethodLabels(_target, prop, { db: options.db });\n }\n // Store inline SQL if provided\n if (options?.sql) {\n setMethodLabels(_target, prop, { source: { kind: 'inline', sql: options.sql } });\n } else {\n // FS-based resolution\n const { rootDir } = coerceRootPath(rootArg);\n const dir = rootDir ?? guessDecoratorDir();\n if (dir) {\n setMethodLabels(_target, prop, { source: { kind: 'fs', rootDir: dir } });\n }\n }\n\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n const labels = getMethodLabels(this, prop);\n const classLabels = getClassLabels(this);\n\n // 0) Check if the method body returns a SqlFragment (template literal path)\n let fragmentResult: unknown;\n try {\n fragmentResult = await original.apply(this, callArgs);\n } catch {\n // Method body threw (e.g. placeholder `throw new Error('replaced')`) — fall through to file/inline SQL\n fragmentResult = undefined;\n }\n if (fragmentResult instanceof SqlFragment) {\n const { sql: boundSql, params } = fragmentResult.toSQL();\n\n const dbName = labels.db ?? options?.db ?? classLabels?.db;\n const { name: resolvedDbName, db } = SqlStackDB.getEntry(dbName);\n\n let out;\n try {\n const tx = getActiveTransaction(resolvedDbName);\n out = tx ? await tx.query(boundSql, params) : await db.query(boundSql, params);\n } catch (dbError: any) {\n throw new DatabaseError(\n dbError?.message || String(dbError),\n boundSql,\n dbError instanceof Error ? dbError : undefined\n );\n }\n\n if (Array.isArray(out)) {\n const shaped = applyShape(out, labels.shape);\n const transformed = labels.transform ? labels.transform(shaped) : shaped;\n\n if (labels.validateSchema && !isValidationDisabled()) {\n if (Array.isArray(transformed)) {\n for (const row of transformed) validateOutput(labels.validateSchema, row);\n } else if (transformed !== null && transformed !== undefined) {\n validateOutput(labels.validateSchema, transformed);\n }\n }\n return transformed;\n }\n return out;\n }\n\n // 1) prepare args with defaults\n const rawInput = callArgs.length === 1 ? callArgs[0] : callArgs; // object or positional array\n\n // 2) choose database and dialect\n const dbName = labels.db ?? options?.db ?? classLabels?.db;\n const { name: resolvedDbName, db } = SqlStackDB.getEntry(dbName);\n const dialect = options?.dialect ?? db.dialect ?? 'sqlite';\n\n // 3) load SQL source (inline or fs)\n const sql = labels?.source?.kind === 'inline'\n ? labels.source.sql\n : labels?.source?.kind === 'fs'\n ? resolveSqlFromFs(labels.source.rootDir, String(prop), dialect).sql\n : undefined;\n if (!sql) {\n throw new SqlStackError(`No SQL source provided for method ${String(prop)}. Use @Query() with a file/folder path or inline sql.`);\n }\n\n // 4) bind placeholders\n // Decide argument mode: positional if SQL contains :argN\n let workingSql = sql;\n const hasPositional = /:arg[1-9][0-9]*/.test(workingSql);\n\n // Prepare args for binder\n let args: any;\n if (hasPositional) {\n args = callArgs;\n } else {\n const namedBase = callArgs.length > 0 ? callArgs[0] : undefined;\n args = applyDefaults(labels.defaults, namedBase ?? {});\n }\n\n // Compute paging and inject :offset/:limit (named mode) or rewrite to positional\n const pageCfg = labels.page;\n if (pageCfg) {\n const { size, zeroBase } = pageCfg;\n const base = zeroBase ? 0 : 1;\n\n // Extract page number from input (object) or a trailing options object in positional mode\n const pageFromObject = (val: any): number | undefined => {\n if (val && typeof val === 'object' && !Array.isArray(val) && typeof (val as any).page !== 'undefined') {\n const p = (val as any).page;\n if (typeof p === 'number' && isFinite(p)) return p;\n }\n return undefined;\n };\n\n let pageNum: number | undefined;\n if (hasPositional) {\n // Variadic-style: only the LAST argument may carry `{ page }`\n if (callArgs.length > 0) {\n const last = callArgs[callArgs.length - 1];\n pageNum = pageFromObject(last);\n }\n } else {\n // Prefer last-arg options if provided; otherwise read from named object\n if (callArgs.length > 1) {\n const last = callArgs[callArgs.length - 1];\n pageNum = pageFromObject(last);\n }\n if (pageNum === undefined) {\n pageNum = pageFromObject(args);\n }\n }\n\n const effectivePage = typeof pageNum === 'number' ? pageNum : base;\n const offset = (effectivePage - base) * size;\n const limit = size;\n\n if (hasPositional) {\n // Replace occurrences of :offset and :limit with new :argN tokens appended to args\n const baseIndex = callArgs.length; // start numbering after user args\n let nextIndex = baseIndex + 1;\n const additions: unknown[] = [];\n\n const rewrite = (sqlText: string) => {\n const out: string[] = [];\n let i = 0;\n let inSQ = false, inDQ = false, inLC = false, inBC = false;\n const len = sqlText.length;\n while (i < len) {\n const ch = sqlText[i];\n const next = i + 1 < len ? sqlText[i + 1] : '';\n if (inLC) { out.push(ch); if (ch === '\\n') inLC = false; i++; continue; }\n if (inBC) { out.push(ch); if (ch === '*' && next === '/') { out.push(next); i++; inBC = false; } i++; continue; }\n if (inSQ) {\n out.push(ch);\n if (ch === \"'\" && next === \"'\") { out.push(next); i += 2; continue; }\n if (ch === \"'\") inSQ = false;\n i++;\n continue;\n }\n if (inDQ) {\n out.push(ch);\n if (ch === '\"' && next === '\"') { out.push(next); i += 2; continue; }\n if (ch === '\"') inDQ = false;\n i++;\n continue;\n }\n if (ch === '-' && next === '-') { out.push(ch, next); i += 2; inLC = true; continue; }\n if (ch === '/' && next === '*') { out.push(ch, next); i += 2; inBC = true; continue; }\n if (ch === '\"') { out.push(ch); inDQ = true; i++; continue; }\n if (ch === \"'\") { out.push(ch); inSQ = true; i++; continue; }\n if (ch === ':' && next === ':') { out.push('::'); i += 2; continue; }\n if (ch === ':') {\n // parse token\n let j = i + 1;\n const start = j;\n while (j < len) {\n const c = sqlText[j];\n if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c === '_') j++; else break;\n }\n const name = sqlText.slice(start, j);\n if (name === 'offset') {\n const token = `:arg${nextIndex++}`;\n out.push(token);\n additions.push(offset);\n i = j;\n continue;\n }\n if (name === 'limit') {\n const token = `:arg${nextIndex++}`;\n out.push(token);\n additions.push(limit);\n i = j;\n continue;\n }\n // otherwise leave untouched\n out.push(':');\n i++;\n continue;\n }\n out.push(ch);\n i++;\n }\n return out.join('');\n };\n\n const rewritten = rewrite(workingSql);\n if (rewritten !== workingSql) {\n workingSql = rewritten;\n // append values so :argK map correctly\n (args as unknown[]).push(...additions);\n }\n } else {\n // Named-mode: attach computed values (override any provided)\n args = { ...(args as Record<string, unknown>), offset, limit };\n }\n }\n const binder =\n dialect === 'postgres' ? createPostgresBinder() : dialect === 'mysql' ? createMysqlBinder() : createSqliteBinder();\n const { sql: boundSql, params } = binder.bind(workingSql, args as any);\n\n // 5) execute query via registry DB\n let out;\n try {\n const tx = getActiveTransaction(resolvedDbName);\n out = tx ? await tx.query(boundSql, params) : await db.query(boundSql, params);\n } catch (dbError: any) {\n // Attach SQL query (with placeholders) to error for debugging\n throw new DatabaseError(\n dbError?.message || String(dbError),\n boundSql,\n dbError instanceof Error ? dbError : undefined\n );\n }\n\n // 6) If rows were returned (SELECT/CTE), shape + transform + validate\n if (Array.isArray(out)) {\n const shaped = applyShape(out, labels.shape);\n const transformed = labels.transform ? labels.transform(shaped) : shaped;\n\n // 7) validate result via JSON Schema, if provided\n if (labels.validateSchema && !isValidationDisabled()) {\n if (Array.isArray(transformed)) {\n for (const row of transformed) validateOutput(labels.validateSchema, row);\n } else if (transformed !== null && transformed !== undefined) {\n validateOutput(labels.validateSchema, transformed);\n }\n }\n return transformed;\n }\n\n // Non-SELECT: return the write result as-is\n return out;\n };\n\n return desc;\n };\n}\n\nfunction applyDefaults(defaults: Record<string, unknown> | undefined, input: any) {\n if (!defaults) return input;\n if (Array.isArray(input)) return input; // defaults only apply to named-object args\n return { ...defaults, ...(input || {}) };\n}\n\nfunction applyShape<T>(rows: T[], shape?: 'single' | 'only' | 'exist' | 'none'): any {\n switch (shape) {\n case 'single':\n return rows.length > 0 ? rows[0] : null;\n case 'only':\n if (rows.length !== 1) throw new ExactlyOneRowError(rows.length);\n return rows[0];\n case 'exist':\n if (rows.length < 1) throw new NoRowsError();\n return rows[0];\n case 'none':\n if (rows.length > 0) throw new RowsExistError(rows.length);\n return null;\n default:\n return rows;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/decorators/query.ts"],"names":[],"mappings":"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","sourcesContent":["import { getClassLabels, getMethodLabels, setMethodLabels } from '../core/metadata';\nimport { SqlStackError, ExactlyOneRowError, NoRowsError, RowsExistError, DatabaseError } from '../core/errors';\nimport { isValidationDisabled, validateOutput } from '../core/validator';\nimport { resolveExecution } from '../runtime';\nimport { createSqliteBinder, createMysqlBinder, createPostgresBinder } from '../binders/sqlBinder';\nimport { coerceRootPath, resolveSqlFromFs } from '../core/fileResolution';\nimport { registerDecoratedMethod, recordQueryMethodOnClass } from '../core/resolver';\nimport { defaultSourceScanResolver } from '../core/sourceScanResolver';\nimport { getActiveTransaction } from '../transactions';\nimport { SqlFragment } from '../sql';\n\ntype QueryOptions = {\n sql?: string; // optional inline SQL for testing; otherwise, future: load from file\n db?: string; // optional override DB name\n dialect?: 'sqlite' | 'mysql' | 'postgres';\n};\n\nexport function Query(arg?: string | QueryOptions) {\n return function (_target: any, prop: string | symbol, desc: PropertyDescriptor) {\n const options: QueryOptions | undefined = typeof arg === 'string' ? undefined : arg;\n const rootArg: string | undefined = typeof arg === 'string' ? arg : undefined;\n // Store method-level db override if provided\n if (options?.db) {\n setMethodLabels(_target, prop, { db: options.db });\n }\n // Store inline SQL if provided\n if (options?.sql) {\n setMethodLabels(_target, prop, { source: { kind: 'inline', sql: options.sql } });\n } else {\n // FS-based resolution: only an EXPLICIT path argument pins a directory.\n // Bare @Query() resolves through the active resolver (or the default\n // SourceScanResolver) — no stack-based directory guessing, which breaks\n // under bundlers and test transforms.\n const { rootDir } = coerceRootPath(rootArg);\n if (rootDir) {\n setMethodLabels(_target, prop, { source: { kind: 'fs', rootDir } });\n }\n }\n\n // Record for resolver coverage checks: immutable class metadata (the\n // canonical, environment-local source) plus the deprecated global list\n // consulted by the legacy global-resolver path.\n const decoratedEntry = {\n className: _target?.constructor?.name ?? '<anonymous>',\n methodName: String(prop),\n inline: !!options?.sql,\n };\n recordQueryMethodOnClass(_target, decoratedEntry);\n registerDecoratedMethod(decoratedEntry);\n\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n const labels = getMethodLabels(this, prop);\n const classLabels = getClassLabels(this);\n\n // 0) Check if the method body returns a SqlFragment (template literal path)\n let fragmentResult: unknown;\n try {\n fragmentResult = await original.apply(this, callArgs);\n } catch {\n // Method body threw (e.g. placeholder `throw new Error('replaced')`) — fall through to file/inline SQL\n fragmentResult = undefined;\n }\n if (fragmentResult instanceof SqlFragment) {\n const { sql: boundSql, params } = fragmentResult.toSQL();\n\n const dbName = labels.db ?? options?.db ?? classLabels?.db;\n const { entry } = await resolveExecution(dbName);\n const db = entry.db;\n\n let out;\n try {\n const tx = getActiveTransaction(entry);\n out = tx ? await tx.query(boundSql, params) : await db.query(boundSql, params);\n } catch (dbError: any) {\n throw new DatabaseError(\n dbError?.message || String(dbError),\n boundSql,\n dbError instanceof Error ? dbError : undefined\n );\n }\n\n if (Array.isArray(out)) {\n const shaped = applyShape(out, labels.shape);\n const transformed = labels.transform ? labels.transform(shaped) : shaped;\n\n if (labels.validateSchema && !isValidationDisabled()) {\n if (Array.isArray(transformed)) {\n for (const row of transformed) validateOutput(labels.validateSchema, row);\n } else if (transformed !== null && transformed !== undefined) {\n validateOutput(labels.validateSchema, transformed);\n }\n }\n return transformed;\n }\n return out;\n }\n\n // 1) prepare args with defaults\n const rawInput = callArgs.length === 1 ? callArgs[0] : callArgs; // object or positional array\n\n // 2) choose database and dialect via the active environment\n const dbName = labels.db ?? options?.db ?? classLabels?.db;\n const { entry, resolver: activeResolver } = await resolveExecution(dbName);\n const db = entry.db;\n const dialect = options?.dialect ?? db.dialect ?? 'sqlite';\n\n // 3) load SQL source (inline, environment resolver, v1 fs fallback,\n // or the zero-config source scan — bare @Query() with nothing installed)\n const sql = labels?.source?.kind === 'inline'\n ? labels.source.sql\n : activeResolver\n ? activeResolver.resolve({\n className: this?.constructor?.name ?? '<anonymous>',\n methodName: String(prop),\n dialect,\n rootDir: labels?.source?.kind === 'fs' ? labels.source.rootDir : undefined,\n })\n : labels?.source?.kind === 'fs'\n ? resolveSqlFromFs(labels.source.rootDir, String(prop), dialect).sql\n : defaultSourceScanResolver()?.resolve({\n className: this?.constructor?.name ?? '<anonymous>',\n methodName: String(prop),\n dialect,\n });\n if (!sql) {\n throw new SqlStackError(`No SQL source provided for method ${String(prop)}. Use @Query() with a file/folder path or inline sql.`);\n }\n\n // 4) bind placeholders\n // Decide argument mode: positional if SQL contains :argN\n let workingSql = sql;\n const hasPositional = /:arg[1-9][0-9]*/.test(workingSql);\n\n // Prepare args for binder\n let args: any;\n if (hasPositional) {\n args = callArgs;\n } else {\n const namedBase = callArgs.length > 0 ? callArgs[0] : undefined;\n args = applyDefaults(labels.defaults, namedBase ?? {});\n }\n\n // Compute paging and inject :offset/:limit (named mode) or rewrite to positional\n const pageCfg = labels.page;\n if (pageCfg) {\n const { size, zeroBase } = pageCfg;\n const base = zeroBase ? 0 : 1;\n\n // Extract page number from input (object) or a trailing options object in positional mode\n const pageFromObject = (val: any): number | undefined => {\n if (val && typeof val === 'object' && !Array.isArray(val) && typeof (val as any).page !== 'undefined') {\n const p = (val as any).page;\n if (typeof p === 'number' && isFinite(p)) return p;\n }\n return undefined;\n };\n\n let pageNum: number | undefined;\n if (hasPositional) {\n // Variadic-style: only the LAST argument may carry `{ page }`\n if (callArgs.length > 0) {\n const last = callArgs[callArgs.length - 1];\n pageNum = pageFromObject(last);\n }\n } else {\n // Prefer last-arg options if provided; otherwise read from named object\n if (callArgs.length > 1) {\n const last = callArgs[callArgs.length - 1];\n pageNum = pageFromObject(last);\n }\n if (pageNum === undefined) {\n pageNum = pageFromObject(args);\n }\n }\n\n const effectivePage = typeof pageNum === 'number' ? pageNum : base;\n const offset = (effectivePage - base) * size;\n const limit = size;\n\n if (hasPositional) {\n // Replace occurrences of :offset and :limit with new :argN tokens appended to args\n const baseIndex = callArgs.length; // start numbering after user args\n let nextIndex = baseIndex + 1;\n const additions: unknown[] = [];\n\n const rewrite = (sqlText: string) => {\n const out: string[] = [];\n let i = 0;\n let inSQ = false, inDQ = false, inLC = false, inBC = false;\n const len = sqlText.length;\n while (i < len) {\n const ch = sqlText[i];\n const next = i + 1 < len ? sqlText[i + 1] : '';\n if (inLC) { out.push(ch); if (ch === '\\n') inLC = false; i++; continue; }\n if (inBC) { out.push(ch); if (ch === '*' && next === '/') { out.push(next); i++; inBC = false; } i++; continue; }\n if (inSQ) {\n out.push(ch);\n if (ch === \"'\" && next === \"'\") { out.push(next); i += 2; continue; }\n if (ch === \"'\") inSQ = false;\n i++;\n continue;\n }\n if (inDQ) {\n out.push(ch);\n if (ch === '\"' && next === '\"') { out.push(next); i += 2; continue; }\n if (ch === '\"') inDQ = false;\n i++;\n continue;\n }\n if (ch === '-' && next === '-') { out.push(ch, next); i += 2; inLC = true; continue; }\n if (ch === '/' && next === '*') { out.push(ch, next); i += 2; inBC = true; continue; }\n if (ch === '\"') { out.push(ch); inDQ = true; i++; continue; }\n if (ch === \"'\") { out.push(ch); inSQ = true; i++; continue; }\n if (ch === ':' && next === ':') { out.push('::'); i += 2; continue; }\n if (ch === ':') {\n // parse token\n let j = i + 1;\n const start = j;\n while (j < len) {\n const c = sqlText[j];\n if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c === '_') j++; else break;\n }\n const name = sqlText.slice(start, j);\n if (name === 'offset') {\n const token = `:arg${nextIndex++}`;\n out.push(token);\n additions.push(offset);\n i = j;\n continue;\n }\n if (name === 'limit') {\n const token = `:arg${nextIndex++}`;\n out.push(token);\n additions.push(limit);\n i = j;\n continue;\n }\n // otherwise leave untouched\n out.push(':');\n i++;\n continue;\n }\n out.push(ch);\n i++;\n }\n return out.join('');\n };\n\n const rewritten = rewrite(workingSql);\n if (rewritten !== workingSql) {\n workingSql = rewritten;\n // append values so :argK map correctly\n (args as unknown[]).push(...additions);\n }\n } else {\n // Named-mode: attach computed values (override any provided)\n args = { ...(args as Record<string, unknown>), offset, limit };\n }\n }\n const binder =\n dialect === 'postgres' ? createPostgresBinder() : dialect === 'mysql' ? createMysqlBinder() : createSqliteBinder();\n const { sql: boundSql, params } = binder.bind(workingSql, args as any);\n\n // 5) execute query via registry DB\n let out;\n try {\n const tx = getActiveTransaction(entry);\n out = tx ? await tx.query(boundSql, params) : await db.query(boundSql, params);\n } catch (dbError: any) {\n // Attach SQL query (with placeholders) to error for debugging\n throw new DatabaseError(\n dbError?.message || String(dbError),\n boundSql,\n dbError instanceof Error ? dbError : undefined\n );\n }\n\n // 6) If rows were returned (SELECT/CTE), shape + transform + validate\n if (Array.isArray(out)) {\n const shaped = applyShape(out, labels.shape);\n const transformed = labels.transform ? labels.transform(shaped) : shaped;\n\n // 7) validate result via JSON Schema, if provided\n if (labels.validateSchema && !isValidationDisabled()) {\n if (Array.isArray(transformed)) {\n for (const row of transformed) validateOutput(labels.validateSchema, row);\n } else if (transformed !== null && transformed !== undefined) {\n validateOutput(labels.validateSchema, transformed);\n }\n }\n return transformed;\n }\n\n // Non-SELECT: return the write result as-is\n return out;\n };\n\n return desc;\n };\n}\n\nfunction applyDefaults(defaults: Record<string, unknown> | undefined, input: any) {\n if (!defaults) return input;\n if (Array.isArray(input)) return input; // defaults only apply to named-object args\n return { ...defaults, ...(input || {}) };\n}\n\nfunction applyShape<T>(rows: T[], shape?: 'single' | 'only' | 'exist' | 'none'): any {\n switch (shape) {\n case 'single':\n return rows.length > 0 ? rows[0] : null;\n case 'only':\n if (rows.length !== 1) throw new ExactlyOneRowError(rows.length);\n return rows[0];\n case 'exist':\n if (rows.length < 1) throw new NoRowsError();\n return rows[0];\n case 'none':\n if (rows.length > 0) throw new RowsExistError(rows.length);\n return null;\n default:\n return rows;\n }\n}\n"]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { SqlStackError } from '../core/errors.js';
|
|
4
|
+
import { createSqliteDb } from '../adapters/sqlite.js';
|
|
5
|
+
const SIDECARS = ['-wal', '-shm', '-journal'];
|
|
6
|
+
/**
|
|
7
|
+
* Strict SQLite file family (spec 17 §15/§20): open() NEVER creates a missing
|
|
8
|
+
* file — a lost database must surface as a failure, not become a fresh empty
|
|
9
|
+
* migrated database. Creation is explicit.
|
|
10
|
+
*
|
|
11
|
+
* Keys are opaque (spec 17 §18): any non-empty string is accepted, including
|
|
12
|
+
* path-shaped legacy keys. Safety is containment — the resolved file must
|
|
13
|
+
* stay inside the family root — and canonical identity: keyId is the
|
|
14
|
+
* resolved root-relative path, so aliasing spellings collapse to one member.
|
|
15
|
+
*/
|
|
16
|
+
export function sqliteFamily(options) {
|
|
17
|
+
const root = path.resolve(options.directory);
|
|
18
|
+
function resolvePath(key) {
|
|
19
|
+
// Default naming: keys already carrying .db (legacy stored paths) map
|
|
20
|
+
// as-is; bare logical keys get the .db suffix.
|
|
21
|
+
const file = options.filename
|
|
22
|
+
? options.filename(key)
|
|
23
|
+
: key.endsWith('.db')
|
|
24
|
+
? key
|
|
25
|
+
: `${key}.db`;
|
|
26
|
+
const resolved = path.resolve(root, file);
|
|
27
|
+
if (resolved === root || !resolved.startsWith(root + path.sep)) {
|
|
28
|
+
throw new SqlStackError(`SQLite family key "${key}" resolves outside the family directory (${root}). ` +
|
|
29
|
+
'Resolved member files must stay inside the configured root.');
|
|
30
|
+
}
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
async function openConnection(file, mustExist) {
|
|
34
|
+
let BetterSqlite3;
|
|
35
|
+
try {
|
|
36
|
+
BetterSqlite3 = (await import('better-sqlite3')).default;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new SqlStackError('sqliteFamily requires the optional peer "better-sqlite3".');
|
|
40
|
+
}
|
|
41
|
+
const conn = new BetterSqlite3(file, { fileMustExist: mustExist });
|
|
42
|
+
for (const pragma of options.pragmas ?? []) {
|
|
43
|
+
conn.pragma(pragma);
|
|
44
|
+
}
|
|
45
|
+
return createSqliteDb(conn);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
normalizeKey(input) {
|
|
49
|
+
if (typeof input !== 'string' || input.length === 0) {
|
|
50
|
+
throw new SqlStackError(`Invalid SQLite family key ${JSON.stringify(input)}: keys are non-empty strings.`);
|
|
51
|
+
}
|
|
52
|
+
// Containment is validated eagerly so a bad key fails at normalization
|
|
53
|
+
// time with the key in the diagnostic, before any lifecycle operation.
|
|
54
|
+
resolvePath(input);
|
|
55
|
+
return input;
|
|
56
|
+
},
|
|
57
|
+
// Canonical identity (spec 17 §18): aliasing spellings of the same file
|
|
58
|
+
// ("a.db" vs "sub/../a.db") collapse to one keyId, so cache entries,
|
|
59
|
+
// lease counts, and transaction identity can never split across aliases.
|
|
60
|
+
keyId(key) {
|
|
61
|
+
return path.relative(root, resolvePath(key));
|
|
62
|
+
},
|
|
63
|
+
async exists(key) {
|
|
64
|
+
return fs.existsSync(resolvePath(key));
|
|
65
|
+
},
|
|
66
|
+
async open(key) {
|
|
67
|
+
const file = resolvePath(key);
|
|
68
|
+
try {
|
|
69
|
+
return await openConnection(file, true);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (!fs.existsSync(file)) {
|
|
73
|
+
throw new SqlStackError(`SQLite family database "${key}" does not exist at ${file}. ` +
|
|
74
|
+
'open() never creates databases — use create(key) or ensure(key) explicitly.');
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async create(key) {
|
|
80
|
+
const file = resolvePath(key);
|
|
81
|
+
if (options.mkdirs !== false) {
|
|
82
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
// Exclusive file creation makes "must not already exist" race-free; an
|
|
85
|
+
// empty file is a valid brand-new SQLite database.
|
|
86
|
+
try {
|
|
87
|
+
fs.closeSync(fs.openSync(file, 'wx'));
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (err?.code === 'EEXIST') {
|
|
91
|
+
throw new SqlStackError(`SQLite family database "${key}" already exists at ${file}. ` +
|
|
92
|
+
'create() requires a new database — use ensure(key) for create-if-missing.');
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const db = await openConnection(file, true);
|
|
98
|
+
if (options.prepare)
|
|
99
|
+
await options.prepare(db, key);
|
|
100
|
+
return db;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
// Safe cleanup of the storage this create() call owns.
|
|
104
|
+
try {
|
|
105
|
+
fs.rmSync(file, { force: true });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* keep original error */
|
|
109
|
+
}
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async ensure(key) {
|
|
114
|
+
const file = resolvePath(key);
|
|
115
|
+
if (fs.existsSync(file))
|
|
116
|
+
return this.open(key);
|
|
117
|
+
return this.create(key);
|
|
118
|
+
},
|
|
119
|
+
async delete(key) {
|
|
120
|
+
const file = resolvePath(key);
|
|
121
|
+
if (!fs.existsSync(file)) {
|
|
122
|
+
throw new SqlStackError(`SQLite family database "${key}" does not exist at ${file}; nothing to delete.`);
|
|
123
|
+
}
|
|
124
|
+
fs.rmSync(file);
|
|
125
|
+
for (const suffix of SIDECARS) {
|
|
126
|
+
fs.rmSync(file + suffix, { force: true });
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
async inspect(key) {
|
|
130
|
+
const file = resolvePath(key);
|
|
131
|
+
if (!fs.existsSync(file))
|
|
132
|
+
return { state: 'missing', path: file };
|
|
133
|
+
const sizeBytes = fs.statSync(file).size;
|
|
134
|
+
let sidecarBytes = 0;
|
|
135
|
+
for (const suffix of SIDECARS) {
|
|
136
|
+
if (fs.existsSync(file + suffix))
|
|
137
|
+
sidecarBytes += fs.statSync(file + suffix).size;
|
|
138
|
+
}
|
|
139
|
+
return { state: 'closed', path: file, sizeBytes, sidecarBytes };
|
|
140
|
+
},
|
|
141
|
+
async *list() {
|
|
142
|
+
if (!fs.existsSync(root))
|
|
143
|
+
return;
|
|
144
|
+
for (const entry of fs.readdirSync(root)) {
|
|
145
|
+
if (entry.endsWith('.db'))
|
|
146
|
+
yield entry.slice(0, -3);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=sqliteFamily.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqliteFamily.js","sourceRoot":"","sources":["../../../src/families/sqliteFamily.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Database } from '../registry';\nimport { SqlStackError } from '../core/errors';\nimport { createSqliteDb } from '../adapters/sqlite';\nimport type { DatabaseFamilyProvider, DatabaseInspection } from '../family';\n\nexport interface SqliteFamilyOptions {\n /** Root directory that owns every member file. Keys resolve inside it. */\n directory: string;\n /** Filename for a key (default `<key>.db`). Must not escape the directory. */\n filename?: (key: string) => string;\n /** Pragmas applied on every open (e.g. ['journal_mode = WAL']). */\n pragmas?: readonly string[];\n /** Preparation hook (e.g. Proper migrations) run after create/ensure-open. */\n prepare?: (db: Database, key: string) => Promise<void>;\n /** Create parent directories on create/ensure (default true). */\n mkdirs?: boolean;\n}\n\nconst SIDECARS = ['-wal', '-shm', '-journal'];\n\n/**\n * Strict SQLite file family (spec 17 §15/§20): open() NEVER creates a missing\n * file — a lost database must surface as a failure, not become a fresh empty\n * migrated database. Creation is explicit.\n *\n * Keys are opaque (spec 17 §18): any non-empty string is accepted, including\n * path-shaped legacy keys. Safety is containment — the resolved file must\n * stay inside the family root — and canonical identity: keyId is the\n * resolved root-relative path, so aliasing spellings collapse to one member.\n */\nexport function sqliteFamily(options: SqliteFamilyOptions): DatabaseFamilyProvider<string> {\n const root = path.resolve(options.directory);\n\n function resolvePath(key: string): string {\n // Default naming: keys already carrying .db (legacy stored paths) map\n // as-is; bare logical keys get the .db suffix.\n const file = options.filename\n ? options.filename(key)\n : key.endsWith('.db')\n ? key\n : `${key}.db`;\n const resolved = path.resolve(root, file);\n if (resolved === root || !resolved.startsWith(root + path.sep)) {\n throw new SqlStackError(\n `SQLite family key \"${key}\" resolves outside the family directory (${root}). ` +\n 'Resolved member files must stay inside the configured root.'\n );\n }\n return resolved;\n }\n\n async function openConnection(file: string, mustExist: boolean): Promise<Database> {\n let BetterSqlite3: any;\n try {\n BetterSqlite3 = ((await import('better-sqlite3' as string)) as any).default;\n } catch {\n throw new SqlStackError('sqliteFamily requires the optional peer \"better-sqlite3\".');\n }\n const conn = new BetterSqlite3(file, { fileMustExist: mustExist });\n for (const pragma of options.pragmas ?? []) {\n conn.pragma(pragma);\n }\n return createSqliteDb(conn);\n }\n\n return {\n normalizeKey(input: unknown): string {\n if (typeof input !== 'string' || input.length === 0) {\n throw new SqlStackError(\n `Invalid SQLite family key ${JSON.stringify(input)}: keys are non-empty strings.`\n );\n }\n // Containment is validated eagerly so a bad key fails at normalization\n // time with the key in the diagnostic, before any lifecycle operation.\n resolvePath(input);\n return input;\n },\n\n // Canonical identity (spec 17 §18): aliasing spellings of the same file\n // (\"a.db\" vs \"sub/../a.db\") collapse to one keyId, so cache entries,\n // lease counts, and transaction identity can never split across aliases.\n keyId(key: string): string {\n return path.relative(root, resolvePath(key));\n },\n\n async exists(key: string): Promise<boolean> {\n return fs.existsSync(resolvePath(key));\n },\n\n async open(key: string): Promise<Database> {\n const file = resolvePath(key);\n try {\n return await openConnection(file, true);\n } catch (err: any) {\n if (!fs.existsSync(file)) {\n throw new SqlStackError(\n `SQLite family database \"${key}\" does not exist at ${file}. ` +\n 'open() never creates databases — use create(key) or ensure(key) explicitly.'\n );\n }\n throw err;\n }\n },\n\n async create(key: string): Promise<Database> {\n const file = resolvePath(key);\n if (options.mkdirs !== false) {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n }\n // Exclusive file creation makes \"must not already exist\" race-free; an\n // empty file is a valid brand-new SQLite database.\n try {\n fs.closeSync(fs.openSync(file, 'wx'));\n } catch (err: any) {\n if (err?.code === 'EEXIST') {\n throw new SqlStackError(\n `SQLite family database \"${key}\" already exists at ${file}. ` +\n 'create() requires a new database — use ensure(key) for create-if-missing.'\n );\n }\n throw err;\n }\n try {\n const db = await openConnection(file, true);\n if (options.prepare) await options.prepare(db, key);\n return db;\n } catch (err) {\n // Safe cleanup of the storage this create() call owns.\n try {\n fs.rmSync(file, { force: true });\n } catch {\n /* keep original error */\n }\n throw err;\n }\n },\n\n async ensure(key: string): Promise<Database> {\n const file = resolvePath(key);\n if (fs.existsSync(file)) return this.open(key);\n return this.create!(key);\n },\n\n async delete(key: string): Promise<void> {\n const file = resolvePath(key);\n if (!fs.existsSync(file)) {\n throw new SqlStackError(\n `SQLite family database \"${key}\" does not exist at ${file}; nothing to delete.`\n );\n }\n fs.rmSync(file);\n for (const suffix of SIDECARS) {\n fs.rmSync(file + suffix, { force: true });\n }\n },\n\n async inspect(key: string): Promise<DatabaseInspection> {\n const file = resolvePath(key);\n if (!fs.existsSync(file)) return { state: 'missing', path: file };\n const sizeBytes = fs.statSync(file).size;\n let sidecarBytes = 0;\n for (const suffix of SIDECARS) {\n if (fs.existsSync(file + suffix)) sidecarBytes += fs.statSync(file + suffix).size;\n }\n return { state: 'closed', path: file, sizeBytes, sidecarBytes };\n },\n\n async *list(): AsyncIterable<string> {\n if (!fs.existsSync(root)) return;\n for (const entry of fs.readdirSync(root)) {\n if (entry.endsWith('.db')) yield entry.slice(0, -3);\n }\n },\n };\n}\n"]}
|