tina4-nodejs 3.13.9 → 3.13.12
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 +2 -2
- package/package.json +1 -1
- package/packages/orm/src/baseModel.ts +32 -2
- package/packages/orm/src/database.ts +25 -0
- package/packages/orm/src/index.ts +1 -1
package/CLAUDE.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.
|
|
1
|
+
# CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.12)
|
|
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.13.
|
|
7
|
+
Tina4 for Node.js/TypeScript v3.13.12 — The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. 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
|
@@ -481,11 +481,36 @@ export class BaseModel {
|
|
|
481
481
|
const pk = ModelClass.getPkField();
|
|
482
482
|
const pkCol = ModelClass.getPkColumn();
|
|
483
483
|
const pkValue = this[pk];
|
|
484
|
+
const pkField = (ModelClass.fields as Record<string, FieldDefinition>)[pk];
|
|
484
485
|
this._relCache = {}; // Clear relationship cache on save
|
|
485
486
|
|
|
487
|
+
// v3.13.11 (issue #50.2): for non-auto-increment PKs (user-supplied
|
|
488
|
+
// string IDs like "GC-100"), decide INSERT vs UPDATE on row
|
|
489
|
+
// existence, not on whether the PK is set. Pre-v3.13.11 a
|
|
490
|
+
// natural-key save() always chose UPDATE → matched zero rows →
|
|
491
|
+
// silently returned success without inserting anything.
|
|
492
|
+
//
|
|
493
|
+
// Auto-increment behaviour is unchanged: pkValue is null/undefined
|
|
494
|
+
// → INSERT, pkValue is set → UPDATE.
|
|
495
|
+
let isUpdate = false;
|
|
496
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
497
|
+
if (pkField?.autoIncrement) {
|
|
498
|
+
isUpdate = true;
|
|
499
|
+
} else {
|
|
500
|
+
try {
|
|
501
|
+
isUpdate = ModelClass.exists(pkValue);
|
|
502
|
+
} catch {
|
|
503
|
+
// If we can't tell (e.g. table doesn't exist yet), fall back
|
|
504
|
+
// to INSERT so the user sees the real driver error rather
|
|
505
|
+
// than a silent no-op.
|
|
506
|
+
isUpdate = false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
486
511
|
db.startTransaction();
|
|
487
512
|
try {
|
|
488
|
-
if (
|
|
513
|
+
if (isUpdate) {
|
|
489
514
|
// Update
|
|
490
515
|
const updateFields = Object.entries(ModelClass.fields).filter(
|
|
491
516
|
([name, def]) => !def.primaryKey && this[name] !== undefined,
|
|
@@ -511,7 +536,12 @@ export class BaseModel {
|
|
|
511
536
|
values,
|
|
512
537
|
) as { lastInsertRowid?: number };
|
|
513
538
|
|
|
514
|
-
|
|
539
|
+
// v3.13.11 (issue #50.2): only adopt the engine-assigned ID
|
|
540
|
+
// for auto-increment PKs. A natural-key PK was already set by
|
|
541
|
+
// the caller; don't overwrite it with the driver's last_id
|
|
542
|
+
// (which on PG would be a sequence value that doesn't apply
|
|
543
|
+
// to this row).
|
|
544
|
+
if (result.lastInsertRowid && pkField?.autoIncrement) {
|
|
515
545
|
this[pk] = result.lastInsertRowid;
|
|
516
546
|
}
|
|
517
547
|
}
|
|
@@ -2,6 +2,26 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import type { DatabaseAdapter, DatabaseResult as DatabaseWriteResult } from "./types.js";
|
|
3
3
|
import { DatabaseResult } from "./databaseResult.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* v3.13.12 — strip trailing `;` and whitespace from user-supplied SQL
|
|
7
|
+
* before the framework wraps it with COUNT(*) subqueries or appends
|
|
8
|
+
* LIMIT/OFFSET clauses. Without this, `"SELECT * FROM t;"` becomes
|
|
9
|
+
* `"SELECT * FROM t; LIMIT 100 OFFSET 0"` — a syntax error on every
|
|
10
|
+
* engine. Internal semicolons (in string literals, between meaningful
|
|
11
|
+
* statements) are left alone; drivers reject those if the engine
|
|
12
|
+
* doesn't support multi-statement.
|
|
13
|
+
*
|
|
14
|
+
* Exported so adapters and external tooling can compose it.
|
|
15
|
+
*/
|
|
16
|
+
export function stripTrailingSemicolons(sql: string): string {
|
|
17
|
+
if (!sql) return sql;
|
|
18
|
+
let stripped = sql.replace(/\s+$/, "");
|
|
19
|
+
while (stripped.endsWith(";")) {
|
|
20
|
+
stripped = stripped.slice(0, -1).replace(/\s+$/, "");
|
|
21
|
+
}
|
|
22
|
+
return stripped;
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
let activeAdapter: DatabaseAdapter | null = null;
|
|
6
26
|
const namedAdapters: Map<string, DatabaseAdapter> = new Map();
|
|
7
27
|
|
|
@@ -404,6 +424,10 @@ export class Database {
|
|
|
404
424
|
|
|
405
425
|
/** Query rows with optional pagination. Returns a DatabaseResult wrapper. */
|
|
406
426
|
fetch(sql: string, params?: unknown[], limit?: number, offset?: number): DatabaseResult {
|
|
427
|
+
// v3.13.12: strip trailing `;` before the adapter wraps with COUNT(*)
|
|
428
|
+
// or appends LIMIT/OFFSET. Without this, `"SELECT * FROM t;"` becomes
|
|
429
|
+
// `"SELECT * FROM t; LIMIT 100 OFFSET 0"` — a syntax error.
|
|
430
|
+
sql = stripTrailingSemicolons(sql);
|
|
407
431
|
const adapter = this.getNextAdapter();
|
|
408
432
|
const rows = adapter.fetch<Record<string, unknown>>(sql, params, limit, offset);
|
|
409
433
|
return new DatabaseResult(rows, undefined, undefined, limit, offset, adapter, sql);
|
|
@@ -411,6 +435,7 @@ export class Database {
|
|
|
411
435
|
|
|
412
436
|
/** Fetch a single row or null. */
|
|
413
437
|
fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null {
|
|
438
|
+
sql = stripTrailingSemicolons(sql);
|
|
414
439
|
return this.getNextAdapter().fetchOne<T>(sql, params);
|
|
415
440
|
}
|
|
416
441
|
|
|
@@ -14,7 +14,7 @@ export { FetchResult } from "./types.js";
|
|
|
14
14
|
|
|
15
15
|
export { DatabaseResult } from "./databaseResult.js";
|
|
16
16
|
export type { ColumnInfoResult } from "./databaseResult.js";
|
|
17
|
-
export { Database, initDatabase, getAdapter, setAdapter, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool } from "./database.js";
|
|
17
|
+
export { Database, initDatabase, getAdapter, setAdapter, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons } from "./database.js";
|
|
18
18
|
export type { DatabaseConfig, ParsedDatabaseUrl } from "./database.js";
|
|
19
19
|
export { discoverModels } from "./model.js";
|
|
20
20
|
export type { DiscoveredModel } from "./model.js";
|