tina4-nodejs 3.13.14 → 3.13.16

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.13.14)
1
+ # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.16)
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.14 — The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.13.16 — 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
 
@@ -53,7 +53,7 @@ tina4-nodejs/
53
53
  seeder.ts # Database seeding (seedTable, seedOrm)
54
54
  sqlTranslation.ts # Cross-engine SQL translator + query cache
55
55
  swagger/ # OpenAPI spec generator, Swagger UI
56
- twig/ # Optional Twig template engine
56
+ frond/ # Zero-dependency Twig-compatible template engine
57
57
  test/
58
58
  run-all.ts # Test runner — executes all 43 test files
59
59
  integration.ts # Full integration test
@@ -70,7 +70,7 @@ This is an **npm workspaces monorepo**. All packages are in `packages/*`.
70
70
  - **Runtime:** Node.js 20+ (ESM only, `"type": "module"` everywhere)
71
71
  - **HTTP:** Native `node:http` — no Express, no Fastify
72
72
  - **Database:** SQLite via `node:sqlite` (default), with adapters for Postgres, MySQL, MSSQL/SQL Server, and Firebird
73
- - **Templates:** Twig via `twig` npm package (optional)
73
+ - **Templates:** Frond built-in zero-dependency Twig-compatible engine (`@tina4/frond`)
74
74
  - **Dev tooling:** `tsx` for runtime TS execution, `esbuild` for builds
75
75
  - **Testing:** 43 test files via `tsx test/run-all.ts`
76
76
 
@@ -110,10 +110,10 @@ The HTTP foundation. Handles request/response lifecycle, route matching, middlew
110
110
  - `static.ts` — Serves files from `public/` with MIME type detection
111
111
  - `types.ts` — All shared type definitions (`Tina4Request`, `Tina4Response`, `RouteHandler`, etc.)
112
112
  - `events.ts` — Observer-pattern event system (`Events.on`, `emit`, `once`, `off`, `clear`)
113
- - `ai.ts` — AI coding tool detection and context scaffolding (`detectAi`, `installAiContext`, `aiStatusReport`)
113
+ - `ai.ts` — AI coding tool context installer (`AI_TOOLS`, `isInstalled`, `showMenu`, `installSelected`, `installAll`, `generateContext`)
114
114
  - `errorOverlay.ts` — Rich debug error page for dev mode (`renderErrorOverlay`, `renderProductionError`, `isDebugMode`)
115
115
  - `htmlElement.ts` — Programmatic HTML builder (`HtmlElement`, `htmlElement`, `addHtmlHelpers`)
116
- - `testing.ts` — Inline testing framework (`tests`, `assertEqual`, `assertThrows`, `runAllTests`)
116
+ - `testing.ts` — Inline testing framework (`tests`, `assertEqual`, `assertRaises`, `runAll`)
117
117
  - `fakeData.ts` — Core fake data generator (names, emails, addresses, UUIDs, etc.)
118
118
  - `constants.ts` — HTTP status codes (`HTTP_OK`, `HTTP_NOT_FOUND`, etc.) and content types (`APPLICATION_JSON`, `TEXT_HTML`, etc.)
119
119
  - `devAdmin.ts` — Dev toolbar (fixed bottom bar injected into HTML pages) and admin dashboard at `/_dev/`
@@ -152,7 +152,7 @@ Database layer with auto-CRUD generation, seeding, fake data, and SQL translatio
152
152
  - `fakeData.ts` — ORM-aware fake data extending core (adds `forField()` with column-name heuristics)
153
153
  - `seeder.ts` — Database seeding (`seedTable` for raw SQL, `seedOrm` for model-based)
154
154
  - `sqlTranslation.ts` — Cross-engine SQL translator (`SQLTranslator`) and TTL query cache (`QueryCache`)
