@typicalday/firegraph 0.10.0 → 0.11.1
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 +93 -90
- package/bin/firegraph.mjs +21 -7
- package/dist/{backend-BrqFkbid.d.ts → backend-U-MLShlg.d.ts} +1 -1
- package/dist/{backend-73p5Blx7.d.cts → backend-np4gEVhB.d.cts} +1 -1
- package/dist/backend.d.cts +3 -3
- package/dist/backend.d.ts +3 -3
- package/dist/{chunk-LZOIQHYN.js → chunk-6SB34IPQ.js} +20 -7
- package/dist/chunk-6SB34IPQ.js.map +1 -0
- package/dist/{chunk-SU4FNLC3.js → chunk-EEKWRX5E.js} +1 -1
- package/dist/{chunk-SU4FNLC3.js.map → chunk-EEKWRX5E.js.map} +1 -1
- package/dist/{chunk-YLGXLEUE.js → chunk-GJVVRTQT.js} +5 -14
- package/dist/chunk-GJVVRTQT.js.map +1 -0
- package/dist/cloudflare/index.cjs +151 -27
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +78 -3
- package/dist/cloudflare/index.d.ts +78 -3
- package/dist/cloudflare/index.js +135 -23
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.cjs +4 -13
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/codegen/index.js +1 -1
- package/dist/index.cjs +89 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -21
- package/dist/index.d.ts +106 -21
- package/dist/index.js +70 -116
- package/dist/index.js.map +1 -1
- package/dist/query-client/index.cjs.map +1 -1
- package/dist/query-client/index.js +1 -1
- package/dist/{types-DOemdlVA.d.ts → types-BGWxcpI_.d.cts} +75 -1
- package/dist/{types-DOemdlVA.d.cts → types-BGWxcpI_.d.ts} +75 -1
- package/package.json +35 -27
- package/dist/chunk-LZOIQHYN.js.map +0 -1
- package/dist/chunk-YLGXLEUE.js.map +0 -1
- package/dist/editor/client/assets/index-Bq2bfzeY.js +0 -411
- package/dist/editor/client/assets/index-CJ4m_EOL.css +0 -1
- package/dist/editor/client/index.html +0 -16
- package/dist/editor/server/index.mjs +0 -51566
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { W as WritableRecord, U as UpdatePayload, S as StorageBackend, T as TransactionBackend, B as BatchBackend } from '../backend-
|
|
2
|
-
import { i as QueryFilter,
|
|
1
|
+
import { W as WritableRecord, U as UpdatePayload, S as StorageBackend, T as TransactionBackend, B as BatchBackend } from '../backend-np4gEVhB.cjs';
|
|
2
|
+
import { c as GraphRegistry, I as IndexSpec, i as QueryFilter, z as QueryOptions, C as CascadeResult, F as FindEdgesParams, m as BulkOptions, o as BulkResult, a as GraphClient, D as DynamicGraphClient, S as StoredGraphRecord, d as GraphReader, G as GraphClientOptions, e as DynamicRegistryConfig } from '../types-BGWxcpI_.cjs';
|
|
3
3
|
import '@google-cloud/firestore';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -127,6 +127,24 @@ interface FiregraphDOOptions {
|
|
|
127
127
|
table?: string;
|
|
128
128
|
/** Run schema DDL on first boot. Default: `true`. */
|
|
129
129
|
autoMigrate?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Registry whose per-entry `indexes` get compiled into `CREATE INDEX`
|
|
132
|
+
* statements during schema bootstrap. Supply the same registry you pass
|
|
133
|
+
* to `createGraphClient` on the Worker side to keep DO and client in sync.
|
|
134
|
+
*/
|
|
135
|
+
registry?: GraphRegistry;
|
|
136
|
+
/**
|
|
137
|
+
* Replaces the built-in core index preset
|
|
138
|
+
* (`DEFAULT_CORE_INDEXES`). Supply this when the default set of
|
|
139
|
+
* `(aUid, axbType)`, `(axbType, bUid)`, etc. composites doesn't fit your
|
|
140
|
+
* query shapes — e.g., you want descending timestamps or a reduced set.
|
|
141
|
+
* Entry-level `RegistryEntry.indexes` remain additive on top.
|
|
142
|
+
*
|
|
143
|
+
* Pass `[]` to disable core indexes entirely (advanced — only safe when
|
|
144
|
+
* the provided `registry`'s entries cover every query shape your app
|
|
145
|
+
* issues).
|
|
146
|
+
*/
|
|
147
|
+
coreIndexes?: IndexSpec[];
|
|
130
148
|
}
|
|
131
149
|
declare class FiregraphDO {
|
|
132
150
|
/** @internal — exposed for subclass access, not part of the public RPC. */
|
|
@@ -135,6 +153,10 @@ declare class FiregraphDO {
|
|
|
135
153
|
protected readonly env: unknown;
|
|
136
154
|
/** @internal — table name used by every compiled statement. */
|
|
137
155
|
protected readonly table: string;
|
|
156
|
+
/** @internal — registry consulted by `runSchema` for per-entry indexes. */
|
|
157
|
+
protected readonly registry?: GraphRegistry;
|
|
158
|
+
/** @internal — overrides `DEFAULT_CORE_INDEXES` when set. */
|
|
159
|
+
protected readonly coreIndexes?: IndexSpec[];
|
|
138
160
|
constructor(ctx: DurableObjectStateLike, env: unknown, options?: FiregraphDOOptions);
|
|
139
161
|
_fgGetDoc(docId: string): Promise<DORecordWire | null>;
|
|
140
162
|
_fgQuery(filters: QueryFilter[], options?: QueryOptions): Promise<DORecordWire[]>;
|
|
@@ -451,4 +473,57 @@ declare function createDOClient(namespace: FiregraphNamespace, rootKey: string,
|
|
|
451
473
|
declare function createSiblingClient(client: GraphClient, siblingRootKey: string): GraphClient;
|
|
452
474
|
declare function createSiblingClient(client: DynamicGraphClient, siblingRootKey: string): DynamicGraphClient;
|
|
453
475
|
|
|
454
|
-
|
|
476
|
+
/**
|
|
477
|
+
* Flat SQLite schema for a single firegraph DO.
|
|
478
|
+
*
|
|
479
|
+
* Each `FiregraphDO` instance owns its own SQLite database and holds exactly
|
|
480
|
+
* one subgraph's triples. Subgraph isolation is physical (one DO per
|
|
481
|
+
* subgraph), so there is no `scope` column — every row in this DO belongs to
|
|
482
|
+
* the same logical scope. This is the Cloudflare-native design: the scope
|
|
483
|
+
* discriminator used by the legacy shared-table SQLite backend
|
|
484
|
+
* (`src/internal/sqlite-schema.ts`) does not exist here.
|
|
485
|
+
*
|
|
486
|
+
* Document IDs:
|
|
487
|
+
* - Nodes: the UID itself
|
|
488
|
+
* - Edges: `shard:aUid:axbType:bUid`
|
|
489
|
+
*
|
|
490
|
+
* Indexes come from two sources and are appended to the DDL list:
|
|
491
|
+
*
|
|
492
|
+
* 1. The core preset (`DEFAULT_CORE_INDEXES`), overridable per-DO via
|
|
493
|
+
* `FiregraphDOOptions.coreIndexes`.
|
|
494
|
+
* 2. Per-registry-entry `indexes` declared on `RegistryEntry` (from code or
|
|
495
|
+
* `meta.json` via entity discovery).
|
|
496
|
+
*
|
|
497
|
+
* Both sets are deduplicated by canonical fingerprint, so declaring the same
|
|
498
|
+
* composite twice (e.g., by preset + registry entry) collapses to one
|
|
499
|
+
* `CREATE INDEX`.
|
|
500
|
+
*/
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Options controlling DDL emission for `buildDOSchemaStatements`.
|
|
504
|
+
*/
|
|
505
|
+
interface BuildDOSchemaOptions {
|
|
506
|
+
/**
|
|
507
|
+
* Replaces the built-in core preset. Defaults to `DEFAULT_CORE_INDEXES`.
|
|
508
|
+
* Pass `[]` to disable core indexes entirely.
|
|
509
|
+
*/
|
|
510
|
+
coreIndexes?: IndexSpec[];
|
|
511
|
+
/**
|
|
512
|
+
* Registry contributing per-triple `indexes` declarations. Entries with
|
|
513
|
+
* no `indexes` field are ignored; the rest are flattened and deduplicated
|
|
514
|
+
* against the core preset by canonical fingerprint.
|
|
515
|
+
*/
|
|
516
|
+
registry?: GraphRegistry;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* DDL statements that create the firegraph table and its indexes. Returned
|
|
520
|
+
* as separate statements because DO SQLite's `exec()` runs one statement per
|
|
521
|
+
* call. Run via `FiregraphDO.ensureSchema()` on DO boot.
|
|
522
|
+
*
|
|
523
|
+
* The CREATE TABLE statement is always first; index statements follow in
|
|
524
|
+
* deterministic order. Same specs across runs produce the same statements,
|
|
525
|
+
* so `CREATE INDEX IF NOT EXISTS` is idempotent.
|
|
526
|
+
*/
|
|
527
|
+
declare function buildDOSchemaStatements(table: string, options?: BuildDOSchemaOptions): string[];
|
|
528
|
+
|
|
529
|
+
export { type BatchOp, type BuildDOSchemaOptions, type DOClientOptions, DORPCBackend, type DORPCBackendOptions, type DOSqlCursor, type DOSqlExecutor, type DOStorage, type DurableObjectIdLike, type DurableObjectStateLike, FiregraphDO, type FiregraphDOOptions, type FiregraphNamespace, type FiregraphStub, buildDOSchemaStatements, createDOClient, createSiblingClient };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { W as WritableRecord, U as UpdatePayload, S as StorageBackend, T as TransactionBackend, B as BatchBackend } from '../backend-
|
|
2
|
-
import { i as QueryFilter,
|
|
1
|
+
import { W as WritableRecord, U as UpdatePayload, S as StorageBackend, T as TransactionBackend, B as BatchBackend } from '../backend-U-MLShlg.js';
|
|
2
|
+
import { c as GraphRegistry, I as IndexSpec, i as QueryFilter, z as QueryOptions, C as CascadeResult, F as FindEdgesParams, m as BulkOptions, o as BulkResult, a as GraphClient, D as DynamicGraphClient, S as StoredGraphRecord, d as GraphReader, G as GraphClientOptions, e as DynamicRegistryConfig } from '../types-BGWxcpI_.js';
|
|
3
3
|
import '@google-cloud/firestore';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -127,6 +127,24 @@ interface FiregraphDOOptions {
|
|
|
127
127
|
table?: string;
|
|
128
128
|
/** Run schema DDL on first boot. Default: `true`. */
|
|
129
129
|
autoMigrate?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Registry whose per-entry `indexes` get compiled into `CREATE INDEX`
|
|
132
|
+
* statements during schema bootstrap. Supply the same registry you pass
|
|
133
|
+
* to `createGraphClient` on the Worker side to keep DO and client in sync.
|
|
134
|
+
*/
|
|
135
|
+
registry?: GraphRegistry;
|
|
136
|
+
/**
|
|
137
|
+
* Replaces the built-in core index preset
|
|
138
|
+
* (`DEFAULT_CORE_INDEXES`). Supply this when the default set of
|
|
139
|
+
* `(aUid, axbType)`, `(axbType, bUid)`, etc. composites doesn't fit your
|
|
140
|
+
* query shapes — e.g., you want descending timestamps or a reduced set.
|
|
141
|
+
* Entry-level `RegistryEntry.indexes` remain additive on top.
|
|
142
|
+
*
|
|
143
|
+
* Pass `[]` to disable core indexes entirely (advanced — only safe when
|
|
144
|
+
* the provided `registry`'s entries cover every query shape your app
|
|
145
|
+
* issues).
|
|
146
|
+
*/
|
|
147
|
+
coreIndexes?: IndexSpec[];
|
|
130
148
|
}
|
|
131
149
|
declare class FiregraphDO {
|
|
132
150
|
/** @internal — exposed for subclass access, not part of the public RPC. */
|
|
@@ -135,6 +153,10 @@ declare class FiregraphDO {
|
|
|
135
153
|
protected readonly env: unknown;
|
|
136
154
|
/** @internal — table name used by every compiled statement. */
|
|
137
155
|
protected readonly table: string;
|
|
156
|
+
/** @internal — registry consulted by `runSchema` for per-entry indexes. */
|
|
157
|
+
protected readonly registry?: GraphRegistry;
|
|
158
|
+
/** @internal — overrides `DEFAULT_CORE_INDEXES` when set. */
|
|
159
|
+
protected readonly coreIndexes?: IndexSpec[];
|
|
138
160
|
constructor(ctx: DurableObjectStateLike, env: unknown, options?: FiregraphDOOptions);
|
|
139
161
|
_fgGetDoc(docId: string): Promise<DORecordWire | null>;
|
|
140
162
|
_fgQuery(filters: QueryFilter[], options?: QueryOptions): Promise<DORecordWire[]>;
|
|
@@ -451,4 +473,57 @@ declare function createDOClient(namespace: FiregraphNamespace, rootKey: string,
|
|
|
451
473
|
declare function createSiblingClient(client: GraphClient, siblingRootKey: string): GraphClient;
|
|
452
474
|
declare function createSiblingClient(client: DynamicGraphClient, siblingRootKey: string): DynamicGraphClient;
|
|
453
475
|
|
|
454
|
-
|
|
476
|
+
/**
|
|
477
|
+
* Flat SQLite schema for a single firegraph DO.
|
|
478
|
+
*
|
|
479
|
+
* Each `FiregraphDO` instance owns its own SQLite database and holds exactly
|
|
480
|
+
* one subgraph's triples. Subgraph isolation is physical (one DO per
|
|
481
|
+
* subgraph), so there is no `scope` column — every row in this DO belongs to
|
|
482
|
+
* the same logical scope. This is the Cloudflare-native design: the scope
|
|
483
|
+
* discriminator used by the legacy shared-table SQLite backend
|
|
484
|
+
* (`src/internal/sqlite-schema.ts`) does not exist here.
|
|
485
|
+
*
|
|
486
|
+
* Document IDs:
|
|
487
|
+
* - Nodes: the UID itself
|
|
488
|
+
* - Edges: `shard:aUid:axbType:bUid`
|
|
489
|
+
*
|
|
490
|
+
* Indexes come from two sources and are appended to the DDL list:
|
|
491
|
+
*
|
|
492
|
+
* 1. The core preset (`DEFAULT_CORE_INDEXES`), overridable per-DO via
|
|
493
|
+
* `FiregraphDOOptions.coreIndexes`.
|
|
494
|
+
* 2. Per-registry-entry `indexes` declared on `RegistryEntry` (from code or
|
|
495
|
+
* `meta.json` via entity discovery).
|
|
496
|
+
*
|
|
497
|
+
* Both sets are deduplicated by canonical fingerprint, so declaring the same
|
|
498
|
+
* composite twice (e.g., by preset + registry entry) collapses to one
|
|
499
|
+
* `CREATE INDEX`.
|
|
500
|
+
*/
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Options controlling DDL emission for `buildDOSchemaStatements`.
|
|
504
|
+
*/
|
|
505
|
+
interface BuildDOSchemaOptions {
|
|
506
|
+
/**
|
|
507
|
+
* Replaces the built-in core preset. Defaults to `DEFAULT_CORE_INDEXES`.
|
|
508
|
+
* Pass `[]` to disable core indexes entirely.
|
|
509
|
+
*/
|
|
510
|
+
coreIndexes?: IndexSpec[];
|
|
511
|
+
/**
|
|
512
|
+
* Registry contributing per-triple `indexes` declarations. Entries with
|
|
513
|
+
* no `indexes` field are ignored; the rest are flattened and deduplicated
|
|
514
|
+
* against the core preset by canonical fingerprint.
|
|
515
|
+
*/
|
|
516
|
+
registry?: GraphRegistry;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* DDL statements that create the firegraph table and its indexes. Returned
|
|
520
|
+
* as separate statements because DO SQLite's `exec()` runs one statement per
|
|
521
|
+
* call. Run via `FiregraphDO.ensureSchema()` on DO boot.
|
|
522
|
+
*
|
|
523
|
+
* The CREATE TABLE statement is always first; index statements follow in
|
|
524
|
+
* deterministic order. Same specs across runs produce the same statements,
|
|
525
|
+
* so `CREATE INDEX IF NOT EXISTS` is idempotent.
|
|
526
|
+
*/
|
|
527
|
+
declare function buildDOSchemaStatements(table: string, options?: BuildDOSchemaOptions): string[];
|
|
528
|
+
|
|
529
|
+
export { type BatchOp, type BuildDOSchemaOptions, type DOClientOptions, DORPCBackend, type DORPCBackendOptions, type DOSqlCursor, type DOSqlExecutor, type DOStorage, type DurableObjectIdLike, type DurableObjectStateLike, FiregraphDO, type FiregraphDOOptions, type FiregraphNamespace, type FiregraphStub, buildDOSchemaStatements, createDOClient, createSiblingClient };
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_CORE_INDEXES,
|
|
2
3
|
NODE_RELATION,
|
|
3
4
|
buildEdgeQueryPlan,
|
|
4
5
|
computeEdgeDocId,
|
|
5
6
|
computeNodeDocId,
|
|
6
7
|
createGraphClientFromBackend
|
|
7
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-6SB34IPQ.js";
|
|
8
9
|
import {
|
|
9
10
|
FiregraphError
|
|
10
11
|
} from "../chunk-R7CRGYY4.js";
|
|
@@ -34,6 +35,104 @@ var GraphTimestampImpl = class _GraphTimestampImpl {
|
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
// src/internal/sqlite-index-ddl.ts
|
|
39
|
+
var IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
40
|
+
var JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
41
|
+
function quoteIdent(name) {
|
|
42
|
+
if (!IDENT_RE.test(name)) {
|
|
43
|
+
throw new FiregraphError(
|
|
44
|
+
`Invalid SQL identifier in index DDL: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,
|
|
45
|
+
"INVALID_INDEX"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return `"${name}"`;
|
|
49
|
+
}
|
|
50
|
+
function fnv1a32(str) {
|
|
51
|
+
let h = 2166136261;
|
|
52
|
+
for (let i = 0; i < str.length; i++) {
|
|
53
|
+
h ^= str.charCodeAt(i);
|
|
54
|
+
h = Math.imul(h, 16777619);
|
|
55
|
+
}
|
|
56
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
57
|
+
}
|
|
58
|
+
function normalizeFields(fields) {
|
|
59
|
+
return fields.map((f) => {
|
|
60
|
+
if (typeof f === "string") return { path: f, desc: false };
|
|
61
|
+
if (!f.path || typeof f.path !== "string") {
|
|
62
|
+
throw new FiregraphError(
|
|
63
|
+
`IndexSpec field must be a string or { path: string, desc?: boolean }; got ${JSON.stringify(f)}`,
|
|
64
|
+
"INVALID_INDEX"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return { path: f.path, desc: !!f.desc };
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function specFingerprint(spec, leadingColumns) {
|
|
71
|
+
const normalized = {
|
|
72
|
+
lead: leadingColumns,
|
|
73
|
+
fields: normalizeFields(spec.fields),
|
|
74
|
+
where: spec.where ?? ""
|
|
75
|
+
};
|
|
76
|
+
return fnv1a32(JSON.stringify(normalized));
|
|
77
|
+
}
|
|
78
|
+
function compileFieldExpr(path, fieldToColumn) {
|
|
79
|
+
const col = fieldToColumn[path];
|
|
80
|
+
if (col) return quoteIdent(col);
|
|
81
|
+
if (path === "data") {
|
|
82
|
+
return `json_extract("data", '$')`;
|
|
83
|
+
}
|
|
84
|
+
if (path.startsWith("data.")) {
|
|
85
|
+
const suffix = path.slice(5);
|
|
86
|
+
const parts = suffix.split(".");
|
|
87
|
+
for (const part of parts) {
|
|
88
|
+
if (!JSON_PATH_KEY_RE.test(part)) {
|
|
89
|
+
throw new FiregraphError(
|
|
90
|
+
`IndexSpec data path "${path}" has invalid component "${part}". Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,
|
|
91
|
+
"INVALID_INDEX"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return `json_extract("data", '$.${suffix}')`;
|
|
96
|
+
}
|
|
97
|
+
throw new FiregraphError(
|
|
98
|
+
`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'.`,
|
|
99
|
+
"INVALID_INDEX"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function buildIndexDDL(spec, options) {
|
|
103
|
+
const { table, fieldToColumn, leadingColumns = [] } = options;
|
|
104
|
+
if (!spec.fields || spec.fields.length === 0) {
|
|
105
|
+
throw new FiregraphError("IndexSpec.fields must be a non-empty array", "INVALID_INDEX");
|
|
106
|
+
}
|
|
107
|
+
const normalized = normalizeFields(spec.fields);
|
|
108
|
+
const hash = specFingerprint(spec, leadingColumns);
|
|
109
|
+
const indexName = `${table}_idx_${hash}`;
|
|
110
|
+
const cols = [];
|
|
111
|
+
for (const col of leadingColumns) {
|
|
112
|
+
cols.push(quoteIdent(col));
|
|
113
|
+
}
|
|
114
|
+
for (const f of normalized) {
|
|
115
|
+
const expr = compileFieldExpr(f.path, fieldToColumn);
|
|
116
|
+
cols.push(f.desc ? `${expr} DESC` : expr);
|
|
117
|
+
}
|
|
118
|
+
let ddl = `CREATE INDEX IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(table)}(${cols.join(", ")})`;
|
|
119
|
+
if (spec.where) {
|
|
120
|
+
ddl += ` WHERE ${spec.where}`;
|
|
121
|
+
}
|
|
122
|
+
return ddl;
|
|
123
|
+
}
|
|
124
|
+
function dedupeIndexSpecs(specs, leadingColumns = []) {
|
|
125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const spec of specs) {
|
|
128
|
+
const fp = specFingerprint(spec, leadingColumns);
|
|
129
|
+
if (seen.has(fp)) continue;
|
|
130
|
+
seen.add(fp);
|
|
131
|
+
out.push(spec);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
37
136
|
// src/cloudflare/schema.ts
|
|
38
137
|
var DO_FIELD_TO_COLUMN = {
|
|
39
138
|
aType: "a_type",
|
|
@@ -45,9 +144,9 @@ var DO_FIELD_TO_COLUMN = {
|
|
|
45
144
|
createdAt: "created_at",
|
|
46
145
|
updatedAt: "updated_at"
|
|
47
146
|
};
|
|
48
|
-
var
|
|
147
|
+
var IDENT_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
49
148
|
function validateDOTableName(name) {
|
|
50
|
-
if (!
|
|
149
|
+
if (!IDENT_RE2.test(name)) {
|
|
51
150
|
throw new Error(`Invalid SQL identifier: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);
|
|
52
151
|
}
|
|
53
152
|
}
|
|
@@ -55,9 +154,9 @@ function quoteDOIdent(name) {
|
|
|
55
154
|
validateDOTableName(name);
|
|
56
155
|
return `"${name}"`;
|
|
57
156
|
}
|
|
58
|
-
function buildDOSchemaStatements(table) {
|
|
157
|
+
function buildDOSchemaStatements(table, options = {}) {
|
|
59
158
|
const t = quoteDOIdent(table);
|
|
60
|
-
|
|
159
|
+
const statements = [
|
|
61
160
|
`CREATE TABLE IF NOT EXISTS ${t} (
|
|
62
161
|
doc_id TEXT NOT NULL PRIMARY KEY,
|
|
63
162
|
a_type TEXT NOT NULL,
|
|
@@ -69,13 +168,15 @@ function buildDOSchemaStatements(table) {
|
|
|
69
168
|
v INTEGER,
|
|
70
169
|
created_at INTEGER NOT NULL,
|
|
71
170
|
updated_at INTEGER NOT NULL
|
|
72
|
-
)
|
|
73
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_a_uid`)} ON ${t}(a_uid)`,
|
|
74
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_b_uid`)} ON ${t}(b_uid)`,
|
|
75
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_axb_type_b_uid`)} ON ${t}(axb_type, b_uid)`,
|
|
76
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_a_type`)} ON ${t}(a_type)`,
|
|
77
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_b_type`)} ON ${t}(b_type)`
|
|
171
|
+
)`
|
|
78
172
|
];
|
|
173
|
+
const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];
|
|
174
|
+
const fromRegistry = options.registry?.entries().flatMap((e) => e.indexes ?? []) ?? [];
|
|
175
|
+
const deduped = dedupeIndexSpecs([...core, ...fromRegistry]);
|
|
176
|
+
for (const spec of deduped) {
|
|
177
|
+
statements.push(buildIndexDDL(spec, { table, fieldToColumn: DO_FIELD_TO_COLUMN }));
|
|
178
|
+
}
|
|
179
|
+
return statements;
|
|
79
180
|
}
|
|
80
181
|
|
|
81
182
|
// src/cloudflare/sql.ts
|
|
@@ -85,12 +186,14 @@ function compileFieldRef(field) {
|
|
|
85
186
|
return { expr: quoteDOIdent(column) };
|
|
86
187
|
}
|
|
87
188
|
if (field.startsWith("data.")) {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
189
|
+
const suffix = field.slice(5);
|
|
190
|
+
for (const part of suffix.split(".")) {
|
|
191
|
+
validateJsonPathKey(part);
|
|
192
|
+
}
|
|
193
|
+
return { expr: `json_extract("data", '$.${suffix}')` };
|
|
91
194
|
}
|
|
92
195
|
if (field === "data") {
|
|
93
|
-
return { expr:
|
|
196
|
+
return { expr: `json_extract("data", '$')` };
|
|
94
197
|
}
|
|
95
198
|
throw new FiregraphError(
|
|
96
199
|
`DO SQLite backend cannot resolve filter field: ${field}`,
|
|
@@ -127,7 +230,7 @@ function bindValue(value) {
|
|
|
127
230
|
}
|
|
128
231
|
return String(value);
|
|
129
232
|
}
|
|
130
|
-
var
|
|
233
|
+
var JSON_PATH_KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
131
234
|
function validateJsonPathKey(key) {
|
|
132
235
|
if (key.length === 0) {
|
|
133
236
|
throw new FiregraphError(
|
|
@@ -135,7 +238,7 @@ function validateJsonPathKey(key) {
|
|
|
135
238
|
"INVALID_QUERY"
|
|
136
239
|
);
|
|
137
240
|
}
|
|
138
|
-
if (!
|
|
241
|
+
if (!JSON_PATH_KEY_RE2.test(key)) {
|
|
139
242
|
throw new FiregraphError(
|
|
140
243
|
`DO SQLite backend: data field path component "${key}" is not a safe JSON-path identifier. Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceData (full-data overwrite) for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,
|
|
141
244
|
"INVALID_QUERY"
|
|
@@ -143,8 +246,7 @@ function validateJsonPathKey(key) {
|
|
|
143
246
|
}
|
|
144
247
|
}
|
|
145
248
|
function compileFilter(filter, params) {
|
|
146
|
-
const { expr
|
|
147
|
-
if (pathParam !== void 0) params.push(pathParam);
|
|
249
|
+
const { expr } = compileFieldRef(filter.field);
|
|
148
250
|
switch (filter.op) {
|
|
149
251
|
case "==":
|
|
150
252
|
params.push(bindValue(filter.value));
|
|
@@ -199,11 +301,10 @@ function asArray(value, op) {
|
|
|
199
301
|
}
|
|
200
302
|
return value;
|
|
201
303
|
}
|
|
202
|
-
function compileOrderBy(options,
|
|
304
|
+
function compileOrderBy(options, _params) {
|
|
203
305
|
if (!options?.orderBy) return "";
|
|
204
306
|
const { field, direction } = options.orderBy;
|
|
205
|
-
const { expr
|
|
206
|
-
if (pathParam !== void 0) params.push(pathParam);
|
|
307
|
+
const { expr } = compileFieldRef(field);
|
|
207
308
|
const dir = direction === "desc" ? "DESC" : "ASC";
|
|
208
309
|
return ` ORDER BY ${expr} ${dir}`;
|
|
209
310
|
}
|
|
@@ -607,12 +708,18 @@ var FiregraphDO = class {
|
|
|
607
708
|
env;
|
|
608
709
|
/** @internal — table name used by every compiled statement. */
|
|
609
710
|
table;
|
|
711
|
+
/** @internal — registry consulted by `runSchema` for per-entry indexes. */
|
|
712
|
+
registry;
|
|
713
|
+
/** @internal — overrides `DEFAULT_CORE_INDEXES` when set. */
|
|
714
|
+
coreIndexes;
|
|
610
715
|
constructor(ctx, env, options = {}) {
|
|
611
716
|
this.ctx = ctx;
|
|
612
717
|
this.env = env;
|
|
613
718
|
const table = options.table ?? DEFAULT_OPTIONS.table;
|
|
614
719
|
validateDOTableName(table);
|
|
615
720
|
this.table = table;
|
|
721
|
+
this.registry = options.registry;
|
|
722
|
+
this.coreIndexes = options.coreIndexes;
|
|
616
723
|
const autoMigrate = options.autoMigrate ?? DEFAULT_OPTIONS.autoMigrate;
|
|
617
724
|
if (autoMigrate) {
|
|
618
725
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
@@ -802,7 +909,11 @@ var FiregraphDO = class {
|
|
|
802
909
|
// Internals
|
|
803
910
|
// ---------------------------------------------------------------------------
|
|
804
911
|
runSchema() {
|
|
805
|
-
|
|
912
|
+
const statements = buildDOSchemaStatements(this.table, {
|
|
913
|
+
coreIndexes: this.coreIndexes,
|
|
914
|
+
registry: this.registry
|
|
915
|
+
});
|
|
916
|
+
for (const sql of statements) {
|
|
806
917
|
this.ctx.storage.sql.exec(sql).toArray();
|
|
807
918
|
}
|
|
808
919
|
}
|
|
@@ -816,6 +927,7 @@ var FiregraphDO = class {
|
|
|
816
927
|
export {
|
|
817
928
|
DORPCBackend,
|
|
818
929
|
FiregraphDO,
|
|
930
|
+
buildDOSchemaStatements,
|
|
819
931
|
createDOClient,
|
|
820
932
|
createSiblingClient
|
|
821
933
|
};
|