sqlstack 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cjs/core/errors.d.ts +9 -0
  2. package/dist/cjs/core/errors.d.ts.map +1 -1
  3. package/dist/cjs/core/errors.js +10 -0
  4. package/dist/cjs/core/errors.js.map +1 -1
  5. package/dist/cjs/decorators/query.d.ts +14 -0
  6. package/dist/cjs/decorators/query.d.ts.map +1 -1
  7. package/dist/cjs/decorators/query.js +124 -17
  8. package/dist/cjs/decorators/query.js.map +1 -1
  9. package/dist/cjs/index.d.ts +1 -0
  10. package/dist/cjs/index.d.ts.map +1 -1
  11. package/dist/cjs/index.js +2 -1
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/run.d.ts +140 -6
  14. package/dist/cjs/run.d.ts.map +1 -1
  15. package/dist/cjs/run.js +127 -17
  16. package/dist/cjs/run.js.map +1 -1
  17. package/dist/cjs/runtime.d.ts +8 -0
  18. package/dist/cjs/runtime.d.ts.map +1 -1
  19. package/dist/cjs/runtime.js +12 -0
  20. package/dist/cjs/runtime.js.map +1 -1
  21. package/dist/cjs/sql.d.ts +19 -0
  22. package/dist/cjs/sql.d.ts.map +1 -1
  23. package/dist/cjs/sql.js +66 -28
  24. package/dist/cjs/sql.js.map +1 -1
  25. package/dist/cjs/transactions.d.ts +8 -0
  26. package/dist/cjs/transactions.d.ts.map +1 -1
  27. package/dist/cjs/transactions.js +18 -0
  28. package/dist/cjs/transactions.js.map +1 -1
  29. package/dist/esm/core/errors.js +10 -0
  30. package/dist/esm/core/errors.js.map +1 -1
  31. package/dist/esm/decorators/query.js +127 -20
  32. package/dist/esm/decorators/query.js.map +1 -1
  33. package/dist/esm/index.js +2 -1
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/run.js +123 -18
  36. package/dist/esm/run.js.map +1 -1
  37. package/dist/esm/runtime.js +11 -0
  38. package/dist/esm/runtime.js.map +1 -1
  39. package/dist/esm/sql.js +63 -27
  40. package/dist/esm/sql.js.map +1 -1
  41. package/dist/esm/transactions.js +17 -0
  42. package/dist/esm/transactions.js.map +1 -1
  43. package/package.json +1 -1
  44. package/readme.md +128 -39
