sqlstack 1.0.19 → 1.0.21

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAE3C,WAAW;AACX,cAAc,YAAY,CAAC;AAE3B,wBAAwB;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AAEjC,aAAa;AACb,cAAc,aAAa,CAAC;AAI5B,OAAO,EAAE,UAAU,EAAY,MAAM,YAAY,CAAC;AAElD,mDAAmD;AACnD,cAAc,qBAAqB,CAAC","sourcesContent":["// Decorators\nexport * from './decorators/queryBinder';\nexport * from './decorators/query';\nexport * from './decorators/defaults';\nexport * from './decorators/resultShape';\nexport * from './decorators/validateResult';\nexport * from './decorators/transform';\nexport * from './decorators/page';\nexport * from './decorators/missingAsNull';\n\n// Adapters\nexport * from './adapters';\n\n// Errors and Validation\nexport * from './core/errors';\nexport * from './core/validator';\n\n// DTO Helper\nexport * from './utils/dto';\n\n// Database Registry and Types\nexport type { Dialect, WriteResult, PostgresWriteResult, QueryResult, Database } from './registry';\nexport { SqlStackDB, IStackDB } from './registry';\n\n// Legacy exports (kept for backward compatibility)\nexport * from './binders/sqlBinder';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAE3C,eAAe;AACf,cAAc,gBAAgB,CAAC;AAE/B,WAAW;AACX,cAAc,YAAY,CAAC;AAE3B,wBAAwB;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AAEjC,aAAa;AACb,cAAc,aAAa,CAAC;AAI5B,OAAO,EAAE,UAAU,EAAY,MAAM,YAAY,CAAC;AAElD,mDAAmD;AACnD,cAAc,qBAAqB,CAAC","sourcesContent":["// Decorators\nexport * from './decorators/queryBinder';\nexport * from './decorators/query';\nexport * from './decorators/defaults';\nexport * from './decorators/resultShape';\nexport * from './decorators/validateResult';\nexport * from './decorators/transform';\nexport * from './decorators/page';\nexport * from './decorators/missingAsNull';\n\n// Transactions\nexport * from './transactions';\n\n// Adapters\nexport * from './adapters';\n\n// Errors and Validation\nexport * from './core/errors';\nexport * from './core/validator';\n\n// DTO Helper\nexport * from './utils/dto';\n\n// Database Registry and Types\nexport type { Dialect, WriteResult, PostgresWriteResult, QueryResult, Database } from './registry';\nexport { SqlStackDB, IStackDB } from './registry';\n\n// Legacy exports (kept for backward compatibility)\nexport * from './binders/sqlBinder';\n"]}
@@ -12,14 +12,17 @@ class Registry {
12
12
  this.defaultName = name;
13
13
  return this;
14
14
  }
15
- get(name) {
15
+ getEntry(name) {
16
16
  const key = name ?? this.defaultName;
17
17
  if (!key)
18
18
  throw new Error('No default database set');
19
19
  const db = this.map.get(key);
20
20
  if (!db)
21
21
  throw new Error(`No database registered under ${key}`);
22
- return db;
22
+ return { name: key, db };
23
+ }
24
+ get(name) {
25
+ return this.getEntry(name).db;
23
26
  }
24
27
  }
