@typicalday/firegraph 0.14.1 → 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.
Files changed (64) hide show
  1. package/README.md +23 -3
  2. package/dist/{backend-DuvHGgK1.d.cts → backend-BpYLdwCW.d.cts} +1 -1
  3. package/dist/{backend-DuvHGgK1.d.ts → backend-BpYLdwCW.d.ts} +1 -1
  4. package/dist/backend-CvImIwTY.d.cts +137 -0
  5. package/dist/backend-YH5HtawN.d.ts +137 -0
  6. package/dist/backend.d.cts +2 -2
  7. package/dist/backend.d.ts +2 -2
  8. package/dist/{chunk-3AHHXMWX.js → chunk-5HIRYV2S.js} +12 -35
  9. package/dist/chunk-5HIRYV2S.js.map +1 -0
  10. package/dist/{chunk-DJI3VXXA.js → chunk-7IEZ6IYY.js} +2 -2
  11. package/dist/chunk-7IEZ6IYY.js.map +1 -0
  12. package/dist/chunk-FODIMIWY.js +721 -0
  13. package/dist/chunk-FODIMIWY.js.map +1 -0
  14. package/dist/chunk-NGAJCALM.js +34 -0
  15. package/dist/chunk-NGAJCALM.js.map +1 -0
  16. package/dist/chunk-ULRDQ6HZ.js +862 -0
  17. package/dist/chunk-ULRDQ6HZ.js.map +1 -0
  18. package/dist/{client-BKi3vk0Q.d.ts → client-B5o39X79.d.ts} +1 -1
  19. package/dist/{client-BrsaXtDV.d.cts → client-BGHwxwPg.d.cts} +1 -1
  20. package/dist/{client-Bk2Cm6xv.d.cts → client-DoyEdJ5w.d.cts} +1 -1
  21. package/dist/{client-Bk2Cm6xv.d.ts → client-DoyEdJ5w.d.ts} +1 -1
  22. package/dist/cloudflare/index.cjs +148 -158
  23. package/dist/cloudflare/index.cjs.map +1 -1
  24. package/dist/cloudflare/index.d.cts +73 -70
  25. package/dist/cloudflare/index.d.ts +73 -70
  26. package/dist/cloudflare/index.js +53 -588
  27. package/dist/cloudflare/index.js.map +1 -1
  28. package/dist/codegen/index.d.cts +1 -1
  29. package/dist/codegen/index.d.ts +1 -1
  30. package/dist/firestore-enterprise/index.cjs.map +1 -1
  31. package/dist/firestore-enterprise/index.d.cts +3 -3
  32. package/dist/firestore-enterprise/index.d.ts +3 -3
  33. package/dist/firestore-enterprise/index.js +5 -3
  34. package/dist/firestore-enterprise/index.js.map +1 -1
  35. package/dist/firestore-standard/index.cjs.map +1 -1
  36. package/dist/firestore-standard/index.d.cts +3 -3
  37. package/dist/firestore-standard/index.d.ts +3 -3
  38. package/dist/firestore-standard/index.js +3 -2
  39. package/dist/firestore-standard/index.js.map +1 -1
  40. package/dist/index.d.cts +5 -5
  41. package/dist/index.d.ts +5 -5
  42. package/dist/index.js +6 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/query-client/index.d.cts +2 -2
  45. package/dist/query-client/index.d.ts +2 -2
  46. package/dist/{registry-Bc7h6WTM.d.cts → registry-BGh7Jqpb.d.cts} +2 -2
  47. package/dist/{registry-C2KUPVZj.d.ts → registry-tKTb5Kx1.d.ts} +2 -2
  48. package/dist/sqlite/index.cjs +578 -371
  49. package/dist/sqlite/index.cjs.map +1 -1
  50. package/dist/sqlite/index.d.cts +4 -110
  51. package/dist/sqlite/index.d.ts +4 -110
  52. package/dist/sqlite/index.js +7 -1144
  53. package/dist/sqlite/index.js.map +1 -1
  54. package/dist/sqlite/local.cjs +1835 -0
  55. package/dist/sqlite/local.cjs.map +1 -0
  56. package/dist/sqlite/local.d.cts +83 -0
  57. package/dist/sqlite/local.d.ts +83 -0
  58. package/dist/sqlite/local.js +121 -0
  59. package/dist/sqlite/local.js.map +1 -0
  60. package/package.json +15 -1
  61. package/dist/chunk-3AHHXMWX.js.map +0 -1
  62. package/dist/chunk-DJI3VXXA.js.map +0 -1
  63. package/dist/chunk-NNBSUOOF.js +0 -289
  64. package/dist/chunk-NNBSUOOF.js.map +0 -1
