tina4-nodejs 3.13.85 → 3.13.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +37 -31
- package/package.json +22 -5
- package/packages/cli/dist/bin.js +97 -91
- package/packages/core/dist/index.js +97 -91
- package/packages/core/public/js/tina4-dev-admin.min.js +90 -87
- package/packages/core/src/session.ts +2 -2
- package/packages/core/src/sessionHandlers/databaseHandler.ts +5 -6
- package/packages/frond/dist/index.js +11 -5
- package/packages/frond/src/engine.ts +27 -5
- package/packages/orm/dist/index.js +97 -91
- package/packages/orm/src/adapters/firebird.ts +11 -11
- package/packages/orm/src/adapters/mongodb.ts +13 -13
- package/packages/orm/src/adapters/mssql.ts +14 -14
- package/packages/orm/src/adapters/mysql.ts +14 -14
- package/packages/orm/src/adapters/odbc.ts +15 -15
- package/packages/orm/src/adapters/postgres.ts +17 -17
- package/packages/orm/src/adapters/sqlite.ts +14 -14
- package/packages/orm/src/autoCrud.ts +1 -1
- package/packages/orm/src/cachedDatabase.ts +1 -1
- package/packages/orm/src/database.ts +2 -2
- package/packages/orm/src/types.ts +4 -4
|
@@ -370,11 +370,11 @@ export class MongodbAdapter implements DatabaseAdapter {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number;
|
|
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;
|
|
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,
|
|
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,
|
|
458
|
+
return { success: true, affectedRows: result.insertedCount };
|
|
459
459
|
}
|
|
460
460
|
const result = await col.insertOne(data, { session: this.session });
|
|
461
|
-
return { success: true,
|
|
461
|
+
return { success: true, affectedRows: 1, lastId: undefined };
|
|
462
462
|
} catch (e) {
|
|
463
|
-
return { success: false,
|
|
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,
|
|
476
|
+
return { success: true, affectedRows: result.modifiedCount };
|
|
477
477
|
} catch (e) {
|
|
478
|
-
return { success: false,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
513
|
+
return { success: true, affectedRows: result.deletedCount };
|
|
514
514
|
} catch (e) {
|
|
515
|
-
return { success: false,
|
|
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;
|
|
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;
|
|
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,
|
|
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.
|
|
281
|
-
return { success: true,
|
|
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,
|
|
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
|
|
302
|
-
|
|
303
|
-
|
|
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,
|
|
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,
|
|
327
|
+
return { success: true, affectedRows: result.rowCount };
|
|
328
328
|
} catch (e) {
|
|
329
|
-
return { success: false,
|
|
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,
|
|
347
|
+
return { success: true, affectedRows: result.rowCount };
|
|
348
348
|
} catch (e) {
|
|
349
|
-
return { success: false,
|
|
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;
|
|
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;
|
|
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,
|
|
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,
|
|
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.
|
|
204
|
-
return { success: true,
|
|
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,
|
|
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
|
-
|
|
221
|
-
|
|
220
|
+
affectedRows: result.affectedRows ?? 1,
|
|
221
|
+
lastId: result.insertId,
|
|
222
222
|
};
|
|
223
223
|
} catch (e) {
|
|
224
|
-
return { success: false,
|
|
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,
|
|
241
|
+
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
242
242
|
} catch (e) {
|
|
243
|
-
return { success: false,
|
|
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,
|
|
259
|
+
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
260
260
|
} catch (e) {
|
|
261
|
-
return { success: false,
|
|
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;
|
|
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" && "
|
|
156
|
-
this._lastInsertId = (result as any).
|
|
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;
|
|
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,
|
|
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,
|
|
227
|
+
return { success: true, affectedRows: 1, lastId: this._lastInsertId ?? undefined };
|
|
228
228
|
} catch (e) {
|
|
229
|
-
return { success: false,
|
|
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,
|
|
243
|
+
return { success: true, affectedRows: 1 };
|
|
244
244
|
} catch (e) {
|
|
245
|
-
return { success: false,
|
|
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.
|
|
260
|
+
totalAffected += result.affectedRows;
|
|
261
261
|
}
|
|
262
|
-
return { success: true,
|
|
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,
|
|
271
|
+
return { success: true, affectedRows: 1 };
|
|
272
272
|
} catch (e) {
|
|
273
|
-
return { success: false,
|
|
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,
|
|
283
|
+
return { success: true, affectedRows: 1 };
|
|
284
284
|
} catch (e) {
|
|
285
|
-
return { success: false,
|
|
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.
|
|
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;
|
|
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;
|
|
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" && "
|
|
172
|
-
lastId = (result as any).
|
|
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,
|
|
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,
|
|
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.
|
|
253
|
-
return { success: true,
|
|
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,
|
|
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
|
-
|
|
272
|
-
|
|
271
|
+
affectedRows: result.rowCount ?? 1,
|
|
272
|
+
lastId: id ?? undefined,
|
|
273
273
|
};
|
|
274
274
|
} catch (e) {
|
|
275
|
-
return { success: false,
|
|
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,
|
|
296
|
+
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
297
297
|
} catch (e) {
|
|
298
|
-
return { success: false,
|
|
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,
|
|
316
|
+
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
317
317
|
} catch (e) {
|
|
318
|
-
return { success: false,
|
|
318
|
+
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
@@ -115,7 +115,7 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number;
|
|
118
|
+
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
|
|
119
119
|
const stmt = this.db.prepare(sql);
|
|
120
120
|
let totalAffected = 0;
|
|
121
121
|
let lastId: number | bigint | undefined;
|
|
@@ -136,7 +136,7 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
136
136
|
throw e;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
return { totalAffected,
|
|
139
|
+
return { totalAffected, lastId: lastId };
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[] {
|
|
@@ -167,13 +167,13 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
167
167
|
|
|
168
168
|
insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult {
|
|
169
169
|
if (Array.isArray(data)) {
|
|
170
|
-
if (data.length === 0) return { success: true,
|
|
170
|
+
if (data.length === 0) return { success: true, affectedRows: 0 };
|
|
171
171
|
const keys = Object.keys(data[0]);
|
|
172
172
|
const placeholders = keys.map(() => "?").join(", ");
|
|
173
173
|
const sql = `INSERT INTO "${table}" ("${keys.join('", "')}") VALUES (${placeholders})`;
|
|
174
174
|
const paramsList = data.map((row) => keys.map((k) => row[k]));
|
|
175
175
|
const result = this.executeMany(sql, paramsList);
|
|
176
|
-
return { success: true,
|
|
176
|
+
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const keys = Object.keys(data);
|
|
@@ -184,9 +184,9 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
184
184
|
try {
|
|
185
185
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
186
186
|
this._lastInsertId = result.lastInsertRowid;
|
|
187
|
-
return { success: true,
|
|
187
|
+
return { success: true, affectedRows: Number(result.changes), lastId: result.lastInsertRowid };
|
|
188
188
|
} catch (e) {
|
|
189
|
-
return { success: false,
|
|
189
|
+
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -198,9 +198,9 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
198
198
|
|
|
199
199
|
try {
|
|
200
200
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
201
|
-
return { success: true,
|
|
201
|
+
return { success: true, affectedRows: Number(result.changes) };
|
|
202
202
|
} catch (e) {
|
|
203
|
-
return { success: false,
|
|
203
|
+
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
@@ -209,18 +209,18 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
209
209
|
let totalAffected = 0;
|
|
210
210
|
for (const row of filter) {
|
|
211
211
|
const result = this.delete(table, row);
|
|
212
|
-
totalAffected += result.
|
|
212
|
+
totalAffected += result.affectedRows;
|
|
213
213
|
}
|
|
214
|
-
return { success: true,
|
|
214
|
+
return { success: true, affectedRows: totalAffected };
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
if (typeof filter === "string") {
|
|
218
218
|
const sql = filter ? `DELETE FROM "${table}" WHERE ${filter}` : `DELETE FROM "${table}"`;
|
|
219
219
|
try {
|
|
220
220
|
const result = this.db.prepare(sql).run(...toSqlParams(params ?? []));
|
|
221
|
-
return { success: true,
|
|
221
|
+
return { success: true, affectedRows: Number(result.changes) };
|
|
222
222
|
} catch (e) {
|
|
223
|
-
return { success: false,
|
|
223
|
+
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -230,9 +230,9 @@ export class SQLiteAdapter implements DatabaseAdapter {
|
|
|
230
230
|
|
|
231
231
|
try {
|
|
232
232
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
233
|
-
return { success: true,
|
|
233
|
+
return { success: true, affectedRows: Number(result.changes) };
|
|
234
234
|
} catch (e) {
|
|
235
|
-
return { success: false,
|
|
235
|
+
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -233,7 +233,7 @@ export function generateCrudRoutes(models: DiscoveredModel[], options: AutoCrudO
|
|
|
233
233
|
|
|
234
234
|
// Non-SQLite engines can't read a plain INSERT's auto-id back via
|
|
235
235
|
// lastInsertId(); RETURNING the PK column lets us recover it. SQLite
|
|
236
|
-
// tolerates RETURNING but we still prefer its
|
|
236
|
+
// tolerates RETURNING but we still prefer its lastId below.
|
|
237
237
|
const isSqlite = adapter.constructor.name === "SQLiteAdapter";
|
|
238
238
|
const insertSql =
|
|
239
239
|
`INSERT INTO "${tableName}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})` +
|
|
@@ -301,7 +301,7 @@ export class CachedDatabaseAdapter implements DatabaseAdapter {
|
|
|
301
301
|
return this.adapter.execute(sql, params);
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number;
|
|
304
|
+
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint } {
|
|
305
305
|
if (this.enabled) this.invalidate();
|
|
306
306
|
return this.adapter.executeMany(sql, paramsList);
|
|
307
307
|
}
|
|
@@ -117,7 +117,7 @@ export async function adapterCreateTable(
|
|
|
117
117
|
*
|
|
118
118
|
* SQLite returns `{ lastInsertRowid }`. PostgreSQL (pg) returns a result whose
|
|
119
119
|
* `rows[0].id` holds the value when the statement had a `RETURNING` clause
|
|
120
|
-
* (insertAsync adds one). MySQL/MSSQL adapters set the adapter's
|
|
120
|
+
* (insertAsync adds one). MySQL/MSSQL adapters set the adapter's lastId,
|
|
121
121
|
* so callers fall back to `adapter.lastInsertId()` when the result has neither.
|
|
122
122
|
*/
|
|
123
123
|
export function extractLastInsertId(result: unknown): number | bigint | null {
|
|
@@ -125,7 +125,7 @@ export function extractLastInsertId(result: unknown): number | bigint | null {
|
|
|
125
125
|
const r = result as any;
|
|
126
126
|
if (r.lastInsertRowid !== undefined && r.lastInsertRowid !== null) return r.lastInsertRowid;
|
|
127
127
|
if (r.rows?.[0]?.id !== undefined && r.rows[0].id !== null) return r.rows[0].id;
|
|
128
|
-
if (r.
|
|
128
|
+
if (r.lastId !== undefined && r.lastId !== null) return r.lastId;
|
|
129
129
|
}
|
|
130
130
|
return null;
|
|
131
131
|
}
|
|
@@ -51,10 +51,10 @@ export interface ColumnInfo {
|
|
|
51
51
|
|
|
52
52
|
export interface DatabaseResult {
|
|
53
53
|
success: boolean;
|
|
54
|
-
|
|
54
|
+
affectedRows: number;
|
|
55
55
|
// string covers a non-integer primary key (e.g. a PostgreSQL UUID PK returns
|
|
56
56
|
// its id as a string via RETURNING, not a SERIAL integer) — #256.
|
|
57
|
-
|
|
57
|
+
lastId?: number | bigint | string;
|
|
58
58
|
error?: string;
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -63,7 +63,7 @@ export interface DatabaseAdapter {
|
|
|
63
63
|
execute(sql: string, params?: unknown[]): unknown;
|
|
64
64
|
|
|
65
65
|
/** Execute a single SQL statement with multiple parameter sets (batch). */
|
|
66
|
-
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number;
|
|
66
|
+
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastId?: number | bigint };
|
|
67
67
|
|
|
68
68
|
/** Query rows. */
|
|
69
69
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
@@ -74,7 +74,7 @@ export interface DatabaseAdapter {
|
|
|
74
74
|
/** Fetch a single row or null. */
|
|
75
75
|
fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
|
|
76
76
|
|
|
77
|
-
/** Insert one or more rows into a table, returns result with
|
|
77
|
+
/** Insert one or more rows into a table, returns result with lastId. */
|
|
78
78
|
insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
|
|
79
79
|
|
|
80
80
|
/** Update rows in a table matching filter, returns affected row count. */
|