@typicalday/firegraph 0.14.0 → 0.15.0
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/README.md +23 -3
- package/dist/{backend-DuvHGgK1.d.cts → backend-BpYLdwCW.d.cts} +1 -1
- package/dist/{backend-DuvHGgK1.d.ts → backend-BpYLdwCW.d.ts} +1 -1
- package/dist/backend-CvImIwTY.d.cts +137 -0
- package/dist/backend-YH5HtawN.d.ts +137 -0
- package/dist/backend.cjs +2 -3
- package/dist/backend.cjs.map +1 -1
- package/dist/backend.d.cts +2 -2
- package/dist/backend.d.ts +2 -2
- package/dist/backend.js +1 -1
- package/dist/{chunk-WRTFC5NG.js → chunk-5HIRYV2S.js} +13 -36
- package/dist/chunk-5HIRYV2S.js.map +1 -0
- package/dist/{chunk-PAD7WFFU.js → chunk-7IEZ6IYY.js} +36 -10
- package/dist/chunk-7IEZ6IYY.js.map +1 -0
- package/dist/chunk-FODIMIWY.js +721 -0
- package/dist/chunk-FODIMIWY.js.map +1 -0
- package/dist/chunk-NGAJCALM.js +34 -0
- package/dist/chunk-NGAJCALM.js.map +1 -0
- package/dist/{chunk-TK64DNVK.js → chunk-SIHE4UY4.js} +3 -4
- package/dist/chunk-SIHE4UY4.js.map +1 -0
- package/dist/chunk-ULRDQ6HZ.js +862 -0
- package/dist/chunk-ULRDQ6HZ.js.map +1 -0
- package/dist/{client-BKi3vk0Q.d.ts → client-B5o39X79.d.ts} +1 -1
- package/dist/{client-BrsaXtDV.d.cts → client-BGHwxwPg.d.cts} +1 -1
- package/dist/{client-Bk2Cm6xv.d.cts → client-DoyEdJ5w.d.cts} +1 -1
- package/dist/{client-Bk2Cm6xv.d.ts → client-DoyEdJ5w.d.ts} +1 -1
- package/dist/cloudflare/index.cjs +155 -165
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +73 -70
- package/dist/cloudflare/index.d.ts +73 -70
- package/dist/cloudflare/index.js +54 -589
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/firestore-enterprise/index.cjs +42 -40
- package/dist/firestore-enterprise/index.cjs.map +1 -1
- package/dist/firestore-enterprise/index.d.cts +3 -3
- package/dist/firestore-enterprise/index.d.ts +3 -3
- package/dist/firestore-enterprise/index.js +19 -35
- package/dist/firestore-enterprise/index.js.map +1 -1
- package/dist/firestore-standard/index.cjs +34 -37
- package/dist/firestore-standard/index.cjs.map +1 -1
- package/dist/firestore-standard/index.d.cts +3 -3
- package/dist/firestore-standard/index.d.ts +3 -3
- package/dist/firestore-standard/index.js +10 -34
- package/dist/firestore-standard/index.js.map +1 -1
- package/dist/index.cjs +2 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/query-client/index.d.cts +2 -2
- package/dist/query-client/index.d.ts +2 -2
- package/dist/{registry-Bc7h6WTM.d.cts → registry-BGh7Jqpb.d.cts} +2 -2
- package/dist/{registry-C2KUPVZj.d.ts → registry-tKTb5Kx1.d.ts} +2 -2
- package/dist/sqlite/index.cjs +585 -378
- package/dist/sqlite/index.cjs.map +1 -1
- package/dist/sqlite/index.d.cts +4 -110
- package/dist/sqlite/index.d.ts +4 -110
- package/dist/sqlite/index.js +7 -1144
- package/dist/sqlite/index.js.map +1 -1
- package/dist/sqlite/local.cjs +1835 -0
- package/dist/sqlite/local.cjs.map +1 -0
- package/dist/sqlite/local.d.cts +83 -0
- package/dist/sqlite/local.d.ts +83 -0
- package/dist/sqlite/local.js +121 -0
- package/dist/sqlite/local.js.map +1 -0
- package/package.json +15 -1
- package/dist/chunk-4MMQ5W74.js +0 -288
- package/dist/chunk-4MMQ5W74.js.map +0 -1
- package/dist/chunk-PAD7WFFU.js.map +0 -1
- package/dist/chunk-TK64DNVK.js.map +0 -1
- package/dist/chunk-WRTFC5NG.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Database } from 'better-sqlite3';
|
|
2
|
+
import { S as StorageBackend } from '../backend-BpYLdwCW.js';
|
|
3
|
+
import { a as SqliteCapability, S as SqliteBackendOptions, b as SqliteExecutor } from '../backend-YH5HtawN.js';
|
|
4
|
+
import '@google-cloud/firestore';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Local SQLite backend over `better-sqlite3`.
|
|
8
|
+
*
|
|
9
|
+
* This entry point is published as `firegraph/sqlite-local` and is the only
|
|
10
|
+
* module in the library that references `better-sqlite3` — keep it out of
|
|
11
|
+
* `firegraph/sqlite` so that D1 / workerd bundles never see the native
|
|
12
|
+
* dependency. `better-sqlite3` is loaded via dynamic `import()` at factory
|
|
13
|
+
* call time, so merely importing this module stays side-effect free.
|
|
14
|
+
*
|
|
15
|
+
* The factory accepts either a database file path (`':memory:'` works) or an
|
|
16
|
+
* already-open `better-sqlite3` Database. Path-opened databases get
|
|
17
|
+
* `journal_mode = WAL` and a `busy_timeout` applied; caller-provided
|
|
18
|
+
* databases are used as-is (only `busy_timeout` is set) since the caller
|
|
19
|
+
* owns their pragma configuration.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface LocalSqliteBackendOptions extends SqliteBackendOptions {
|
|
23
|
+
/** Root graph table name. Defaults to `'firegraph'`. */
|
|
24
|
+
tableName?: string;
|
|
25
|
+
/**
|
|
26
|
+
* `PRAGMA busy_timeout` in milliseconds — how long a connection waits on a
|
|
27
|
+
* lock held by another process before erroring. Defaults to 5000.
|
|
28
|
+
*/
|
|
29
|
+
busyTimeoutMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Extra pragmas applied after the defaults, e.g.
|
|
32
|
+
* `{ synchronous: 'NORMAL', cache_size: -64000 }`. Applied in object
|
|
33
|
+
* order via `PRAGMA <key> = <value>`.
|
|
34
|
+
*/
|
|
35
|
+
pragmas?: Record<string, string | number>;
|
|
36
|
+
/**
|
|
37
|
+
* When opening by path: throw if the file does not already exist instead
|
|
38
|
+
* of creating it. Defaults to false.
|
|
39
|
+
*/
|
|
40
|
+
fileMustExist?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface LocalSqliteBackend {
|
|
43
|
+
/** The graph storage backend — pass to `createGraphClient`. */
|
|
44
|
+
backend: StorageBackend<SqliteCapability>;
|
|
45
|
+
/** The underlying better-sqlite3 database, for raw access. */
|
|
46
|
+
db: Database;
|
|
47
|
+
/**
|
|
48
|
+
* Close the database. No-op when the factory was given an already-open
|
|
49
|
+
* Database (the caller owns its lifecycle).
|
|
50
|
+
*/
|
|
51
|
+
close(): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build a transaction-capable `SqliteExecutor` over a better-sqlite3
|
|
55
|
+
* Database. Interactive transactions use manual `BEGIN IMMEDIATE` /
|
|
56
|
+
* `COMMIT` / `ROLLBACK` because `db.transaction()` requires a synchronous
|
|
57
|
+
* callback while `SqliteExecutor.transaction` callbacks are async.
|
|
58
|
+
*
|
|
59
|
+
* Exported for callers that want to wire `createSqliteBackend` directly
|
|
60
|
+
* (e.g. to share one executor across several root tables).
|
|
61
|
+
*/
|
|
62
|
+
declare function createBetterSqliteExecutor(db: Database): SqliteExecutor;
|
|
63
|
+
/**
|
|
64
|
+
* Open (or wrap) a local SQLite database and return a graph storage backend
|
|
65
|
+
* over it.
|
|
66
|
+
*
|
|
67
|
+
* ```typescript
|
|
68
|
+
* import { createLocalSqliteBackend } from 'firegraph/sqlite-local';
|
|
69
|
+
* import { createGraphClient } from 'firegraph/sqlite';
|
|
70
|
+
*
|
|
71
|
+
* const { backend, close } = await createLocalSqliteBackend('./graph.db');
|
|
72
|
+
* const client = createGraphClient(backend);
|
|
73
|
+
* // ... use the client ...
|
|
74
|
+
* close();
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* Requires `better-sqlite3` to be installed (declared as an optional peer
|
|
78
|
+
* dependency). The factory is async because the driver is loaded via
|
|
79
|
+
* dynamic `import()`.
|
|
80
|
+
*/
|
|
81
|
+
declare function createLocalSqliteBackend(pathOrDb: string | Database, options?: LocalSqliteBackendOptions): Promise<LocalSqliteBackend>;
|
|
82
|
+
|
|
83
|
+
export { type LocalSqliteBackend, type LocalSqliteBackendOptions, createBetterSqliteExecutor, createLocalSqliteBackend };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSqliteBackend
|
|
3
|
+
} from "../chunk-FODIMIWY.js";
|
|
4
|
+
import "../chunk-ULRDQ6HZ.js";
|
|
5
|
+
import "../chunk-2DHMNTV6.js";
|
|
6
|
+
import "../chunk-N5HFDWQX.js";
|
|
7
|
+
import "../chunk-NGAJCALM.js";
|
|
8
|
+
import {
|
|
9
|
+
FiregraphError
|
|
10
|
+
} from "../chunk-SIHE4UY4.js";
|
|
11
|
+
import "../chunk-EQJUUVFG.js";
|
|
12
|
+
|
|
13
|
+
// src/sqlite/local.ts
|
|
14
|
+
function createBetterSqliteExecutor(db) {
|
|
15
|
+
return {
|
|
16
|
+
async all(sql, params) {
|
|
17
|
+
return db.prepare(sql).all(...params);
|
|
18
|
+
},
|
|
19
|
+
async run(sql, params) {
|
|
20
|
+
db.prepare(sql).run(...params);
|
|
21
|
+
},
|
|
22
|
+
async batch(statements) {
|
|
23
|
+
const tx = db.transaction((stmts) => {
|
|
24
|
+
for (const s of stmts) {
|
|
25
|
+
db.prepare(s.sql).run(...s.params);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
tx(statements);
|
|
29
|
+
},
|
|
30
|
+
async transaction(fn) {
|
|
31
|
+
db.exec("BEGIN IMMEDIATE");
|
|
32
|
+
try {
|
|
33
|
+
const result = await fn({
|
|
34
|
+
async all(sql, params) {
|
|
35
|
+
return db.prepare(sql).all(...params);
|
|
36
|
+
},
|
|
37
|
+
async run(sql, params) {
|
|
38
|
+
db.prepare(sql).run(...params);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
db.exec("COMMIT");
|
|
42
|
+
return result;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
db.exec("ROLLBACK");
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function isDatabase(value) {
|
|
51
|
+
return typeof value === "object" && value !== null && typeof value.prepare === "function" && typeof value.exec === "function";
|
|
52
|
+
}
|
|
53
|
+
var PRAGMA_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
54
|
+
var PRAGMA_VALUE_PATTERN = /^-?[A-Za-z0-9_]+$/;
|
|
55
|
+
function applyPragmas(db, pragmas) {
|
|
56
|
+
for (const [key, value] of Object.entries(pragmas)) {
|
|
57
|
+
if (!PRAGMA_KEY_PATTERN.test(key)) {
|
|
58
|
+
throw new FiregraphError(`Invalid pragma name: ${JSON.stringify(key)}`, "INVALID_ARGUMENT");
|
|
59
|
+
}
|
|
60
|
+
if (!PRAGMA_VALUE_PATTERN.test(String(value)) || typeof value === "number" && !Number.isFinite(value)) {
|
|
61
|
+
throw new FiregraphError(
|
|
62
|
+
`Invalid pragma value for ${key}: ${JSON.stringify(value)}`,
|
|
63
|
+
"INVALID_ARGUMENT"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
db.pragma(`${key} = ${value}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function createLocalSqliteBackend(pathOrDb, options = {}) {
|
|
70
|
+
const {
|
|
71
|
+
tableName = "firegraph",
|
|
72
|
+
busyTimeoutMs = 5e3,
|
|
73
|
+
pragmas,
|
|
74
|
+
fileMustExist,
|
|
75
|
+
...backendOptions
|
|
76
|
+
} = options;
|
|
77
|
+
let db;
|
|
78
|
+
let ownsDb;
|
|
79
|
+
if (typeof pathOrDb === "string") {
|
|
80
|
+
let Database;
|
|
81
|
+
try {
|
|
82
|
+
Database = (await import("better-sqlite3")).default;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
throw new FiregraphError(
|
|
85
|
+
`createLocalSqliteBackend requires the optional peer dependency 'better-sqlite3' \u2014 install it to use the local SQLite backend (${err instanceof Error ? err.message : String(err)})`,
|
|
86
|
+
"MISSING_DEPENDENCY"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
db = new Database(pathOrDb, fileMustExist ? { fileMustExist: true } : {});
|
|
90
|
+
ownsDb = true;
|
|
91
|
+
db.pragma("journal_mode = WAL");
|
|
92
|
+
} else if (isDatabase(pathOrDb)) {
|
|
93
|
+
db = pathOrDb;
|
|
94
|
+
ownsDb = false;
|
|
95
|
+
} else {
|
|
96
|
+
throw new FiregraphError(
|
|
97
|
+
"createLocalSqliteBackend expects a file path or an open better-sqlite3 Database",
|
|
98
|
+
"INVALID_ARGUMENT"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);
|
|
102
|
+
if (pragmas) {
|
|
103
|
+
applyPragmas(db, pragmas);
|
|
104
|
+
}
|
|
105
|
+
const backend = createSqliteBackend(createBetterSqliteExecutor(db), tableName, backendOptions);
|
|
106
|
+
let closed = false;
|
|
107
|
+
return {
|
|
108
|
+
backend,
|
|
109
|
+
db,
|
|
110
|
+
close() {
|
|
111
|
+
if (closed || !ownsDb) return;
|
|
112
|
+
closed = true;
|
|
113
|
+
db.close();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
createBetterSqliteExecutor,
|
|
119
|
+
createLocalSqliteBackend
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/sqlite/local.ts"],"sourcesContent":["/**\n * Local SQLite backend over `better-sqlite3`.\n *\n * This entry point is published as `firegraph/sqlite-local` and is the only\n * module in the library that references `better-sqlite3` — keep it out of\n * `firegraph/sqlite` so that D1 / workerd bundles never see the native\n * dependency. `better-sqlite3` is loaded via dynamic `import()` at factory\n * call time, so merely importing this module stays side-effect free.\n *\n * The factory accepts either a database file path (`':memory:'` works) or an\n * already-open `better-sqlite3` Database. Path-opened databases get\n * `journal_mode = WAL` and a `busy_timeout` applied; caller-provided\n * databases are used as-is (only `busy_timeout` is set) since the caller\n * owns their pragma configuration.\n */\n\nimport type { Database as BetterSqliteDb, default as BetterSqliteDatabase } from 'better-sqlite3';\n\nimport { FiregraphError } from '../errors.js';\nimport type { StorageBackend } from '../internal/backend.js';\nimport type { SqliteExecutor, SqliteTxExecutor } from '../internal/sqlite-executor.js';\nimport type { SqliteBackendOptions, SqliteCapability } from './backend.js';\nimport { createSqliteBackend } from './backend.js';\n\nexport interface LocalSqliteBackendOptions extends SqliteBackendOptions {\n /** Root graph table name. Defaults to `'firegraph'`. */\n tableName?: string;\n /**\n * `PRAGMA busy_timeout` in milliseconds — how long a connection waits on a\n * lock held by another process before erroring. Defaults to 5000.\n */\n busyTimeoutMs?: number;\n /**\n * Extra pragmas applied after the defaults, e.g.\n * `{ synchronous: 'NORMAL', cache_size: -64000 }`. Applied in object\n * order via `PRAGMA <key> = <value>`.\n */\n pragmas?: Record<string, string | number>;\n /**\n * When opening by path: throw if the file does not already exist instead\n * of creating it. Defaults to false.\n */\n fileMustExist?: boolean;\n}\n\nexport interface LocalSqliteBackend {\n /** The graph storage backend — pass to `createGraphClient`. */\n backend: StorageBackend<SqliteCapability>;\n /** The underlying better-sqlite3 database, for raw access. */\n db: BetterSqliteDb;\n /**\n * Close the database. No-op when the factory was given an already-open\n * Database (the caller owns its lifecycle).\n */\n close(): void;\n}\n\n/**\n * Build a transaction-capable `SqliteExecutor` over a better-sqlite3\n * Database. Interactive transactions use manual `BEGIN IMMEDIATE` /\n * `COMMIT` / `ROLLBACK` because `db.transaction()` requires a synchronous\n * callback while `SqliteExecutor.transaction` callbacks are async.\n *\n * Exported for callers that want to wire `createSqliteBackend` directly\n * (e.g. to share one executor across several root tables).\n */\nexport function createBetterSqliteExecutor(db: BetterSqliteDb): SqliteExecutor {\n return {\n async all(sql: string, params: unknown[]): Promise<Record<string, unknown>[]> {\n return db.prepare(sql).all(...params) as Record<string, unknown>[];\n },\n async run(sql: string, params: unknown[]): Promise<void> {\n db.prepare(sql).run(...params);\n },\n async batch(statements): Promise<void> {\n const tx = db.transaction((stmts: typeof statements) => {\n for (const s of stmts) {\n db.prepare(s.sql).run(...s.params);\n }\n });\n tx(statements);\n },\n async transaction<T>(fn: (tx: SqliteTxExecutor) => Promise<T>): Promise<T> {\n db.exec('BEGIN IMMEDIATE');\n try {\n const result = await fn({\n async all(sql: string, params: unknown[]) {\n return db.prepare(sql).all(...params) as Record<string, unknown>[];\n },\n async run(sql: string, params: unknown[]) {\n db.prepare(sql).run(...params);\n },\n });\n db.exec('COMMIT');\n return result;\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n },\n };\n}\n\nfunction isDatabase(value: unknown): value is BetterSqliteDb {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { prepare?: unknown }).prepare === 'function' &&\n typeof (value as { exec?: unknown }).exec === 'function'\n );\n}\n\nconst PRAGMA_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n// Pragma values are identifiers (WAL, NORMAL) or integers — never compound\n// expressions, so anything else is rejected rather than interpolated.\nconst PRAGMA_VALUE_PATTERN = /^-?[A-Za-z0-9_]+$/;\n\nfunction applyPragmas(db: BetterSqliteDb, pragmas: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(pragmas)) {\n if (!PRAGMA_KEY_PATTERN.test(key)) {\n throw new FiregraphError(`Invalid pragma name: ${JSON.stringify(key)}`, 'INVALID_ARGUMENT');\n }\n if (\n !PRAGMA_VALUE_PATTERN.test(String(value)) ||\n (typeof value === 'number' && !Number.isFinite(value))\n ) {\n throw new FiregraphError(\n `Invalid pragma value for ${key}: ${JSON.stringify(value)}`,\n 'INVALID_ARGUMENT',\n );\n }\n db.pragma(`${key} = ${value}`);\n }\n}\n\n/**\n * Open (or wrap) a local SQLite database and return a graph storage backend\n * over it.\n *\n * ```typescript\n * import { createLocalSqliteBackend } from 'firegraph/sqlite-local';\n * import { createGraphClient } from 'firegraph/sqlite';\n *\n * const { backend, close } = await createLocalSqliteBackend('./graph.db');\n * const client = createGraphClient(backend);\n * // ... use the client ...\n * close();\n * ```\n *\n * Requires `better-sqlite3` to be installed (declared as an optional peer\n * dependency). The factory is async because the driver is loaded via\n * dynamic `import()`.\n */\nexport async function createLocalSqliteBackend(\n pathOrDb: string | BetterSqliteDb,\n options: LocalSqliteBackendOptions = {},\n): Promise<LocalSqliteBackend> {\n const {\n tableName = 'firegraph',\n busyTimeoutMs = 5000,\n pragmas,\n fileMustExist,\n ...backendOptions\n } = options;\n\n let db: BetterSqliteDb;\n let ownsDb: boolean;\n if (typeof pathOrDb === 'string') {\n let Database: typeof BetterSqliteDatabase;\n try {\n Database = (await import('better-sqlite3')).default;\n } catch (err) {\n throw new FiregraphError(\n `createLocalSqliteBackend requires the optional peer dependency 'better-sqlite3' — install it to use the local SQLite backend (${\n err instanceof Error ? err.message : String(err)\n })`,\n 'MISSING_DEPENDENCY',\n );\n }\n db = new Database(pathOrDb, fileMustExist ? { fileMustExist: true } : {});\n ownsDb = true;\n // WAL lets concurrent readers coexist with a writer — the right default\n // for a long-lived local graph file. On ':memory:' databases SQLite\n // reports 'memory' and ignores the request, which is fine.\n db.pragma('journal_mode = WAL');\n } else if (isDatabase(pathOrDb)) {\n db = pathOrDb;\n ownsDb = false;\n } else {\n throw new FiregraphError(\n 'createLocalSqliteBackend expects a file path or an open better-sqlite3 Database',\n 'INVALID_ARGUMENT',\n );\n }\n\n db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);\n if (pragmas) {\n applyPragmas(db, pragmas);\n }\n\n const backend = createSqliteBackend(createBetterSqliteExecutor(db), tableName, backendOptions);\n let closed = false;\n return {\n backend,\n db,\n close(): void {\n if (closed || !ownsDb) return;\n closed = true;\n db.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typicalday/firegraph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Generic Firestore adjacency graph client with smart query planning",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -96,6 +96,16 @@
|
|
|
96
96
|
"types": "./dist/sqlite/index.d.cts",
|
|
97
97
|
"default": "./dist/sqlite/index.cjs"
|
|
98
98
|
}
|
|
99
|
+
},
|
|
100
|
+
"./sqlite-local": {
|
|
101
|
+
"import": {
|
|
102
|
+
"types": "./dist/sqlite/local.d.ts",
|
|
103
|
+
"default": "./dist/sqlite/local.js"
|
|
104
|
+
},
|
|
105
|
+
"require": {
|
|
106
|
+
"types": "./dist/sqlite/local.d.cts",
|
|
107
|
+
"default": "./dist/sqlite/local.cjs"
|
|
108
|
+
}
|
|
99
109
|
}
|
|
100
110
|
},
|
|
101
111
|
"bin": {
|
|
@@ -136,6 +146,7 @@
|
|
|
136
146
|
"peerDependencies": {
|
|
137
147
|
"@cloudflare/workers-types": "^4.20240419.0",
|
|
138
148
|
"@google-cloud/firestore": "^8.5.0",
|
|
149
|
+
"better-sqlite3": ">=9.0.0",
|
|
139
150
|
"react": "^18.0.0 || ^19.0.0",
|
|
140
151
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
141
152
|
"svelte": "^5.0.0"
|
|
@@ -144,6 +155,9 @@
|
|
|
144
155
|
"@cloudflare/workers-types": {
|
|
145
156
|
"optional": true
|
|
146
157
|
},
|
|
158
|
+
"better-sqlite3": {
|
|
159
|
+
"optional": true
|
|
160
|
+
},
|
|
147
161
|
"react": {
|
|
148
162
|
"optional": true
|
|
149
163
|
},
|
package/dist/chunk-4MMQ5W74.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FiregraphError,
|
|
3
|
-
isDeleteSentinel
|
|
4
|
-
} from "./chunk-TK64DNVK.js";
|
|
5
|
-
import {
|
|
6
|
-
SERIALIZATION_TAG
|
|
7
|
-
} from "./chunk-EQJUUVFG.js";
|
|
8
|
-
|
|
9
|
-
// src/internal/sqlite-index-ddl.ts
|
|
10
|
-
var IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
11
|
-
var JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
12
|
-
function quoteIdent(name) {
|
|
13
|
-
if (!IDENT_RE.test(name)) {
|
|
14
|
-
throw new FiregraphError(
|
|
15
|
-
`Invalid SQL identifier in index DDL: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,
|
|
16
|
-
"INVALID_INDEX"
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return `"${name}"`;
|
|
20
|
-
}
|
|
21
|
-
function fnv1a32(str) {
|
|
22
|
-
let h = 2166136261;
|
|
23
|
-
for (let i = 0; i < str.length; i++) {
|
|
24
|
-
h ^= str.charCodeAt(i);
|
|
25
|
-
h = Math.imul(h, 16777619);
|
|
26
|
-
}
|
|
27
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
28
|
-
}
|
|
29
|
-
function normalizeFields(fields) {
|
|
30
|
-
return fields.map((f) => {
|
|
31
|
-
if (typeof f === "string") return { path: f, desc: false };
|
|
32
|
-
if (!f.path || typeof f.path !== "string") {
|
|
33
|
-
throw new FiregraphError(
|
|
34
|
-
`IndexSpec field must be a string or { path: string, desc?: boolean }; got ${JSON.stringify(f)}`,
|
|
35
|
-
"INVALID_INDEX"
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return { path: f.path, desc: !!f.desc };
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function specFingerprint(spec, leadingColumns) {
|
|
42
|
-
const normalized = {
|
|
43
|
-
lead: leadingColumns,
|
|
44
|
-
fields: normalizeFields(spec.fields),
|
|
45
|
-
where: spec.where ?? ""
|
|
46
|
-
};
|
|
47
|
-
return fnv1a32(JSON.stringify(normalized));
|
|
48
|
-
}
|
|
49
|
-
function compileFieldExpr(path, fieldToColumn) {
|
|
50
|
-
const col = fieldToColumn[path];
|
|
51
|
-
if (col) return quoteIdent(col);
|
|
52
|
-
if (path === "data") {
|
|
53
|
-
return `json_extract("data", '$')`;
|
|
54
|
-
}
|
|
55
|
-
if (path.startsWith("data.")) {
|
|
56
|
-
const suffix = path.slice(5);
|
|
57
|
-
const parts = suffix.split(".");
|
|
58
|
-
for (const part of parts) {
|
|
59
|
-
if (!JSON_PATH_KEY_RE.test(part)) {
|
|
60
|
-
throw new FiregraphError(
|
|
61
|
-
`IndexSpec data path "${path}" has invalid component "${part}". Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,
|
|
62
|
-
"INVALID_INDEX"
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return `json_extract("data", '$.${suffix}')`;
|
|
67
|
-
}
|
|
68
|
-
throw new FiregraphError(
|
|
69
|
-
`IndexSpec field "${path}" is not a known firegraph field. Use a top-level field (aType, aUid, axbType, bType, bUid, createdAt, updatedAt, v) or a dotted data path like 'data.status'.`,
|
|
70
|
-
"INVALID_INDEX"
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
function buildIndexDDL(spec, options) {
|
|
74
|
-
const { table, fieldToColumn, leadingColumns = [] } = options;
|
|
75
|
-
if (!spec.fields || spec.fields.length === 0) {
|
|
76
|
-
throw new FiregraphError("IndexSpec.fields must be a non-empty array", "INVALID_INDEX");
|
|
77
|
-
}
|
|
78
|
-
const normalized = normalizeFields(spec.fields);
|
|
79
|
-
const hash = specFingerprint(spec, leadingColumns);
|
|
80
|
-
const indexName = `${table}_idx_${hash}`;
|
|
81
|
-
const cols = [];
|
|
82
|
-
for (const col of leadingColumns) {
|
|
83
|
-
cols.push(quoteIdent(col));
|
|
84
|
-
}
|
|
85
|
-
for (const f of normalized) {
|
|
86
|
-
const expr = compileFieldExpr(f.path, fieldToColumn);
|
|
87
|
-
cols.push(f.desc ? `${expr} DESC` : expr);
|
|
88
|
-
}
|
|
89
|
-
let ddl = `CREATE INDEX IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(table)}(${cols.join(", ")})`;
|
|
90
|
-
if (spec.where) {
|
|
91
|
-
ddl += ` WHERE ${spec.where}`;
|
|
92
|
-
}
|
|
93
|
-
return ddl;
|
|
94
|
-
}
|
|
95
|
-
function dedupeIndexSpecs(specs, leadingColumns = []) {
|
|
96
|
-
const seen = /* @__PURE__ */ new Set();
|
|
97
|
-
const out = [];
|
|
98
|
-
for (const spec of specs) {
|
|
99
|
-
const fp = specFingerprint(spec, leadingColumns);
|
|
100
|
-
if (seen.has(fp)) continue;
|
|
101
|
-
seen.add(fp);
|
|
102
|
-
out.push(spec);
|
|
103
|
-
}
|
|
104
|
-
return out;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/internal/sqlite-data-ops.ts
|
|
108
|
-
var FIRESTORE_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
109
|
-
"Timestamp",
|
|
110
|
-
"GeoPoint",
|
|
111
|
-
"VectorValue",
|
|
112
|
-
"DocumentReference",
|
|
113
|
-
"FieldValue"
|
|
114
|
-
]);
|
|
115
|
-
function isFirestoreSpecialType(value) {
|
|
116
|
-
const ctorName = value.constructor?.name;
|
|
117
|
-
if (ctorName && FIRESTORE_TYPE_NAMES.has(ctorName)) return ctorName;
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
var JSON_PATH_KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
121
|
-
function validateJsonPathKey(key, backendLabel) {
|
|
122
|
-
if (key.length === 0) {
|
|
123
|
-
throw new FiregraphError(
|
|
124
|
-
`${backendLabel}: empty JSON path component is not allowed`,
|
|
125
|
-
"INVALID_QUERY"
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
if (!JSON_PATH_KEY_RE2.test(key)) {
|
|
129
|
-
throw new FiregraphError(
|
|
130
|
-
`${backendLabel}: data field path component "${key}" is not a safe JSON-path identifier. Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceNode/replaceEdge (full-data overwrite) for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,
|
|
131
|
-
"INVALID_QUERY"
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
function jsonBind(value, backendLabel) {
|
|
136
|
-
if (value === void 0) return "null";
|
|
137
|
-
if (value !== null && typeof value === "object") {
|
|
138
|
-
const firestoreType = isFirestoreSpecialType(value);
|
|
139
|
-
if (firestoreType) {
|
|
140
|
-
throw new FiregraphError(
|
|
141
|
-
`${backendLabel} cannot persist a Firestore ${firestoreType} value. Convert to a primitive before writing (e.g. \`ts.toMillis()\` for Timestamp).`,
|
|
142
|
-
"INVALID_ARGUMENT"
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return JSON.stringify(value);
|
|
147
|
-
}
|
|
148
|
-
function compileDataOpsExpr(ops, base, params, backendLabel) {
|
|
149
|
-
if (ops.length === 0) return null;
|
|
150
|
-
const deletes = [];
|
|
151
|
-
const sets = [];
|
|
152
|
-
for (const op of ops) (op.delete ? deletes : sets).push(op);
|
|
153
|
-
let expr = base;
|
|
154
|
-
if (deletes.length > 0) {
|
|
155
|
-
const placeholders = deletes.map(() => "?").join(", ");
|
|
156
|
-
expr = `json_remove(${expr}, ${placeholders})`;
|
|
157
|
-
for (const op of deletes) {
|
|
158
|
-
for (const seg of op.path) validateJsonPathKey(seg, backendLabel);
|
|
159
|
-
params.push(`$.${op.path.join(".")}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (sets.length > 0) {
|
|
163
|
-
const pieces = sets.map(() => "?, json(?)").join(", ");
|
|
164
|
-
expr = `json_set(${expr}, ${pieces})`;
|
|
165
|
-
for (const op of sets) {
|
|
166
|
-
for (const seg of op.path) validateJsonPathKey(seg, backendLabel);
|
|
167
|
-
params.push(`$.${op.path.join(".")}`);
|
|
168
|
-
params.push(jsonBind(op.value, backendLabel));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return expr;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// src/internal/sqlite-payload-guard.ts
|
|
175
|
-
var FIRESTORE_TYPE_NAMES2 = /* @__PURE__ */ new Set([
|
|
176
|
-
"Timestamp",
|
|
177
|
-
"GeoPoint",
|
|
178
|
-
"VectorValue",
|
|
179
|
-
"DocumentReference",
|
|
180
|
-
"FieldValue"
|
|
181
|
-
]);
|
|
182
|
-
function assertJsonSafePayload(data, label) {
|
|
183
|
-
walk(data, [], label);
|
|
184
|
-
}
|
|
185
|
-
function walk(node, path, label) {
|
|
186
|
-
if (node === null || node === void 0) return;
|
|
187
|
-
if (isDeleteSentinel(node)) {
|
|
188
|
-
throw new FiregraphError(
|
|
189
|
-
`${label} backend cannot persist a deleteField() sentinel inside a full-data payload (replaceNode/replaceEdge or first-insert). The sentinel is only valid inside an updateNode/updateEdge dataOps patch. Path: ${formatPath(path)}.`,
|
|
190
|
-
"INVALID_ARGUMENT"
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
const t = typeof node;
|
|
194
|
-
if (t === "symbol" || t === "function") {
|
|
195
|
-
throw new FiregraphError(
|
|
196
|
-
`${label} backend cannot persist a value of type ${t}. JSON.stringify drops it silently. Path: ${formatPath(path)}.`,
|
|
197
|
-
"INVALID_ARGUMENT"
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
if (t === "bigint") {
|
|
201
|
-
throw new FiregraphError(
|
|
202
|
-
`${label} backend cannot persist a value of type bigint. JSON.stringify cannot serialize this type (throws TypeError). Path: ${formatPath(path)}.`,
|
|
203
|
-
"INVALID_ARGUMENT"
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
if (t !== "object") return;
|
|
207
|
-
if (Array.isArray(node)) {
|
|
208
|
-
for (let i = 0; i < node.length; i++) {
|
|
209
|
-
walk(node[i], [...path, String(i)], label);
|
|
210
|
-
}
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const obj = node;
|
|
214
|
-
if (Object.prototype.hasOwnProperty.call(obj, SERIALIZATION_TAG)) {
|
|
215
|
-
const tagValue = obj[SERIALIZATION_TAG];
|
|
216
|
-
throw new FiregraphError(
|
|
217
|
-
`${label} backend cannot persist an object with a \`${SERIALIZATION_TAG}\` key (value: ${formatTagValue(tagValue)}). Recognised tags are valid only on the Firestore backend (migration-sandbox output); a literal \`${SERIALIZATION_TAG}\` field in user data is reserved and not allowed. Path: ${formatPath(path)}.`,
|
|
218
|
-
"INVALID_ARGUMENT"
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
const proto = Object.getPrototypeOf(node);
|
|
222
|
-
if (proto !== null && proto !== Object.prototype) {
|
|
223
|
-
const ctor = node.constructor;
|
|
224
|
-
const ctorName = ctor && typeof ctor.name === "string" ? ctor.name : "<anonymous>";
|
|
225
|
-
if (FIRESTORE_TYPE_NAMES2.has(ctorName)) {
|
|
226
|
-
throw new FiregraphError(
|
|
227
|
-
`${label} backend cannot persist a Firestore ${ctorName} value. Convert to a primitive before writing (e.g. \`ts.toMillis()\` for Timestamp, \`{lat,lng}\` for GeoPoint). Path: ${formatPath(path)}.`,
|
|
228
|
-
"INVALID_ARGUMENT"
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
if (node instanceof Date) return;
|
|
232
|
-
throw new FiregraphError(
|
|
233
|
-
`${label} backend cannot persist a class instance of type ${ctorName}. Only plain objects, arrays, and primitives round-trip safely through JSON storage. Path: ${formatPath(path)}.`,
|
|
234
|
-
"INVALID_ARGUMENT"
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
for (const key of Object.keys(obj)) {
|
|
238
|
-
walk(obj[key], [...path, key], label);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function formatPath(path) {
|
|
242
|
-
return path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
|
|
243
|
-
}
|
|
244
|
-
function formatTagValue(value) {
|
|
245
|
-
if (value === null) return "null";
|
|
246
|
-
if (value === void 0) return "undefined";
|
|
247
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
248
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
249
|
-
return String(value);
|
|
250
|
-
}
|
|
251
|
-
return typeof value;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// src/timestamp.ts
|
|
255
|
-
var GraphTimestampImpl = class _GraphTimestampImpl {
|
|
256
|
-
constructor(seconds, nanoseconds) {
|
|
257
|
-
this.seconds = seconds;
|
|
258
|
-
this.nanoseconds = nanoseconds;
|
|
259
|
-
}
|
|
260
|
-
toDate() {
|
|
261
|
-
return new Date(this.toMillis());
|
|
262
|
-
}
|
|
263
|
-
toMillis() {
|
|
264
|
-
return this.seconds * 1e3 + Math.floor(this.nanoseconds / 1e6);
|
|
265
|
-
}
|
|
266
|
-
toJSON() {
|
|
267
|
-
return { seconds: this.seconds, nanoseconds: this.nanoseconds };
|
|
268
|
-
}
|
|
269
|
-
static fromMillis(ms) {
|
|
270
|
-
const seconds = Math.floor(ms / 1e3);
|
|
271
|
-
const nanoseconds = (ms - seconds * 1e3) * 1e6;
|
|
272
|
-
return new _GraphTimestampImpl(seconds, nanoseconds);
|
|
273
|
-
}
|
|
274
|
-
static now() {
|
|
275
|
-
return _GraphTimestampImpl.fromMillis(Date.now());
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
export {
|
|
280
|
-
isFirestoreSpecialType,
|
|
281
|
-
validateJsonPathKey,
|
|
282
|
-
compileDataOpsExpr,
|
|
283
|
-
assertJsonSafePayload,
|
|
284
|
-
GraphTimestampImpl,
|
|
285
|
-
buildIndexDDL,
|
|
286
|
-
dedupeIndexSpecs
|
|
287
|
-
};
|
|
288
|
-
//# sourceMappingURL=chunk-4MMQ5W74.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/sqlite-index-ddl.ts","../src/internal/sqlite-data-ops.ts","../src/internal/sqlite-payload-guard.ts","../src/timestamp.ts"],"sourcesContent":["/**\n * Translator from `IndexSpec` to SQLite `CREATE INDEX` DDL.\n *\n * Shared between the DO SQLite backend (`src/cloudflare/schema.ts`) and the\n * legacy single-table SQLite backend (`src/internal/sqlite-schema.ts`). The\n * two backends differ only in:\n *\n * 1. Their field→column mapping (no `scope` column in the DO schema).\n * 2. Whether a fixed `scope` leading column is prepended to every index\n * (legacy backend only — DO rows are scoped by DO-instance identity).\n *\n * Both differences are handled via the `fieldToColumn` and `leadingColumns`\n * options; the rest of the emission logic is identical.\n *\n * ## JSON path expression indexes\n *\n * Data-field specs (`data.foo`, `data.nested.bar`) compile to\n * `json_extract(\"data\", '$.foo')` expression indexes. The JSON path\n * literal is inlined — not parametrized — so the SQLite query planner can\n * match the index against the expression emitted by the query compiler\n * (which also inlines the literal after this PR). Path components are\n * validated against a safe identifier pattern so inlining is not an\n * injection risk.\n *\n * ## Index naming\n *\n * Names are `{table}_idx_{hash}` where `hash` is a short FNV-1a of a\n * canonicalized spec. This keeps names stable across runs (so\n * `CREATE INDEX IF NOT EXISTS` is idempotent) and prevents collisions\n * between similar specs. The hash includes the field list, per-field\n * direction, and the `where` predicate.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { IndexFieldSpec, IndexSpec } from '../types.js';\n\n/**\n * Valid SQLite identifier pattern — used for table and column names.\n * Mirrors the validation in `sqlite-schema.ts` / `cloudflare/schema.ts` so\n * this module doesn't need to import one over the other.\n */\nconst IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Safe JSON path component. Must match `JSON_PATH_KEY_RE` in the SQLite\n * query compilers — an index is only useful if the query emits an\n * identical `json_extract` expression.\n */\nconst JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\nfunction quoteIdent(name: string): string {\n if (!IDENT_RE.test(name)) {\n throw new FiregraphError(\n `Invalid SQL identifier in index DDL: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,\n 'INVALID_INDEX',\n );\n }\n return `\"${name}\"`;\n}\n\n/**\n * FNV-1a 32-bit hash, returned as 8-char hex. Non-cryptographic;\n * used only to produce short, stable index names.\n */\nfunction fnv1a32(str: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\nfunction normalizeFields(\n fields: Array<string | IndexFieldSpec>,\n): Array<{ path: string; desc: boolean }> {\n return fields.map((f) => {\n if (typeof f === 'string') return { path: f, desc: false };\n if (!f.path || typeof f.path !== 'string') {\n throw new FiregraphError(\n `IndexSpec field must be a string or { path: string, desc?: boolean }; got ${JSON.stringify(f)}`,\n 'INVALID_INDEX',\n );\n }\n return { path: f.path, desc: !!f.desc };\n });\n}\n\nfunction specFingerprint(spec: IndexSpec, leadingColumns: string[]): string {\n // Canonical form: JSON of normalized fields + leading cols + where.\n // Leading columns are part of the fingerprint so the same spec under\n // two different backends gets distinct names (though in practice only\n // one backend compiles a given spec).\n const normalized = {\n lead: leadingColumns,\n fields: normalizeFields(spec.fields),\n where: spec.where ?? '',\n };\n return fnv1a32(JSON.stringify(normalized));\n}\n\n/**\n * Compile one field path to its SQLite column expression.\n *\n * - Firegraph top-level fields (`aType`, `createdAt`, …) → mapped column.\n * - `data.foo` / `data.foo.bar` → `json_extract(\"data\", '$.foo.bar')`.\n * - `data` alone → `json_extract(\"data\", '$')`.\n */\nfunction compileFieldExpr(path: string, fieldToColumn: Record<string, string>): string {\n const col = fieldToColumn[path];\n if (col) return quoteIdent(col);\n\n if (path === 'data') {\n return `json_extract(\"data\", '$')`;\n }\n if (path.startsWith('data.')) {\n const suffix = path.slice(5);\n const parts = suffix.split('.');\n for (const part of parts) {\n if (!JSON_PATH_KEY_RE.test(part)) {\n throw new FiregraphError(\n `IndexSpec data path \"${path}\" has invalid component \"${part}\". ` +\n `Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,\n 'INVALID_INDEX',\n );\n }\n }\n // Inline the path literal (no parameter). Validated components above\n // are safe to embed — no quote or escape characters.\n return `json_extract(\"data\", '$.${suffix}')`;\n }\n\n throw new FiregraphError(\n `IndexSpec field \"${path}\" is not a known firegraph field. ` +\n `Use a top-level field (aType, aUid, axbType, bType, bUid, createdAt, updatedAt, v) ` +\n `or a dotted data path like 'data.status'.`,\n 'INVALID_INDEX',\n );\n}\n\nexport interface SqliteIndexDDLOptions {\n /** Target table. */\n table: string;\n /** Map from firegraph field name to SQLite column name. */\n fieldToColumn: Record<string, string>;\n /**\n * Columns prepended to every index's field list (leading ASC). Used by\n * the legacy shared-table SQLite backend to lead every index with\n * `scope`, matching the predicate its query compiler emits.\n *\n * Identifier names only — no JSON paths or expressions.\n */\n leadingColumns?: string[];\n}\n\n/**\n * Emit the `CREATE INDEX IF NOT EXISTS` DDL for one `IndexSpec`.\n *\n * Returns a single SQL string. Name is deterministic (same spec → same\n * name across runs), so re-running the bootstrap is idempotent.\n */\nexport function buildIndexDDL(spec: IndexSpec, options: SqliteIndexDDLOptions): string {\n const { table, fieldToColumn, leadingColumns = [] } = options;\n\n if (!spec.fields || spec.fields.length === 0) {\n throw new FiregraphError('IndexSpec.fields must be a non-empty array', 'INVALID_INDEX');\n }\n\n const normalized = normalizeFields(spec.fields);\n const hash = specFingerprint(spec, leadingColumns);\n const indexName = `${table}_idx_${hash}`;\n\n const cols: string[] = [];\n for (const col of leadingColumns) {\n cols.push(quoteIdent(col));\n }\n for (const f of normalized) {\n const expr = compileFieldExpr(f.path, fieldToColumn);\n cols.push(f.desc ? `${expr} DESC` : expr);\n }\n\n let ddl = `CREATE INDEX IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(table)}(${cols.join(', ')})`;\n\n if (spec.where) {\n // The predicate is inlined verbatim. It comes from library/app\n // configuration — never from user data — so we don't attempt to\n // parse, rewrite, or validate it. Callers authoring partial indexes\n // are responsible for writing a valid SQLite WHERE clause.\n ddl += ` WHERE ${spec.where}`;\n }\n\n return ddl;\n}\n\n/**\n * Deduplicate index specs by their deterministic fingerprint. Same spec\n * declared twice (e.g., by core preset + registry entry) collapses to a\n * single DDL statement.\n */\nexport function dedupeIndexSpecs(\n specs: ReadonlyArray<IndexSpec>,\n leadingColumns: string[] = [],\n): IndexSpec[] {\n const seen = new Set<string>();\n const out: IndexSpec[] = [];\n for (const spec of specs) {\n const fp = specFingerprint(spec, leadingColumns);\n if (seen.has(fp)) continue;\n seen.add(fp);\n out.push(spec);\n }\n return out;\n}\n","/**\n * Shared `dataOps` SQL compilation helpers used by both SQLite-style backends\n * (`internal/sqlite-sql.ts` for the shared-table backend and `cloudflare/sql.ts`\n * for the per-DO backend).\n *\n * The two backends differ in identifier quoting and scope handling, but the\n * `data` column lives in JSON in both, the deep-merge / replace contract is\n * identical, and the `json_set` / `json_remove` expression they emit for a\n * `DataPathOp[]` is byte-for-byte the same. Lifting the helpers here keeps\n * that shape in one place — the comment in `cloudflare/sql.ts` used to read\n * \"keep them in sync\"; this module is what they keep in sync against.\n *\n * The helpers take a `backendLabel` parameter so error messages still\n * distinguish `\"SQLite backend\"` (shared-table) from `\"DO SQLite backend\"`\n * (per-Durable-Object). Identifier quoting is the caller's job — the helpers\n * here only emit JSON-path expressions against an opaque `base` argument,\n * never bare column names.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { DataPathOp } from './write-plan.js';\n\n/**\n * Constructor names of Firestore special types that don't survive a plain\n * `JSON.stringify` round-trip — they have non-enumerable accessors (e.g.\n * `Timestamp.seconds`) or class identity that JSON loses. Detection is by\n * `constructor.name` to keep this module dependency-free (importing\n * `@google-cloud/firestore` here would pollute the Cloudflare Workers bundle —\n * see tests/unit/bundle-pollution.test.ts).\n */\nexport const FIRESTORE_TYPE_NAMES = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n]);\n\nexport function isFirestoreSpecialType(value: object): string | null {\n const ctorName = (value as { constructor?: { name?: string } }).constructor?.name;\n if (ctorName && FIRESTORE_TYPE_NAMES.has(ctorName)) return ctorName;\n return null;\n}\n\n/**\n * Identifiers accepted in `data.<key>` paths and `dataOps` path segments.\n * The pattern (`/^[A-Za-z_][A-Za-z0-9_-]*$/`) covers code-style identifiers\n * (camel, snake, kebab). Silently quoting exotic keys would require symmetric\n * quoting at every read/write call site; any drift produces silent data\n * corruption. Failing loudly at compile time is safer — users with exotic\n * keys can use `replaceNode` / `replaceEdge` (full-data overwrite) instead.\n */\nexport const JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\nexport function validateJsonPathKey(key: string, backendLabel: string): void {\n if (key.length === 0) {\n throw new FiregraphError(\n `${backendLabel}: empty JSON path component is not allowed`,\n 'INVALID_QUERY',\n );\n }\n if (!JSON_PATH_KEY_RE.test(key)) {\n throw new FiregraphError(\n `${backendLabel}: data field path component \"${key}\" is not a safe JSON-path identifier. ` +\n `Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceNode/replaceEdge (full-data overwrite) ` +\n `for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,\n 'INVALID_QUERY',\n );\n }\n}\n\n/**\n * Bind a value as a JSON-serializable string for `json(?)` placeholders in\n * the compiled `json_set` expression. `assertJsonSafePayload` already runs\n * eagerly at the write boundary, so the Firestore-special-type rejection\n * here is defense-in-depth — left in place per the team's preference for\n * symmetric guards across the SQLite compilers.\n */\nexport function jsonBind(value: unknown, backendLabel: string): string {\n if (value === undefined) return 'null';\n if (value !== null && typeof value === 'object') {\n const firestoreType = isFirestoreSpecialType(value);\n if (firestoreType) {\n throw new FiregraphError(\n `${backendLabel} cannot persist a Firestore ${firestoreType} value. ` +\n `Convert to a primitive before writing (e.g. \\`ts.toMillis()\\` for Timestamp).`,\n 'INVALID_ARGUMENT',\n );\n }\n }\n return JSON.stringify(value);\n}\n\n/**\n * Build the SQL expression that applies a list of `DataPathOp`s onto an\n * existing JSON column reference (e.g. `\"data\"` or `COALESCE(\"data\", '{}')`).\n *\n * Returns the full expression (already parenthesised where needed) and pushes\n * the bound parameters onto `params` in left-to-right order. Returns `null`\n * when there are no ops at all — the caller picks a fallback expression.\n *\n * Strategy:\n * 1. `json_remove(<base>, '$.a.b', '$.c', …)` strips delete-ops.\n * 2. `json_set(<#1>, '$.x.y', json(?), '$.z', json(?), …)` writes value-ops.\n * `json(?)` ensures non-string values bind as JSON (objects, arrays,\n * numbers, booleans, null).\n */\nexport function compileDataOpsExpr(\n ops: readonly DataPathOp[],\n base: string,\n params: unknown[],\n backendLabel: string,\n): string | null {\n if (ops.length === 0) return null;\n\n const deletes: DataPathOp[] = [];\n const sets: DataPathOp[] = [];\n for (const op of ops) (op.delete ? deletes : sets).push(op);\n\n let expr = base;\n\n if (deletes.length > 0) {\n const placeholders = deletes.map(() => '?').join(', ');\n expr = `json_remove(${expr}, ${placeholders})`;\n for (const op of deletes) {\n for (const seg of op.path) validateJsonPathKey(seg, backendLabel);\n params.push(`$.${op.path.join('.')}`);\n }\n }\n\n if (sets.length > 0) {\n const pieces = sets.map(() => '?, json(?)').join(', ');\n expr = `json_set(${expr}, ${pieces})`;\n for (const op of sets) {\n for (const seg of op.path) validateJsonPathKey(seg, backendLabel);\n params.push(`$.${op.path.join('.')}`);\n params.push(jsonBind(op.value, backendLabel));\n }\n }\n\n return expr;\n}\n","/**\n * Shared eager-validation helper for SQLite-style backends\n * (`internal/sqlite-sql.ts` and `cloudflare/sql.ts`).\n *\n * Both backends serialise `record.data` (and `update.replaceData`) as a raw\n * JSON blob via `JSON.stringify`. Two classes of value silently corrupt that\n * representation and the cross-backend contract:\n *\n * 1. **Firestore special types** (`Timestamp`, `GeoPoint`, `VectorValue`,\n * `DocumentReference`, `FieldValue`). They have non-enumerable accessors\n * or rely on class identity that JSON drops, so they round-trip as `{}`\n * or garbage. Callers must convert to primitives before writing.\n * 2. **`DELETE_FIELD` sentinel.** A `Symbol` is invisible to\n * `JSON.stringify`. If a caller embeds the sentinel in a `replaceNode`\n * payload or in a fresh-insert (no existing row), the field would\n * silently disappear instead of erroring loudly the way it does for the\n * `dataOps` path — so we reject it eagerly here.\n * 3. **Tagged serialization payloads** (`__firegraph_ser__`). These are the\n * sandbox migration boundary marshalling form. They are valid inside\n * Firestore (the Firestore backend re-hydrates them via\n * `deserializeFirestoreTypes`), but on SQLite they would persist as\n * opaque tagged objects that no downstream reader knows how to interpret.\n * Reject them at the boundary so the failure is loud.\n *\n * The Firestore backend does NOT call this — it accepts those types natively\n * and `deserializeFirestoreTypes` rebuilds tagged values into real Firestore\n * objects on its own write path.\n *\n * Detection avoids `instanceof` so this module stays free of\n * `@google-cloud/firestore`. Constructor-name + duck-type matches the\n * approach used by `bindValue`/`jsonBind` elsewhere in the SQLite compilers.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport { SERIALIZATION_TAG } from './serialization-tag.js';\nimport { isDeleteSentinel } from './write-plan.js';\n\nconst FIRESTORE_TYPE_NAMES = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n]);\n\n/**\n * Walk `data` and throw on any value that the SQLite-style raw-JSON\n * persistence path can't faithfully serialise. `label` distinguishes the\n * caller in error messages (e.g. `'shared-table SQLite'` vs `'DO SQLite'`).\n *\n * Plain objects recurse. Arrays recurse element-wise. Primitives, `null`,\n * and `undefined` are accepted (mirroring how `flattenPatch` treats them\n * during the merge path).\n */\nexport function assertJsonSafePayload(data: unknown, label: string): void {\n walk(data, [], label);\n}\n\nfunction walk(node: unknown, path: readonly string[], label: string): void {\n if (node === null || node === undefined) return;\n if (isDeleteSentinel(node)) {\n throw new FiregraphError(\n `${label} backend cannot persist a deleteField() sentinel inside a ` +\n `full-data payload (replaceNode/replaceEdge or first-insert). The ` +\n `sentinel is only valid inside an updateNode/updateEdge dataOps patch. ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n const t = typeof node;\n if (t === 'symbol' || t === 'function') {\n throw new FiregraphError(\n `${label} backend cannot persist a value of type ${t}. ` +\n `JSON.stringify drops it silently. Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n if (t === 'bigint') {\n throw new FiregraphError(\n `${label} backend cannot persist a value of type bigint. ` +\n `JSON.stringify cannot serialize this type (throws TypeError). ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n if (t !== 'object') return;\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n walk(node[i], [...path, String(i)], label);\n }\n return;\n }\n // Reject any object carrying the firegraph serialization tag — both legit\n // tagged Firestore-type payloads (the migration-sandbox output that round-\n // trips through Firestore) and bogus user data that happens to put a\n // literal `__firegraph_ser__` key on a plain object. SQLite has no\n // Timestamp class to rebuild the tag into, and silently writing the\n // envelope would produce an unreadable column.\n const obj = node as Record<string, unknown>;\n if (Object.prototype.hasOwnProperty.call(obj, SERIALIZATION_TAG)) {\n const tagValue = obj[SERIALIZATION_TAG];\n throw new FiregraphError(\n `${label} backend cannot persist an object with a \\`${SERIALIZATION_TAG}\\` ` +\n `key (value: ${formatTagValue(tagValue)}). Recognised tags are valid only on ` +\n `the Firestore backend (migration-sandbox output); a literal ` +\n `\\`${SERIALIZATION_TAG}\\` field in user data is reserved and not allowed. ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n // Class instances: reject Firestore special types loudly; reject every\n // other class instance generically (Map, Set, Date are caller's\n // responsibility to convert — Date is allowed in filter binds via\n // `bindValue` but not as a stored payload value because JSON.stringify\n // produces a string, not a real Date).\n const proto = Object.getPrototypeOf(node);\n if (proto !== null && proto !== Object.prototype) {\n const ctor = (node as { constructor?: { name?: string } }).constructor;\n const ctorName = ctor && typeof ctor.name === 'string' ? ctor.name : '<anonymous>';\n if (FIRESTORE_TYPE_NAMES.has(ctorName)) {\n throw new FiregraphError(\n `${label} backend cannot persist a Firestore ${ctorName} value. ` +\n `Convert to a primitive before writing (e.g. \\`ts.toMillis()\\` for ` +\n `Timestamp, \\`{lat,lng}\\` for GeoPoint). Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n // Accept Date as an alias for its epoch-ms — it round-trips as an ISO\n // string via JSON.stringify, which the caller chose; not our place to\n // reject. Same for Buffer / typed arrays — they'll JSON-serialize as\n // best they can. Reject only opaque exotic instances that JSON drops.\n if (node instanceof Date) return;\n throw new FiregraphError(\n `${label} backend cannot persist a class instance of type ${ctorName}. ` +\n `Only plain objects, arrays, and primitives round-trip safely through ` +\n `JSON storage. Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n for (const key of Object.keys(obj)) {\n walk(obj[key], [...path, key], label);\n }\n}\n\nfunction formatPath(path: readonly string[]): string {\n return path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n}\n\nfunction formatTagValue(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return typeof value;\n}\n","/**\n * Backend-agnostic timestamp.\n *\n * Structurally compatible with `@google-cloud/firestore`'s `Timestamp` so\n * that records returned by either the Firestore or SQLite backend can be\n * consumed through the same `StoredGraphRecord` shape.\n *\n * Firestore's native `Timestamp` already satisfies this interface, so\n * existing Firestore consumers see no behavior change. The SQLite backend\n * returns instances of `GraphTimestampImpl` which also satisfies it.\n */\n\nexport interface GraphTimestamp {\n readonly seconds: number;\n readonly nanoseconds: number;\n toDate(): Date;\n toMillis(): number;\n}\n\n/**\n * Concrete `GraphTimestamp` implementation used by non-Firestore backends.\n * Mirrors the surface of Firestore's `Timestamp` enough for typical use.\n */\nexport class GraphTimestampImpl implements GraphTimestamp {\n constructor(\n public readonly seconds: number,\n public readonly nanoseconds: number,\n ) {}\n\n toDate(): Date {\n return new Date(this.toMillis());\n }\n\n toMillis(): number {\n return this.seconds * 1000 + Math.floor(this.nanoseconds / 1e6);\n }\n\n toJSON(): { seconds: number; nanoseconds: number } {\n return { seconds: this.seconds, nanoseconds: this.nanoseconds };\n }\n\n static fromMillis(ms: number): GraphTimestampImpl {\n const seconds = Math.floor(ms / 1000);\n const nanoseconds = (ms - seconds * 1000) * 1e6;\n return new GraphTimestampImpl(seconds, nanoseconds);\n }\n\n static now(): GraphTimestampImpl {\n return GraphTimestampImpl.fromMillis(Date.now());\n }\n}\n\n/**\n * Sentinel returned by `StorageBackend.serverTimestamp()` when the backend\n * has no native server-time concept and just wants a placeholder that the\n * adapter resolves to a concrete time at write commit. SQLite backends\n * substitute the wall-clock millis at the moment of `setDoc`/`updateDoc`.\n */\nexport const SERVER_TIMESTAMP_SENTINEL = Symbol.for('firegraph.serverTimestamp');\nexport type ServerTimestampSentinel = typeof SERVER_TIMESTAMP_SENTINEL;\n\nexport function isServerTimestampSentinel(value: unknown): value is ServerTimestampSentinel {\n return value === SERVER_TIMESTAMP_SENTINEL;\n}\n"],"mappings":"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","names":["JSON_PATH_KEY_RE","FIRESTORE_TYPE_NAMES"]}
|