@@ -1 +1 @@
1
- {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,GAAG,gDAAgD,CAAC;AACpE,MAAM,WAAW,GAAG,QAAQ,CAAC;AAE7B,MAAM,OAAO,WAAW;IACtB,YACkB,OAA0B,EAC1B,MAA0B;QAD1B,YAAO,GAAP,OAAO,CAAmB;QAC1B,WAAM,GAAN,MAAM,CAAoB;IACzC,CAAC;IAEJ,KAAK;QACH,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAc,EAAE,CAAC;QAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC3B,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAChD,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;gBAEvD,IAAI,SAAS,IAAI,UAAU,EAAE,CAAC;oBAC5B,iDAAiD;oBACjD,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC1D,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;oBAE9D,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3F,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACpB,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBACnB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5C,CAAC;CACF;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,KAAc;IAC/C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,iBAAiB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YACtC,MAAM,GAAG,GAAG,KAAgC,CAAC;YAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YACjF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpD,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,aAAa,YAAY,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,iBAAiB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;YACpG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;YAC5F,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBACjD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;aAClC,CAAC;QACJ,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,iBAAiB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;YACpG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;YAC/F,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBACpD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;aAClC,CAAC;QACJ,CAAC;QACD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAC3E,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YACvF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YAC7D,MAAM,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnE,OAAO;gBACL,IAAI,EAAE,IAAI,IAAI,YAAY,eAAe,EAAE;gBAC3C,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,GAA+B,CAAC,CAAC,CAAC,CAAC,CAAC;aACjF,CAAC;QACJ,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,IAAI,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,KAAa;IACtD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8CAA8C,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,OAA6B,EAAE,GAAG,MAAiB;IACrE,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * Tagged template literal for SQL queries with `?` placeholders.\n *\n * Usage:\n * sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`\n * sql`INSERT INTO users :insert(${data})`\n * sql`UPDATE users SET :update(${data}) WHERE id = ${id}`\n * sql`SELECT * FROM users WHERE :filter(${criteria})`\n * sql`INSERT INTO users :batch_insert(${rows})`\n */\nconst MACRO_OPEN = /:(insert|update|filter|batch_insert)\\s*\\(\\s*$/i;\nconst MACRO_CLOSE = /^\\s*\\)/;\n\nexport class SqlFragment {\n constructor(\n public readonly strings: readonly string[],\n public readonly values: readonly unknown[],\n ) {}\n\n toSQL(): { sql: string; params: unknown[] } {\n const segments = [...this.strings];\n const sqlParts: string[] = [];\n const params: unknown[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n if (i < this.values.length) {\n const openMatch = segments[i].match(MACRO_OPEN);\n const closeMatch = segments[i + 1]?.match(MACRO_CLOSE);\n\n if (openMatch && closeMatch) {\n // Binder macro: trim the macro syntax and expand\n sqlParts.push(segments[i].slice(0, -openMatch[0].length));\n segments[i + 1] = segments[i + 1].slice(closeMatch[0].length);\n\n const { text, params: expanded } = expandMacro(openMatch[1].toLowerCase(), this.values[i]);\n sqlParts.push(text);\n params.push(...expanded);\n } else {\n sqlParts.push(segments[i]);\n sqlParts.push('?');\n params.push(this.values[i]);\n }\n } else {\n sqlParts.push(segments[i]);\n }\n }\n\n return { sql: sqlParts.join(''), params };\n }\n}\n\nfunction expandMacro(type: string, value: unknown): { text: string; params: unknown[] } {\n switch (type) {\n case 'insert': {\n assertPlainObject(value, ':insert()');\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length === 0) throw new Error(':insert() requires at least one column');\n const cols = keys.join(', ');\n const placeholders = keys.map(() => '?').join(', ');\n return { text: `(${cols}) VALUES (${placeholders})`, params: keys.map(k => obj[k]) };\n }\n case 'update': {\n assertPlainObject(value, ':update()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':update() requires at least one defined column');\n return {\n text: entries.map(([k]) => `${k} = ?`).join(', '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'filter': {\n assertPlainObject(value, ':filter()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':filter() requires at least one defined condition');\n return {\n text: entries.map(([k]) => `${k} = ?`).join(' AND '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'batch_insert': {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(':batch_insert() requires a non-empty array of objects');\n }\n const keys = Object.keys(value[0]);\n if (keys.length === 0) throw new Error(':batch_insert() requires at least one column');\n const cols = keys.join(', ');\n const rowPlaceholder = `(${keys.map(() => '?').join(', ')})`;\n const allPlaceholders = value.map(() => rowPlaceholder).join(', ');\n return {\n text: `(${cols}) VALUES ${allPlaceholders}`,\n params: value.flatMap(row => keys.map(k => (row as Record<string, unknown>)[k])),\n };\n }\n default:\n throw new Error(`Unknown binder macro :${type}()`);\n }\n}\n\nfunction assertPlainObject(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} in sql\\`\\` template requires a plain object`);\n }\n}\n\nexport function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment {\n return new SqlFragment(strings, values);\n}\n"]}
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dialect } from './registry';\n\n/**\n * Tagged template literal for SQL queries with `?` placeholders.\n *\n * Usage:\n * sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`\n * sql`INSERT INTO users :insert(${data})`\n * sql`UPDATE users SET :update(${data}) WHERE id = ${id}`\n * sql`SELECT * FROM users WHERE :filter(${criteria})`\n * sql`INSERT INTO users :batch_insert(${rows})`\n *\n * Execute a fragment with `run(db, fragment)` (or `run(fragment)` inside a\n * @Query method): run binds it for the target dialect (`?` for SQLite/MySQL,\n * `$1..` for Postgres) and wraps failures in DatabaseError.\n */\nconst MACRO_OPEN = /:(insert|update|filter|batch_insert)\\s*\\(\\s*$/i;\nconst MACRO_CLOSE = /^\\s*\\)/;\n\n// Cross-copy identity for fragments: duplicated module instances (npm link,\n// dual ESM/CJS loads) each define their own SqlFragment class, so `instanceof`\n// alone would misclassify a fragment built by another copy.\nconst FRAGMENT_BRAND = Symbol.for('noego.sqlstack.SqlFragment');\n\nexport class SqlFragment {\n constructor(\n public readonly strings: readonly string[],\n public readonly values: readonly unknown[],\n ) {}\n\n /**\n * Compile to SQL with `?` placeholders and the matching parameter list.\n *\n * @throws {Error} When a binder macro (`:insert`, `:update`, `:filter`,\n * `:batch_insert`) receives invalid input. `run(...)` wraps this in\n * DatabaseError with the original error in `.cause`.\n */\n toSQL(): { sql: string; params: unknown[] } {\n return compileParts(this.strings, this.values, () => '?');\n }\n}\n\nObject.defineProperty(SqlFragment.prototype, FRAGMENT_BRAND, { value: true });\n\n/** @internal True for a SqlFragment from this or any other sqlstack copy. */\nexport function isSqlFragment(value: unknown): value is SqlFragment {\n if (value instanceof SqlFragment) return true;\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [FRAGMENT_BRAND]?: unknown })[FRAGMENT_BRAND] === true\n );\n}\n\n/**\n * @internal Compile a fragment for a dialect: `$1, $2, ...` for Postgres,\n * `?` for SQLite and MySQL. Throws plain Error on invalid macro input\n * (callers in run() wrap it).\n */\nexport function compileFragment(fragment: SqlFragment, dialect: Dialect): { sql: string; params: unknown[] } {\n let next = 0;\n const placeholder = dialect === 'postgres' ? () => `$${++next}` : () => '?';\n return compileParts(fragment.strings, fragment.values, placeholder);\n}\n\nfunction compileParts(\n strings: readonly string[],\n values: readonly unknown[],\n placeholder: () => string,\n): { sql: string; params: unknown[] } {\n const segments = [...strings];\n const sqlParts: string[] = [];\n const params: unknown[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n if (i < values.length) {\n const openMatch = segments[i].match(MACRO_OPEN);\n const closeMatch = segments[i + 1]?.match(MACRO_CLOSE);\n\n if (openMatch && closeMatch) {\n // Binder macro: trim the macro syntax and expand\n sqlParts.push(segments[i].slice(0, -openMatch[0].length));\n segments[i + 1] = segments[i + 1].slice(closeMatch[0].length);\n\n const { text, params: expanded } = expandMacro(openMatch[1].toLowerCase(), values[i], placeholder);\n sqlParts.push(text);\n params.push(...expanded);\n } else {\n sqlParts.push(segments[i]);\n sqlParts.push(placeholder());\n params.push(values[i]);\n }\n } else {\n sqlParts.push(segments[i]);\n }\n }\n\n return { sql: sqlParts.join(''), params };\n}\n\nfunction expandMacro(type: string, value: unknown, placeholder: () => string): { text: string; params: unknown[] } {\n switch (type) {\n case 'insert': {\n assertPlainObject(value, ':insert()');\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length === 0) throw new Error(':insert() requires at least one column');\n const cols = keys.join(', ');\n const placeholders = keys.map(() => placeholder()).join(', ');\n return { text: `(${cols}) VALUES (${placeholders})`, params: keys.map(k => obj[k]) };\n }\n case 'update': {\n assertPlainObject(value, ':update()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':update() requires at least one defined column');\n return {\n text: entries.map(([k]) => `${k} = ${placeholder()}`).join(', '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'filter': {\n assertPlainObject(value, ':filter()');\n const entries = Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined);\n if (entries.length === 0) throw new Error(':filter() requires at least one defined condition');\n return {\n text: entries.map(([k]) => `${k} = ${placeholder()}`).join(' AND '),\n params: entries.map(([, v]) => v),\n };\n }\n case 'batch_insert': {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(':batch_insert() requires a non-empty array of objects');\n }\n const keys = Object.keys(value[0]);\n if (keys.length === 0) throw new Error(':batch_insert() requires at least one column');\n const cols = keys.join(', ');\n const allPlaceholders = value.map(() => `(${keys.map(() => placeholder()).join(', ')})`).join(', ');\n return {\n text: `(${cols}) VALUES ${allPlaceholders}`,\n params: value.flatMap(row => keys.map(k => (row as Record<string, unknown>)[k])),\n };\n }\n default:\n throw new Error(`Unknown binder macro :${type}()`);\n }\n}\n\nfunction assertPlainObject(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} in sql\\`\\` template requires a plain object`);\n }\n}\n\nexport function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment {\n return new SqlFragment(strings, values);\n}\n"]}
@@ -288,4 +288,21 @@ export function getActiveTransaction(entry) {
288
288
  const store = getStore();
289
289
  return store?.txByDb.get(entry);
290
290
  }
