tina4-nodejs 3.13.139 → 3.13.140

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.
@@ -0,0 +1,178 @@
1
+ /*
2
+ Copyright (c) 2026 Code Infinity
3
+ SPDX-License-Identifier: MPL-2.0
4
+ This Source Code Form is subject to the terms of the Mozilla Public
5
+ License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+ /**
10
+ * One CRUD write composer for every SQL engine, instead of one per adapter.
11
+ *
12
+ * `sqlDialect.ts` already collapsed the SQL *fragment* builders (columns, the
13
+ * SET list, the WHERE list). What it did NOT collapse was the METHOD BODIES:
14
+ * every SQL adapter's `insert`/`update`/`delete` re-implemented the SAME
15
+ * envelope around those fragments — the string-filter branch vs the hash-filter
16
+ * branch, the `[...Object.values(data), ...(params ?? [])]` value assembly, the
17
+ * batch fan-out — differing only in the two things a `Dialect` already captures
18
+ * (identifier quoting, parameter marker) plus three per-engine knobs:
19
+ *
20
+ * INSERT SUFFIX "" | " RETURNING *" | "; SELECT SCOPE_IDENTITY() AS id"
21
+ * MARKER START 1 (PostgreSQL $N) | 0 (MSSQL @pN) | ignored (`?` engines)
22
+ * FRAGMENT REWRITE a raw "id = ?" WHERE string: identity on a `?` engine,
23
+ * convertPlaceholders/bindable on a positional one
24
+ *
25
+ * This is the Node port of the Python master's `SqlCrudMixin`
26
+ * (tina4_python/database/adapter.py): build the SQL and the ordered parameter
27
+ * list engine-neutrally from a dialect + those knobs, and hand the pair back to
28
+ * the adapter to EXECUTE.
29
+ *
30
+ * Like `sqlDialect.ts`, these functions build STRINGS and parameter arrays and
31
+ * NOTHING else. Execution and result extraction stay in the adapters on purpose:
32
+ * `this.db.prepare().run()` vs `await this.client!.query()` vs tedious vs a
33
+ * Firebird transaction handle, and RETURNING / SCOPE_IDENTITY() / lastId /
34
+ * affectedRows normalisation, are genuinely per-driver — folding them in here
35
+ * would trade a real duplication for a fake abstraction.
36
+ *
37
+ * MongoDB has no entry: it does not build SQL at all.
38
+ */
39
+
40
+ import { type Dialect, buildInsert, buildSetClause, buildWhereClause } from "./sqlDialect.js";
41
+
42
+ /** A composed statement: the SQL text and its ordered parameter list. */
43
+ export interface CrudSql {
44
+ sql: string;
45
+ values: unknown[];
46
+ }
47
+
48
+ /**
49
+ * A composed batch INSERT: one SQL statement plus one parameter row per input
50
+ * record, ready for the adapter's own `executeMany`.
51
+ */
52
+ export interface CrudBatchSql {
53
+ sql: string;
54
+ paramsList: unknown[][];
55
+ }
56
+
57
+ /** The only per-engine differences in a composed write's SQL + parameter list. */
58
+ export interface WriteOptions {
59
+ /**
60
+ * 1-based position of the FIRST parameter marker. PostgreSQL numbers `$N`
61
+ * from 1; MSSQL names `@pN` from 0 and BINDS by that name, so the start must
62
+ * match its binding loop. Engines whose marker is `?` ignore it. Default 1.
63
+ */
64
+ startAt?: number;
65
+ /**
66
+ * Rewrite a raw string WHERE fragment's `?` markers for this engine, given the
67
+ * 1-based position of the fragment's first marker. `?` engines leave it
68
+ * untouched (the default); a positional engine passes its `convertPlaceholders`
69
+ * / `bindable`. Only ever applied to the developer-supplied string filter,
70
+ * never to the framework-built hash WHERE.
71
+ */
72
+ convertFragment?: (fragment: string, startAt: number) => string;
73
+ }
74
+
75
+ const identityFragment = (fragment: string): string => fragment;
76
+
77
+ /**
78
+ * A single-row INSERT: `INSERT INTO t (a, b) VALUES (?, ?)<suffix>` and the row's
79
+ * values in column order. `suffix` carries the genuinely engine-specific tail
80
+ * (PostgreSQL's ` RETURNING *`, MSSQL's SCOPE_IDENTITY() probe).
81
+ */
82
+ export function buildInsertRow(
83
+ dialect: Dialect,
84
+ table: string,
85
+ data: Record<string, unknown>,
86
+ suffix = "",
87
+ options: WriteOptions = {},
88
+ ): CrudSql {
89
+ const keys = Object.keys(data);
90
+ const sql = buildInsert(dialect, table, keys, suffix, options.startAt ?? 1);
91
+ return { sql, values: Object.values(data) };
92
+ }
93
+
94
+ /**
95
+ * A batch INSERT from a list of rows — one parameterised statement the adapter
96
+ * runs once per row via its own `executeMany`. Returns `null` for an empty list
97
+ * (the adapters treat that as a no-op affecting zero rows). The keys come from
98
+ * the first row, exactly as every adapter already assumed.
99
+ */
100
+ export function buildInsertRows(
101
+ dialect: Dialect,
102
+ table: string,
103
+ rows: Record<string, unknown>[],
104
+ ): CrudBatchSql | null {
105
+ if (rows.length === 0) return null;
106
+ const keys = Object.keys(rows[0]);
107
+ const sql = buildInsert(dialect, table, keys);
108
+ const paramsList = rows.map((row) => keys.map((k) => row[k]));
109
+ return { sql, paramsList };
110
+ }
111
+
112
+ /**
113
+ * An UPDATE from a data object and either a hash filter (`{id: 5}` →
114
+ * `WHERE "id" = ?`) or a raw string filter (`"id = ?"` + `params`). The SET
115
+ * values always lead the parameter list; the WHERE values follow.
116
+ */
117
+ export function buildUpdate(
118
+ dialect: Dialect,
119
+ table: string,
120
+ data: Record<string, unknown>,
121
+ filter: Record<string, unknown> | string,
122
+ params?: unknown[],
123
+ options: WriteOptions = {},
124
+ ): CrudSql {
125
+ const startAt = options.startAt ?? 1;
126
+ const convertFragment = options.convertFragment ?? identityFragment;
127
+ const dataKeys = Object.keys(data);
128
+ const setClauses = buildSetClause(dialect, dataKeys, startAt);
129
+ const whereStart = startAt + dataKeys.length;
130
+
131
+ if (typeof filter === "string") {
132
+ const where = filter ? ` WHERE ${convertFragment(filter, whereStart)}` : "";
133
+ return {
134
+ sql: `UPDATE ${dialect.quote(table)} SET ${setClauses}${where}`,
135
+ values: [...Object.values(data), ...(params ?? [])],
136
+ };
137
+ }
138
+
139
+ const whereClauses = buildWhereClause(dialect, Object.keys(filter), whereStart);
140
+ return {
141
+ sql: `UPDATE ${dialect.quote(table)} SET ${setClauses} WHERE ${whereClauses}`,
142
+ values: [...Object.values(data), ...Object.values(filter)],
143
+ };
144
+ }
145
+
146
+ /**
147
+ * A DELETE from either a hash filter (`{id: 5}` → `WHERE "id" = ?`) or a raw
148
+ * string filter (`"age < ?"` + `params`). An empty string filter deletes the
149
+ * whole table (the explicit `truncate()` spelling routes through here).
150
+ *
151
+ * The array-of-dicts filter form is NOT handled here: only SQLite and ODBC
152
+ * accept it, and both loop it into per-row `delete` calls that land back in one
153
+ * of the two branches above — that fan-out stays in those two adapters.
154
+ */
155
+ export function buildDelete(
156
+ dialect: Dialect,
157
+ table: string,
158
+ filter: Record<string, unknown> | string,
159
+ params?: unknown[],
160
+ options: WriteOptions = {},
161
+ ): CrudSql {
162
+ const startAt = options.startAt ?? 1;
163
+ const convertFragment = options.convertFragment ?? identityFragment;
164
+
165
+ if (typeof filter === "string") {
166
+ const where = filter ? ` WHERE ${convertFragment(filter, startAt)}` : "";
167
+ return {
168
+ sql: `DELETE FROM ${dialect.quote(table)}${where}`,
169
+ values: params ?? [],
170
+ };
171
+ }
172
+
173
+ const whereClauses = buildWhereClause(dialect, Object.keys(filter), startAt);
174
+ return {
175
+ sql: `DELETE FROM ${dialect.quote(table)} WHERE ${whereClauses}`,
176
+ values: Object.values(filter),
177
+ };
178
+ }
@@ -7,9 +7,10 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
7
  */
