@typicalday/firegraph 0.10.0 → 0.11.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.
Files changed (40) hide show
  1. package/README.md +93 -90
  2. package/bin/firegraph.mjs +21 -7
  3. package/dist/{backend-BrqFkbid.d.ts → backend-U-MLShlg.d.ts} +1 -1
  4. package/dist/{backend-73p5Blx7.d.cts → backend-np4gEVhB.d.cts} +1 -1
  5. package/dist/backend.d.cts +3 -3
  6. package/dist/backend.d.ts +3 -3
  7. package/dist/{chunk-LZOIQHYN.js → chunk-6SB34IPQ.js} +20 -7
  8. package/dist/chunk-6SB34IPQ.js.map +1 -0
  9. package/dist/{chunk-SU4FNLC3.js → chunk-EEKWRX5E.js} +1 -1
  10. package/dist/{chunk-SU4FNLC3.js.map → chunk-EEKWRX5E.js.map} +1 -1
  11. package/dist/{chunk-YLGXLEUE.js → chunk-GJVVRTQT.js} +5 -14
  12. package/dist/chunk-GJVVRTQT.js.map +1 -0
  13. package/dist/cloudflare/index.cjs +151 -27
  14. package/dist/cloudflare/index.cjs.map +1 -1
  15. package/dist/cloudflare/index.d.cts +78 -3
  16. package/dist/cloudflare/index.d.ts +78 -3
  17. package/dist/cloudflare/index.js +135 -23
  18. package/dist/cloudflare/index.js.map +1 -1
  19. package/dist/codegen/index.cjs +4 -13
  20. package/dist/codegen/index.cjs.map +1 -1
  21. package/dist/codegen/index.d.cts +1 -1
  22. package/dist/codegen/index.d.ts +1 -1
  23. package/dist/codegen/index.js +1 -1
  24. package/dist/index.cjs +89 -132
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +106 -21
  27. package/dist/index.d.ts +106 -21
  28. package/dist/index.js +70 -116
  29. package/dist/index.js.map +1 -1
  30. package/dist/query-client/index.cjs.map +1 -1
  31. package/dist/query-client/index.js +1 -1
  32. package/dist/{types-DOemdlVA.d.ts → types-BGWxcpI_.d.cts} +75 -1
  33. package/dist/{types-DOemdlVA.d.cts → types-BGWxcpI_.d.ts} +75 -1
  34. package/package.json +35 -27
  35. package/dist/chunk-LZOIQHYN.js.map +0 -1
  36. package/dist/chunk-YLGXLEUE.js.map +0 -1
  37. package/dist/editor/client/assets/index-Bq2bfzeY.js +0 -411
  38. package/dist/editor/client/assets/index-CJ4m_EOL.css +0 -1
  39. package/dist/editor/client/index.html +0 -16
  40. 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-73p5Blx7.cjs';
2
- import { i as QueryFilter, y as QueryOptions, C as CascadeResult, F as FindEdgesParams, m as BulkOptions, o as BulkResult, a as GraphClient, D as DynamicGraphClient, c as GraphRegistry, S as StoredGraphRecord, d as GraphReader, G as GraphClientOptions, e as DynamicRegistryConfig } from '../types-DOemdlVA.cjs';
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
- export { type BatchOp, type DOClientOptions, DORPCBackend, type DORPCBackendOptions, type DOSqlCursor, type DOSqlExecutor, type DOStorage, type DurableObjectIdLike, type DurableObjectStateLike, FiregraphDO, type FiregraphDOOptions, type FiregraphNamespace, type FiregraphStub, createDOClient, createSiblingClient };
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-BrqFkbid.js';
2
- import { i as QueryFilter, y as QueryOptions, C as CascadeResult, F as FindEdgesParams, m as BulkOptions, o as BulkResult, a as GraphClient, D as DynamicGraphClient, c as GraphRegistry, S as StoredGraphRecord, d as GraphReader, G as GraphClientOptions, e as DynamicRegistryConfig } from '../types-DOemdlVA.js';
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
- export { type BatchOp, type DOClientOptions, DORPCBackend, type DORPCBackendOptions, type DOSqlCursor, type DOSqlExecutor, type DOStorage, type DurableObjectIdLike, type DurableObjectStateLike, FiregraphDO, type FiregraphDOOptions, type FiregraphNamespace, type FiregraphStub, createDOClient, createSiblingClient };
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,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-LZOIQHYN.js";
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 IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
147
+ var IDENT_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
49
148
  function validateDOTableName(name) {
50
- if (!IDENT_RE.test(name)) {
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
- return [
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 key = field.slice(5);
89
- validateJsonPathKey(key);
90
- return { expr: 'json_extract("data", ?)', pathParam: `$.${key}` };
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: 'json_extract("data", ?)', pathParam: "$" };
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 JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
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 (!JSON_PATH_KEY_RE.test(key)) {
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, pathParam } = compileFieldRef(filter.field);
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, params) {
304
+ function compileOrderBy(options, _params) {
203
305
  if (!options?.orderBy) return "";
204
306
  const { field, direction } = options.orderBy;
205
- const { expr, pathParam } = compileFieldRef(field);
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
- for (const sql of buildDOSchemaStatements(this.table)) {
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
  };