@@ -172,7 +172,7 @@ __export(cloudflare_exports, {
172
172
  FiregraphDO: () => FiregraphDO,
173
173
  META_EDGE_TYPE: () => META_EDGE_TYPE,
174
174
  META_NODE_TYPE: () => META_NODE_TYPE,
175
- buildDOSchemaStatements: () => buildDOSchemaStatements,
175
+ buildDOSchemaStatements: () => buildSchemaStatements,
176
176
  createCapabilities: () => createCapabilities,
177
177
  createDOClient: () => createDOClient,
178
178
  createMergedRegistry: () => createMergedRegistry,
@@ -281,6 +281,31 @@ var BUILTIN_FIELDS = /* @__PURE__ */ new Set([
281
281
  ]);
282
282
  var SHARD_SEPARATOR = ":";
283
283
 
284
+ // src/timestamp.ts
285
+ var GraphTimestampImpl = class _GraphTimestampImpl {
286
+ constructor(seconds, nanoseconds) {
287
+ this.seconds = seconds;
288
+ this.nanoseconds = nanoseconds;
289
+ }
290
+ toDate() {
291
+ return new Date(this.toMillis());
292
+ }
293
+ toMillis() {
294
+ return this.seconds * 1e3 + Math.floor(this.nanoseconds / 1e6);
295
+ }
296
+ toJSON() {
297
+ return { seconds: this.seconds, nanoseconds: this.nanoseconds };
298
+ }
299
+ static fromMillis(ms) {
300
+ const seconds = Math.floor(ms / 1e3);
301
+ const nanoseconds = (ms - seconds * 1e3) * 1e6;
302
+ return new _GraphTimestampImpl(seconds, nanoseconds);
303
+ }
304
+ static now() {
305
+ return _GraphTimestampImpl.fromMillis(Date.now());
306
+ }
307
+ };
308
+
284
309
  // src/internal/sqlite-data-ops.ts
285
310
  var FIRESTORE_TYPE_NAMES = /* @__PURE__ */ new Set([
286
311
  "Timestamp",
@@ -571,31 +596,6 @@ function formatTagValue(value) {
571
596
  return typeof value;
572
597
  }
573
598
 
574
- // src/timestamp.ts
575
- var GraphTimestampImpl = class _GraphTimestampImpl {
576
- constructor(seconds, nanoseconds) {
577
- this.seconds = seconds;
578
- this.nanoseconds = nanoseconds;
579
- }
580
- toDate() {
581
- return new Date(this.toMillis());
582
- }
583
- toMillis() {
584
- return this.seconds * 1e3 + Math.floor(this.nanoseconds / 1e6);
585
- }
586
- toJSON() {
587
- return { seconds: this.seconds, nanoseconds: this.nanoseconds };
588
- }
589
- static fromMillis(ms) {
590
- const seconds = Math.floor(ms / 1e3);
591
- const nanoseconds = (ms - seconds * 1e3) * 1e6;
592
- return new _GraphTimestampImpl(seconds, nanoseconds);
593
- }
594
- static now() {
595
- return _GraphTimestampImpl.fromMillis(Date.now());
596
- }
597
- };
598
-
599
599
  // src/default-indexes.ts
600
600
  var DEFAULT_CORE_INDEXES = Object.freeze([
601
601
  { fields: ["aUid"] },
@@ -640,9 +640,9 @@ function normalizeFields(fields) {
640
640
  return { path: f.path, desc: !!f.desc };
641
641
  });
642
642
  }
643
- function specFingerprint(spec, leadingColumns) {
643
+ function specFingerprint(spec) {
644
644
  const normalized = {
645
- lead: leadingColumns,
645
+ lead: [],
646
646
  fields: normalizeFields(spec.fields),
647
647
  where: spec.where ?? ""
648
648
  };
@@ -673,17 +673,14 @@ function compileFieldExpr(path, fieldToColumn) {
673
673
  );
674
674
  }
675
675
  function buildIndexDDL(spec, options) {
676
- const { table, fieldToColumn, leadingColumns = [] } = options;
676
+ const { table, fieldToColumn } = options;
677
677
  if (!spec.fields || spec.fields.length === 0) {
678
678
  throw new FiregraphError("IndexSpec.fields must be a non-empty array", "INVALID_INDEX");
679
679
  }
680
680
  const normalized = normalizeFields(spec.fields);
681
- const hash = specFingerprint(spec, leadingColumns);
681
+ const hash = specFingerprint(spec);
682
682
  const indexName = `${table}_idx_${hash}`;
683
683
  const cols = [];
684
- for (const col of leadingColumns) {
685
- cols.push(quoteIdent(col));
686
- }
687
684
  for (const f of normalized) {
688
685
  const expr = compileFieldExpr(f.path, fieldToColumn);
689
686
  cols.push(f.desc ? `${expr} DESC` : expr);
@@ -694,11 +691,11 @@ function buildIndexDDL(spec, options) {
694
691
  }
695
692
  return ddl;
696
693
  }
697
- function dedupeIndexSpecs(specs, leadingColumns = []) {
694
+ function dedupeIndexSpecs(specs) {
698
695
  const seen = /* @__PURE__ */ new Set();
699
696
  const out = [];
700
697
  for (const spec of specs) {
701
- const fp = specFingerprint(spec, leadingColumns);
698
+ const fp = specFingerprint(spec);
702
699
  if (seen.has(fp)) continue;
703
700
  seen.add(fp);
704
701
  out.push(spec);
@@ -706,8 +703,8 @@ function dedupeIndexSpecs(specs, leadingColumns = []) {
706
703
  return out;
707
704
  }
708
705
 
709
- // src/cloudflare/schema.ts
710
- var DO_FIELD_TO_COLUMN = {
706
+ // src/internal/sqlite-schema.ts
707
+ var FIELD_TO_COLUMN = {
711
708
  aType: "a_type",
712
709
  aUid: "a_uid",
713
710
  axbType: "axb_type",
@@ -717,21 +714,8 @@ var DO_FIELD_TO_COLUMN = {
717
714
  createdAt: "created_at",
718
715
  updatedAt: "updated_at"
719
716
  };
720
- var IDENT_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
721
- function validateDOTableName(name) {
722
- if (!IDENT_RE2.test(name)) {
723
- throw new Error(`Invalid SQL identifier: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);
724
- }
725
- }
726
- function quoteDOIdent(name) {
727
- validateDOTableName(name);
728
- return `"${name}"`;
729
- }
730
- function quoteDOColumnAlias(label) {
731
- return `"${label.replace(/"/g, '""')}"`;
732
- }
733
- function buildDOSchemaStatements(table, options = {}) {
734
- const t = quoteDOIdent(table);
717
+ function buildSchemaStatements(table, options = {}) {
718
+ const t = quoteIdent2(table);
735
719
  const statements = [
736
720
  `CREATE TABLE IF NOT EXISTS ${t} (
737
721
  doc_id TEXT NOT NULL PRIMARY KEY,
@@ -750,33 +734,42 @@ function buildDOSchemaStatements(table, options = {}) {
750
734
  const fromRegistry = options.registry?.entries().flatMap((e) => e.indexes ?? []) ?? [];
751
735
  const deduped = dedupeIndexSpecs([...core, ...fromRegistry]);
752
736
  for (const spec of deduped) {
753
- statements.push(buildIndexDDL(spec, { table, fieldToColumn: DO_FIELD_TO_COLUMN }));
737
+ statements.push(buildIndexDDL(spec, { table, fieldToColumn: FIELD_TO_COLUMN }));
754
738
  }
755
739
  return statements;
756
740
  }
741
+ function quoteIdent2(name) {
742
+ validateTableName(name);
743
+ return `"${name}"`;
744
+ }
745
+ function validateTableName(name) {
746
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
747
+ throw new Error(`Invalid SQL identifier: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);
748
+ }
749
+ }
750
+ function quoteColumnAlias(label) {
751
+ return `"${label.replace(/"/g, '""')}"`;
752
+ }
757
753
 
758
- // src/cloudflare/sql.ts
759
- var DO_BACKEND_LABEL = "DO SQLite";
760
- var DO_BACKEND_ERR_LABEL = "DO SQLite backend";
754
+ // src/internal/sqlite-sql.ts
755
+ var BACKEND_LABEL = "SQLite";
756
+ var BACKEND_ERR_LABEL = "SQLite backend";
761
757
  function compileFieldRef(field) {
762
- const column = DO_FIELD_TO_COLUMN[field];
758
+ const column = FIELD_TO_COLUMN[field];
763
759
  if (column) {
764
- return { expr: quoteDOIdent(column) };
760
+ return { expr: quoteIdent2(column) };
765
761
  }
766
762
  if (field.startsWith("data.")) {
767
763
  const suffix = field.slice(5);
768
764
  for (const part of suffix.split(".")) {
769
- validateJsonPathKey(part, DO_BACKEND_ERR_LABEL);
765
+ validateJsonPathKey(part, BACKEND_ERR_LABEL);
770
766
  }
771
767
  return { expr: `json_extract("data", '$.${suffix}')` };
772
768
  }
773
769
  if (field === "data") {
774
770
  return { expr: `json_extract("data", '$')` };
775
771
  }
776
- throw new FiregraphError(
777
- `DO SQLite backend cannot resolve filter field: ${field}`,
778
- "INVALID_QUERY"
779
- );
772
+ throw new FiregraphError(`SQLite backend cannot resolve filter field: ${field}`, "INVALID_QUERY");
780
773
  }
781
774
  function bindValue(value) {
782
775
  if (value === null || value === void 0) return null;
@@ -788,7 +781,7 @@ function bindValue(value) {
788
781
  const firestoreType = isFirestoreSpecialType(value);
789
782
  if (firestoreType) {
790
783
  throw new FiregraphError(
791
- `DO SQLite backend cannot bind a Firestore ${firestoreType} value \u2014 JSON serialization would silently drop fields and the resulting bind would never match a stored row. Convert to a primitive (e.g. \`ts.toMillis()\` for Timestamp) before filtering or updating.`,
784
+ `SQLite backend cannot bind a Firestore ${firestoreType} value \u2014 JSON serialization would silently drop fields and the resulting bind would never match a stored row. Convert to a primitive (e.g. \`ts.toMillis()\` for Timestamp) before filtering or updating.`,
792
785
  "INVALID_QUERY"
793
786
  );
794
787
  }
@@ -841,7 +834,7 @@ function compileFilter(filter, params) {
841
834
  }
842
835
  default:
843
836
  throw new FiregraphError(
844
- `DO SQLite backend does not support filter operator: ${String(filter.op)}`,
837
+ `SQLite backend does not support filter operator: ${String(filter.op)}`,
845
838
  "INVALID_QUERY"
846
839
  );
847
840
  }
@@ -864,22 +857,22 @@ function compileLimit(options, params) {
864
857
  params.push(options.limit);
865
858
  return ` LIMIT ?`;
866
859
  }
867
- function compileDOSelect(table, filters, options) {
860
+ function compileSelect(table, filters, options) {
868
861
  const params = [];
869
862
  const conditions = [];
870
863
  for (const f of filters) {
871
864
  conditions.push(compileFilter(f, params));
872
865
  }
873
866
  const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
874
- let sql = `SELECT * FROM ${quoteDOIdent(table)}${where}`;
867
+ let sql = `SELECT * FROM ${quoteIdent2(table)}${where}`;
875
868
  sql += compileOrderBy(options, params);
876
869
  sql += compileLimit(options, params);
877
870
  return { sql, params };
878
871
  }
879
- function compileDOExpand(table, params) {
872
+ function compileExpand(table, params) {
880
873
  if (params.sources.length === 0) {
881
874
  throw new FiregraphError(
882
- "compileDOExpand requires a non-empty sources list \u2014 empty IN () is invalid SQL.",
875
+ "compileExpand requires a non-empty sources list \u2014 empty IN () is invalid SQL.",
883
876
  "INVALID_QUERY"
884
877
  );
885
878
  }
@@ -906,7 +899,7 @@ function compileDOExpand(table, params) {
906
899
  if (params.axbType === NODE_RELATION) {
907
900
  conditions.push(`${aUidCol} != ${bUidCol}`);
908
901
  }
909
- let sql = `SELECT * FROM ${quoteDOIdent(table)} WHERE ${conditions.join(" AND ")}`;
902
+ let sql = `SELECT * FROM ${quoteIdent2(table)} WHERE ${conditions.join(" AND ")}`;
910
903
  if (params.orderBy) {
911
904
  sql += compileOrderBy({ orderBy: params.orderBy }, sqlParams);
912
905
  }
@@ -917,10 +910,10 @@ function compileDOExpand(table, params) {
917
910
  }
918
911
  return { sql, params: sqlParams };
919
912
  }
920
- function compileDOExpandHydrate(table, targetUids) {
913
+ function compileExpandHydrate(table, targetUids) {
921
914
  if (targetUids.length === 0) {
922
915
  throw new FiregraphError(
923
- "compileDOExpandHydrate requires a non-empty target list \u2014 empty IN () is invalid SQL.",
916
+ "compileExpandHydrate requires a non-empty target list \u2014 empty IN () is invalid SQL.",
924
917
  "INVALID_QUERY"
925
918
  );
926
919
  }
@@ -931,25 +924,25 @@ function compileDOExpandHydrate(table, targetUids) {
931
924
  const bUidCol = compileFieldRef("bUid").expr;
932
925
  const axbTypeCol = compileFieldRef("axbType").expr;
933
926
  return {
934
- sql: `SELECT * FROM ${quoteDOIdent(table)} WHERE ${axbTypeCol} = ? AND ${aUidCol} = ${bUidCol} AND ${bUidCol} IN (${placeholders})`,
927
+ sql: `SELECT * FROM ${quoteIdent2(table)} WHERE ${axbTypeCol} = ? AND ${aUidCol} = ${bUidCol} AND ${bUidCol} IN (${placeholders})`,
935
928
  params: sqlParams
936
929
  };
937
930
  }
938
- function compileDOSelectByDocId(table, docId) {
931
+ function compileSelectByDocId(table, docId) {
939
932
  return {
940
- sql: `SELECT * FROM ${quoteDOIdent(table)} WHERE "doc_id" = ? LIMIT 1`,
933
+ sql: `SELECT * FROM ${quoteIdent2(table)} WHERE "doc_id" = ? LIMIT 1`,
941
934
  params: [docId]
942
935
  };
943
936
  }
944
- function normalizeDOProjectionField(field) {
945
- if (field in DO_FIELD_TO_COLUMN) return field;
937
+ function normalizeProjectionField(field) {
938
+ if (field in FIELD_TO_COLUMN) return field;
946
939
  if (field === "data" || field.startsWith("data.")) return field;
947
940
  return `data.${field}`;
948
941
  }
949
- function compileDOFindEdgesProjected(table, select, filters, options) {
942
+ function compileFindEdgesProjected(table, select, filters, options) {
950
943
  if (select.length === 0) {
951
944
  throw new FiregraphError(
952
- "compileDOFindEdgesProjected requires a non-empty select list \u2014 an empty projection has no SQL representation distinct from `findEdges`.",
945
+ "compileFindEdgesProjected requires a non-empty select list \u2014 an empty projection has no SQL representation distinct from `findEdges`.",
953
946
  "INVALID_QUERY"
954
947
  );
955
948
  }
@@ -965,9 +958,9 @@ function compileDOFindEdgesProjected(table, select, filters, options) {
965
958
  const columns = [];
966
959
  for (let idx = 0; idx < uniqueFields.length; idx++) {
967
960
  const field = uniqueFields[idx];
968
- const canonical = normalizeDOProjectionField(field);
961
+ const canonical = normalizeProjectionField(field);
969
962
  const { expr } = compileFieldRef(canonical);
970
- const alias = quoteDOColumnAlias(field);
963
+ const alias = quoteColumnAlias(field);
971
964
  projections.push(`${expr} AS ${alias}`);
972
965
  let kind;
973
966
  let typeAliasName;
@@ -976,7 +969,7 @@ function compileDOFindEdgesProjected(table, select, filters, options) {
976
969
  } else if (canonical.startsWith("data.")) {
977
970
  kind = "json";
978
971
  typeAliasName = `__fg_t_${idx}`;
979
- const typeAlias = quoteDOColumnAlias(typeAliasName);
972
+ const typeAlias = quoteColumnAlias(typeAliasName);
980
973
  projections.push(`json_type("data", '$.${canonical.slice(5)}') AS ${typeAlias}`);
981
974
  } else {
982
975
  if (canonical === "v") kind = "builtin-int";
@@ -991,12 +984,12 @@ function compileDOFindEdgesProjected(table, select, filters, options) {
991
984
  conditions.push(compileFilter(f, params));
992
985
  }
993
986
  const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
994
- let sql = `SELECT ${projections.join(", ")} FROM ${quoteDOIdent(table)}${where}`;
987
+ let sql = `SELECT ${projections.join(", ")} FROM ${quoteIdent2(table)}${where}`;
995
988
  sql += compileOrderBy(options, params);
996
989
  sql += compileLimit(options, params);
997
990
  return { stmt: { sql, params }, columns };
998
991
  }
999
- function decodeDOProjectedRow(row, columns) {
992
+ function decodeProjectedRow(row, columns) {
1000
993
  const out = {};
1001
994
  for (const c of columns) {
1002
995
  const raw = row[c.field];
@@ -1016,7 +1009,7 @@ function decodeDOProjectedRow(row, columns) {
1016
1009
  }
1017
1010
  break;
1018
1011
  case "builtin-timestamp": {
1019
- const ms = toMillis(raw);
1012
+ const ms = rowTimestampToMillis(raw);
1020
1013
  out[c.field] = GraphTimestampImpl.fromMillis(ms);
1021
1014
  break;
1022
1015
  }
@@ -1044,7 +1037,7 @@ function decodeDOProjectedRow(row, columns) {
1044
1037
  }
1045
1038
  return out;
1046
1039
  }
1047
- function compileDOAggregate(table, spec, filters) {
1040
+ function compileAggregate(table, spec, filters) {
1048
1041
  const aliases = Object.keys(spec);
1049
1042
  if (aliases.length === 0) {
1050
1043
  throw new FiregraphError(
@@ -1055,7 +1048,7 @@ function compileDOAggregate(table, spec, filters) {
1055
1048
  const projections = [];
1056
1049
  for (const alias of aliases) {
1057
1050
  const { op, field } = spec[alias];
1058
- validateJsonPathKey(alias, DO_BACKEND_ERR_LABEL);
1051
+ validateJsonPathKey(alias, BACKEND_ERR_LABEL);
1059
1052
  if (op === "count") {
1060
1053
  if (field !== void 0) {
1061
1054
  throw new FiregraphError(
@@ -1063,7 +1056,7 @@ function compileDOAggregate(table, spec, filters) {
1063
1056
  "INVALID_QUERY"
1064
1057
  );
1065
1058
  }
1066
- projections.push(`COUNT(*) AS ${quoteDOIdent(alias)}`);
1059
+ projections.push(`COUNT(*) AS ${quoteIdent2(alias)}`);
1067
1060
  continue;
1068
1061
  }
1069
1062
  if (!field) {
@@ -1074,13 +1067,13 @@ function compileDOAggregate(table, spec, filters) {
1074
1067
  }
1075
1068
  const { expr } = compileFieldRef(field);
1076
1069
  const numeric = `CAST(${expr} AS REAL)`;
1077
- if (op === "sum") projections.push(`SUM(${numeric}) AS ${quoteDOIdent(alias)}`);
1078
- else if (op === "avg") projections.push(`AVG(${numeric}) AS ${quoteDOIdent(alias)}`);
1079
- else if (op === "min") projections.push(`MIN(${numeric}) AS ${quoteDOIdent(alias)}`);
1080
- else if (op === "max") projections.push(`MAX(${numeric}) AS ${quoteDOIdent(alias)}`);
1070
+ if (op === "sum") projections.push(`SUM(${numeric}) AS ${quoteIdent2(alias)}`);
1071
+ else if (op === "avg") projections.push(`AVG(${numeric}) AS ${quoteIdent2(alias)}`);
1072
+ else if (op === "min") projections.push(`MIN(${numeric}) AS ${quoteIdent2(alias)}`);
1073
+ else if (op === "max") projections.push(`MAX(${numeric}) AS ${quoteIdent2(alias)}`);
1081
1074
  else
1082
1075
  throw new FiregraphError(
1083
- `DO SQLite backend does not support aggregate op: ${String(op)}`,
1076
+ `SQLite backend does not support aggregate op: ${String(op)}`,
1084
1077
  "INVALID_QUERY"
1085
1078
  );
1086
1079
  }
@@ -1090,13 +1083,13 @@ function compileDOAggregate(table, spec, filters) {
1090
1083
  conditions.push(compileFilter(f, params));
1091
1084
  }
1092
1085
  const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1093
- const sql = `SELECT ${projections.join(", ")} FROM ${quoteDOIdent(table)}${where}`;
1086
+ const sql = `SELECT ${projections.join(", ")} FROM ${quoteIdent2(table)}${where}`;
1094
1087
  return { stmt: { sql, params }, aliases };
1095
1088
  }
1096
- function compileDOSet(table, docId, record, nowMillis, mode) {
1097
- assertJsonSafePayload(record.data, DO_BACKEND_LABEL);
1089
+ function compileSet(table, docId, record, nowMillis, mode) {
1090
+ assertJsonSafePayload(record.data, BACKEND_LABEL);
1098
1091
  if (mode === "replace") {
1099
- const sql2 = `INSERT OR REPLACE INTO ${quoteDOIdent(table)} (
1092
+ const sql2 = `INSERT OR REPLACE INTO ${quoteIdent2(table)} (
1100
1093
  doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at
1101
1094
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
1102
1095
  const params = [
@@ -1127,8 +1120,8 @@ function compileDOSet(table, docId, record, nowMillis, mode) {
1127
1120
  ];
1128
1121
  const ops = flattenPatch(record.data ?? {});
1129
1122
  const updateParams = [];
1130
- const dataExpr = compileDataOpsExpr(ops, `COALESCE("data", '{}')`, updateParams, DO_BACKEND_ERR_LABEL) ?? `COALESCE("data", '{}')`;
1131
- const sql = `INSERT INTO ${quoteDOIdent(table)} (
1123
+ const dataExpr = compileDataOpsExpr(ops, `COALESCE("data", '{}')`, updateParams, BACKEND_ERR_LABEL) ?? `COALESCE("data", '{}')`;
1124
+ const sql = `INSERT INTO ${quoteIdent2(table)} (
1132
1125
  doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at
1133
1126
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1134
1127
  ON CONFLICT(doc_id) DO UPDATE SET
@@ -1143,23 +1136,23 @@ function compileDOSet(table, docId, record, nowMillis, mode) {
1143
1136
  "updated_at" = excluded."updated_at"`;
1144
1137
  return { sql, params: [...insertParams, ...updateParams] };
1145
1138
  }
1146
- function compileDOUpdate(table, docId, update, nowMillis) {
1139
+ function compileUpdate(table, docId, update, nowMillis) {
1147
1140
  assertUpdatePayloadExclusive(update);
1148
1141
  const setClauses = [];
1149
1142
  const params = [];
1150
1143
  if (update.replaceData) {
1151
- assertJsonSafePayload(update.replaceData, DO_BACKEND_LABEL);
1144
+ assertJsonSafePayload(update.replaceData, BACKEND_LABEL);
1152
1145
  setClauses.push(`"data" = ?`);
1153
1146
  params.push(JSON.stringify(update.replaceData));
1154
1147
  } else if (update.dataOps && update.dataOps.length > 0) {
1155
1148
  for (const op of update.dataOps) {
1156
- if (!op.delete) assertJsonSafePayload(op.value, DO_BACKEND_LABEL);
1149
+ if (!op.delete) assertJsonSafePayload(op.value, BACKEND_LABEL);
1157
1150
  }
1158
1151
  const expr = compileDataOpsExpr(
1159
1152
  update.dataOps,
1160
1153
  `COALESCE("data", '{}')`,
1161
1154
  params,
1162
- DO_BACKEND_ERR_LABEL
1155
+ BACKEND_ERR_LABEL
1163
1156
  );
1164
1157
  if (expr !== null) {
1165
1158
  setClauses.push(`"data" = ${expr}`);
@@ -1173,17 +1166,17 @@ function compileDOUpdate(table, docId, update, nowMillis) {
1173
1166
  params.push(nowMillis);
1174
1167
  params.push(docId);
1175
1168
  return {
1176
- sql: `UPDATE ${quoteDOIdent(table)} SET ${setClauses.join(", ")} WHERE "doc_id" = ?`,
1169
+ sql: `UPDATE ${quoteIdent2(table)} SET ${setClauses.join(", ")} WHERE "doc_id" = ?`,
1177
1170
  params
1178
1171
  };
1179
1172
  }
1180
- function compileDODelete(table, docId) {
1173
+ function compileDelete(table, docId) {
1181
1174
  return {
1182
- sql: `DELETE FROM ${quoteDOIdent(table)} WHERE "doc_id" = ?`,
1175
+ sql: `DELETE FROM ${quoteIdent2(table)} WHERE "doc_id" = ?`,
1183
1176
  params: [docId]
1184
1177
  };
1185
1178
  }
1186
- function compileDOBulkDelete(table, filters) {
1179
+ function compileBulkDelete(table, filters) {
1187
1180
  const params = [];
1188
1181
  const conditions = [];
1189
1182
  for (const f of filters) {
@@ -1191,11 +1184,11 @@ function compileDOBulkDelete(table, filters) {
1191
1184
  }
1192
1185
  const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1193
1186
  return {
1194
- sql: `DELETE FROM ${quoteDOIdent(table)}${where}`,
1187
+ sql: `DELETE FROM ${quoteIdent2(table)}${where}`,
1195
1188
  params
1196
1189
  };
1197
1190
  }
1198
- function compileDOBulkUpdate(table, filters, patchData, nowMillis) {
1191
+ function compileBulkUpdate(table, filters, patchData, nowMillis) {
1199
1192
  const dataOps = flattenPatch(patchData);
1200
1193
  if (dataOps.length === 0) {
1201
1194
  throw new FiregraphError(
@@ -1204,15 +1197,10 @@ function compileDOBulkUpdate(table, filters, patchData, nowMillis) {
1204
1197
  );
1205
1198
  }
1206
1199
  for (const op of dataOps) {
1207
- if (!op.delete) assertJsonSafePayload(op.value, DO_BACKEND_LABEL);
1200
+ if (!op.delete) assertJsonSafePayload(op.value, BACKEND_LABEL);
1208
1201
  }
1209
1202
  const setParams = [];
1210
- const expr = compileDataOpsExpr(
1211
- dataOps,
1212
- `COALESCE("data", '{}')`,
1213
- setParams,
1214
- DO_BACKEND_ERR_LABEL
1215
- );
1203
+ const expr = compileDataOpsExpr(dataOps, `COALESCE("data", '{}')`, setParams, BACKEND_ERR_LABEL);
1216
1204
  if (expr === null) {
1217
1205
  throw new FiregraphError(
1218
1206
  "bulkUpdate() patch produced no SQL operations \u2014 internal invariant violated.",
@@ -1228,21 +1216,35 @@ function compileDOBulkUpdate(table, filters, patchData, nowMillis) {
1228
1216
  }
1229
1217
  const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1230
1218
  return {
1231
- sql: `UPDATE ${quoteDOIdent(table)} SET ${setClauses.join(", ")}${where}`,
1219
+ sql: `UPDATE ${quoteIdent2(table)} SET ${setClauses.join(", ")}${where}`,
1232
1220
  params: [...setParams, ...whereParams]
1233
1221
  };
1234
1222
  }
1235
- function compileDODeleteAll(table) {
1223
+ function compileDeleteAll(table) {
1236
1224
  return {
1237
- sql: `DELETE FROM ${quoteDOIdent(table)}`,
1225
+ sql: `DELETE FROM ${quoteIdent2(table)}`,
1238
1226
  params: []
1239
1227
  };
1240
1228
  }
1229
+ function rowTimestampToMillis(value) {
1230
+ if (typeof value === "number") return value;
1231
+ if (typeof value === "bigint") return Number(value);
1232
+ if (typeof value === "string") {
1233
+ const n = Number(value);
1234
+ if (Number.isFinite(n)) return n;
1235
+ }
1236
+ throw new FiregraphError(
1237
+ `SQLite row has non-numeric timestamp column: ${typeof value} (${String(value)})`,
1238
+ "INVALID_QUERY"
1239
+ );
1240
+ }
1241
+
1242
+ // src/cloudflare/sql.ts
1241
1243
  function rowToDORecord(row) {
1242
1244
  const dataString = row.data;
1243
1245
  const data = dataString ? JSON.parse(dataString) : {};
1244
- const createdAtMs = toMillis(row.created_at);
1245
- const updatedAtMs = toMillis(row.updated_at);
1246
+ const createdAtMs = rowTimestampToMillis(row.created_at);
1247
+ const updatedAtMs = rowTimestampToMillis(row.updated_at);
1246
1248
  const record = {
1247
1249
  aType: row.a_type,
1248
1250
  aUid: row.a_uid,
@@ -1274,18 +1276,6 @@ function hydrateDORecord(wire) {
1274
1276
  }
1275
1277
  return record;
1276
1278
  }
1277
- function toMillis(value) {
1278
- if (typeof value === "number") return value;
1279
- if (typeof value === "bigint") return Number(value);
1280
- if (typeof value === "string") {
1281
- const n = Number(value);
1282
- if (Number.isFinite(n)) return n;
1283
- }
1284
- throw new FiregraphError(
1285
- `DO SQLite row has non-numeric timestamp column: ${typeof value} (${String(value)})`,
1286
- "INVALID_QUERY"
1287
- );
1288
- }
1289
1279
 
1290
1280
  // src/cloudflare/backend.ts
1291
1281
  function validateSegment(value, label) {
@@ -1549,7 +1539,7 @@ var DORPCBackend = class _DORPCBackend {
1549
1539
  );
1550
1540
  }
1551
1541
  const { rows, columns } = await stub._fgFindEdgesProjected(select, filters, options);
1552
- return rows.map((row) => decodeDOProjectedRow(row, columns));
1542
+ return rows.map((row) => decodeProjectedRow(row, columns));
1553
1543
  }
1554
1544
  // --- Cross-scope queries ---
1555
1545
  //
@@ -3627,7 +3617,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3627
3617
  super(ctx, env);
3628
3618
  this.state = ctx;
3629
3619
  const table = options.table ?? DEFAULT_OPTIONS.table;
3630
- validateDOTableName(table);
3620
+ validateTableName(table);
3631
3621
  this.table = table;
3632
3622
  this.registry = options.registry;
3633
3623
  this.coreIndexes = options.coreIndexes;
@@ -3646,12 +3636,12 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3646
3636
  // `src/cloudflare/backend.ts` calls these directly on the DO stub.
3647
3637
  // ---------------------------------------------------------------------------
3648
3638
  async _fgGetDoc(docId) {
3649
- const stmt = compileDOSelectByDocId(this.table, docId);
3639
+ const stmt = compileSelectByDocId(this.table, docId);
3650
3640
  const rows = this.execAll(stmt);
3651
3641
  return rows.length === 0 ? null : rowToDORecord(rows[0]);
3652
3642
  }
3653
3643
  async _fgQuery(filters, options) {
3654
- const stmt = compileDOSelect(this.table, filters, options);
3644
+ const stmt = compileSelect(this.table, filters, options);
3655
3645
  const rows = this.execAll(stmt);
3656
3646
  return rows.map(rowToDORecord);
3657
3647
  }
@@ -3663,7 +3653,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3663
3653
  * the wire payload stays a plain row of (alias → number | null).
3664
3654
  */
3665
3655
  async _fgAggregate(spec, filters) {
3666
- const { stmt, aliases } = compileDOAggregate(this.table, spec, filters);
3656
+ const { stmt, aliases } = compileAggregate(this.table, spec, filters);
3667
3657
  const rows = this.execAll(stmt);
3668
3658
  const row = rows[0] ?? {};
3669
3659
  const out = {};
@@ -3680,11 +3670,11 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3680
3670
  // RPC: writes
3681
3671
  // ---------------------------------------------------------------------------
3682
3672
  async _fgSetDoc(docId, record, mode) {
3683
- const stmt = compileDOSet(this.table, docId, record, Date.now(), mode);
3673
+ const stmt = compileSet(this.table, docId, record, Date.now(), mode);
3684
3674
  this.execRun(stmt);
3685
3675
  }
3686
3676
  async _fgUpdateDoc(docId, update) {
3687
- const stmt = compileDOUpdate(this.table, docId, update, Date.now());
3677
+ const stmt = compileUpdate(this.table, docId, update, Date.now());
3688
3678
  const sqlWithReturning = `${stmt.sql} RETURNING "doc_id"`;
3689
3679
  const rows = this.state.storage.sql.exec(sqlWithReturning, ...stmt.params).toArray();
3690
3680
  if (rows.length === 0) {
@@ -3692,7 +3682,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3692
3682
  }
3693
3683
  }
3694
3684
  async _fgDeleteDoc(docId) {
3695
- const stmt = compileDODelete(this.table, docId);
3685
+ const stmt = compileDelete(this.table, docId);
3696
3686
  this.execRun(stmt);
3697
3687
  }
3698
3688
  // ---------------------------------------------------------------------------
@@ -3710,11 +3700,11 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3710
3700
  const statements = ops.map((op) => {
3711
3701
  switch (op.kind) {
3712
3702
  case "set":
3713
- return compileDOSet(this.table, op.docId, op.record, now, op.mode);
3703
+ return compileSet(this.table, op.docId, op.record, now, op.mode);
3714
3704
  case "update":
3715
- return compileDOUpdate(this.table, op.docId, op.update, now);
3705
+ return compileUpdate(this.table, op.docId, op.update, now);
3716
3706
  case "delete":
3717
- return compileDODelete(this.table, op.docId);
3707
+ return compileDelete(this.table, op.docId);
3718
3708
  }
3719
3709
  });
3720
3710
  this.state.storage.transactionSync(() => {
@@ -3733,8 +3723,8 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3733
3723
  // Without that topology the DO has no way to enumerate its children.
3734
3724
  // ---------------------------------------------------------------------------
3735
3725
  async _fgRemoveNodeCascade(uid) {
3736
- const outgoingStmt = compileDOSelect(this.table, [{ field: "aUid", op: "==", value: uid }]);
3737
- const incomingStmt = compileDOSelect(this.table, [{ field: "bUid", op: "==", value: uid }]);
3726
+ const outgoingStmt = compileSelect(this.table, [{ field: "aUid", op: "==", value: uid }]);
3727
+ const incomingStmt = compileSelect(this.table, [{ field: "bUid", op: "==", value: uid }]);
3738
3728
  const outgoingRows = this.execAll(outgoingStmt);
3739
3729
  const incomingRows = this.execAll(incomingStmt);
3740
3730
  const seen = /* @__PURE__ */ new Set();
@@ -3754,9 +3744,9 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3754
3744
  edgeDocIds.push(docId);
3755
3745
  }
3756
3746
  }
3757
- const statements = edgeDocIds.map((id) => compileDODelete(this.table, id));
3747
+ const statements = edgeDocIds.map((id) => compileDelete(this.table, id));
3758
3748
  if (nodeExists) {
3759
- statements.push(compileDODelete(this.table, computeNodeDocId(uid)));
3749
+ statements.push(compileDelete(this.table, computeNodeDocId(uid)));
3760
3750
  }
3761
3751
  if (statements.length === 0) {
3762
3752
  return {
@@ -3795,11 +3785,11 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3795
3785
  const plan = buildEdgeQueryPlan(params);
3796
3786
  let docIds;
3797
3787
  if (plan.strategy === "get") {
3798
- const existsStmt = compileDOSelectByDocId(this.table, plan.docId);
3788
+ const existsStmt = compileSelectByDocId(this.table, plan.docId);
3799
3789
  const rows = this.execAll(existsStmt);
3800
3790
  docIds = rows.length > 0 ? [plan.docId] : [];
3801
3791
  } else {
3802
- const selectStmt = compileDOSelect(this.table, plan.filters, plan.options);
3792
+ const selectStmt = compileSelect(this.table, plan.filters, plan.options);
3803
3793
  const rows = this.execAll(selectStmt);
3804
3794
  docIds = rows.map(
3805
3795
  (row) => computeEdgeDocId(row.a_uid, row.axb_type, row.b_uid)
@@ -3808,7 +3798,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3808
3798
  if (docIds.length === 0) {
3809
3799
  return { deleted: 0, batches: 0, errors: [] };
3810
3800
  }
3811
- const deleteStmts = docIds.map((id) => compileDODelete(this.table, id));
3801
+ const deleteStmts = docIds.map((id) => compileDelete(this.table, id));
3812
3802
  try {
3813
3803
  this.state.storage.transactionSync(() => {
3814
3804
  for (const stmt of deleteStmts) {
@@ -3853,12 +3843,12 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3853
3843
  "INVALID_ARGUMENT"
3854
3844
  );
3855
3845
  }
3856
- const stmt = compileDOBulkDelete(this.table, filters);
3846
+ const stmt = compileBulkDelete(this.table, filters);
3857
3847
  return this.execDmlWithReturning(stmt);
3858
3848
  }
3859
3849
  async _fgBulkUpdate(filters, patch, _options) {
3860
3850
  void _options;
3861
- const stmt = compileDOBulkUpdate(this.table, filters, patch.data, Date.now());
3851
+ const stmt = compileBulkUpdate(this.table, filters, patch.data, Date.now());
3862
3852
  return this.execDmlWithReturning(stmt);
3863
3853
  }
3864
3854
  // ---------------------------------------------------------------------------
@@ -3875,7 +3865,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3875
3865
  if (params.sources.length === 0) {
3876
3866
  return params.hydrate ? { edges: [], targets: [] } : { edges: [] };
3877
3867
  }
3878
- const stmt = compileDOExpand(this.table, params);
3868
+ const stmt = compileExpand(this.table, params);
3879
3869
  const rows = this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
3880
3870
  const edges = rows.map((row) => rowToDORecord(row));
3881
3871
  if (!params.hydrate) {
@@ -3887,7 +3877,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3887
3877
  if (uniqueTargets.length === 0) {
3888
3878
  return { edges, targets: [] };
3889
3879
  }
3890
- const hydrateStmt = compileDOExpandHydrate(this.table, uniqueTargets);
3880
+ const hydrateStmt = compileExpandHydrate(this.table, uniqueTargets);
3891
3881
  const hydrateRows = this.state.storage.sql.exec(hydrateStmt.sql, ...hydrateStmt.params).toArray();
3892
3882
  const byUid = /* @__PURE__ */ new Map();
3893
3883
  for (const row of hydrateRows) {
@@ -3909,7 +3899,7 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3909
3899
  // a typical projection); structured clone copes happily.
3910
3900
  // ---------------------------------------------------------------------------
3911
3901
  async _fgFindEdgesProjected(select, filters, options) {
3912
- const { stmt, columns } = compileDOFindEdgesProjected(this.table, select, filters, options);
3902
+ const { stmt, columns } = compileFindEdgesProjected(this.table, select, filters, options);
3913
3903
  const rows = this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
3914
3904
  return { rows, columns };
3915
3905
  }
@@ -3947,14 +3937,14 @@ var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
3947
3937
  * forever), but its storage can be emptied.
3948
3938
  */
3949
3939
  async _fgDestroy() {
3950
- const stmt = compileDODeleteAll(this.table);
3940
+ const stmt = compileDeleteAll(this.table);
3951
3941
  this.execRun(stmt);
3952
3942
  }
3953
3943
  // ---------------------------------------------------------------------------
3954
3944
  // Internals
3955
3945
  // ---------------------------------------------------------------------------
3956
3946
  runSchema() {
3957
- const statements = buildDOSchemaStatements(this.table, {
3947
+ const statements = buildSchemaStatements(this.table, {
3958
3948
  coreIndexes: this.coreIndexes,
3959
3949
  registry: this.registry
3960
3950
  });