tina4-nodejs 3.10.12 → 3.10.14

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 CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.12)
1
+ # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.14)
2
2
 
3
3
  > This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
4
4
 
5
5
  ## What This Project Is
6
6
 
7
- Tina4 for Node.js/TypeScript v3.10.12 — a convention-over-configuration structural paradigm. **Not a framework.** The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.10.14 — a convention-over-configuration structural paradigm. **Not a framework.** The developer writes TypeScript; Tina4 is invisible infrastructure.
8
8
 
9
9
  The philosophy: zero ceremony, batteries included, file system as source of truth.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.12",
3
+ "version": "3.10.14",
4
4
  "type": "module",
5
5
  "description": "This is not a framework. Tina4 for Node.js/TypeScript — zero deps, 38 built-in features.",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -266,6 +266,7 @@ export class BaseModel {
266
266
  this[pk] = result.lastInsertRowid;
267
267
  }
268
268
  }
269
+ db.commit();
269
270
  }
270
271
 
271
272
  /**
@@ -294,6 +295,7 @@ export class BaseModel {
294
295
  [pkValue],
295
296
  );
296
297
  }
298
+ db.commit();
297
299
  }
298
300
 
299
301
  /**
@@ -459,6 +461,7 @@ export class BaseModel {
459
461
  const sql = `CREATE TABLE IF NOT EXISTS "${this.tableName}" (${colDefs.join(", ")})`;
460
462
  db.execute(sql);
461
463
  }
464
+ db.commit();
462
465
  }
463
466
 
464
467
  /**
@@ -505,6 +508,7 @@ export class BaseModel {
505
508
  `DELETE FROM "${ModelClass.tableName}" WHERE "${pkCol}" = ?`,
506
509
  [pkValue],
507
510
  );
511
+ db.commit();
508
512
  }
509
513
 
510
514
  /**
@@ -529,6 +533,7 @@ export class BaseModel {
529
533
  `UPDATE "${ModelClass.tableName}" SET is_deleted = 0 WHERE "${pkCol}" = ?`,
530
534
  [pkValue],
531
535
  );
536
+ db.commit();
532
537
  this.is_deleted = 0;
533
538
  }
534
539
 
@@ -208,6 +208,9 @@ export class Database {
208
208
  /** Whether to automatically commit after each write operation */
209
209
  private autoCommit: boolean = process.env.TINA4_AUTOCOMMIT === "true";
210
210
 
211
+ /** Database engine type (sqlite, postgres, mysql, mssql, firebird) */
212
+ private dbType: string = "sqlite";
213
+
211
214
  /**
212
215
  * Create a Database wrapping an existing adapter.
213
216
  * For creating a Database from a URL, use the async static factories:
@@ -227,6 +230,8 @@ export class Database {
227
230
  * @param pool - Number of pooled connections (0 = single, N>0 = round-robin)
228
231
  */
229
232
  static async create(url: string, username?: string, password?: string, pool: number = 0): Promise<Database> {
233
+ const parsed = parseDatabaseUrl(url, username, password);
234
+
230
235
  if (pool > 0) {
231
236
  // Pooled mode — create all adapters eagerly
232
237
  const adapters: DatabaseAdapter[] = [];
@@ -243,13 +248,16 @@ export class Database {
243
248
  db.poolIndex = 0;
244
249
  db.adapter = null; // Don't use single-adapter path
245
250
  db.adapterFactory = () => createAdapterFromUrl(url, username, password);
251
+ db.dbType = parsed.type;
246
252
  return db;
247
253
  }
248
254
 
249
255
  // Single-connection mode — current behavior
250
256
  const adapter = await createAdapterFromUrl(url, username, password);
251
257
  setAdapter(adapter);
252
- return new Database(adapter);
258
+ const db = new Database(adapter);
259
+ db.dbType = parsed.type;
260
+ return db;
253
261
  }
254
262
 
255
263
  /**
@@ -391,6 +399,55 @@ export class Database {
391
399
  if (id === null) return 0;
392
400
  return typeof id === "bigint" ? id.toString() : id;
393
401
  }
402
+
403
+ /**
404
+ * Pre-generate the next available primary key ID using engine-aware strategies.
405
+ *
406
+ * - Firebird: auto-creates a generator if missing, then increments via GEN_ID.
407
+ * - PostgreSQL: tries nextval() on the standard sequence, falls through to MAX+1.
408
+ * - SQLite/MySQL/MSSQL: uses MAX(pk) + 1.
409
+ * - Returns 1 if the table is empty or does not exist.
410
+ */
411
+ getNextId(table: string, pkColumn = "id", generatorName?: string): number {
412
+ const adapter = this.getNextAdapter();
413
+
414
+ // Firebird — use generators
415
+ if (this.dbType === "firebird") {
416
+ const genName = generatorName ?? `GEN_${table.toUpperCase()}_ID`;
417
+
418
+ // Auto-create the generator if it does not exist
419
+ try {
420
+ adapter.execute(`CREATE GENERATOR ${genName}`);
421
+ } catch {
422
+ // Generator already exists — ignore
423
+ }
424
+
425
+ const row = adapter.fetchOne<Record<string, unknown>>(`SELECT GEN_ID(${genName}, 1) AS NEXT_ID FROM RDB$DATABASE`);
426
+ return Number(row?.NEXT_ID ?? row?.next_id ?? 1);
427
+ }
428
+
429
+ // PostgreSQL — try sequence first, fall through to MAX
430
+ if (this.dbType === "postgres") {
431
+ const seqName = `${table.toLowerCase()}_${pkColumn.toLowerCase()}_seq`;
432
+ try {
433
+ const row = adapter.fetchOne<Record<string, unknown>>(`SELECT nextval('${seqName}') AS next_id`);
434
+ if (row?.next_id != null) {
435
+ return Number(row.next_id);
436
+ }
437
+ } catch {
438
+ // No sequence — fall through to MAX
439
+ }
440
+ }
441
+
442
+ // SQLite / MySQL / MSSQL / PostgreSQL fallback — MAX + 1
443
+ try {
444
+ const row = adapter.fetchOne<Record<string, unknown>>(`SELECT MAX(${pkColumn}) + 1 AS next_id FROM ${table}`);
445
+ const nextId = row?.next_id;
446
+ return nextId != null ? Number(nextId) : 1;
447
+ } catch {
448
+ return 1;
449
+ }
450
+ }
394
451
  }
395
452
 
396
453
  /**