8
8
 
9
9
  import { DatabaseSync } from "node:sqlite";
10
- import { ANSI_DIALECT, buildInsert, buildSetClause, buildWhereClause } from "./sqlDialect.js";
10
+ import { ANSI_DIALECT } from "./sqlDialect.js";
11
+ import { buildDelete, buildInsertRow, buildInsertRows, buildUpdate } from "./sqlCrud.js";
11
12
  import { mkdirSync } from "node:fs";
12
- import { dirname, isAbsolute, join, resolve } from "node:path";
13
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
13
14
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
14
15
  import { SQLTranslator } from "../sqlTranslator.js";
15
16
 
@@ -76,31 +77,45 @@ function sqliteVersionAtLeast(major: number, minor: number, patch: number): bool
76
77
  *
77
78
  * Matches the tina4-python + tina4-php convention:
78
79
  * ":memory:" → passthrough
79
- * "data/app.db" → {cwd}/data/app.db (auto-mkdir under cwd)
80
+ * "data/app.db" → {cwd}/data/app.db (parent auto-created, mode 0775)
80
81
  * "/abs/app.db" → /abs/app.db (NO auto-mkdir; user's responsibility)
81
- * "C:/Users/app.db" → C:/Users/app.db (NO auto-mkdir)
82
+ * "C:/Users/app.db" → C:/Users/app.db (recognised as absolute on EVERY OS)
83
+ * "../../etc/foo.db" → REFUSED loudly (its parent escapes cwd)
82
84
  *
83
- * Never mkdir a directory that isn't a descendant of cwd — that was the
84
- * root cause of the `EROFS: read-only file system, mkdir '/data'` crash
85
- * reported on macOS.
85
+ * The canonical contract is ADR-0086. Two things this pins that node:path alone
86
+ * gets wrong: a drive-letter path is absolute on POSIX too (isAbsolute does not
87
+ * recognise it, so it would be re-rooted under cwd), and a relative path whose
88
+ * parent escapes cwd must be REFUSED — never the silent mkdir outside the
89
+ * project that was the root cause of the `EROFS: mkdir '/data'` crash.
86
90
  */
87
- function resolveSqlitePath(dbPath: string): string {
91
+ export function resolveSqlitePath(dbPath: string): string {
88
92
  if (dbPath === ":memory:") return dbPath;
89
93
 
90
- let path = dbPath;
91
- if (!isAbsolute(path)) {
92
- path = join(process.cwd(), path);
93
- // Auto-mkdir is safe here — we know the parent is under cwd
94
- mkdirSync(dirname(path), { recursive: true });
95
- } else {
96
- // Absolute path. Only auto-mkdir if it's a descendant of cwd.
97
- const cwd = resolve(process.cwd());
98
- const abs = resolve(path);
99
- if (abs.startsWith(cwd + "/") || abs === cwd) {
100
- mkdirSync(dirname(abs), { recursive: true });
101
- }
102
- // Otherwise, trust the user — don't touch the filesystem.
94
+ // A Windows drive-letter path (C:/... or C:\...) is absolute on EVERY OS.
95
+ // node:path.isAbsolute does NOT recognise it on POSIX (it would re-root the
96
+ // path under cwd), so recognise it explicitly for parity (ADR-0086).
97
+ const isWindowsAbs = /^[A-Za-z]:[\\/]/.test(dbPath);
98
+ if (isAbsolute(dbPath) || isWindowsAbs) {
99
+ // Absolute — trust the user; never auto-mkdir (parity with the other three).
100
+ return dbPath;
103
101
  }
102
+
103
+ // Relative — resolve under cwd; create the parent (mode 0775) ONLY when it
104
+ // stays inside cwd. A relative path whose parent escapes the project is
105
+ // REFUSED loudly — never a silent mkdir outside the project.
106
+ const cwd = resolve(process.cwd());
107
+ const path = join(cwd, dbPath);
108
+ const parent = dirname(path);
109
+ const absParent = resolve(parent);
110
+ if (absParent !== cwd && !absParent.startsWith(cwd + sep)) {
111
+ throw new Error(
112
+ `SQLite path "${dbPath}" resolves outside the project directory: ` +
113
+ `"${absParent}" is not within "${cwd}". Tina4 refuses to create ` +
114
+ `directories outside the project (ADR-0086). Use an absolute path for a ` +
115
+ `database that lives outside the project.`,
116
+ );
117
+ }
118
+ mkdirSync(parent, { recursive: true, mode: 0o775 });
104
119
  return path;
105
120
  }
106
121
 
@@ -264,17 +279,13 @@ export class SQLiteAdapter implements DatabaseAdapter {
264
279
 
265
280
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult {
266
281
  if (Array.isArray(data)) {
267
- if (data.length === 0) return { success: true, affectedRows: 0 };
268
- const keys = Object.keys(data[0]);
269
- const sql = buildInsert(ANSI_DIALECT, table, keys);
270
- const paramsList = data.map((row) => keys.map((k) => row[k]));
271
- const result = this.executeMany(sql, paramsList);
282
+ const batch = buildInsertRows(ANSI_DIALECT, table, data);
283
+ if (!batch) return { success: true, affectedRows: 0 };
284
+ const result = this.executeMany(batch.sql, batch.paramsList);
272
285
  return { success: true, affectedRows: result.affectedRows, lastId: result.lastId };
273
286
  }
274
287
 
275
- const keys = Object.keys(data);
276
- const sql = buildInsert(ANSI_DIALECT, table, keys);
277
- const values = Object.values(data);
288
+ const { sql, values } = buildInsertRow(ANSI_DIALECT, table, data);
278
289
 
279
290
  try {
280
291
  const result = this.db.prepare(sql).run(...toSqlParams(values));
@@ -286,30 +297,11 @@ export class SQLiteAdapter implements DatabaseAdapter {
286
297
  }
287
298
 
288
299
  update(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): DatabaseResult {
289
- const setClauses = buildSetClause(ANSI_DIALECT, Object.keys(data));
290
-
291
- // A raw WHERE fragment + params is half the write_path contract's filter
292
- // form ("a string filter with params works the same as a hash filter").
293
- // Without this branch Object.keys("id = ?") yields the STRING INDICES
294
- // ["0","1",...], producing `WHERE "0" = ? AND "1" = ?` and SQLite reports
295
- // `no such column: "0"`. delete() below already carried this branch and
296
- // update() did not — the same gap 3.13.94 closed in the postgres/mysql/
297
- // mssql/firebird adapters, still open here on the DEFAULT engine.
298
- if (typeof filter === "string") {
299
- const where = filter ? ` WHERE ${filter}` : "";
300
- const sql = `UPDATE ${ANSI_DIALECT.quote(table)} SET ${setClauses}${where}`;
301
- const values = [...Object.values(data), ...(params ?? [])];
302
- try {
303
- const result = this.db.prepare(sql).run(...toSqlParams(values));
304
- return { success: true, affectedRows: Number(result.changes) };
305
- } catch (e) {
306
- return { success: false, affectedRows: 0, error: (e as Error).message };
307
- }
308
- }
309
-
310
- const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
311
- const sql = `UPDATE ${ANSI_DIALECT.quote(table)} SET ${setClauses} WHERE ${whereClauses}`;
312
- const values = [...Object.values(data), ...Object.values(filter)];
300
+ // The string-filter vs hash-filter branch and the value assembly live in the
301
+ // shared composer (sqlCrud.ts); SQLite supplies only its driver execution.
302
+ // A raw WHERE fragment carries `?` markers, which SQLite uses natively, so no
303
+ // fragment rewrite is needed.
304
+ const { sql, values } = buildUpdate(ANSI_DIALECT, table, data, filter, params);
313
305
 
314
306
  try {
315
307
  const result = this.db.prepare(sql).run(...toSqlParams(values));
@@ -329,19 +321,7 @@ export class SQLiteAdapter implements DatabaseAdapter {
329
321
  return { success: true, affectedRows: totalAffected };
330
322
  }
331
323
 
332
- if (typeof filter === "string") {
333
- const sql = filter ? `DELETE FROM "${table}" WHERE ${filter}` : `DELETE FROM "${table}"`;
334
- try {
335
- const result = this.db.prepare(sql).run(...toSqlParams(params ?? []));
336
- return { success: true, affectedRows: Number(result.changes) };
337
- } catch (e) {
338
- return { success: false, affectedRows: 0, error: (e as Error).message };
339
- }
340
- }
341
-
342
- const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
343
- const sql = `DELETE FROM ${ANSI_DIALECT.quote(table)} WHERE ${whereClauses}`;
344
- const values = Object.values(filter);
324
+ const { sql, values } = buildDelete(ANSI_DIALECT, table, filter, params);
345
325
 
346
326
  try {
347
327
  const result = this.db.prepare(sql).run(...toSqlParams(values));
@@ -93,7 +93,7 @@ export type {
93
93
  } from "./docstore.js";
94
94
 
95
95
  // Database adapters
96
- export { SQLiteAdapter } from "./adapters/sqlite.js";
96
+ export { SQLiteAdapter, resolveSqlitePath } from "./adapters/sqlite.js";
97
97
  export { PostgresAdapter } from "./adapters/postgres.js";
98
98
  export type { PostgresConfig } from "./adapters/postgres.js";
99
99
  export { MysqlAdapter } from "./adapters/mysql.js";
@@ -0,0 +1,90 @@
1
+ /**
2
+ * One CRUD write composer for every SQL engine, instead of one per adapter.
3
+ *
4
+ * `sqlDialect.ts` already collapsed the SQL *fragment* builders (columns, the
5
+ * SET list, the WHERE list). What it did NOT collapse was the METHOD BODIES:
6
+ * every SQL adapter's `insert`/`update`/`delete` re-implemented the SAME
7
+ * envelope around those fragments — the string-filter branch vs the hash-filter
8
+ * branch, the `[...Object.values(data), ...(params ?? [])]` value assembly, the
9
+ * batch fan-out — differing only in the two things a `Dialect` already captures
10
+ * (identifier quoting, parameter marker) plus three per-engine knobs:
11
+ *
12
+ * INSERT SUFFIX "" | " RETURNING *" | "; SELECT SCOPE_IDENTITY() AS id"
13
+ * MARKER START 1 (PostgreSQL $N) | 0 (MSSQL @pN) | ignored (`?` engines)
14
+ * FRAGMENT REWRITE a raw "id = ?" WHERE string: identity on a `?` engine,
15
+ * convertPlaceholders/bindable on a positional one
16
+ *
17
+ * This is the Node port of the Python master's `SqlCrudMixin`
18
+ * (tina4_python/database/adapter.py): build the SQL and the ordered parameter
19
+ * list engine-neutrally from a dialect + those knobs, and hand the pair back to
20
+ * the adapter to EXECUTE.
21
+ *
22
+ * Like `sqlDialect.ts`, these functions build STRINGS and parameter arrays and
23
+ * NOTHING else. Execution and result extraction stay in the adapters on purpose:
24
+ * `this.db.prepare().run()` vs `await this.client!.query()` vs tedious vs a
25
+ * Firebird transaction handle, and RETURNING / SCOPE_IDENTITY() / lastId /
26
+ * affectedRows normalisation, are genuinely per-driver — folding them in here
27
+ * would trade a real duplication for a fake abstraction.
28
+ *
29
+ * MongoDB has no entry: it does not build SQL at all.
30
+ */
31
+ import { type Dialect } from "./sqlDialect.js";
32
+ /** A composed statement: the SQL text and its ordered parameter list. */
33
+ export interface CrudSql {
34
+ sql: string;
35
+ values: unknown[];
36
+ }
37
+ /**
38
+ * A composed batch INSERT: one SQL statement plus one parameter row per input
39
+ * record, ready for the adapter's own `executeMany`.
40
+ */
41
+ export interface CrudBatchSql {
42
+ sql: string;
43
+ paramsList: unknown[][];
44
+ }
45
+ /** The only per-engine differences in a composed write's SQL + parameter list. */
46
+ export interface WriteOptions {
47
+ /**
48
+ * 1-based position of the FIRST parameter marker. PostgreSQL numbers `$N`
49
+ * from 1; MSSQL names `@pN` from 0 and BINDS by that name, so the start must
50
+ * match its binding loop. Engines whose marker is `?` ignore it. Default 1.
51
+ */
52
+ startAt?: number;
53
+ /**
54
+ * Rewrite a raw string WHERE fragment's `?` markers for this engine, given the
55
+ * 1-based position of the fragment's first marker. `?` engines leave it
56
+ * untouched (the default); a positional engine passes its `convertPlaceholders`
57
+ * / `bindable`. Only ever applied to the developer-supplied string filter,
58
+ * never to the framework-built hash WHERE.
59
+ */
60
+ convertFragment?: (fragment: string, startAt: number) => string;
61
+ }
62
+ /**
63
+ * A single-row INSERT: `INSERT INTO t (a, b) VALUES (?, ?)<suffix>` and the row's
64
+ * values in column order. `suffix` carries the genuinely engine-specific tail
65
+ * (PostgreSQL's ` RETURNING *`, MSSQL's SCOPE_IDENTITY() probe).
66
+ */
67
+ export declare function buildInsertRow(dialect: Dialect, table: string, data: Record<string, unknown>, suffix?: string, options?: WriteOptions): CrudSql;
68
+ /**
69
+ * A batch INSERT from a list of rows — one parameterised statement the adapter
70
+ * runs once per row via its own `executeMany`. Returns `null` for an empty list
71
+ * (the adapters treat that as a no-op affecting zero rows). The keys come from
72
+ * the first row, exactly as every adapter already assumed.
73
+ */
74
+ export declare function buildInsertRows(dialect: Dialect, table: string, rows: Record<string, unknown>[]): CrudBatchSql | null;
75
+ /**
76
+ * An UPDATE from a data object and either a hash filter (`{id: 5}` →
77
+ * `WHERE "id" = ?`) or a raw string filter (`"id = ?"` + `params`). The SET
78
+ * values always lead the parameter list; the WHERE values follow.
79
+ */
80
+ export declare function buildUpdate(dialect: Dialect, table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[], options?: WriteOptions): CrudSql;
81
+ /**
82
+ * A DELETE from either a hash filter (`{id: 5}` → `WHERE "id" = ?`) or a raw
83
+ * string filter (`"age < ?"` + `params`). An empty string filter deletes the
84
+ * whole table (the explicit `truncate()` spelling routes through here).
85
+ *
86
+ * The array-of-dicts filter form is NOT handled here: only SQLite and ODBC
87
+ * accept it, and both loop it into per-row `delete` calls that land back in one
88
+ * of the two branches above — that fan-out stays in those two adapters.
89
+ */
90
+ export declare function buildDelete(dialect: Dialect, table: string, filter: Record<string, unknown> | string, params?: unknown[], options?: WriteOptions): CrudSql;
@@ -1,4 +1,21 @@
1
1
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
2
+ /**
3
+ * Resolve a SQLite path argument against the project root (cwd).
4
+ *
5
+ * Matches the tina4-python + tina4-php convention:
6
+ * ":memory:" → passthrough
7
+ * "data/app.db" → {cwd}/data/app.db (parent auto-created, mode 0775)
8
+ * "/abs/app.db" → /abs/app.db (NO auto-mkdir; user's responsibility)
9
+ * "C:/Users/app.db" → C:/Users/app.db (recognised as absolute on EVERY OS)
10
+ * "../../etc/foo.db" → REFUSED loudly (its parent escapes cwd)
11
+ *
12
+ * The canonical contract is ADR-0086. Two things this pins that node:path alone
13
+ * gets wrong: a drive-letter path is absolute on POSIX too (isAbsolute does not
14
+ * recognise it, so it would be re-rooted under cwd), and a relative path whose
15
+ * parent escapes cwd must be REFUSED — never the silent mkdir outside the
16
+ * project that was the root cause of the `EROFS: mkdir '/data'` crash.
17
+ */
18
+ export declare function resolveSqlitePath(dbPath: string): string;
2
19
  export declare class SQLiteAdapter implements DatabaseAdapter {
3
20
  private db;
4
21
  private _lastInsertId;
@@ -31,7 +31,7 @@ export { seedTable, seedOrm, seedModels, autoFieldMap } from "./seeder.js";
31
31
  export type { SeedSummary, SeedOptions } from "./seeder.js";
32
32
  export { ObjectId, InvalidId, DocStoreDriverMissing, SqliteDatabase, SqliteCollection, Cursor, getCollection, isServerless, resetDefaultStore, closeDocStore, encodeValue, decodeValue, compileFilter, } from "./docstore.js";
33
33
  export type { InsertOneResult, InsertManyResult, UpdateResult, DeleteResult, } from "./docstore.js";
34
- export { SQLiteAdapter } from "./adapters/sqlite.js";
34
+ export { SQLiteAdapter, resolveSqlitePath } from "./adapters/sqlite.js";
35
35
  export { PostgresAdapter } from "./adapters/postgres.js";
36
36
  export type { PostgresConfig } from "./adapters/postgres.js";
37
37
  export { MysqlAdapter } from "./adapters/mysql.js";