155
- - **Instance methods:** `save(): this|null` (fluent, null on failure), `delete()`, `forceDelete()`, `restore()`, `load(sql, params?, include?): boolean`, `validate(): string[]`, `toDict(include?)`, `toAssoc(include?)`, `toObject()`, `toArray(): unknown[]`, `toList()`, `toJson(include?)`, `hasOne(class, fk)`, `hasMany(class, fk, limit?, offset?)`, `belongsTo(class, fk)`
155
+ - **Instance methods:** `save(): this|false` (fluent, false on failure), `delete()`, `forceDelete()`, `restore()`, `load(sql, params?, include?): boolean`, `validate(): string[]`, `toDict(include?)`, `toAssoc(include?)`, `toObject()`, `toArray(): unknown[]`, `toList()`, `toJson(include?)`, `hasOne(class, fk)`, `hasMany(class, fk, limit?, offset?)`, `belongsTo(class, fk)`
156
156
  - **Static methods:** `find(id, include?)`, `findById(id, include?)`, `findOrFail(id)`, `create(data)`, `all(where?, params?, include?)`, `select(sql, params?)`, `selectOne(sql, params?, include?)`, `where(conditions, params?, limit?, offset?, include?)`, `count(conditions?, params?)`, `withTrashed(conditions?, params?, limit?, offset?)`, `scope(name, filterSql, params?)` (registers reusable method), `createTable()`, `query()`, `_processForeignKeys()`, `_applyFkRegistry()`
157
157
  - **Foreign key auto-wire:** Declare a field with `type: "foreignKey"` and `references: "ModelName"` to auto-wire both `belongsTo` on the declaring model and `hasMany` on the referenced model. Optional `relatedName` overrides the has-many key. Models must be registered via `BaseModel.registerModel(name, class)` for name-based resolution. Example: `user_id: { type: "foreignKey", references: "User" }` → `post.belongsTo(User, "user_id")` and `user.hasMany(Post, "user_id")` both resolve without extra wiring.
158
158
  - QueryBuilder supports `toMongo()` for generating MongoDB query documents from the same fluent API
@@ -240,7 +240,7 @@ req.cookies: Record<string, string> // parsed from Cookie header
240
240
  req.contentType: string // from content-type header
241
241
  req.query: Record<string, string> // query string params
242
242
  response.xml(content, status?): Tina4Response
243
- response.stream(generator, contentType?: string, status?: number): void // SSE/streaming
243
+ response.stream(source: AsyncIterable<string | Buffer>, contentType?: string): Promise<Tina4Response> // SSE/streaming
244
244
  ```
245
245
 
246
246
  ### Queue
@@ -258,12 +258,11 @@ Auto-generates OpenAPI 3.0 docs.
258
258
  - `generator.ts` — Produces OpenAPI spec from route table + model definitions
259
259
  - `ui.ts` — Serves Swagger UI HTML (CDN-based) at `/swagger` and spec at `/swagger/openapi.json`
260
260
 
261
- ### @tina4/twig (`packages/twig/`)
262
- Optional server-side template rendering.
261
+ ### @tina4/frond (`packages/frond/`)
262
+ Built-in zero-dependency Twig-compatible template engine (the only template engine; there is no `twig` npm dependency).
263
263
 
264
264
  **Key files:**
265
- - `engine.ts` — Wraps the `twig` npm package, `renderTemplate(path, data)`
266
- - `middleware.ts` — Adds `res.render(template, data)` to response objects
265
+ - `engine.ts` — The `Frond` class: `render(path, data)`, `renderString(template, data)`, filters/globals/tests, sandbox mode
267
266
 
268
267
  ### tina4 CLI (`packages/cli/`)
269
268
  Developer-facing CLI commands.
@@ -305,26 +304,29 @@ Events.clear();
305
304
 
306
305
  ## Module: AI (`packages/core/src/ai.ts`)
307
306
 
308
- Detects AI coding tools (Claude Code, Cursor, Copilot, Windsurf, Aider, Cline, Codex) by checking for their config files/directories. Can scaffold a universal Tina4 context document into each tool's expected location.
307
+ Installs Tina4 context files for AI coding tools (Claude Code, Cursor, Copilot, Windsurf, Aider, Cline, Codex). `AI_TOOLS` is the ordered list of known tools; the installer writes a marker-bracketed Tina4 skill block into each tool's context file, preserving existing content.
309
308
 
310
309
  ```typescript
311
- import { detectAi, installAiContext, aiStatusReport } from "@tina4/core";
310
+ import { AI_TOOLS, isInstalled, showMenu, installSelected, installAll, generateContext } from "@tina4/core";
312
311
 
