tina4-nodejs 3.13.85 → 3.13.87

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.
@@ -236,10 +236,36 @@ export class FirebirdAdapter implements DatabaseAdapter {
236
236
  return translated;
237
237
  }
238
238
 
239
+ /**
240
+ * The handle every statement runs on. While an explicit transaction is open
241
+ * (startTransactionAsync set `this.transaction`), statements MUST run on that
242
+ * transaction object so they are undone by rollbackAsync() / persisted by
243
+ * commitAsync() — node-firebird's transaction exposes the same
244
+ * query()/execute() as the connection. With no transaction open we run on
245
+ * `this.db`, whose per-statement work auto-commits on the connection.
246
+ *
247
+ * This matches the Python master's contract (tina4_python/database/firebird.py):
248
+ * there, ALL statements run on the single connection and start_transaction()
249
+ * merely suppresses the per-statement autocommit in execute() so the batch
250
+ * stays open until commit()/rollback(). node-firebird has no such suppression
251
+ * hook — its `db.query/execute` always auto-commit — so the equivalent is to
252
+ * route statements through the transaction object instead. Same observable
253
+ * behaviour: an open transaction is atomic and rolls back cleanly.
254
+ *
255
+ * Previously every statement ran on `this.db` unconditionally, so the
256
+ * transaction created by startTransactionAsync() never saw a single statement
257
+ * — rollbackAsync() rolled back an EMPTY transaction and the already
258
+ * auto-committed write survived (silent no-op). Twin of the PHP pdo_firebird
259
+ * bug fixed in 3.13.86.
260
+ */
261
+ private statementHandle(): any {
262
+ return this.transaction ?? this.db;
263
+ }
264
+
239
265
  private queryPromise(sql: string, params?: unknown[]): Promise<any[]> {
240
266
  return new Promise((resolve, reject) => {
241
267
  const translated = this.translateSql(sql);
242
- this.db.query(translated, params ?? [], (err: Error | null, result: any[]) => {
268
+ this.statementHandle().query(translated, params ?? [], (err: Error | null, result: any[]) => {
243
269
  if (err) reject(err);
244
270
  else resolve(result ?? []);
245
271
  });
@@ -249,7 +275,7 @@ export class FirebirdAdapter implements DatabaseAdapter {
249
275
  private executePromise(sql: string, params?: unknown[]): Promise<void> {
250
276
  return new Promise((resolve, reject) => {
251
277
  const translated = this.translateSql(sql);
252
- this.db.execute(translated, params ?? [], (err: Error | null) => {
278
+ this.statementHandle().execute(translated, params ?? [], (err: Error | null) => {
253
279
  if (err) reject(err);
254
280
  else resolve();
255
281
  });
@@ -260,11 +286,11 @@ export class FirebirdAdapter implements DatabaseAdapter {
260
286
  throw new Error("Use executeAsync() for Firebird — async adapter requires async methods.");
261
287
  }
262
288
 
263
- executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
289
+ executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
264
290
  throw new Error("Use executeManyAsync() for Firebird — async adapter requires async methods.");
265
291
  }
266
292
 
267
- async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
293
+ async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastId?: number | bigint }> {
268
294
  let totalAffected = 0;
269
295
  for (const params of paramsList) {
270
296
  await this.executeAsync(sql, params);
@@ -333,16 +359,16 @@ export class FirebirdAdapter implements DatabaseAdapter {
333
359
  // the batch reports affectedRows == row count and no lastInsertId (same as the
334
360
  // single-row path). See PostgresAdapter for the array-crash rationale.
335
361
  if (Array.isArray(data)) {
336
- if (data.length === 0) return { success: true, rowsAffected: 0 };
362
+ if (data.length === 0) return { success: true, affectedRows: 0 };
337
363
  const keys = Object.keys(data[0]);
338
364
  const placeholders = keys.map(() => "?").join(", ");
339
365
  const sql = `INSERT INTO "${table}" ("${keys.join('", "')}") VALUES (${placeholders})`;
340
366
  const paramsList = data.map((row) => keys.map((k) => row[k]));
341
367
  try {
342
368
  const result = await this.executeManyAsync(sql, paramsList);
343
- return { success: true, rowsAffected: result.totalAffected, lastInsertId: result.lastInsertId };
369
+ return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
344
370
  } catch (e) {
345
- return { success: false, rowsAffected: 0, error: (e as Error).message };
371
+ return { success: false, affectedRows: 0, error: (e as Error).message };
346
372
  }
347
373
  }
348
374
 
@@ -356,10 +382,10 @@ export class FirebirdAdapter implements DatabaseAdapter {
356
382
  // Firebird doesn't have a generic last_insert_id — return success without id
357
383
  return {
358
384
  success: true,
359
- rowsAffected: 1,
385
+ affectedRows: 1,
360
386
  };
361
387
  } catch (e) {
362
- return { success: false, rowsAffected: 0, error: (e as Error).message };
388
+ return { success: false, affectedRows: 0, error: (e as Error).message };
363
389
  }
364
390
  }
365
391
 
@@ -376,9 +402,9 @@ export class FirebirdAdapter implements DatabaseAdapter {
376
402
 
377
403
  try {
378
404
  await this.executePromise(sql, values);
379
- return { success: true, rowsAffected: 1 };
405
+ return { success: true, affectedRows: 1 };
380
406
  } catch (e) {
381
- return { success: false, rowsAffected: 0, error: (e as Error).message };
407
+ return { success: false, affectedRows: 0, error: (e as Error).message };
382
408
  }
383
409
  }
384
410
 
@@ -394,9 +420,9 @@ export class FirebirdAdapter implements DatabaseAdapter {
394
420
 
395
421
  try {
396
422
  await this.executePromise(sql, values);
397
- return { success: true, rowsAffected: 1 };
423
+ return { success: true, affectedRows: 1 };
398
424
  } catch (e) {
399
- return { success: false, rowsAffected: 0, error: (e as Error).message };
425
+ return { success: false, affectedRows: 0, error: (e as Error).message };
400
426
  }
401
427
  }
402
428
 
@@ -370,11 +370,11 @@ export class MongodbAdapter implements DatabaseAdapter {
370
370
  }
371
371
  }
372
372
 
373
- executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
373
+ executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
374
374
  throw new Error("Use executeManyAsync() for MongoDB — async adapter requires async methods.");
375
375
  }
376
376
 
377
- async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
377
+ async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastId?: number | bigint }> {
378
378
  let totalAffected = 0;
379
379
  for (const params of paramsList) {
380
380
  await this.executeAsync(sql, params);
@@ -453,14 +453,14 @@ export class MongodbAdapter implements DatabaseAdapter {
453
453
  const col = this.db.collection(table);
454
454
  try {
455
455
  if (Array.isArray(data)) {
456
- if (data.length === 0) return { success: true, rowsAffected: 0 };
456
+ if (data.length === 0) return { success: true, affectedRows: 0 };
457
457
  const result = await col.insertMany(data, { session: this.session });
458
- return { success: true, rowsAffected: result.insertedCount };
458
+ return { success: true, affectedRows: result.insertedCount };
459
459
  }
460
460
  const result = await col.insertOne(data, { session: this.session });
461
- return { success: true, rowsAffected: 1, lastInsertId: undefined };
461
+ return { success: true, affectedRows: 1, lastId: undefined };
462
462
  } catch (e) {
463
- return { success: false, rowsAffected: 0, error: (e as Error).message };
463
+ return { success: false, affectedRows: 0, error: (e as Error).message };
464
464
  }
465
465
  }
466
466
 
@@ -473,9 +473,9 @@ export class MongodbAdapter implements DatabaseAdapter {
473
473
  const col = this.db.collection(table);
474
474
  try {
475
475
  const result = await col.updateMany(filter, { $set: data }, { session: this.session });
476
- return { success: true, rowsAffected: result.modifiedCount };
476
+ return { success: true, affectedRows: result.modifiedCount };
477
477
  } catch (e) {
478
- return { success: false, rowsAffected: 0, error: (e as Error).message };
478
+ return { success: false, affectedRows: 0, error: (e as Error).message };
479
479
  }
480
480
  }
481
481
 
@@ -493,7 +493,7 @@ export class MongodbAdapter implements DatabaseAdapter {
493
493
  const r = await col.deleteMany(f, { session: this.session });
494
494
  total += r.deletedCount;
495
495
  }
496
- return { success: true, rowsAffected: total };
496
+ return { success: true, affectedRows: total };
497
497
  }
498
498
 
499
499
  // String WHERE clause — not directly translatable; delete nothing safely
@@ -501,18 +501,18 @@ export class MongodbAdapter implements DatabaseAdapter {
501
501
  if (!filter.trim()) {
502
502
  // Empty filter = delete all documents
503
503
  const r = await col.deleteMany({}, { session: this.session });
504
- return { success: true, rowsAffected: r.deletedCount };
504
+ return { success: true, affectedRows: r.deletedCount };
505
505
  }
506
506
  // Attempt parse via dummy SELECT wrapping
507
507
  const { filter: parsedFilter } = parseWhereClause(filter, []);
508
508
  const r = await col.deleteMany(parsedFilter, { session: this.session });
509
- return { success: true, rowsAffected: r.deletedCount };
509
+ return { success: true, affectedRows: r.deletedCount };
510
510
  }
511
511
 
512
512
  const result = await col.deleteMany(filter as Record<string, unknown>, { session: this.session });
513
- return { success: true, rowsAffected: result.deletedCount };
513
+ return { success: true, affectedRows: result.deletedCount };
514
514
  } catch (e) {
515
- return { success: false, rowsAffected: 0, error: (e as Error).message };
515
+ return { success: false, affectedRows: 0, error: (e as Error).message };
516
516
  }
517
517
  }
518
518
 
@@ -178,11 +178,11 @@ export class MssqlAdapter implements DatabaseAdapter {
178
178
  throw new Error("Use executeAsync() for MSSQL — async adapter requires async methods.");
179
179
  }
180
180
 
181
- executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
181
+ executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
182
182
  throw new Error("Use executeManyAsync() for MSSQL — async adapter requires async methods.");
183
183
  }
184
184
 
185
- async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
185
+ async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastId?: number | bigint }> {
186
186
  // Run the whole batch in ONE transaction so it is atomic (all-or-nothing) —
187
187
  // a bad row mid-batch rolls back the rows already inserted instead of
188
188
  // leaving a partial write. Mirrors the documented "wrapped in a transaction"
@@ -268,7 +268,7 @@ export class MssqlAdapter implements DatabaseAdapter {
268
268
  // tracked for a batch — affectedRows == row count is what callers rely on).
269
269
  // See PostgresAdapter for the array-crash rationale this branch fixes.
270
270
  if (Array.isArray(data)) {
271
- if (data.length === 0) return { success: true, rowsAffected: 0 };
271
+ if (data.length === 0) return { success: true, affectedRows: 0 };
272
272
  const keys = Object.keys(data[0]);
273
273
  // `?` placeholders — executeManyAsync -> executeAsync runs convertPlaceholders,
274
274
  // which rewrites them to @p0, @p1, ... for tedious.
@@ -277,10 +277,10 @@ export class MssqlAdapter implements DatabaseAdapter {
277
277
  const paramsList = data.map((row) => keys.map((k) => row[k]));
278
278
  try {
279
279
  const result = await this.executeManyAsync(sql, paramsList);
280
- if (result.lastInsertId !== undefined) this._lastInsertId = result.lastInsertId;
281
- return { success: true, rowsAffected: result.totalAffected, lastInsertId: result.lastInsertId };
280
+ if (result.lastId !== undefined) this._lastInsertId = result.lastId;
281
+ return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
282
282
  } catch (e) {
283
- return { success: false, rowsAffected: 0, error: (e as Error).message };
283
+ return { success: false, affectedRows: 0, error: (e as Error).message };
284
284
  }
285
285
  }
286
286
 
@@ -298,12 +298,12 @@ export class MssqlAdapter implements DatabaseAdapter {
298
298
  // A single-object insert affects exactly one row. Do NOT use
299
299
  // result.rowCount here: the statement is "INSERT ...; SELECT
300
300
  // SCOPE_IDENTITY()", and tedious sums the row counts of BOTH statements
301
- // (1 for the INSERT + 1 for the SELECT), which reported rowsAffected=2.
302
- rowsAffected: 1,
303
- lastInsertId: id ?? undefined,
301
+ // (1 for the INSERT + 1 for the SELECT), which reported affectedRows=2.
302
+ affectedRows: 1,
303
+ lastId: id ?? undefined,
304
304
  };
305
305
  } catch (e) {
306
- return { success: false, rowsAffected: 0, error: (e as Error).message };
306
+ return { success: false, affectedRows: 0, error: (e as Error).message };
307
307
  }
308
308
  }
309
309
 
@@ -324,9 +324,9 @@ export class MssqlAdapter implements DatabaseAdapter {
324
324
 
325
325
  try {
326
326
  const result = await this.execSqlPromise(sql, values);
327
- return { success: true, rowsAffected: result.rowCount };
327
+ return { success: true, affectedRows: result.rowCount };
328
328
  } catch (e) {
329
- return { success: false, rowsAffected: 0, error: (e as Error).message };
329
+ return { success: false, affectedRows: 0, error: (e as Error).message };
330
330
  }
331
331
  }
332
332
 
@@ -344,9 +344,9 @@ export class MssqlAdapter implements DatabaseAdapter {
344
344
 
345
345
  try {
346
346
  const result = await this.execSqlPromise(sql, values);
347
- return { success: true, rowsAffected: result.rowCount };
347
+ return { success: true, affectedRows: result.rowCount };
348
348
  } catch (e) {
349
- return { success: false, rowsAffected: 0, error: (e as Error).message };
349
+ return { success: false, affectedRows: 0, error: (e as Error).message };
350
350
  }
351
351
  }
352
352
 
@@ -107,11 +107,11 @@ export class MysqlAdapter implements DatabaseAdapter {
107
107
  throw new Error("Use executeAsync() for MySQL — async adapter requires async methods.");
108
108
  }
109
109
 
110
- executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
110
+ executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
111
111
  throw new Error("Use executeManyAsync() for MySQL — async adapter requires async methods.");
112
112
  }
113
113
 
114
- async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
114
+ async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastId?: number | bigint }> {
115
115
  // Run the whole batch in ONE transaction so it is atomic (all-or-nothing) —
116
116
  // a bad row mid-batch rolls back the rows already inserted instead of
117
117
  // leaving a partial write. Mirrors the documented "wrapped in a transaction"
@@ -135,7 +135,7 @@ export class MysqlAdapter implements DatabaseAdapter {
135
135
  }
136
136
  throw e;
137
137
  }
138
- return { totalAffected, lastInsertId: lastId };
138
+ return { totalAffected, lastId: lastId };
139
139
  }
140
140
 
141
141
  async executeAsync(sql: string, params?: unknown[]): Promise<unknown> {
@@ -193,17 +193,17 @@ export class MysqlAdapter implements DatabaseAdapter {
193
193
  // executeManyAsync (ONE connection). See PostgresAdapter for the rationale;
194
194
  // without this branch a list crashed/mis-built SQL via Object.keys() on the array.
195
195
  if (Array.isArray(data)) {
196
- if (data.length === 0) return { success: true, rowsAffected: 0 };
196
+ if (data.length === 0) return { success: true, affectedRows: 0 };
197
197
  const keys = Object.keys(data[0]);
198
198
  const placeholders = keys.map(() => "?").join(", ");
199
199
  const sql = `INSERT INTO \`${table}\` (\`${keys.join("`, `")}\`) VALUES (${placeholders})`;
200
200
  const paramsList = data.map((row) => keys.map((k) => row[k]));
201
201
  try {
202
202
  const result = await this.executeManyAsync(sql, paramsList);
203
- if (result.lastInsertId !== undefined) this._lastInsertId = result.lastInsertId;
204
- return { success: true, rowsAffected: result.totalAffected, lastInsertId: result.lastInsertId };
203
+ if (result.lastId !== undefined) this._lastInsertId = result.lastId;
204
+ return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
205
205
  } catch (e) {
206
- return { success: false, rowsAffected: 0, error: (e as Error).message };
206
+ return { success: false, affectedRows: 0, error: (e as Error).message };
207
207
  }
208
208
  }
209
209
 
@@ -217,11 +217,11 @@ export class MysqlAdapter implements DatabaseAdapter {
217
217
  this._lastInsertId = result.insertId ?? null;
218
218
  return {
219
219
  success: true,
220
- rowsAffected: result.affectedRows ?? 1,
221
- lastInsertId: result.insertId,
220
+ affectedRows: result.affectedRows ?? 1,
221
+ lastId: result.insertId,
222
222
  };
223
223
  } catch (e) {
224
- return { success: false, rowsAffected: 0, error: (e as Error).message };
224
+ return { success: false, affectedRows: 0, error: (e as Error).message };
225
225
  }
226
226
  }
227
227
 
@@ -238,9 +238,9 @@ export class MysqlAdapter implements DatabaseAdapter {
238
238
 
239
239
  try {
240
240
  const result = await this.queryPromise(sql, values);
241
- return { success: true, rowsAffected: result.affectedRows ?? 0 };
241
+ return { success: true, affectedRows: result.affectedRows ?? 0 };
242
242
  } catch (e) {
243
- return { success: false, rowsAffected: 0, error: (e as Error).message };
243
+ return { success: false, affectedRows: 0, error: (e as Error).message };
244
244
  }
245
245
  }
246
246
 
@@ -256,9 +256,9 @@ export class MysqlAdapter implements DatabaseAdapter {
256
256
 
257
257
  try {
258
258
  const result = await this.queryPromise(sql, values);
259
- return { success: true, rowsAffected: result.affectedRows ?? 0 };
259
+ return { success: true, affectedRows: result.affectedRows ?? 0 };
260
260
  } catch (e) {
261
- return { success: false, rowsAffected: 0, error: (e as Error).message };
261
+ return { success: false, affectedRows: 0, error: (e as Error).message };
262
262
  }
263
263
  }
264
264
 
@@ -79,7 +79,7 @@ export class OdbcAdapter implements DatabaseAdapter {
79
79
  throw new Error("Use executeAsync() for ODBC — async adapter requires async methods.");
80
80
  }
81
81
 
82
- executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
82
+ executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
83
83
  throw new Error("Use executeManyAsync() for ODBC — async adapter requires async methods.");
84
84
  }
85
85
 
@@ -152,14 +152,14 @@ export class OdbcAdapter implements DatabaseAdapter {
152
152
  this.ensureConnected();
153
153
  const result = await this.connection.query(sql, params ?? []);
154
154
  // Try to capture last insert id from result metadata if present
155
- if (result && typeof result === "object" && "lastInsertId" in result) {
156
- this._lastInsertId = (result as any).lastInsertId;
155
+ if (result && typeof result === "object" && "lastId" in result) {
156
+ this._lastInsertId = (result as any).lastId;
157
157
  }
158
158
  return result;
159
159
  }
160
160
 
161
161
  /** Execute a statement with multiple parameter sets inside a single transaction. */
162
- async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
162
+ async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastId?: number | bigint }> {
163
163
  this.ensureConnected();
164
164
  let totalAffected = 0;
165
165
  let lastId: number | bigint | undefined;
@@ -177,7 +177,7 @@ export class OdbcAdapter implements DatabaseAdapter {
177
177
  }
178
178
 
179
179
  if (lastId !== undefined) this._lastInsertId = lastId;
180
- return { totalAffected, lastInsertId: lastId };
180
+ return { totalAffected, lastId: lastId };
181
181
  }
182
182
 
183
183
  /** Run a SELECT and return all matching rows. */
@@ -224,9 +224,9 @@ export class OdbcAdapter implements DatabaseAdapter {
224
224
 
225
225
  try {
226
226
  await this.connection.query(sql, values);
227
- return { success: true, rowsAffected: 1, lastInsertId: this._lastInsertId ?? undefined };
227
+ return { success: true, affectedRows: 1, lastId: this._lastInsertId ?? undefined };
228
228
  } catch (e) {
229
- return { success: false, rowsAffected: 0, error: (e as Error).message };
229
+ return { success: false, affectedRows: 0, error: (e as Error).message };
230
230
  }
231
231
  }
232
232
 
@@ -240,9 +240,9 @@ export class OdbcAdapter implements DatabaseAdapter {
240
240
 
241
241
  try {
242
242
  await this.connection.query(sql, values);
243
- return { success: true, rowsAffected: 1 };
243
+ return { success: true, affectedRows: 1 };
244
244
  } catch (e) {
245
- return { success: false, rowsAffected: 0, error: (e as Error).message };
245
+ return { success: false, affectedRows: 0, error: (e as Error).message };
246
246
  }
247
247
  }
248
248
 
@@ -257,9 +257,9 @@ export class OdbcAdapter implements DatabaseAdapter {
257
257
  let totalAffected = 0;
258
258
  for (const row of filter) {
259
259
  const result = await this.deleteAsync(table, row);
260
- totalAffected += result.rowsAffected;
260
+ totalAffected += result.affectedRows;
261
261
  }
262
- return { success: true, rowsAffected: totalAffected };
262
+ return { success: true, affectedRows: totalAffected };
263
263
  }
264
264
 
265
265
  if (typeof filter === "string") {
@@ -268,9 +268,9 @@ export class OdbcAdapter implements DatabaseAdapter {
268
268
  : `DELETE FROM "${table}"`;
269
269
  try {
270
270
  await this.connection.query(sql, []);
271
- return { success: true, rowsAffected: 1 };
271
+ return { success: true, affectedRows: 1 };
272
272
  } catch (e) {
273
- return { success: false, rowsAffected: 0, error: (e as Error).message };
273
+ return { success: false, affectedRows: 0, error: (e as Error).message };
274
274
  }
275
275
  }
276
276
 
@@ -280,9 +280,9 @@ export class OdbcAdapter implements DatabaseAdapter {
280
280
 
281
281
  try {
282
282
  await this.connection.query(sql, values);
283
- return { success: true, rowsAffected: 1 };
283
+ return { success: true, affectedRows: 1 };
284
284
  } catch (e) {
285
- return { success: false, rowsAffected: 0, error: (e as Error).message };
285
+ return { success: false, affectedRows: 0, error: (e as Error).message };
286
286
  }
287
287
  }
288
288
 
@@ -122,7 +122,7 @@ export class PostgresAdapter implements DatabaseAdapter {
122
122
 
123
123
  /**
124
124
  * Normalise an `id` column value (typed `unknown` because pg row values are
125
- * `unknown`) into the shape `_lastInsertId` / `DatabaseResult.lastInsertId`
125
+ * `unknown`) into the shape `_lastInsertId` / `DatabaseResult.lastId`
126
126
  * expect. At runtime PG returns numeric PKs as number/bigint (the int8/numeric
127
127
  * type parsers above coerce them to Number); a numeric string is coerced to a
128
128
  * number so the SERIAL path always returns the integer id.
@@ -148,12 +148,12 @@ export class PostgresAdapter implements DatabaseAdapter {
148
148
  throw new Error("Use executeAsync() for PostgreSQL — async adapter requires async methods.");
149
149
  }
150
150
 
151
- executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
151
+ executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
152
152
  throw new Error("Use executeManyAsync() for PostgreSQL — async adapter requires async methods.");
153
153
  }
154
154
 
155
155
  /** Async executeMany for real usage. */
156
- async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
156
+ async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastId?: number | bigint }> {
157
157
  // Run the whole batch in ONE transaction so it is atomic (all-or-nothing) —
158
158
  // a bad row mid-batch rolls back the rows already inserted instead of
159
159
  // leaving a partial write. Mirrors the documented "wrapped in a transaction"
@@ -168,8 +168,8 @@ export class PostgresAdapter implements DatabaseAdapter {
168
168
  for (const params of paramsList) {
169
169
  const result = await this.executeAsync(sql, params);
170
170
  totalAffected++;
171
- if (result && typeof result === "object" && "lastInsertId" in (result as any)) {
172
- lastId = (result as any).lastInsertId;
171
+ if (result && typeof result === "object" && "lastId" in (result as any)) {
172
+ lastId = (result as any).lastId;
173
173
  }
174
174
  }
175
175
  if (owns) await this.commitAsync();
@@ -179,7 +179,7 @@ export class PostgresAdapter implements DatabaseAdapter {
179
179
  }
180
180
  throw e;
181
181
  }
182
- return { totalAffected, lastInsertId: lastId };
182
+ return { totalAffected, lastId: lastId };
183
183
  }
184
184
 
185
185
  /** Async execute for real usage. */
@@ -242,17 +242,17 @@ export class PostgresAdapter implements DatabaseAdapter {
242
242
  // — producing garbage SQL (mirrors the Python `'list' has no attribute keys`
243
243
  // crash this fix addresses).
244
244
  if (Array.isArray(data)) {
245
- if (data.length === 0) return { success: true, rowsAffected: 0 };
245
+ if (data.length === 0) return { success: true, affectedRows: 0 };
246
246
  const keys = Object.keys(data[0]);
247
247
  const placeholders = keys.map(() => "?").join(", ");
248
248
  const sql = `INSERT INTO "${table}" ("${keys.join('", "')}") VALUES (${placeholders})`;
249
249
  const paramsList = data.map((row) => keys.map((k) => row[k]));
250
250
  try {
251
251
  const result = await this.executeManyAsync(sql, paramsList);
252
- if (result.lastInsertId !== undefined) this._lastInsertId = result.lastInsertId;
253
- return { success: true, rowsAffected: result.totalAffected, lastInsertId: result.lastInsertId };
252
+ if (result.lastId !== undefined) this._lastInsertId = result.lastId;
253
+ return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
254
254
  } catch (e) {
255
- return { success: false, rowsAffected: 0, error: (e as Error).message };
255
+ return { success: false, affectedRows: 0, error: (e as Error).message };
256
256
  }
257
257
  }
258
258
 
@@ -268,11 +268,11 @@ export class PostgresAdapter implements DatabaseAdapter {
268
268
  if (id !== null) this._lastInsertId = id;
269
269
  return {
270
270
  success: true,
271
- rowsAffected: result.rowCount ?? 1,
272
- lastInsertId: id ?? undefined,
271
+ affectedRows: result.rowCount ?? 1,
272
+ lastId: id ?? undefined,
273
273
  };
274
274
  } catch (e) {
275
- return { success: false, rowsAffected: 0, error: (e as Error).message };
275
+ return { success: false, affectedRows: 0, error: (e as Error).message };
276
276
  }
277
277
  }
278
278
 
@@ -293,9 +293,9 @@ export class PostgresAdapter implements DatabaseAdapter {
293
293
 
294
294
  try {
295
295
  const result = await this.client!.query(sql, values);
296
- return { success: true, rowsAffected: result.rowCount ?? 0 };
296
+ return { success: true, affectedRows: result.rowCount ?? 0 };
297
297
  } catch (e) {
298
- return { success: false, rowsAffected: 0, error: (e as Error).message };
298
+ return { success: false, affectedRows: 0, error: (e as Error).message };
299
299
  }
300
300
  }
301
301
 
@@ -313,9 +313,9 @@ export class PostgresAdapter implements DatabaseAdapter {
313
313
 
314
314
  try {
315
315
  const result = await this.client!.query(sql, values);
316
- return { success: true, rowsAffected: result.rowCount ?? 0 };
316
+ return { success: true, affectedRows: result.rowCount ?? 0 };
317
317
  } catch (e) {
318
- return { success: false, rowsAffected: 0, error: (e as Error).message };
318
+ return { success: false, affectedRows: 0, error: (e as Error).message };
319
319
  }
320
320
  }
321
321
 
@@ -325,6 +325,12 @@ export class PostgresAdapter implements DatabaseAdapter {
325
325
 
326
326
  async startTransactionAsync(): Promise<void> {
327
327
  await this.executeAsync("BEGIN");
328
+ // Mark the connection as inside an explicit transaction so executeManyAsync's
329
+ // owns-guard (owns = !_inTransaction) joins THIS transaction instead of
330
+ // opening its own inner BEGIN/COMMIT (which would commit this outer
331
+ // transaction early and defeat a later rollback). Mirrors the Python master
332
+ // (tina4_python/database/postgres.py start_transaction -> _in_transaction = True).
333
+ this._inTransaction = true;
328
334
  }
329
335
 
330
336
  commit(): void {
@@ -333,6 +339,9 @@ export class PostgresAdapter implements DatabaseAdapter {
333
339
 
334
340
  async commitAsync(): Promise<void> {
335
341
  await this.executeAsync("COMMIT");
342
+ // Transaction closed — clear the flag so subsequent standalone batches own
343
+ // their own transaction again (parity with Python master commit()).
344
+ this._inTransaction = false;
336
345
  }
337
346
 
338
347
  rollback(): void {
@@ -341,6 +350,8 @@ export class PostgresAdapter implements DatabaseAdapter {
341
350
 
342
351
  async rollbackAsync(): Promise<void> {
343
352
  await this.executeAsync("ROLLBACK");
353
+ // Transaction closed — clear the flag (parity with Python master rollback()).
354
+ this._inTransaction = false;
344
355
  }
345
356
 
346
357
  tables(): string[] {