291
+ /**
292
+ * Active transaction whose registered database is exactly `db` (object
293
+ * identity), if any. `run(db, ...)` uses this so a handle passed at call time
294
+ * joins a transaction opened on the same database (by `@transaction` or
295
+ * `withTransaction`) instead of bypassing it. A handle that was never
296
+ * registered with a SqlStack (or the legacy registry) never matches.
297
+ */
298
+ export function getActiveTransactionForDatabase(db) {
299
+ const store = getStore();
300
+ if (!store)
301
+ return undefined;
302
+ for (const state of store.txByDb.values()) {
303
+ if (state.db === db)
304
+ return state;
305
+ }
306
+ return undefined;
307
+ }
291
308
  //# sourceMappingURL=transactions.js.map
@@ -1 +1 @@
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/**\n * Whether this async call carries any transaction records. This observation\n * never resolves a database, opens a connection, or consults legacy defaults.\n * It does not choose an owner: use currentTransactionFor(entry) for that.\n */\nexport function hasTransactionContext(): boolean {\n return (getStore()?.txByDb.size ?? 0) > 0;\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"]}
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/**\n * Whether this async call carries any transaction records. This observation\n * never resolves a database, opens a connection, or consults legacy defaults.\n * It does not choose an owner: use currentTransactionFor(entry) for that.\n */\nexport function hasTransactionContext(): boolean {\n return (getStore()?.txByDb.size ?? 0) > 0;\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\n/**\n * Active transaction whose registered database is exactly `db` (object\n * identity), if any. `run(db, ...)` uses this so a handle passed at call time\n * joins a transaction opened on the same database (by `@transaction` or\n * `withTransaction`) instead of bypassing it. A handle that was never\n * registered with a SqlStack (or the legacy registry) never matches.\n */\nexport function getActiveTransactionForDatabase(db: object): ActiveTransaction | undefined {\n const store = getStore();\n if (!store) return undefined;\n for (const state of store.txByDb.values()) {\n if (state.db === db) return state;\n }\n return undefined;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlstack",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
package/readme.md CHANGED
@@ -16,6 +16,7 @@ Write real SQL next to your code, and use small, composable decorators to bind,
16
16
  - [Error Handling](#error-handling)
17
17
  - [Transactions](#transactions)
18
18
  - [Inline SQL](#inline-sql)
19
+ - [Choosing the Database at Call Time](#choosing-the-database-at-call-time)
19
20
  - [Advanced: Direct Database Access](#advanced-direct-database-access)
20
21
  - [Database Support](#database-support)
21
22
  - [Philosophy](#philosophy)
@@ -78,15 +79,16 @@ Load this file once during app startup.
78
79
 
79
80
  ```ts
80
81
  // src/users/index.ts
81
- import { QueryBinder, Query, SqlStackError } from "sqlstack";
82
+ import { QueryBinder, Query, run } from "sqlstack";
82
83
 
83
84
  @QueryBinder() // looks for .sql files in the same folder by default
84
85
  export class UsersRepository {
85
86
  @Query()
86
87
  async findByEmail(params: { email: string }): Promise<User[]> {
87
- // Implementation bodies are never called; this method exists
88
- // only to define the TypeScript signature for @Query.
89
- throw new SqlStackError("replaced by @Query");
88
+ // run() executes this method's SQL (findByEmail.sql) with the method's
89
+ // arguments; the result type is inferred from the declared return type.
90
+ // Code before/after `await run()` runs normally (logging, try/catch, ...).
91
+ return run();
90
92
  }
91
93
  }
92
94
  ```
@@ -116,7 +118,7 @@ const users = await repo.findByEmail({ email: "alice@example.com" });
116
118
  console.log(users); // User[]
117
119
  ```
118
120
 
119
- That's it! The `@Query` decorator intercepts the method, loads and executes the SQL, and returns results.
121
+ That's it! The `@Query` decorator wraps the method; the body's `return run()` loads and executes the SQL and returns the shaped results. (Older code whose body is a placeholder `throw` still works — the wrapper falls back to running the SQL — but `return run()` is the supported form.)
120
122
 
121
123
  You can then add more methods on the same repository that use sqlstack’s SQL helpers:
122
124
 
@@ -125,22 +127,22 @@ You can then add more methods on the same repository that use sqlstack’s SQL h
125
127
  export class UsersRepository {
126
128
  @Query()
127
129
  async findByEmail(params: { email: string }): Promise<User[]> {
128
- throw new SqlStackError("replaced by @Query");
130
+ return run();
129
131
  }
130
132
 
131
133
  @Query()
132
134
  async updateProfile(params: { id: string; name?: string; status?: string }): Promise<WriteResult> {
133
- throw new SqlStackError("replaced by @Query");
135
+ return run();
134
136
  }
135
137
 
136
138
  @Query()
137
139
  async createUser(params: { id: string; email: string; name?: string }): Promise<WriteResult> {
138
- throw new SqlStackError("replaced by @Query");
140
+ return run();
139
141
  }
140
142
 
141
143
  @Query()
142
144
  async search(filters: { name?: string; status?: string }): Promise<User[]> {
143
- throw new SqlStackError("replaced by @Query");
145
+ return run();
144
146
  }
145
147
  }
146
148
  ```
@@ -407,13 +409,13 @@ SqlStackDB.register('sqlite-existing', createSqliteDb(sqlite));
407
409
  Use `@QueryBinder({ db: "name" })` to set a default database for all methods in a class:
408
410
 
409
411
  ```ts
410
- import { QueryBinder, Query, SqlStackError } from "sqlstack";
412
+ import { QueryBinder, Query, run } from "sqlstack";
411
413
 
412
414
  @QueryBinder({ db: "analytics" }) // all methods use "analytics"
413
415
  export class ReportsRepo {
414
416
  @Query()
415
417
  async topPages(_a: { since: string }): Promise<Row[]> {
416
- throw new SqlStackError("replaced");
418
+ return run();
417
419
  }
418
420
  }
419
421
  ```
@@ -427,12 +429,12 @@ Use `@Query({ db: "name" })` to override the class default for a specific method
427
429
  export class MixedRepo {
428
430
  @Query() // uses "primary"
429
431
  async dailySummary(_a: { date: string }): Promise<Row[]> {
430
- throw new SqlStackError("replaced");
432
+ return run();
431
433
  }
432
434
 
433
435
  @Query({ db: "analytics" }) // override to use "analytics"
434
436
  async topPages(_a: { since: string }): Promise<Row[]> {
435
- throw new SqlStackError("replaced");
437
+ return run();
436
438
  }
437
439
  }
438
440
  ```
@@ -514,7 +516,7 @@ No argument to `@Query()` → looks in the same folder as the class file:
514
516
  class UsersRepo {
515
517
  @Query()
516
518
  async findByEmail(_a: { email: string }): Promise<User[]> {
517
- throw new Error();
519
+ return run();
518
520
  }
519
521
  }
520
522
  ```
@@ -560,14 +562,14 @@ Use decorators to add behavior to your methods. They compose freely and always e
560
562
  Provide default parameter values:
561
563
 
562
564
  ```ts
563
- import { QueryBinder, Defaults, Query, SqlStackError } from "sqlstack";
565
+ import { QueryBinder, Defaults, Query, run } from "sqlstack";
564
566
 
565
567
  @QueryBinder()
566
568
  class UsersRepo {
567
569
  @Defaults({ status: "active", limit: 10 })
568
570
  @Query()
569
571
  async listUsers(_a: { status?: string; limit?: number }): Promise<User[]> {
570
- throw new SqlStackError();
572
+ return run();
571
573
  }
572
574
  }
573
575
  ```
@@ -583,14 +585,14 @@ await repo.listUsers({ status: "inactive" }); // { status: "inactive", limit: 10
583
585
  Add pagination with `:offset` and `:limit` parameters. Pages are 1-based by default:
584
586
 
585
587
  ```ts
586
- import { QueryBinder, Page, Query, SqlStackError } from "sqlstack";
588
+ import { QueryBinder, Page, Query, run } from "sqlstack";
587
589
 
588
590
  @QueryBinder()
589
591
  class UsersRepo {
590
592
  @Page(10) // limit = 10, pages 1-based
591
593
  @Query()
592
594
  async listUsers(params: { status?: string; page?: number }): Promise<User[]> {
593
- throw new SqlStackError();
595
+ return run();
594
596
  }
595
597
  }
596
598
  ```
@@ -623,7 +625,7 @@ await repo.list(userId, { page: 2 }); // positional id + trailing option
623
625
  Shape single-query results without parameters:
624
626
 
625
627
  ```ts
626
- import { QueryBinder, Single, Only, Exist, None, Query, SqlStackError } from "sqlstack";
628
+ import { QueryBinder, Single, Only, Exist, None, Query, run } from "sqlstack";
627
629
 
628
630
  @QueryBinder()
629
631
  class UsersRepo {
@@ -631,28 +633,28 @@ class UsersRepo {
631
633
  @Single
632
634
  @Query()
633
635
  async firstOrNull(_a: { email: string }): Promise<User | null> {
634
- throw new SqlStackError();
636
+ return run();
635
637
  }
636
638
 
637
639
  // @Only → require exactly one row; throw if 0 or >1
638
640
  @Only
639
641
  @Query()
640
642
  async exactlyOne(_a: { id: string }): Promise<User> {
641
- throw new SqlStackError();
643
+ return run();
642
644
  }
643
645
 
644
646
  // @Exist → require at least one row; return first row; throw if none
645
647
  @Exist
646
648
  @Query()
647
649
  async requireOne(_a: { email: string }): Promise<User> {
648
- throw new SqlStackError();
650
+ return run();
649
651
  }
650
652
 
651
653
  // @None → require zero rows; return null; throw if any rows
652
654
  @None
653
655
  @Query()
654
656
  async ensureNone(_a: { status: string }): Promise<null> {
655
- throw new SqlStackError();
657
+ return run();
656
658
  }
657
659
  }
658
660
  ```
@@ -670,7 +672,7 @@ Validate query results against a JSON Schema:
670
672
 
671
673
  ```ts
672
674
  import schema from "./user.row.schema.json" assert { type: "json" };
673
- import { QueryBinder, ValidateResult, Single, Query, SqlStackError } from "sqlstack";
675
+ import { QueryBinder, ValidateResult, Single, Query, run } from "sqlstack";
674
676
 
675
677
  @QueryBinder()
676
678
  class UsersRepo {
@@ -678,7 +680,7 @@ class UsersRepo {
678
680
  @Single
679
681
  @Query()
680
682
  async findByEmail(_a: { email: string }): Promise<User | null> {
681
- throw new SqlStackError();
683
+ return run();
682
684
  }
683
685
  }
684
686
  ```
@@ -735,7 +737,7 @@ class Repo {
735
737
  WHERE g.id = :id
736
738
  ORDER BY m.name, e.name
737
739
  ` })
738
- async getGroup(_a: { id: string }): Promise<any | null> { throw new Error('replaced'); }
740
+ async getGroup(_a: { id: string }): Promise<any | null> { return run(); }
739
741
  }
740
742
  ```
741
743
 
@@ -806,13 +808,13 @@ Returns an array of rows, which can be shaped via decorators:
806
808
  class Repo {
807
809
  @Query()
808
810
  async allUsers(): Promise<User[]> {
809
- throw new Error();
811
+ return run();
810
812
  }
811
813
 
812
814
  @Single
813
815
  @Query()
814
816
  async firstUser(): Promise<User | null> {
815
- throw new Error();
817
+ return run();
816
818
  }
817
819
  }
818
820
  ```
@@ -867,16 +869,18 @@ This lets you start with very simple queries and incrementally adopt more comple
867
869
 
868
870
  ## Error Handling
869
871
 
870
- sqlstack provides a base error class and specific error types. Import and catch as needed:
872
+ sqlstack throws; it does not return result objects. Its errors extend `SqlStackError` (with one historical exception, noted below). Import and catch as needed:
871
873
 
872
874
  ```ts
873
- import { SqlStackError } from "sqlstack";
875
+ import { SqlStackError, DatabaseError } from "sqlstack";
874
876
  import { ExactlyOneRowError, NoRowsError, ValidationError } from "sqlstack/errors";
875
877
 
876
878
  try {
877
879
  const user = await repo.findByEmail({ email: "alice@example.com" });
878
880
  } catch (err) {
879
- if (err instanceof ExactlyOneRowError) {
881
+ if (err instanceof DatabaseError) {
882
+ console.log("statement failed:", err.sql, err.code, err.cause);
883
+ } else if (err instanceof ExactlyOneRowError) {
880
884
  console.log("@Only: expected exactly one row, got a different number");
881
885
  } else if (err instanceof NoRowsError) {
882
886
  console.log("@Exist: expected at least one row, got none");
@@ -891,11 +895,15 @@ try {
891
895
  ```
892
896
 
893
897
  Available error classes:
894
- - `SqlStackError` — base class for all sqlstack errors
898
+ - `SqlStackError` — base class for all sqlstack errors (also used for usage errors such as calling `run()` outside a `@Query` method)
899
+ - `DatabaseError` — a statement failed. `.sql` holds the statement (with placeholders), `.cause` holds the original error (the driver's error, or the macro/binding/name-resolution error), and `.code` copies the cause's string driver code (`SQLITE_CONSTRAINT_UNIQUE`, `23505`, `ER_DUP_ENTRY`, ...) when present
895
900
  - `ExactlyOneRowError` — thrown by `@Only` when row count ≠ 1
896
901
  - `NoRowsError` — thrown by `@Exist` when no rows found
902
+ - `RowsExistError` — thrown by `@None` when rows exist
903
+ - `ExpectedRowsError` — a shape decorator was applied to a statement that produced no result set
897
904
  - `ValidationError` — thrown by `@ValidateResult` on schema mismatch
898
- - Database driver errors — pass through unchanged
905
+
906
+ **The error contract.** Driver errors never pass through raw: they are always wrapped in `DatabaseError` with the original in `.cause`. Parameter-binding errors such as `Missing named parameter :email` are a `DatabaseError` with the original in `.cause` in every `run(...)` form, including the zero-argument `run()`. For the `run(...)` forms that take arguments (`run(query)`, `run(db, query)`, `run(db, params)`), *every* failure before result shaping — macro errors from an `` sql`...` `` fragment, parameter-binding errors, an unknown database name — is a `DatabaseError` with the original in `.cause`; invalid arguments are `SqlStackError`. Errors thrown by your own code — the method body or a `@Transform` function — propagate unchanged.
899
907
 
900
908
  ---
901
909
 
@@ -929,7 +937,7 @@ Queries in `@Query` decorated methods automatically participate in the active tr
929
937
  class UsersRepo {
930
938
  @Query({ sql: 'INSERT INTO users (email, name) VALUES (:email, :name)' })
931
939
  async insert(data: { email: string; name: string }): Promise<WriteResult> {
932
- throw new Error("replaced by @Query");
940
+ return run();
933
941
  }
934
942
  }
935
943
 
@@ -937,7 +945,7 @@ class UsersRepo {
937
945
  class ProfilesRepo {
938
946
  @Query({ sql: 'INSERT INTO profiles (user_id, bio) VALUES (:userId, :bio)' })
939
947
  async insert(data: { userId: string; bio: string }): Promise<WriteResult> {
940
- throw new Error("replaced by @Query");
948
+ return run();
941
949
  }
942
950
  }
943
951
  ```
@@ -1184,7 +1192,7 @@ async createUser() {
1184
1192
 
1185
1193
  ## Inline SQL
1186
1194
 
1187
- For simple or dynamic queries, use `@Query({ sql: "..." })` with inline SQL:
1195
+ For simple queries, use `@Query({ sql: "..." })` with inline SQL:
1188
1196
 
1189
1197
  ```ts
1190
1198
  @QueryBinder()
@@ -1195,18 +1203,99 @@ class Repo {
1195
1203
  WHERE email = :email
1196
1204
  ` })
1197
1205
  async findByEmail(_a: { email: string }): Promise<User[]> {
1198
- throw new Error("replaced");
1206
+ return run();
1199
1207
  }
1200
1208
  }
1201
1209
  ```
1202
1210
 
1203
1211
  sqlstack applies the same parameter binding and result shaping as file-based SQL.
1204
1212
 
1213
+ For SQL built in code, pass an `` sql`...` `` fragment to `run`. The fragment replaces the method's SQL file; its `${}` values are the bound parameters (the method's own arguments are not auto-bound). The database still comes from `@Query({ db })` / `@QueryBinder({ db })` / the default, the active transaction is joined, and the shape/transform/validation decorators still apply:
1214
+
1215
+ ```ts
1216
+ import { QueryBinder, Query, Single, run, sql } from "sqlstack";
1217
+
1218
+ @QueryBinder()
1219
+ class Repo {
1220
+ @Single
1221
+ @Query()
1222
+ async findByEmail(email: string): Promise<User | null> {
1223
+ return run(sql`SELECT id, email, name FROM users WHERE email = ${email}`);
1224
+ }
1225
+ }
1226
+ ```
1227
+
1228
+ SQL is never accepted as a plain string — always build it with the `sql` tag, and pass the fragment itself (not its `.toSQL()` output) so `run` can bind it for the target dialect.
1229
+
1230
+ > **Deprecated:** a `@Query` body that *returns* an `` sql`...` `` fragment (instead of `` return run(sql`...`) ``) still executes, but only type-checks with an `as any`-style cast. It emits a one-time `DeprecationWarning` (`SQLSTACK_FRAGMENT_BODY`) per method and will be removed in the next major version.
1231
+
1232
+ ---
1233
+
1234
+ ## Choosing the Database at Call Time
1235
+
1236
+ When the database handle is only known at runtime (per-tenant or per-thread databases, a handle passed into a repository method), pass it to `run` as the first argument. The handle is anything with `query(sql, params)` — a sqlstack `Database` from `createPgDb` / `createMysqlDb` / `createSqliteDb`, or your application's own wrapper — or the name of a registered database. sqlstack never opens databases by path.
1237
+
1238
+ ### `run(db, query)` — anywhere
1239
+
1240
+ ```ts
1241
+ import { run, sql } from "sqlstack";
1242
+
1243
+ const rows = await run<ImportRow[]>(accountsDb, sql`
1244
+ SELECT 1 AS present FROM data_root_imports
1245
+ WHERE data_root = ${dataRoot} AND kind = ${kind}
1246
+ LIMIT 1
1247
+ `);
1248
+
1249
+ await run(accountsDb, sql`INSERT INTO data_root_imports :insert(${{ data_root: dataRoot, kind }})`);
1250
+ ```
1251
+
1252
+ Outside a `@Query` method this returns the raw result: a rows array for statements that produce a result set, `{ rowsAffected, lastInsertId? }` otherwise. Without an explicit type argument the result is `unknown`, never `any`. Inside a `@Query` method, `` return run(db, sql`...`) `` runs the fragment on `db` and applies the method's shape/transform/validation decorators; the result type is inferred from the method's declared return type.
1253
+
1254
+ ### `run(db, params)` — inside a `@Query` method
1255
+
1256
+ Runs the method's own SQL (its `.sql` file or inline `@Query({ sql })`) against `db`, binding `params` — an object for `:named` placeholders, an array for `:arg1, :arg2, ...`. In this form the method's raw arguments are **not** auto-bound; `params` is the whole binding input. `@Defaults`, `@Page`, shaping, transform, and validation still apply.
1257
+
1258
+ ```ts
1259
+ @QueryBinder()
1260
+ class AccountsRepo {
1261
+ @Single
1262
+ @Query() // AccountsRepo/findByEmail.sql: SELECT * FROM accounts WHERE email = :email
1263
+ async findByEmail(db: Database, params: { email: string }): Promise<Account | null> {
1264
+ return run(db, params);
1265
+ }
1266
+ }
1267
+ ```
1268
+
1269
+ The SQL file is located through the same resolver as `run()` (under an active IoC environment that means the root's registered `SqlStack`). Calling `run(db, params)` outside a `@Query` method throws `SqlStackError` — there is no SQL to run.
1270
+
1271
+ ### The forms at a glance
1272
+
1273
+ | Call | Where | SQL | Database | Binding |
1274
+ |---|---|---|---|---|
1275
+ | `run()` | `@Query` body | method's file / inline SQL | decorator context | method arguments |
1276
+ | `run(query)` | `@Query` body | the fragment | decorator context | fragment values |
1277
+ | `run(db, query)` | anywhere | the fragment | `db` | fragment values |
1278
+ | `run(db, params)` | `@Query` body | method's file / inline SQL | `db` | `params` only |
1279
+
1280
+ Type rules (enforced by the overloads, and mirrored at runtime for untyped callers): SQL strings are rejected, `.toSQL()` output (`{ sql, params }`) is rejected, the first argument must be a handle or a name, and params must be an object or array. All forms follow the [error contract](#error-handling): failures surface as `DatabaseError` with the original error in `.cause`.
1281
+
1282
+ ### Dialects
1283
+
1284
+ `run` binds fragments for the target database: `$1, $2, ...` for Postgres, `?` for MySQL and SQLite (including inside `:insert`, `:update`, `:filter`, and `:batch_insert`). The dialect comes from the handle's `dialect` property and defaults to `sqlite` when the handle has none. `fragment.toSQL()` itself is unchanged and always emits `?`.
1285
+
1286
+ ### Transactions
1287
+
1288
+ A database passed to `run` joins an active transaction (from `@transaction` / `withTransaction`) that was opened **on that same database**: by name, or — for a handle — when it is the very `Database` object registered under the transaction's name. A handle that is not registered with sqlstack (for example an application-owned SQLite connection) never joins a sqlstack transaction; manage its transactions with your driver.
1289
+
1290
+ ### Inside `@Query`, all `run` calls belong to the method
1291
+
1292
+ `run` finds the enclosing `@Query` invocation through async context. Any `run(...)` executed while a `@Query` method body is running — including inside helper functions the body awaits — is treated as that method's query and gets its shape/transform/validation decorators. Keep `run(db, query)` calls meant to return raw results out of `@Query` bodies.
1293
+
1205
1294
  ---
1206
1295
 
1207
1296
  ## Advanced: Direct Database Access
1208
1297
 
1209
- Every `Database` instance exposes the underlying driver via `.conn`, so you can drop down to raw queries when needed:
1298
+ For SQL that should stay typed and dialect-safe, prefer `` run(db, sql`...`) `` (see [Choosing the Database at Call Time](#choosing-the-database-at-call-time)). Every `Database` instance also exposes the underlying driver via `.conn`, so you can drop down to raw driver calls when needed:
1210
1299
 
1211
1300
  ```ts
1212
1301
  import { SqlStackDB } from 'sqlstack/registry';
@@ -1238,7 +1327,7 @@ More adapters are planned.
1238
1327
 
1239
1328
  ### Dialect Inference
1240
1329
 
1241
- The placeholder style is inferred from the selected database. Override per-method with `@Query({ dialect: "pg" })` if needed.
1330
+ The placeholder style is inferred from the selected database. Override per-method with `@Query({ dialect: "postgres" })` if needed.
1242
1331
 
1243
1332
  ---
1244
1333