25
28
  // Ensure a single shared instance across module copies/usages
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAwBA,MAAM,QAAQ;IAAd;QACU,QAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;IAqB5C,CAAC;IAlBC,QAAQ,CAAC,IAAY,EAAE,EAAY;QACjC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU,CAAC,IAAY;QACrB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CAAC,IAAa;QACf,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACrD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,CAAC,GAAG,UAAiB,CAAC;AAC5B,IAAI,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC;IAC9B,CAAC,CAAC,sBAAsB,GAAG,IAAI,QAAQ,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAa,CAAC,CAAC,sBAAsB,CAAC","sourcesContent":["export type Dialect = 'sqlite' | 'mysql' | 'postgres';\n\n// Result for non-SELECT statements across adapters\nexport interface WriteResult {\n rowsAffected: number;\n lastInsertId?: unknown;\n}\n\n// Postgres-specific extension when using RETURNING on write statements\nexport interface PostgresWriteResult extends WriteResult {\n // Only present when the SQL includes RETURNING and rows are returned\n returning?: any[];\n}\n\nexport type QueryResult = any[] | WriteResult | PostgresWriteResult;\n\nexport interface Database {\n dialect: Dialect;\n query(sql: string, params: unknown[]): Promise<QueryResult>;\n // Underlying driver connection or pool (driver-specific)\n conn: unknown;\n close: () => Promise<void> | void;\n}\n\nclass Registry {\n private map = new Map<string, Database>();\n private defaultName?: string;\n\n register(name: string, db: Database) {\n this.map.set(name, db);\n return this;\n }\n\n setDefault(name: string) {\n if (!this.map.has(name)) throw new Error(`No database registered under ${name}`);\n this.defaultName = name;\n return this;\n }\n\n get(name?: string): Database {\n const key = name ?? this.defaultName;\n if (!key) throw new Error('No default database set');\n const db = this.map.get(key);\n if (!db) throw new Error(`No database registered under ${key}`);\n return db;\n }\n}\n\n// Ensure a single shared instance across module copies/usages\nconst g = globalThis as any;\nif (!g.__SQLSTACK_SINGLETON__) {\n g.__SQLSTACK_SINGLETON__ = new Registry();\n}\n\nexport const SqlStackDB: Registry = g.__SQLSTACK_SINGLETON__;\n\nexport type IStackDB = typeof SqlStackDB;\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAwBA,MAAM,QAAQ;IAAd;QACU,QAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;IAyB5C,CAAC;IAtBC,QAAQ,CAAC,IAAY,EAAE,EAAY;QACjC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU,CAAC,IAAY;QACrB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,IAAa;QACpB,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACrD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAC3B,CAAC;IAED,GAAG,CAAC,IAAa;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;IAChC,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,CAAC,GAAG,UAAiB,CAAC;AAC5B,IAAI,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC;IAC9B,CAAC,CAAC,sBAAsB,GAAG,IAAI,QAAQ,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAa,CAAC,CAAC,sBAAsB,CAAC","sourcesContent":["export type Dialect = 'sqlite' | 'mysql' | 'postgres';\n\n// Result for non-SELECT statements across adapters\nexport interface WriteResult {\n rowsAffected: number;\n lastInsertId?: unknown;\n}\n\n// Postgres-specific extension when using RETURNING on write statements\nexport interface PostgresWriteResult extends WriteResult {\n // Only present when the SQL includes RETURNING and rows are returned\n returning?: any[];\n}\n\nexport type QueryResult = any[] | WriteResult | PostgresWriteResult;\n\nexport interface Database {\n dialect: Dialect;\n query(sql: string, params: unknown[]): Promise<QueryResult>;\n // Underlying driver connection or pool (driver-specific)\n conn: unknown;\n close: () => Promise<void> | void;\n}\n\nclass Registry {\n private map = new Map<string, Database>();\n private defaultName?: string;\n\n register(name: string, db: Database) {\n this.map.set(name, db);\n return this;\n }\n\n setDefault(name: string) {\n if (!this.map.has(name)) throw new Error(`No database registered under ${name}`);\n this.defaultName = name;\n return this;\n }\n\n getEntry(name?: string): { name: string; db: Database } {\n const key = name ?? this.defaultName;\n if (!key) throw new Error('No default database set');\n const db = this.map.get(key);\n if (!db) throw new Error(`No database registered under ${key}`);\n return { name: key, db };\n }\n\n get(name?: string): Database {\n return this.getEntry(name).db;\n }\n}\n\n// Ensure a single shared instance across module copies/usages\nconst g = globalThis as any;\nif (!g.__SQLSTACK_SINGLETON__) {\n g.__SQLSTACK_SINGLETON__ = new Registry();\n}\n\nexport const SqlStackDB: Registry = g.__SQLSTACK_SINGLETON__;\n\nexport type IStackDB = typeof SqlStackDB;\n"]}
@@ -0,0 +1,268 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ 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");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ 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");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _TransactionHandle_state, _TransactionHandle_owned, _TransactionHandle_finished;
13
+ import { AsyncLocalStorage } from 'node:async_hooks';
14
+ import { SqlStackDB } from './registry.js';
15
+ import { createPgDb, createMysqlDb } from './adapters/index.js';
16
+ import { RollbackTransactionError, SqlStackError } from './core/errors.js';
17
+ const als = new AsyncLocalStorage();
18
+ function getStore() {
19
+ return als.getStore();
20
+ }
21
+ function getOrCreateStore() {
22
+ const existing = getStore();
23
+ if (existing)
24
+ return { store: existing, isNew: false };
25
+ return { store: { txByDb: new Map() }, isNew: true };
26
+ }
27
+ function isRollbackTransactionError(err) {
28
+ return err instanceof RollbackTransactionError;
29
+ }
30
+ function unwrapRollbackCause(err) {
31
+ return typeof err.cause === 'undefined' ? err : err.cause;
32
+ }
33
+ export class TransactionHandle {
34
+ constructor(dbName, state, owned) {
35
+ _TransactionHandle_state.set(this, void 0);
36
+ _TransactionHandle_owned.set(this, void 0);
37
+ _TransactionHandle_finished.set(this, false);
38
+ this.dbName = dbName;
39
+ __classPrivateFieldSet(this, _TransactionHandle_state, state, "f");
40
+ __classPrivateFieldSet(this, _TransactionHandle_owned, owned, "f");
41
+ }
42
+ async ensureStarted() {
43
+ await __classPrivateFieldGet(this, _TransactionHandle_state, "f").ensureStarted();
44
+ }
45
+ rollbackOnly(errorToThrow) {
46
+ __classPrivateFieldGet(this, _TransactionHandle_state, "f").rollbackOnly = true;
47
+ if (typeof errorToThrow !== 'undefined') {
48
+ __classPrivateFieldGet(this, _TransactionHandle_state, "f").rollbackError = errorToThrow;
49
+ }
50
+ }
51
+ async commit() {
52
+ if (!__classPrivateFieldGet(this, _TransactionHandle_owned, "f"))
53
+ return;
54
+ await __classPrivateFieldGet(this, _TransactionHandle_state, "f").commitIfStarted();
55
+ }
56
+ async rollback() {
57
+ if (!__classPrivateFieldGet(this, _TransactionHandle_owned, "f"))
58
+ return;
59
+ await __classPrivateFieldGet(this, _TransactionHandle_state, "f").rollbackIfStarted();
60
+ }
61
+ async release() {
62
+ if (!__classPrivateFieldGet(this, _TransactionHandle_owned, "f"))
63
+ return;
64
+ await __classPrivateFieldGet(this, _TransactionHandle_state, "f").release();
65
+ }
66
+ async finishOnSuccess() {
67
+ if (!__classPrivateFieldGet(this, _TransactionHandle_owned, "f") || __classPrivateFieldGet(this, _TransactionHandle_finished, "f"))
68
+ return;
69
+ __classPrivateFieldSet(this, _TransactionHandle_finished, true, "f");
70
+ await __classPrivateFieldGet(this, _TransactionHandle_state, "f").finishOnSuccess();
71
+ }
72
+ async finishOnError(err) {
73
+ if (!__classPrivateFieldGet(this, _TransactionHandle_owned, "f") || __classPrivateFieldGet(this, _TransactionHandle_finished, "f"))
74
+ return;
75
+ __classPrivateFieldSet(this, _TransactionHandle_finished, true, "f");
76
+ await __classPrivateFieldGet(this, _TransactionHandle_state, "f").finishOnError(err);
77
+ }
78
+ }
79
+ _TransactionHandle_state = new WeakMap(), _TransactionHandle_owned = new WeakMap(), _TransactionHandle_finished = new WeakMap();
80
+ class TxState {
81
+ constructor(dbName, db, options) {
82
+ this.depth = 1;
83
+ this.started = false;
84
+ this.completed = false;
85
+ this.rollbackOnly = false;
86
+ this.dbName = dbName;
87
+ this.db = db;
88
+ this.options = {
89
+ lazy: options?.lazy ?? true,
90
+ nested: options?.nested ?? 'join',
91
+ };
92
+ }
93
+ get activeDb() {
94
+ return this.txDb ?? this.db;
95
+ }
96
+ async ensureStarted() {
97
+ if (this.started)
98
+ return;
99
+ const dialect = this.db.dialect ?? 'sqlite';
100
+ if (dialect === 'postgres') {
101
+ const pool = this.db.conn;
102
+ if (!pool || typeof pool.connect !== 'function') {
103
+ throw new SqlStackError('Postgres transactions require a pg Pool with .connect() as Database.conn');
104
+ }
105
+ const client = await pool.connect();
106
+ this.txDb = createPgDb(client);
107
+ this.releaseFn = async () => {
108
+ if (typeof client.release === 'function')
109
+ client.release();
110
+ };
111
+ await this.txDb.query('BEGIN', []);
112
+ this.started = true;
113
+ return;
114
+ }
115
+ if (dialect === 'mysql') {
116
+ const pool = this.db.conn;
117
+ if (!pool || typeof pool.getConnection !== 'function') {
118
+ throw new SqlStackError('MySQL transactions require a mysql2 Pool with .getConnection() as Database.conn');
119
+ }
120
+ const conn = await pool.getConnection();
121
+ this.txDb = createMysqlDb(conn);
122
+ this.releaseFn = async () => {
123
+ if (typeof conn.release === 'function')
124
+ conn.release();
125
+ };
126
+ await this.txDb.query('START TRANSACTION', []);
127
+ this.started = true;
128
+ return;
129
+ }
130
+ // sqlite: existing connection is already bound
131
+ await this.db.query('BEGIN', []);
132
+ this.started = true;
133
+ }
134
+ async query(sql, params) {
135
+ await this.ensureStarted();
136
+ return await this.activeDb.query(sql, params);
137
+ }
138
+ async commitIfStarted() {
139
+ if (this.completed)
140
+ return;
141
+ if (!this.started) {
142
+ this.completed = true;
143
+ return;
144
+ }
145
+ await this.activeDb.query('COMMIT', []);
146
+ this.completed = true;
147
+ }
148
+ async rollbackIfStarted() {
149
+ if (this.completed)
150
+ return;
151
+ if (!this.started) {
152
+ this.completed = true;
153
+ return;
154
+ }
155
+ await this.activeDb.query('ROLLBACK', []);
156
+ this.completed = true;
157
+ }
158
+ async release() {
159
+ try {
160
+ if (this.releaseFn)
161
+ await this.releaseFn();
162
+ }
163
+ finally {
164
+ this.txDb = undefined;
165
+ this.releaseFn = undefined;
166
+ }
167
+ }
168
+ async finishOnSuccess() {
169
+ try {
170
+ if (this.rollbackOnly) {
171
+ await this.rollbackIfStarted();
172
+ if (typeof this.rollbackError !== 'undefined')
173
+ throw this.rollbackError;
174
+ throw new RollbackTransactionError();
175
+ }
176
+ await this.commitIfStarted();
177
+ }
178
+ finally {
179
+ await this.release();
180
+ }
181
+ }
182
+ async finishOnError(err) {
183
+ try {
184
+ await this.rollbackIfStarted();
185
+ }
186
+ finally {
187
+ await this.release();
188
+ }
189
+ if (isRollbackTransactionError(err)) {
190
+ throw unwrapRollbackCause(err);
191
+ }
192
+ throw err;
193
+ }
194
+ }
195
+ function enterTx(dbName, options) {
196
+ const { store, isNew } = getOrCreateStore();
197
+ const existing = store.txByDb.get(dbName);
198
+ if (existing) {
199
+ existing.depth++;
200
+ return { handle: new TransactionHandle(dbName, existing, false), store, isNewStore: isNew };
201
+ }
202
+ const { db } = SqlStackDB.getEntry(dbName);
203
+ const state = new TxState(dbName, db, options);
204
+ store.txByDb.set(dbName, state);
205
+ return { handle: new TransactionHandle(dbName, state, true), store, isNewStore: isNew };
206
+ }
207
+ async function exitTx(dbName, store) {
208
+ const state = store.txByDb.get(dbName);
209
+ if (!state)
210
+ return;
211
+ state.depth = Math.max(0, state.depth - 1);
212
+ if (state.depth === 0) {
213
+ store.txByDb.delete(dbName);
214
+ }
215
+ }
216
+ export function currentTransaction(db) {
217
+ const store = getStore();
218
+ if (!store)
219
+ return undefined;
220
+ const resolved = SqlStackDB.getEntry(db).name;
221
+ const state = store.txByDb.get(resolved);
222
+ if (!state)
223
+ return undefined;
224
+ return new TransactionHandle(resolved, state, false);
225
+ }
226
+ export async function withTransaction(fn, options) {
227
+ const resolved = SqlStackDB.getEntry(options?.db).name;
228
+ const { handle, store, isNewStore } = enterTx(resolved, options);
229
+ const run = async () => {
230
+ try {
231
+ const out = await fn();
232
+ await handle.finishOnSuccess();
233
+ return out;
234
+ }
235
+ catch (err) {
236
+ await handle.finishOnError(err);
237
+ throw err;
238
+ }
239
+ finally {
240
+ await exitTx(resolved, store);
241
+ }
242
+ };
243
+ return isNewStore ? await als.run(store, run) : await run();
244
+ }
245
+ export const transaction = function (...args) {
246
+ // @transaction
247
+ if (args.length === 3 && typeof args[2]?.value === 'function') {
248
+ const [target, prop, desc] = args;
249
+ wrapMethod(target, prop, desc, undefined);
250
+ return;
251
+ }
252
+ // @transaction() or @transaction({ ... })
253
+ const options = args.length > 0 ? args[0] : undefined;
254
+ return function (target, prop, desc) {
255
+ wrapMethod(target, prop, desc, options);
256
+ };
257
+ };
258
+ function wrapMethod(_target, _prop, desc, options) {
259
+ const original = desc.value;
260
+ desc.value = async function (...callArgs) {
261
+ return await withTransaction(() => original.apply(this, callArgs), options);
262
+ };
263
+ }
264
+ export function getActiveTransaction(dbName) {
265
+ const store = getStore();
266
+ return store?.txByDb.get(dbName);
267
+ }
268
+ //# sourceMappingURL=transactions.js.map
@@ -0,0 +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 { SqlStackDB } from './registry';\nimport { createPgDb, createMysqlDb } from './adapters';\nimport { RollbackTransactionError, SqlStackError } from './core/errors';\n\nexport type TransactionOptions = {\n db?: string;\n lazy?: boolean;\n nested?: 'join';\n};\n\ntype TxStore = {\n txByDb: Map<string, TxState>;\n};\n\nconst als = new AsyncLocalStorage<TxStore>();\n\nfunction getStore(): TxStore | undefined {\n return als.getStore();\n}\n\nfunction getOrCreateStore(): { store: TxStore; isNew: boolean } {\n const existing = getStore();\n if (existing) return { store: existing, isNew: false };\n return { store: { txByDb: new Map() }, isNew: true };\n}\n\nfunction isRollbackTransactionError(err: unknown): err is RollbackTransactionError {\n return err instanceof RollbackTransactionError;\n}\n\nfunction unwrapRollbackCause(err: RollbackTransactionError): unknown {\n return typeof err.cause === 'undefined' ? err : err.cause;\n}\n\nexport class TransactionHandle {\n readonly dbName: string;\n #state: TxState;\n #owned: boolean;\n #finished = false;\n\n constructor(dbName: string, state: TxState, owned: boolean) {\n this.dbName = dbName;\n this.#state = state;\n this.#owned = owned;\n }\n\n async ensureStarted() {\n await this.#state.ensureStarted();\n }\n\n rollbackOnly(errorToThrow?: unknown) {\n this.#state.rollbackOnly = true;\n if (typeof errorToThrow !== 'undefined') {\n this.#state.rollbackError = errorToThrow;\n }\n }\n\n async commit() {\n if (!this.#owned) return;\n await this.#state.commitIfStarted();\n }\n\n async rollback() {\n if (!this.#owned) return;\n await this.#state.rollbackIfStarted();\n }\n\n async release() {\n if (!this.#owned) return;\n await this.#state.release();\n }\n\n async finishOnSuccess() {\n if (!this.#owned || this.#finished) return;\n this.#finished = true;\n await this.#state.finishOnSuccess();\n }\n\n async finishOnError(err: unknown) {\n if (!this.#owned || this.#finished) return;\n this.#finished = true;\n await this.#state.finishOnError(err);\n }\n}\n\nclass TxState {\n readonly dbName: string;\n readonly db: Database;\n readonly options: Required<Pick<TransactionOptions, 'lazy'>> & Pick<TransactionOptions, 'nested'>;\n depth = 1;\n started = false;\n completed = false;\n rollbackOnly = false;\n rollbackError: unknown | undefined;\n\n private txDb: Database | undefined;\n private releaseFn: (() => Promise<void>) | undefined;\n\n constructor(dbName: string, db: Database, options: TransactionOptions | undefined) {\n this.dbName = dbName;\n this.db = db;\n this.options = {\n lazy: options?.lazy ?? true,\n nested: options?.nested ?? 'join',\n };\n }\n\n get activeDb(): Database {\n return this.txDb ?? this.db;\n }\n\n async ensureStarted() {\n if (this.started) return;\n\n const dialect = this.db.dialect ?? 'sqlite';\n if (dialect === 'postgres') {\n const pool: any = this.db.conn;\n if (!pool || typeof pool.connect !== 'function') {\n throw new SqlStackError('Postgres transactions require a pg Pool with .connect() as Database.conn');\n }\n const client = await pool.connect();\n this.txDb = createPgDb(client);\n this.releaseFn = async () => {\n if (typeof client.release === 'function') client.release();\n };\n await this.txDb.query('BEGIN', []);\n this.started = true;\n return;\n }\n\n if (dialect === 'mysql') {\n const pool: any = this.db.conn;\n if (!pool || typeof pool.getConnection !== 'function') {\n throw new SqlStackError('MySQL transactions require a mysql2 Pool with .getConnection() as Database.conn');\n }\n const conn = await pool.getConnection();\n this.txDb = createMysqlDb(conn);\n this.releaseFn = async () => {\n if (typeof conn.release === 'function') conn.release();\n };\n await this.txDb.query('START TRANSACTION', []);\n this.started = true;\n return;\n }\n\n // sqlite: existing connection is already bound\n await this.db.query('BEGIN', []);\n this.started = true;\n }\n\n async query(sql: string, params: unknown[]): Promise<QueryResult> {\n await this.ensureStarted();\n return await this.activeDb.query(sql, params);\n }\n\n async commitIfStarted() {\n if (this.completed) return;\n if (!this.started) {\n this.completed = true;\n return;\n }\n await this.activeDb.query('COMMIT', []);\n this.completed = true;\n }\n\n async rollbackIfStarted() {\n if (this.completed) return;\n if (!this.started) {\n this.completed = true;\n return;\n }\n await this.activeDb.query('ROLLBACK', []);\n this.completed = true;\n }\n\n async release() {\n try {\n if (this.releaseFn) await this.releaseFn();\n } finally {\n this.txDb = undefined;\n this.releaseFn = undefined;\n }\n }\n\n async finishOnSuccess() {\n try {\n if (this.rollbackOnly) {\n await this.rollbackIfStarted();\n if (typeof this.rollbackError !== 'undefined') throw this.rollbackError;\n throw new RollbackTransactionError();\n }\n await this.commitIfStarted();\n } finally {\n await this.release();\n }\n }\n\n async finishOnError(err: unknown) {\n try {\n await this.rollbackIfStarted();\n } finally {\n await this.release();\n }\n if (isRollbackTransactionError(err)) {\n throw unwrapRollbackCause(err);\n }\n throw err;\n }\n}\n\nfunction enterTx(dbName: string, options?: TransactionOptions): { handle: TransactionHandle; store: TxStore; isNewStore: boolean } {\n const { store, isNew } = getOrCreateStore();\n const existing = store.txByDb.get(dbName);\n if (existing) {\n existing.depth++;\n return { handle: new TransactionHandle(dbName, existing, false), store, isNewStore: isNew };\n }\n const { db } = SqlStackDB.getEntry(dbName);\n const state = new TxState(dbName, db, options);\n store.txByDb.set(dbName, state);\n return { handle: new TransactionHandle(dbName, state, true), store, isNewStore: isNew };\n}\n\nasync function exitTx(dbName: string, store: TxStore) {\n const state = store.txByDb.get(dbName);\n if (!state) return;\n state.depth = Math.max(0, state.depth - 1);\n if (state.depth === 0) {\n store.txByDb.delete(dbName);\n }\n}\n\nexport function currentTransaction(db?: string): TransactionHandle | undefined {\n const store = getStore();\n if (!store) return undefined;\n const resolved = SqlStackDB.getEntry(db).name;\n const state = store.txByDb.get(resolved);\n if (!state) return undefined;\n return new TransactionHandle(resolved, state, false);\n}\n\nexport async function withTransaction<T>(fn: () => Promise<T>, options?: TransactionOptions): Promise<T> {\n const resolved = SqlStackDB.getEntry(options?.db).name;\n const { handle, store, isNewStore } = enterTx(resolved, options);\n\n const run = async () => {\n try {\n const out = await fn();\n await handle.finishOnSuccess();\n return out;\n } catch (err) {\n await handle.finishOnError(err);\n throw err;\n } finally {\n await exitTx(resolved, store);\n }\n };\n\n return isNewStore ? await als.run(store, run) : await run();\n}\n\nexport type TransactionDecorator = {\n (target: any, prop: string | symbol, desc: PropertyDescriptor): void;\n (options?: TransactionOptions): (target: any, prop: string | symbol, desc: PropertyDescriptor) => void;\n};\n\nexport const transaction: TransactionDecorator = function (...args: any[]) {\n // @transaction\n if (args.length === 3 && typeof args[2]?.value === 'function') {\n const [target, prop, desc] = args as [any, string | symbol, PropertyDescriptor];\n wrapMethod(target, prop, desc, undefined);\n return;\n }\n // @transaction() or @transaction({ ... })\n const options: TransactionOptions | undefined = args.length > 0 ? args[0] : undefined;\n return function (target: any, prop: string | symbol, desc: PropertyDescriptor) {\n wrapMethod(target, prop, desc, options);\n };\n} as any;\n\nfunction wrapMethod(_target: any, _prop: string | symbol, desc: PropertyDescriptor, options?: TransactionOptions) {\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n return await withTransaction(() => original.apply(this, callArgs), options);\n };\n}\n\nexport type ActiveTransaction = {\n query(sql: string, params: unknown[]): Promise<QueryResult>;\n};\n\nexport function getActiveTransaction(dbName: string): ActiveTransaction | undefined {\n const store = getStore();\n return store?.txByDb.get(dbName);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlstack",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
package/readme.md CHANGED
@@ -199,46 +199,89 @@ sqlstack adds a small set of macros that expand into standard SQL, so you keep f
199
199
  #### `:insert(...)`
200
200
 
201
201
  ```sql
202
- -- invoices/createInvoice.sql
203
- INSERT INTO invoices :insert(case_id, invoice_number, invoice_date, due_date, status = 'draft');
202
+ -- posts/createPost.sql
203
+ INSERT INTO posts :insert(id, author_id, title, body, status = 'published');
204
204
  ```
205
205
 
206
206
  ```ts
207
- await repo.createInvoice({
208
- case_id: 1,
209
- invoice_number: "INV-001",
210
- invoice_date: "2024-01-01",
211
- // due_date: undefined
207
+ await repo.createPost({
208
+ id: 'post_1',
209
+ author_id: 'user_1',
210
+ title: 'Hello world',
211
+ body: 'First post!',
212
+ // status: undefined → defaults to 'published' from SQL literal
212
213
  });
213
214
  ```
214
215
 
215
216
  ```sql
216
217
  -- expands to
217
- INSERT INTO invoices (case_id, invoice_number, invoice_date, status)
218
- VALUES (?, ?, ?, 'draft');
218
+ INSERT INTO posts (id, author_id, title, body, status)
219
+ VALUES (?, ?, ?, ?, 'published');
219
220
  ```
220
221
 
222
+ **Additional behaviors and rules**
223
+
224
+ - Columns whose values are `undefined` are skipped; at least one non-literal column must be defined or a literal must be present.
225
+ - Pure literal expressions (e.g. `created_at = now()`) are allowed and included even with no params.
226
+ - `null` values are allowed and are bound as `NULL`.
227
+ - Column order in the generated `INSERT` follows the order listed in `:insert(...)`, regardless of object key order.
228
+ - `:insert()` only works with **named parameters**; using positional args throws.
229
+
230
+ #### `:batch_insert(...)`
231
+
232
+ Use `:batch_insert(...)` to build an efficient multi-row `VALUES` clause from one or more array arguments (plus optional scalars):
233
+
234
+ ```sql
235
+ -- comments/createComments.sql
236
+ INSERT INTO comments (post_id, author_id, body)
237
+ :batch_insert(post_id, comment.author_id, comment.body);
238
+ ```
239
+
240
+ ```ts
241
+ await repo.createComments({
242
+ post_id: 'post_1',
243
+ comment: [
244
+ { author_id: 'user_1', body: 'Nice post!' },
245
+ { author_id: 'user_2', body: 'Subscribed.' },
246
+ ],
247
+ });
248
+ ```
249
+
250
+ ```sql
251
+ -- expands (MySQL/SQLite)
252
+ INSERT INTO comments (post_id, author_id, body)
253
+ VALUES (?, ?, ?), (?, ?, ?);
254
+
255
+ -- expands (Postgres)
256
+ INSERT INTO comments (post_id, author_id, body)
257
+ VALUES ($1, $2, $3), ($4, $5, $6);
258
+ ```
259
+
260
+ - At least one argument must resolve to an array; all array arguments must have the same length and be non-empty.
261
+ - Arguments can be scalars (reused for every row) or dot-paths into nested objects/arrays (e.g. `comment.body`, `root.ids`).
262
+ - `:batch_insert()` only works with **named parameters**, not positional ones.
263
+
221
264
  #### `:update(...)`
222
265
 
223
266
  ```sql
224
- -- invoices/updateInvoice.sql
225
- UPDATE invoices
226
- :update(updated_at = now(), invoice_date, due_date)
267
+ -- posts/updatePost.sql
268
+ UPDATE posts
269
+ :update(updated_at = now(), title, body)
227
270
  WHERE id = :id;
228
271
  ```
229
272
 
230
273
  ```ts
231
- await repo.updateInvoice({
232
- id: 1,
233
- invoice_date: "2024-01-01",
234
- // due_date: undefined
274
+ await repo.updatePost({
275
+ id: 'post_1',
276
+ title: 'Updated title',
277
+ // body: undefined → not updated
235
278
  });
236
279
  ```
237
280
 
238
281
  ```sql
239
282
  -- expands to
240
- UPDATE invoices
241
- SET updated_at = now(), invoice_date = ?
283
+ UPDATE posts
284
+ SET updated_at = now(), title = ?
242
285
  WHERE id = ?;
243
286
  ```
244
287