313
- // Detect which AI tools are present in a project directory
314
- const tools = detectAi(".");
315
- // → [{ name: "claude-code", description: "Claude Code (Anthropic CLI)",
316
- // configFile: "CLAUDE.md", status: "detected" }, ...]
312
+ // The known tools (name, description, contextFile, configDir)
313
+ AI_TOOLS; // [{ name: "claude-code", description: "Claude Code", contextFile: "CLAUDE.md", configDir: ".claude" }, ...]
317
314
 
318
- // Install context files for all detected tools (creates CLAUDE.md, .cursorules, etc.)
319
- const created = installAiContext(".", { force: false });
320
- // → ["CLAUDE.md", ".cursorules"]
315
+ // Check whether a tool's context file already exists in a project directory
316
+ isInstalled(".", AI_TOOLS[0]); // boolean
321
317
 
322
- // Install for ALL known tools, not just detected ones
323
- import { installAllAiContext } from "@tina4/core";
324
- installAllAiContext(".", true); // force overwrite
318
+ // Show the interactive numbered menu and read the user's selection (returns a Promise)
319
+ const selection = await showMenu(".");
325
320
 
326
- // Print a human-readable status report
327
- console.log(aiStatusReport("."));
321
+ // Install context files for a selection ("1,2,3" or "all") — returns created/updated paths
322
+ const created = installSelected(".", selection);
323
+ // → ["CLAUDE.md", ".cursorules", ...]
324
+
325
+ // Install for ALL known tools, non-interactive
326
+ installAll(".");
327
+
328
+ // Generate the context document string for a specific tool (defaults to "claude-code")
329
+ const doc = generateContext("cursor");
328
330
  ```
329
331
 
330
332
  ## Module: Error Overlay (`packages/core/src/errorOverlay.ts`)
@@ -384,16 +386,16 @@ Void tags (`br`, `hr`, `img`, `input`, `meta`, etc.) render without closing tags
384
386
 
385
387
  ## Module: Inline Testing (`packages/core/src/testing.ts`)
386
388
 
387
- Attach test assertions directly to functions. Tests are registered globally and run with `runAllTests()`. No external test runner needed.
389
+ Attach test assertions directly to functions. Tests are registered globally and run with `runAll()`. No external test runner needed.
388
390
 
389
391
  ```typescript
390
- import { tests, assertEqual, assertThrows, assertTrue, assertFalse, runAllTests, resetTests } from "@tina4/core";
392
+ import { tests, assertEqual, assertRaises, assertTrue, assertFalse, runAll, reset } from "@tina4/core";
391
393
 
392
394
  // Decorate a function with inline tests
393
395
  const add = tests(
394
396
  assertEqual([5, 3], 8), // add(5, 3) === 8
395
397
  assertEqual([0, 0], 0), // add(0, 0) === 0
396
- assertThrows(Error, [null]), // add(null) throws Error
398
+ assertRaises(Error, [null]), // add(null) throws Error
397
399
  )(function add(a: number, b: number | null = null): number {
398
400
  if (b === null) throw new Error("b required");
399
401
  return a + b;
@@ -403,7 +405,7 @@ const add = tests(
403
405
  add(2, 3); // 5
404
406
 
405
407
  // Run all registered tests
406
- const results = runAllTests({ quiet: false, failfast: false });
408
+ const results = runAll({ quiet: false, failfast: false });
407
409
  // → { passed: 3, failed: 0, errors: 0, details: [...] }
408
410
 
409
411
  // Additional assertion types
@@ -411,7 +413,7 @@ assertTrue([someArgs]); // result is truthy
411
413
  assertFalse([someArgs]); // result is falsy
412
414
 
413
415
  // Reset registry between test runs
414
- resetTests();
416
+ reset();
415
417
  ```
416
418
 
417
419
  ## Module: Seeder / FakeData (`packages/orm/src/seeder.ts`, `packages/orm/src/fakeData.ts`)
@@ -600,7 +602,7 @@ db.pool
600
602
  Active-Record base class. Models live in `src/models/` and are auto-discovered. Use `static fields` (not decorators) — same convention across all four frameworks.
601
603
 
602
604
  ```typescript
603
- import { BaseModel, ormBind } from "@tina4/orm";
605
+ import { BaseModel, initDatabase, setAdapter } from "@tina4/orm";
604
606
 
605
607
  export default class User extends BaseModel {
606
608
  static tableName = "users";
@@ -614,7 +616,7 @@ export default class User extends BaseModel {
614
616
 
615
617
  // Instance methods (chainable where it makes sense)
616
618
  const user = new User({ email: "alice@example.com" });
617
- user.save(); // returns this on success, null on failure
619
+ user.save(); // returns this on success, false on failure
618
620
  user.delete(); // soft-delete if enabled, otherwise hard
619
621
  user.forceDelete(); // bypasses soft-delete
620
622
  user.restore(); // clears soft-delete marker
@@ -643,7 +645,11 @@ User.createTable();
643
645
  User.query(): QueryBuilder;
644
646
  BaseModel.registerModel(name, class); // for foreignKey name resolution
645
647
 
646
- ormBind(db); // bind a Database instance for all models in the registry
648
+ // Models bind to the active adapter, not a Database wrapper. initDatabase() sets it
649
+ // automatically; setAdapter() lets you bind one explicitly. Models read it via getAdapter().
650
+ await initDatabase({ url: "sqlite:///app.db" }); // sets the active adapter for all models
651
+ // or, with an adapter you constructed yourself:
652
+ setAdapter(adapter);
647
653
  ```
648
654
 
649
655
  **Soft delete:** set `static softDelete = true`. Adds an `is_deleted` INTEGER column (0/1). `delete()` flips the flag, `forceDelete()` removes the row, `restore()` clears it.
@@ -726,7 +732,7 @@ frond.sandbox(["upper"], ["if"], ["x"]); // allowed: filters, tags, vars
726
732
  frond.unsandbox();
727
733
  ```
728
734
 
729
- - **SafeString** — filters can return `new SafeString(value)` to bypass auto-escaping.
735
+ - **Safe output** — Frond's built-in `raw`/`safe`-style filters (and the `{% autoescape %}` controls) mark output as already-escaped so it bypasses auto-escaping. The internal `SafeString` wrapper backing this is not exported from `@tina4/frond` (only `Frond`, `FilterFn`, `TestFn` are public).
730
736
  - **Fragment caching** — `{% cache "key" 300 %}...{% endcache %}` caches block output for TTL seconds.
731
737
  - **Raw blocks** — `{% raw %}...{% endraw %}` outputs literal template syntax.
732
738
  - **Pre-compiled regexes** + token caching (cleared on file mtime change in dev mode) for ~2.8x render improvement over the naive path.
@@ -1056,7 +1062,7 @@ When adding new features, add a corresponding `test/<feature>.test.ts` file.
1056
1062
  ## v3 Features Summary
1057
1063
 
1058
1064
  - **45 built-in features**, zero third-party dependencies
1059
- - **1,812 tests** passing across all modules
1065
+ - **3,644 tests** passing across all modules
1060
1066
  - **Race-safe `getNextId()`** with atomic sequence table (`tina4_sequences`) for SQLite/MySQL/MSSQL; PostgreSQL auto-creates sequences
1061
1067
  - **Frond template engine optimizations**: pre-compiled regexes, lazy loop context (copy-on-write), filter chain caching, path split caching, inline common filters (11-15% speedup)
1062
1068
  - **Production server auto-detect**: `npx tina4nodejs serve --production` auto-uses cluster mode
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- "version": "3.13.14",
6
+ "version": "3.13.16",
7
7
 
8
8
  "type": "module",
9
9
  "description": "Tina4 for Node.js/TypeScript \u2014 54 built-in features, zero dependencies",
@@ -32,6 +32,7 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
32
32
  let recordMigration: typeof import("../../../orm/src/index.js").recordMigration;
33
33
  let getNextBatch: typeof import("../../../orm/src/index.js").getNextBatch;
34
34
  let getAdapter: typeof import("../../../orm/src/index.js").getAdapter;
35
+ let adapterExecute: typeof import("../../../orm/src/index.js").adapterExecute;
35
36
 
36
37
  try {
37
38
  const orm = await import("../../../orm/src/index.js");
@@ -41,6 +42,7 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
41
42
  recordMigration = orm.recordMigration;
42
43
  getNextBatch = orm.getNextBatch;
43
44
  getAdapter = orm.getAdapter;
45
+ adapterExecute = orm.adapterExecute;
44
46
  } catch {
45
47
  console.error(" Error: @tina4/orm is required to run migrations.");
46
48
  process.exit(1);
@@ -57,7 +59,7 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
57
59
  process.exit(1);
58
60
  }
59
61
 
60
- ensureMigrationTable();
62
+ await ensureMigrationTable();
61
63
 
62
64
  // Collect .sql files, excluding .down.sql, sorted by numeric prefix
63
65
  const files = readdirSync(dir)
@@ -79,13 +81,13 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
79
81
  return;
80
82
  }
81
83
 
82
- const batch = getNextBatch();
84
+ const batch = await getNextBatch();
83
85
  let applied = 0;
84
86
 
85
87
  for (const file of files) {
86
88
  const name = file.replace(/\.sql$/, "");
87
89
 
88
- if (isMigrationApplied(name)) {
90
+ if (await isMigrationApplied(name)) {
89
91
  continue;
90
92
  }
91
93
 
@@ -100,7 +102,7 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
100
102
 
101
103
  for (const stmt of statements) {
102
104
  try {
103
- adapter.execute(stmt);
105
+ await adapterExecute(adapter, stmt);
104
106
  } catch (err) {
105
107
  const msg = err instanceof Error ? err.message : String(err);
106
108
  console.error(` Error in ${file}: ${msg}`);
@@ -108,7 +110,7 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
108
110
  }
109
111
  }
110
112
 
111
- recordMigration(name, batch);
113
+ await recordMigration(name, batch);
112
114
  applied++;
113
115
  }
114
116
 
@@ -44,9 +44,9 @@ export async function migrateRollback(migrationDir?: string): Promise<void> {
44
44
  process.exit(1);
45
45
  }
46
46
 
47
- ensureMigrationTable();
47
+ await ensureMigrationTable();
48
48
 
49
- const lastBatch = getLastBatchMigrations();
49
+ const lastBatch = await getLastBatchMigrations();
50
50
  if (lastBatch.length === 0) {
51
51
  console.log(" Nothing to rollback — no migrations have been applied.");
52
52
  return;
@@ -54,7 +54,7 @@ export async function migrateRollback(migrationDir?: string): Promise<void> {
54
54
 
55
55
  console.log(` Rolling back batch ${lastBatch[0].batch} (${lastBatch.length} migration(s))...`);
56
56
 
57
- const rolledBack = rollbackFn(dir);
57
+ const rolledBack = await rollbackFn(dir);
58
58
 
59
59
  if (rolledBack.length === 0) {
60
60
  console.log(" Nothing was rolled back.");
@@ -832,7 +832,7 @@ ${reset}
832
832
  }
833
833
  if (models.length > 0) {
834
834
  console.log(`\n Models discovered:`);
835
- orm.syncModels(models);
835
+ await orm.syncModels(models);
836
836
  for (const { definition } of models) {
837
837
  console.log(` \x1b[35m${definition.tableName}\x1b[0m (${Object.keys(definition.fields).length} fields)`);
838
838
  }
@@ -1,6 +1,6 @@
1
1
  import type { RouteDefinition, Tina4Request, Tina4Response } from "@tina4/core";
2
2
  import type { DiscoveredModel } from "./model.js";
3
- import { getAdapter } from "./database.js";
3
+ import { getAdapter, adapterQuery, adapterExecute } from "./database.js";
4
4
  import { buildQuery, parseQueryString } from "./query.js";
5
5
  import { validate } from "./validation.js";
6
6
 
@@ -109,9 +109,9 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
109
109
 
110
110
  // params includes limit and offset at the end; countSql doesn't need them
111
111
  const countParams = params.slice(0, -2);
112
- const rows = adapter.query(sql, params);
112
+ const rows = await adapterQuery(adapter, sql, params);
113
113
 
114
- const countRow = adapter.query(countSql, countParams);
114
+ const countRow = await adapterQuery(adapter, countSql, countParams);
115
115
  const total = Number(countRow[0]?.total ?? 0);
116
116
  const limit = qp.limit ?? 100;
117
117
  const page = qp.page ?? 1;
@@ -140,7 +140,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
140
140
  const adapter = getAdapter();
141
141
 
142
142
  const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
143
- const rows = adapter.query(
143
+ const rows = await adapterQuery(adapter,
144
144
  `SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
145
145
  [req.params.id],
146
146
  );
@@ -183,14 +183,30 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
183
183
  const values = Object.values(dbBody);
184
184
  const placeholders = columns.map(() => "?").join(", ");
185
185
 
186
- adapter.execute(
187
- `INSERT INTO "${tableName}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
188
- values,
189
- );
186
+ // Non-SQLite engines can't read a plain INSERT's auto-id back via
187
+ // lastInsertId(); RETURNING the PK column lets us recover it. SQLite
188
+ // tolerates RETURNING but we still prefer its lastInsertId below.
189
+ const isSqlite = adapter.constructor.name === "SQLiteAdapter";
190
+ const insertSql =
191
+ `INSERT INTO "${tableName}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})` +
192
+ (isSqlite ? "" : ` RETURNING "${pkColumn}"`);
193
+
194
+ const insertResult = await adapterExecute(adapter, insertSql, values);
195
+
196
+ // Recover the new PK: SQLite via lastInsertId(); others via RETURNING.
197
+ let lastId: unknown = isSqlite ? adapter.lastInsertId() : null;
198
+ if (lastId === null && insertResult && typeof insertResult === "object") {
199
+ const rrows = (insertResult as any).rows;
200
+ if (Array.isArray(rrows) && rrows[0]) {
201
+ lastId = rrows[0][pkColumn] ?? rrows[0].id ?? null;
202
+ }
203
+ }
204
+ if (lastId === null || lastId === undefined) {
205
+ lastId = adapter.lastInsertId();
206
+ }
190
207
 
191
208
  // Fetch the created record to include auto-generated fields (e.g. id)
192
- const lastId = adapter.lastInsertId();
193
- const created = adapter.query(
209
+ const created = await adapterQuery(adapter,
194
210
  `SELECT * FROM "${tableName}" WHERE "${pkColumn}" = ?`,
195
211
  [lastId],
196
212
  );
@@ -212,7 +228,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
212
228
  const body = req.body as Record<string, unknown>;
213
229
 
214
230
  const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
215
- const existing = adapter.query(
231
+ const existing = await adapterQuery(adapter,
216
232
  `SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
217
233
  [req.params.id],
218
234
  );
@@ -232,12 +248,12 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
232
248
  .join(", ");
233
249
  const values = [...Object.values(dbBody), req.params.id];
234
250
 
235
- adapter.execute(
251
+ await adapterExecute(adapter,
236
252
  `UPDATE "${tableName}" SET ${setClauses} WHERE "${pkColumn}" = ?`,
237
253
  values,
238
254
  );
239
255
 
240
- const updated = adapter.query(
256
+ const updated = await adapterQuery(adapter,
241
257
  `SELECT * FROM "${tableName}" WHERE "${pkColumn}" = ?`,
242
258
  [req.params.id],
243
259
  );
@@ -258,7 +274,7 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
258
274
  const adapter = getAdapter();
259
275
 
260
276
  const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
261
- const existing = adapter.query(
277
+ const existing = await adapterQuery(adapter,
262
278
  `SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
263
279
  [req.params.id],
264
280
  );
@@ -268,13 +284,13 @@ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[]
268
284
  }
269
285
 
270
286
  if (softDelete) {
271
- adapter.execute(
287
+ await adapterExecute(adapter,
272
288
  `UPDATE "${tableName}" SET is_deleted = 1 WHERE "${pkColumn}" = ?`,
273
289
  [req.params.id],
274
290
  );
275
291
  res.json({ message: "Deleted (soft)", data: existing[0] });
276
292
  } else {
277
- adapter.execute(
293
+ await adapterExecute(adapter,
278
294
  `DELETE FROM "${tableName}" WHERE "${pkColumn}" = ?`,
279
295
  [req.params.id],
280
296
  );