@nicia-ai/typegraph 0.20.0 → 0.22.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 (101) hide show
  1. package/dist/{ast-Bh2NDeaK.d.cts → ast-CSLbPIOC.d.ts} +56 -3
  2. package/dist/{ast-COMyNeMn.d.ts → ast-DogwbBqA.d.cts} +56 -3
  3. package/dist/backend/postgres/index.cjs +123 -45
  4. package/dist/backend/postgres/index.cjs.map +1 -1
  5. package/dist/backend/postgres/index.d.cts +15 -7
  6. package/dist/backend/postgres/index.d.ts +15 -7
  7. package/dist/backend/postgres/index.js +101 -23
  8. package/dist/backend/postgres/index.js.map +1 -1
  9. package/dist/backend/sqlite/index.cjs +15 -15
  10. package/dist/backend/sqlite/index.d.cts +22 -7
  11. package/dist/backend/sqlite/index.d.ts +22 -7
  12. package/dist/backend/sqlite/index.js +5 -5
  13. package/dist/backend/sqlite/libsql.cjs +8 -8
  14. package/dist/backend/sqlite/libsql.d.cts +4 -5
  15. package/dist/backend/sqlite/libsql.d.ts +4 -5
  16. package/dist/backend/sqlite/libsql.js +5 -5
  17. package/dist/backend/sqlite/local.cjs +27 -10
  18. package/dist/backend/sqlite/local.cjs.map +1 -1
  19. package/dist/backend/sqlite/local.d.cts +4 -5
  20. package/dist/backend/sqlite/local.d.ts +4 -5
  21. package/dist/backend/sqlite/local.js +22 -6
  22. package/dist/backend/sqlite/local.js.map +1 -1
  23. package/dist/{chunk-MME3H4ZF.cjs → chunk-3JK66E3I.cjs} +387 -9
  24. package/dist/chunk-3JK66E3I.cjs.map +1 -0
  25. package/dist/{chunk-6GWJH6AR.js → chunk-3PPEHLGK.js} +4 -4
  26. package/dist/{chunk-6GWJH6AR.js.map → chunk-3PPEHLGK.js.map} +1 -1
  27. package/dist/{chunk-QFZQGEQ3.js → chunk-5ZMMHUWR.js} +199 -38
  28. package/dist/chunk-5ZMMHUWR.js.map +1 -0
  29. package/dist/{chunk-GNIYZKBI.js → chunk-753F33CV.js} +378 -8
  30. package/dist/chunk-753F33CV.js.map +1 -0
  31. package/dist/{chunk-K7SQ3SWP.js → chunk-EYELZHKY.js} +120 -12
  32. package/dist/chunk-EYELZHKY.js.map +1 -0
  33. package/dist/{chunk-HXJXTOXR.cjs → chunk-GUYI6VAS.cjs} +101 -21
  34. package/dist/chunk-GUYI6VAS.cjs.map +1 -0
  35. package/dist/{chunk-X33WV2UF.cjs → chunk-HRNQ3DVJ.cjs} +24 -21
  36. package/dist/chunk-HRNQ3DVJ.cjs.map +1 -0
  37. package/dist/{chunk-W6M7XWKX.js → chunk-JYUQA77I.js} +21 -18
  38. package/dist/chunk-JYUQA77I.js.map +1 -0
  39. package/dist/{chunk-LLHFS53Q.js → chunk-NSLTDXVH.js} +91 -11
  40. package/dist/chunk-NSLTDXVH.js.map +1 -0
  41. package/dist/{chunk-LELLOHJK.cjs → chunk-PEE4EWXZ.cjs} +35 -35
  42. package/dist/{chunk-LELLOHJK.cjs.map → chunk-PEE4EWXZ.cjs.map} +1 -1
  43. package/dist/{chunk-G5CYWKRF.js → chunk-RS5OLGEW.js} +2 -2
  44. package/dist/{chunk-G5CYWKRF.js.map → chunk-RS5OLGEW.js.map} +1 -1
  45. package/dist/{chunk-IYWWXOVK.cjs → chunk-SCXOGKLD.cjs} +252 -91
  46. package/dist/chunk-SCXOGKLD.cjs.map +1 -0
  47. package/dist/{chunk-P5CNM325.cjs → chunk-UVZHTBZ5.cjs} +123 -11
  48. package/dist/chunk-UVZHTBZ5.cjs.map +1 -0
  49. package/dist/{chunk-5GZMPJOP.cjs → chunk-WRWVWKQL.cjs} +2 -2
  50. package/dist/{chunk-5GZMPJOP.cjs.map → chunk-WRWVWKQL.cjs.map} +1 -1
  51. package/dist/{ddl-BBo2EcFc.d.cts → ddl-BqkTShcR.d.ts} +28 -6
  52. package/dist/{ddl-D4jlcOH_.d.ts → ddl-Dk1NDN3M.d.cts} +28 -6
  53. package/dist/index.cjs +1809 -513
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +53 -11
  56. package/dist/index.d.ts +53 -11
  57. package/dist/index.js +1668 -398
  58. package/dist/index.js.map +1 -1
  59. package/dist/indexes/index.cjs +17 -17
  60. package/dist/indexes/index.d.cts +5 -5
  61. package/dist/indexes/index.d.ts +5 -5
  62. package/dist/indexes/index.js +3 -3
  63. package/dist/interchange/index.cjs +6 -6
  64. package/dist/interchange/index.d.cts +4 -5
  65. package/dist/interchange/index.d.ts +4 -5
  66. package/dist/interchange/index.js +1 -1
  67. package/dist/{manager-Chhrq1vl.d.ts → manager-0jVZtiE8.d.ts} +1 -2
  68. package/dist/{manager-Bj1UEnhE.d.cts → manager-BPK-2PUI.d.cts} +1 -2
  69. package/dist/profiler/index.cjs +15 -8
  70. package/dist/profiler/index.cjs.map +1 -1
  71. package/dist/profiler/index.d.cts +7 -8
  72. package/dist/profiler/index.d.ts +7 -8
  73. package/dist/profiler/index.js +8 -1
  74. package/dist/profiler/index.js.map +1 -1
  75. package/dist/schema/index.cjs +21 -21
  76. package/dist/schema/index.d.cts +4 -5
  77. package/dist/schema/index.d.ts +4 -5
  78. package/dist/schema/index.js +3 -3
  79. package/dist/{sqlite-DE-6NWtC.d.cts → sqlite-D1rrwcir.d.cts} +14 -1
  80. package/dist/{sqlite-Blq-AhmK.d.ts → sqlite-W3rmJMlc.d.ts} +14 -1
  81. package/dist/{store-BMrRi_mY.d.ts → store-2QW2SIRk.d.cts} +404 -5
  82. package/dist/{store-DRRvALu8.d.cts → store-C0q13mtJ.d.ts} +404 -5
  83. package/dist/{types-B3mmOMJV.d.cts → types-CQF88McG.d.cts} +475 -2
  84. package/dist/{types-ThB4cFLp.d.ts → types-CQF88McG.d.ts} +475 -2
  85. package/dist/{types-COPePE8_.d.ts → types-CglvhPKz.d.ts} +1 -1
  86. package/dist/{types-CVtGFpB9.d.cts → types-D23slCOA.d.cts} +1 -1
  87. package/dist/{types-Ceb01czq.d.ts → types-IMQCuser.d.ts} +2 -2
  88. package/dist/{types-CZd2PEOc.d.cts → types-vgjar7Ch.d.cts} +2 -2
  89. package/package.json +9 -9
  90. package/dist/chunk-GNIYZKBI.js.map +0 -1
  91. package/dist/chunk-HXJXTOXR.cjs.map +0 -1
  92. package/dist/chunk-IYWWXOVK.cjs.map +0 -1
  93. package/dist/chunk-K7SQ3SWP.js.map +0 -1
  94. package/dist/chunk-LLHFS53Q.js.map +0 -1
  95. package/dist/chunk-MME3H4ZF.cjs.map +0 -1
  96. package/dist/chunk-P5CNM325.cjs.map +0 -1
  97. package/dist/chunk-QFZQGEQ3.js.map +0 -1
  98. package/dist/chunk-W6M7XWKX.js.map +0 -1
  99. package/dist/chunk-X33WV2UF.cjs.map +0 -1
  100. package/dist/types-DMzKq0d5.d.cts +0 -181
  101. package/dist/types-DMzKq0d5.d.ts +0 -181
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var chunkX33WV2UF_cjs = require('./chunk-X33WV2UF.cjs');
4
- var chunk5GZMPJOP_cjs = require('./chunk-5GZMPJOP.cjs');
3
+ var chunkHRNQ3DVJ_cjs = require('./chunk-HRNQ3DVJ.cjs');
4
+ var chunkWRWVWKQL_cjs = require('./chunk-WRWVWKQL.cjs');
5
5
  var chunkO5PST6TB_cjs = require('./chunk-O5PST6TB.cjs');
6
- var chunkMME3H4ZF_cjs = require('./chunk-MME3H4ZF.cjs');
7
- var chunkP5CNM325_cjs = require('./chunk-P5CNM325.cjs');
6
+ var chunk3JK66E3I_cjs = require('./chunk-3JK66E3I.cjs');
7
+ var chunkUVZHTBZ5_cjs = require('./chunk-UVZHTBZ5.cjs');
8
8
  var zod = require('zod');
9
9
  var drizzleOrm = require('drizzle-orm');
10
10
  var nanoid = require('nanoid');
@@ -29,6 +29,44 @@ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
29
29
  "constructor",
30
30
  "prototype"
31
31
  ]);
32
+ var RESERVED_PROPERTY_PREFIX = "$";
33
+ function isReservedPropertyName(name) {
34
+ return name.startsWith(RESERVED_PROPERTY_PREFIX);
35
+ }
36
+ function assertSchemaKeysAreFree(entityKind, name, keys, reservedStructuralKeys) {
37
+ const structuralConflicts = keys.filter(
38
+ (key) => reservedStructuralKeys.has(key)
39
+ );
40
+ if (structuralConflicts.length > 0) {
41
+ const label = entityKind.toLowerCase();
42
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
43
+ `${entityKind} "${name}" schema contains reserved property names: ${structuralConflicts.join(", ")}`,
44
+ {
45
+ [`${label}Type`]: name,
46
+ conflicts: structuralConflicts,
47
+ reservedKeys: [...reservedStructuralKeys]
48
+ },
49
+ {
50
+ suggestion: `Rename the conflicting properties. Reserved names (${[...reservedStructuralKeys].join(", ")}) are added automatically to all ${label}s.`
51
+ }
52
+ );
53
+ }
54
+ const prefixConflicts = keys.filter((key) => isReservedPropertyName(key));
55
+ if (prefixConflicts.length > 0) {
56
+ const label = entityKind.toLowerCase();
57
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
58
+ `${entityKind} "${name}" schema uses the reserved "${RESERVED_PROPERTY_PREFIX}" prefix on: ${prefixConflicts.join(", ")}`,
59
+ {
60
+ [`${label}Type`]: name,
61
+ conflicts: prefixConflicts,
62
+ reservedPrefix: RESERVED_PROPERTY_PREFIX
63
+ },
64
+ {
65
+ suggestion: `Property names starting with "${RESERVED_PROPERTY_PREFIX}" are reserved for TypeGraph accessors (e.g. $fulltext). Rename each field.`
66
+ }
67
+ );
68
+ }
69
+ }
32
70
  function validateProjectionField(field2, entityType, kind) {
33
71
  const reserved = entityType === "node" ? RESERVED_NODE_KEYS : RESERVED_EDGE_KEYS;
34
72
  if (reserved.has(field2)) {
@@ -62,24 +100,17 @@ function filterReservedKeys(props, reservedKeys) {
62
100
 
63
101
  // src/core/node.ts
64
102
  function validateSchemaKeys(schema, name) {
65
- const shape = schema.shape;
66
- const conflicts = Object.keys(shape).filter(
67
- (key) => RESERVED_NODE_KEYS.has(key)
103
+ assertSchemaKeysAreFree(
104
+ "Node",
105
+ name,
106
+ Object.keys(schema.shape),
107
+ RESERVED_NODE_KEYS
68
108
  );
69
- if (conflicts.length > 0) {
70
- throw new chunkO5PST6TB_cjs.ConfigurationError(
71
- `Node "${name}" schema contains reserved property names: ${conflicts.join(", ")}`,
72
- { nodeType: name, conflicts, reservedKeys: [...RESERVED_NODE_KEYS] },
73
- {
74
- suggestion: `Rename the conflicting properties. Reserved names (id, kind, meta) are added automatically to all nodes.`
75
- }
76
- );
77
- }
78
109
  }
79
110
  function defineNode(name, options) {
80
111
  validateSchemaKeys(options.schema, name);
81
112
  return Object.freeze({
82
- [chunk5GZMPJOP_cjs.NODE_TYPE_BRAND]: true,
113
+ [chunkWRWVWKQL_cjs.NODE_TYPE_BRAND]: true,
83
114
  kind: name,
84
115
  schema: options.schema,
85
116
  description: options.description
@@ -87,25 +118,18 @@ function defineNode(name, options) {
87
118
  }
88
119
  var EMPTY_SCHEMA = zod.z.object({});
89
120
  function validateSchemaKeys2(schema, name) {
90
- const shape = schema.shape;
91
- const conflicts = Object.keys(shape).filter(
92
- (key) => RESERVED_EDGE_KEYS.has(key)
121
+ assertSchemaKeysAreFree(
122
+ "Edge",
123
+ name,
124
+ Object.keys(schema.shape),
125
+ RESERVED_EDGE_KEYS
93
126
  );
94
- if (conflicts.length > 0) {
95
- throw new chunkO5PST6TB_cjs.ConfigurationError(
96
- `Edge "${name}" schema contains reserved property names: ${conflicts.join(", ")}`,
97
- { edgeType: name, conflicts, reservedKeys: [...RESERVED_EDGE_KEYS] },
98
- {
99
- suggestion: `Rename the conflicting properties. Reserved names are added automatically to all edges.`
100
- }
101
- );
102
- }
103
127
  }
104
128
  function defineEdge(name, options) {
105
129
  const schema = options?.schema ?? EMPTY_SCHEMA;
106
130
  validateSchemaKeys2(schema, name);
107
131
  return Object.freeze({
108
- [chunk5GZMPJOP_cjs.EDGE_TYPE_BRAND]: true,
132
+ [chunkWRWVWKQL_cjs.EDGE_TYPE_BRAND]: true,
109
133
  kind: name,
110
134
  schema,
111
135
  description: options?.description,
@@ -125,7 +149,7 @@ function metaEdge(name, options = {}) {
125
149
  description: options.description
126
150
  };
127
151
  return Object.freeze({
128
- [chunkX33WV2UF_cjs.META_EDGE_BRAND]: true,
152
+ [chunkHRNQ3DVJ_cjs.META_EDGE_BRAND]: true,
129
153
  name,
130
154
  properties
131
155
  });
@@ -161,7 +185,7 @@ function createExternalRef(table) {
161
185
  // src/ontology/core-meta-edges.ts
162
186
  function createMetaEdge(name, properties) {
163
187
  return Object.freeze({
164
- [chunkX33WV2UF_cjs.META_EDGE_BRAND]: true,
188
+ [chunkHRNQ3DVJ_cjs.META_EDGE_BRAND]: true,
165
189
  name,
166
190
  properties: {
167
191
  transitive: properties.transitive ?? false,
@@ -173,7 +197,7 @@ function createMetaEdge(name, properties) {
173
197
  }
174
198
  });
175
199
  }
176
- var subClassOfMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_SUB_CLASS_OF, {
200
+ var subClassOfMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_SUB_CLASS_OF, {
177
201
  transitive: true,
178
202
  inference: "subsumption",
179
203
  description: "Type inheritance (Podcast subClassOf Media)"
@@ -185,9 +209,9 @@ function subClassOf(child, parent) {
185
209
  to: parent
186
210
  };
187
211
  }
188
- var broaderMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_BROADER, {
212
+ var broaderMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_BROADER, {
189
213
  transitive: true,
190
- inverse: chunkX33WV2UF_cjs.META_EDGE_NARROWER,
214
+ inverse: chunkHRNQ3DVJ_cjs.META_EDGE_NARROWER,
191
215
  inference: "hierarchy",
192
216
  description: "Broader concept (ML broader AI)"
193
217
  });
@@ -198,9 +222,9 @@ function broader(narrowerConcept, broaderConcept) {
198
222
  to: broaderConcept
199
223
  };
200
224
  }
201
- var narrowerMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_NARROWER, {
225
+ var narrowerMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_NARROWER, {
202
226
  transitive: true,
203
- inverse: chunkX33WV2UF_cjs.META_EDGE_BROADER,
227
+ inverse: chunkHRNQ3DVJ_cjs.META_EDGE_BROADER,
204
228
  inference: "hierarchy",
205
229
  description: "Narrower concept (AI narrower ML)"
206
230
  });
@@ -211,7 +235,7 @@ function narrower(broaderConcept, narrowerConcept) {
211
235
  to: narrowerConcept
212
236
  };
213
237
  }
214
- var relatedToMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_RELATED_TO, {
238
+ var relatedToMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_RELATED_TO, {
215
239
  symmetric: true,
216
240
  inference: "association",
217
241
  description: "Non-hierarchical association"
@@ -223,7 +247,7 @@ function relatedTo(conceptA, conceptB) {
223
247
  to: conceptB
224
248
  };
225
249
  }
226
- var equivalentToMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_EQUIVALENT_TO, {
250
+ var equivalentToMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_EQUIVALENT_TO, {
227
251
  symmetric: true,
228
252
  transitive: true,
229
253
  inference: "substitution",
@@ -236,7 +260,7 @@ function equivalentTo(kindA, kindBOrIri) {
236
260
  to: kindBOrIri
237
261
  };
238
262
  }
239
- var sameAsMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_SAME_AS, {
263
+ var sameAsMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_SAME_AS, {
240
264
  symmetric: true,
241
265
  transitive: true,
242
266
  inference: "substitution",
@@ -249,7 +273,7 @@ function sameAs(kindA, kindBOrIri) {
249
273
  to: kindBOrIri
250
274
  };
251
275
  }
252
- var differentFromMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_DIFFERENT_FROM, {
276
+ var differentFromMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_DIFFERENT_FROM, {
253
277
  symmetric: true,
254
278
  inference: "constraint",
255
279
  description: "Explicitly different individuals"
@@ -261,7 +285,7 @@ function differentFrom(kindA, kindB) {
261
285
  to: kindB
262
286
  };
263
287
  }
264
- var disjointWithMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_DISJOINT_WITH, {
288
+ var disjointWithMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_DISJOINT_WITH, {
265
289
  symmetric: true,
266
290
  inference: "constraint",
267
291
  description: "Mutually exclusive types"
@@ -273,9 +297,9 @@ function disjointWith(kindA, kindB) {
273
297
  to: kindB
274
298
  };
275
299
  }
276
- var partOfMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_PART_OF, {
300
+ var partOfMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_PART_OF, {
277
301
  transitive: true,
278
- inverse: chunkX33WV2UF_cjs.META_EDGE_HAS_PART,
302
+ inverse: chunkHRNQ3DVJ_cjs.META_EDGE_HAS_PART,
279
303
  inference: "composition",
280
304
  description: "X is part of Y"
281
305
  });
@@ -286,9 +310,9 @@ function partOf(part, whole) {
286
310
  to: whole
287
311
  };
288
312
  }
289
- var hasPartMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_HAS_PART, {
313
+ var hasPartMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_HAS_PART, {
290
314
  transitive: true,
291
- inverse: chunkX33WV2UF_cjs.META_EDGE_PART_OF,
315
+ inverse: chunkHRNQ3DVJ_cjs.META_EDGE_PART_OF,
292
316
  inference: "composition",
293
317
  description: "Y has part X"
294
318
  });
@@ -299,12 +323,12 @@ function hasPart(whole, part) {
299
323
  to: part
300
324
  };
301
325
  }
302
- var inverseOfMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_INVERSE_OF, {
326
+ var inverseOfMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_INVERSE_OF, {
303
327
  symmetric: true,
304
328
  inference: "none",
305
329
  description: "Edge A is inverse of edge B"
306
330
  });
307
- var impliesMetaEdge = createMetaEdge(chunkX33WV2UF_cjs.META_EDGE_IMPLIES, {
331
+ var impliesMetaEdge = createMetaEdge(chunkHRNQ3DVJ_cjs.META_EDGE_IMPLIES, {
308
332
  transitive: true,
309
333
  inference: "none",
310
334
  description: "Edge A implies edge B exists"
@@ -565,6 +589,41 @@ function stringField(field2) {
565
589
  ilike: (pattern) => stringOp("ilike", field2, pattern)
566
590
  };
567
591
  }
592
+ var DEFAULT_FULLTEXT_MATCH_K = 50;
593
+ function createFulltextAccessor(alias, hasSearchableField) {
594
+ return {
595
+ matches: (query, k, options) => {
596
+ if (!hasSearchableField()) {
597
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
598
+ `Cannot call .$fulltext.matches() on alias "${alias}" \u2014 its node kind has no fields declared with searchable(). Add at least one: \`title: searchable({ language: "english" })\`.`
599
+ );
600
+ }
601
+ return fulltextMatch(
602
+ fieldRef(alias, ["$fulltext"]),
603
+ query,
604
+ k ?? DEFAULT_FULLTEXT_MATCH_K,
605
+ options
606
+ );
607
+ }
608
+ };
609
+ }
610
+ function fulltextMatch(field2, query, limit, options) {
611
+ if (!Number.isFinite(limit) || limit <= 0) {
612
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
613
+ `matches() limit must be a positive finite number, got ${String(limit)}`
614
+ );
615
+ }
616
+ const expr = {
617
+ __type: "fulltext_match",
618
+ field: field2,
619
+ query,
620
+ mode: options?.mode ?? "websearch",
621
+ limit,
622
+ ...options?.language === void 0 ? {} : { language: options.language },
623
+ ...options?.minScore === void 0 ? {} : { minScore: options.minScore }
624
+ };
625
+ return predicate(expr);
626
+ }
568
627
  function numberField(field2) {
569
628
  return {
570
629
  ...baseFieldBuilder(field2),
@@ -664,7 +723,7 @@ function buildFieldBuilderForTypeInfo(field2, typeInfo) {
664
723
  }
665
724
  }
666
725
  function resolvePointerTypeInfo(typeInfo, pointer) {
667
- return chunkP5CNM325_cjs.resolveFieldTypeInfoAtJsonPointer(typeInfo, pointer);
726
+ return chunkUVZHTBZ5_cjs.resolveFieldTypeInfoAtJsonPointer(typeInfo, pointer);
668
727
  }
669
728
  function objectOp(op, field2, pointer, value, options) {
670
729
  const expr = {
@@ -687,8 +746,8 @@ function objectField(field2, options) {
687
746
  return {
688
747
  ...baseFieldBuilder(field2),
689
748
  get: (key) => {
690
- const pointer = chunkP5CNM325_cjs.jsonPointer([key]);
691
- const nestedPointer = chunkP5CNM325_cjs.joinJsonPointers(basePointer, pointer);
749
+ const pointer = chunkUVZHTBZ5_cjs.jsonPointer([key]);
750
+ const nestedPointer = chunkUVZHTBZ5_cjs.joinJsonPointers(basePointer, pointer);
692
751
  const resolved = resolvePointerTypeInfo(options?.typeInfo, pointer);
693
752
  const nestedRef = fieldRef(field2.alias, field2.path, {
694
753
  jsonPointer: nestedPointer,
@@ -702,17 +761,17 @@ function objectField(field2, options) {
702
761
  return buildFieldBuilderForTypeInfo(nestedRef, resolved);
703
762
  },
704
763
  hasKey: (key) => {
705
- const pointer = chunkP5CNM325_cjs.jsonPointer([key]);
764
+ const pointer = chunkUVZHTBZ5_cjs.jsonPointer([key]);
706
765
  resolvePointerTypeInfo(options?.typeInfo, pointer);
707
766
  return objectOp("hasKey", field2, pointer);
708
767
  },
709
768
  hasPath: (pointer) => {
710
- const normalized = chunkP5CNM325_cjs.normalizeJsonPointer(pointer);
769
+ const normalized = chunkUVZHTBZ5_cjs.normalizeJsonPointer(pointer);
711
770
  resolvePointerTypeInfo(options?.typeInfo, normalized);
712
771
  return objectOp("hasPath", field2, normalized);
713
772
  },
714
773
  pathEquals: (pointer, value) => {
715
- const normalized = chunkP5CNM325_cjs.normalizeJsonPointer(pointer);
774
+ const normalized = chunkUVZHTBZ5_cjs.normalizeJsonPointer(pointer);
716
775
  const resolved = resolvePointerTypeInfo(options?.typeInfo, normalized);
717
776
  if (resolved && (resolved.valueType === "array" || resolved.valueType === "object")) {
718
777
  throw new Error("pathEquals is only supported for scalar JSON values");
@@ -721,7 +780,7 @@ function objectField(field2, options) {
721
780
  return objectOp("pathEquals", field2, normalized, value, predicateOptions);
722
781
  },
723
782
  pathContains: (pointer, value) => {
724
- const normalized = chunkP5CNM325_cjs.normalizeJsonPointer(pointer);
783
+ const normalized = chunkUVZHTBZ5_cjs.normalizeJsonPointer(pointer);
725
784
  const resolved = resolvePointerTypeInfo(options?.typeInfo, normalized);
726
785
  if (resolved && resolved.valueType !== "array") {
727
786
  throw new Error("pathContains is only supported for JSON array values");
@@ -736,7 +795,7 @@ function objectField(field2, options) {
736
795
  );
737
796
  },
738
797
  pathIsNull: (pointer) => {
739
- const normalized = chunkP5CNM325_cjs.normalizeJsonPointer(pointer);
798
+ const normalized = chunkUVZHTBZ5_cjs.normalizeJsonPointer(pointer);
740
799
  const resolved = resolvePointerTypeInfo(options?.typeInfo, normalized);
741
800
  const predicateOptions = resolved?.valueType === void 0 ? void 0 : { valueType: resolved.valueType };
742
801
  return objectOp(
@@ -748,7 +807,7 @@ function objectField(field2, options) {
748
807
  );
749
808
  },
750
809
  pathIsNotNull: (pointer) => {
751
- const normalized = chunkP5CNM325_cjs.normalizeJsonPointer(pointer);
810
+ const normalized = chunkUVZHTBZ5_cjs.normalizeJsonPointer(pointer);
752
811
  const resolved = resolvePointerTypeInfo(options?.typeInfo, normalized);
753
812
  const predicateOptions = resolved?.valueType === void 0 ? void 0 : { valueType: resolved.valueType };
754
813
  return objectOp(
@@ -760,8 +819,8 @@ function objectField(field2, options) {
760
819
  );
761
820
  },
762
821
  field: (pointer) => {
763
- const normalized = chunkP5CNM325_cjs.normalizeJsonPointer(pointer);
764
- const nestedPointer = chunkP5CNM325_cjs.joinJsonPointers(basePointer, normalized);
822
+ const normalized = chunkUVZHTBZ5_cjs.normalizeJsonPointer(pointer);
823
+ const nestedPointer = chunkUVZHTBZ5_cjs.joinJsonPointers(basePointer, normalized);
765
824
  const resolved = resolvePointerTypeInfo(options?.typeInfo, normalized);
766
825
  const nestedRef = fieldRef(field2.alias, field2.path, {
767
826
  jsonPointer: nestedPointer,
@@ -891,7 +950,7 @@ function getFieldPointer(field2) {
891
950
  return field2.jsonPointer;
892
951
  }
893
952
  if (field2.path.length > 1 && field2.path[0] === "props") {
894
- return chunkP5CNM325_cjs.jsonPointer(field2.path.slice(1));
953
+ return chunkUVZHTBZ5_cjs.jsonPointer(field2.path.slice(1));
895
954
  }
896
955
  return void 0;
897
956
  }
@@ -1140,11 +1199,13 @@ function compilePredicateExpression(expr, ctx) {
1140
1199
  case "in_subquery": {
1141
1200
  return compileInSubquery(expr, ctx);
1142
1201
  }
1143
- case "vector_similarity": {
1144
- return compileVectorSimilarityPredicate();
1202
+ case "vector_similarity":
1203
+ case "fulltext_match": {
1204
+ return STRUCTURAL_PREDICATE_NOOP;
1145
1205
  }
1146
1206
  }
1147
1207
  }
1208
+ var STRUCTURAL_PREDICATE_NOOP = drizzleOrm.sql.raw("1=1");
1148
1209
  function compileComparisonPredicate(expr, dialect, cteColumnPrefix) {
1149
1210
  if (isParameterRef(expr.right)) {
1150
1211
  const parameterValueType = normalizeValueType2(expr.right.valueType) ?? normalizeValueType2(expr.left.valueType);
@@ -1248,7 +1309,7 @@ function compileArrayPredicate(expr, dialect, cteColumnPrefix) {
1248
1309
  }
1249
1310
  function compileObjectPredicate(expr, dialect, cteColumnPrefix) {
1250
1311
  const basePointer = getFieldPointer(expr.field);
1251
- const pointer = chunkP5CNM325_cjs.joinJsonPointers(basePointer, expr.pointer);
1312
+ const pointer = chunkUVZHTBZ5_cjs.joinJsonPointers(basePointer, expr.pointer);
1252
1313
  const column = compileFieldColumn(expr.field, void 0, cteColumnPrefix);
1253
1314
  switch (expr.op) {
1254
1315
  case "hasKey":
@@ -1398,45 +1459,61 @@ function compileInSubquery(expr, ctx) {
1398
1459
  const subSql = ctx.compileQuery(expr.subquery, expr.subquery.graphId);
1399
1460
  return expr.negated ? drizzleOrm.sql`${fieldSql} NOT IN (${subSql})` : drizzleOrm.sql`${fieldSql} IN (${subSql})`;
1400
1461
  }
1401
- function compileVectorSimilarityPredicate(_expr, _ctx) {
1402
- return drizzleOrm.sql.raw("1=1");
1403
- }
1404
- function extractVectorSimilarityPredicates(predicates) {
1462
+ function extractStructuralPredicates(predicates, isMatch, rejectionMessage) {
1405
1463
  const results = [];
1406
1464
  function visit(expr, inDisallowedBranch) {
1407
- switch (expr.__type) {
1408
- case "vector_similarity": {
1409
- if (inDisallowedBranch) {
1410
- throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
1411
- "Vector similarity predicates cannot be nested under OR or NOT. Use top-level AND combinations instead."
1412
- );
1413
- }
1414
- results.push(expr);
1415
- break;
1465
+ if (isMatch(expr)) {
1466
+ if (inDisallowedBranch) {
1467
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(rejectionMessage);
1416
1468
  }
1469
+ results.push(expr);
1470
+ return;
1471
+ }
1472
+ switch (expr.__type) {
1417
1473
  case "and": {
1418
- for (const predicate2 of expr.predicates) {
1419
- visit(predicate2, inDisallowedBranch);
1420
- }
1421
- break;
1474
+ for (const child of expr.predicates) visit(child, inDisallowedBranch);
1475
+ return;
1422
1476
  }
1423
1477
  case "or": {
1424
- for (const predicate2 of expr.predicates) {
1425
- visit(predicate2, true);
1426
- }
1427
- break;
1478
+ for (const child of expr.predicates) visit(child, true);
1479
+ return;
1428
1480
  }
1429
1481
  case "not": {
1430
1482
  visit(expr.predicate, true);
1431
- break;
1483
+ return;
1484
+ }
1485
+ case "comparison":
1486
+ case "string_op":
1487
+ case "null_check":
1488
+ case "between":
1489
+ case "array_op":
1490
+ case "object_op":
1491
+ case "aggregate_comparison":
1492
+ case "exists":
1493
+ case "in_subquery":
1494
+ case "vector_similarity":
1495
+ case "fulltext_match": {
1496
+ return;
1432
1497
  }
1433
1498
  }
1434
1499
  }
1435
- for (const pred of predicates) {
1436
- visit(pred.expression, false);
1437
- }
1500
+ for (const pred of predicates) visit(pred.expression, false);
1438
1501
  return results;
1439
1502
  }
1503
+ function extractVectorSimilarityPredicates(predicates) {
1504
+ return extractStructuralPredicates(
1505
+ predicates,
1506
+ (expr) => expr.__type === "vector_similarity",
1507
+ "Vector similarity predicates cannot be nested under OR or NOT. Use top-level AND combinations instead."
1508
+ );
1509
+ }
1510
+ function extractFulltextMatchPredicates(predicates) {
1511
+ return extractStructuralPredicates(
1512
+ predicates,
1513
+ (expr) => expr.__type === "fulltext_match",
1514
+ "Fulltext match predicates cannot be nested under OR or NOT. Use top-level AND combinations instead."
1515
+ );
1516
+ }
1440
1517
 
1441
1518
  // src/query/compiler/predicate-utils.ts
1442
1519
  var EMPTY_PREDICATES = [];
@@ -1495,6 +1572,10 @@ function getNodeKindsForAlias(ast, alias) {
1495
1572
  }
1496
1573
  throw new chunkO5PST6TB_cjs.CompilerInvariantError(`Unknown traversal source alias: ${alias}`);
1497
1574
  }
1575
+ function getHybridTargetAlias(vectorPredicate, fulltextPredicate) {
1576
+ if (!vectorPredicate || !fulltextPredicate) return void 0;
1577
+ return vectorPredicate.field.alias === fulltextPredicate.field.alias ? vectorPredicate.field.alias : void 0;
1578
+ }
1498
1579
 
1499
1580
  // src/query/compiler/emitter/plan-inspector.ts
1500
1581
  function collectPlanOperations(node, ops) {
@@ -1502,6 +1583,7 @@ function collectPlanOperations(node, ops) {
1502
1583
  switch (node.op) {
1503
1584
  case "aggregate":
1504
1585
  case "filter":
1586
+ case "fulltext_match":
1505
1587
  case "join":
1506
1588
  case "limit_offset":
1507
1589
  case "project":
@@ -1530,6 +1612,7 @@ function findUnaryNodeInProjectChain(rootNode, op) {
1530
1612
  switch (currentNode.op) {
1531
1613
  case "aggregate":
1532
1614
  case "filter":
1615
+ case "fulltext_match":
1533
1616
  case "join":
1534
1617
  case "limit_offset":
1535
1618
  case "recursive_expand":
@@ -1565,6 +1648,7 @@ function inspectProjectPlan(logicalPlan) {
1565
1648
  );
1566
1649
  return {
1567
1650
  hasAggregate: operations.has("aggregate"),
1651
+ hasFulltextMatch: operations.has("fulltext_match"),
1568
1652
  hasLimitOffset: operations.has("limit_offset"),
1569
1653
  hasRecursiveExpand: operations.has("recursive_expand"),
1570
1654
  hasSetOperation: operations.has("set_op"),
@@ -1616,6 +1700,7 @@ function findTopLevelLimitOffsetNode(rootNode) {
1616
1700
  switch (currentNode.op) {
1617
1701
  case "aggregate":
1618
1702
  case "filter":
1703
+ case "fulltext_match":
1619
1704
  case "join":
1620
1705
  case "project":
1621
1706
  case "recursive_expand":
@@ -1640,6 +1725,7 @@ function findTopLevelSortNode(rootNode) {
1640
1725
  switch (currentNode.op) {
1641
1726
  case "aggregate":
1642
1727
  case "filter":
1728
+ case "fulltext_match":
1643
1729
  case "join":
1644
1730
  case "limit_offset":
1645
1731
  case "project":
@@ -1774,16 +1860,16 @@ function assertStandardEmitterClauseAlignment(logicalPlan, input) {
1774
1860
  { component: "standard-emitter" }
1775
1861
  );
1776
1862
  }
1777
- const expectsOrderBy = planShape.hasSort || planShape.hasVectorKnn;
1863
+ const expectsOrderBy = planShape.hasSort || planShape.hasVectorKnn || planShape.hasFulltextMatch;
1778
1864
  if (expectsOrderBy && input.orderBy === void 0) {
1779
1865
  throw new chunkO5PST6TB_cjs.CompilerInvariantError(
1780
- "Standard SQL emitter expected ORDER BY clause for plan containing a sort or vector_knn node",
1866
+ "Standard SQL emitter expected ORDER BY clause for plan containing a sort, vector_knn, or fulltext_match node",
1781
1867
  { component: "standard-emitter" }
1782
1868
  );
1783
1869
  }
1784
1870
  if (!expectsOrderBy && input.orderBy !== void 0) {
1785
1871
  throw new chunkO5PST6TB_cjs.CompilerInvariantError(
1786
- "Standard SQL emitter received ORDER BY clause for a plan without sort or vector_knn nodes",
1872
+ "Standard SQL emitter received ORDER BY clause for a plan without sort, vector_knn, or fulltext_match nodes",
1787
1873
  { component: "standard-emitter" }
1788
1874
  );
1789
1875
  }
@@ -1822,6 +1908,62 @@ function emitStandardQuerySql(input) {
1822
1908
  return drizzleOrm.sql.join(parts, drizzleOrm.sql` `);
1823
1909
  }
1824
1910
 
1911
+ // src/query/ast.ts
1912
+ var DEFAULT_RRF_K = 60;
1913
+ var DEFAULT_RRF_WEIGHT = 1;
1914
+ function mergeEdgeKinds(traversal) {
1915
+ const inverse = traversal.inverseEdgeKinds;
1916
+ if (inverse === void 0 || inverse.length === 0) return traversal.edgeKinds;
1917
+ return [
1918
+ ...traversal.edgeKinds,
1919
+ ...inverse.filter((kind) => !traversal.edgeKinds.includes(kind))
1920
+ ];
1921
+ }
1922
+ var DEFAULT_TABLE_NAMES = {
1923
+ nodes: "typegraph_nodes",
1924
+ edges: "typegraph_edges",
1925
+ embeddings: "typegraph_node_embeddings",
1926
+ fulltext: "typegraph_node_fulltext"
1927
+ };
1928
+ var VALID_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_$]*$/i;
1929
+ function validateTableName(name, label) {
1930
+ if (!name || name.length === 0) {
1931
+ throw new chunkO5PST6TB_cjs.ConfigurationError(`${label} table name cannot be empty`);
1932
+ }
1933
+ if (name.length > chunk3JK66E3I_cjs.MAX_PG_IDENTIFIER_LENGTH) {
1934
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
1935
+ `${label} table name exceeds maximum length of ${chunk3JK66E3I_cjs.MAX_PG_IDENTIFIER_LENGTH} characters`
1936
+ );
1937
+ }
1938
+ if (!VALID_IDENTIFIER_PATTERN.test(name)) {
1939
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
1940
+ `${label} table name "${name}" is not a valid SQL identifier. Table names must start with a letter or underscore and contain only letters, digits, underscores, or dollar signs.`
1941
+ );
1942
+ }
1943
+ }
1944
+ function quoteIdentifier(name) {
1945
+ return `"${name.replaceAll('"', '""')}"`;
1946
+ }
1947
+ function createSqlSchema(names = {}) {
1948
+ const tables = { ...DEFAULT_TABLE_NAMES, ...names };
1949
+ validateTableName(tables.nodes, "nodes");
1950
+ validateTableName(tables.edges, "edges");
1951
+ validateTableName(tables.embeddings, "embeddings");
1952
+ validateTableName(tables.fulltext, "fulltext");
1953
+ return {
1954
+ tables,
1955
+ nodesTable: drizzleOrm.sql.raw(quoteIdentifier(tables.nodes)),
1956
+ edgesTable: drizzleOrm.sql.raw(quoteIdentifier(tables.edges)),
1957
+ embeddingsTable: drizzleOrm.sql.raw(quoteIdentifier(tables.embeddings)),
1958
+ fulltextTable: drizzleOrm.sql.raw(quoteIdentifier(tables.fulltext))
1959
+ };
1960
+ }
1961
+ var DEFAULT_SQL_SCHEMA = createSqlSchema();
1962
+ var ALIAS_CTE_PREFIX = "cte_";
1963
+ var EMBEDDINGS_CTE_ALIAS = "cte_embeddings";
1964
+ var FULLTEXT_CTE_ALIAS = "cte_fulltext";
1965
+ var HYBRID_CANDIDATES_CTE_ALIAS = "cte_relevance_candidates";
1966
+
1825
1967
  // src/query/compiler/typed-json-extract.ts
1826
1968
  function compileTypedJsonExtract(input) {
1827
1969
  const { column, dialect, pointer, valueType } = input;
@@ -1875,7 +2017,7 @@ var EDGE_COLUMNS = [
1875
2017
  "deleted_at"
1876
2018
  ];
1877
2019
  var EMPTY_REQUIRED_COLUMNS = /* @__PURE__ */ new Set();
1878
- function quoteIdentifier(identifier) {
2020
+ function quoteIdentifier2(identifier) {
1879
2021
  return drizzleOrm.sql.raw(`"${identifier.replaceAll('"', '""')}"`);
1880
2022
  }
1881
2023
  function shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns) {
@@ -1929,6 +2071,21 @@ function compileColumnReference(tableAlias, column) {
1929
2071
  }
1930
2072
  return drizzleOrm.sql`${drizzleOrm.sql.raw(tableAlias)}.${drizzleOrm.sql.raw(column)}`;
1931
2073
  }
2074
+ function qualifyColumn(owner, name) {
2075
+ return drizzleOrm.sql`${quoteIdentifier2(owner)}.${quoteIdentifier2(name)}`;
2076
+ }
2077
+ var SCOPED_RELEVANCE_NODES_ALIAS = "scoped_relevance_nodes";
2078
+ function buildScopedNodeIdsSubquery(nodeAlias) {
2079
+ const cteAlias = `${ALIAS_CTE_PREFIX}${nodeAlias}`;
2080
+ return drizzleOrm.sql`
2081
+ (
2082
+ SELECT DISTINCT
2083
+ ${qualifyColumn(cteAlias, `${nodeAlias}_id`)} AS node_id,
2084
+ ${qualifyColumn(cteAlias, `${nodeAlias}_kind`)} AS node_kind
2085
+ FROM ${quoteIdentifier2(cteAlias)}
2086
+ ) AS ${drizzleOrm.sql.raw(SCOPED_RELEVANCE_NODES_ALIAS)}
2087
+ `;
2088
+ }
1932
2089
  function compileNodeSelectColumns(tableAlias, alias, requiredColumns) {
1933
2090
  return NODE_COLUMNS.filter(
1934
2091
  (column) => column === "id" || column === "kind" || shouldProjectColumn(requiredColumns, column)
@@ -1947,6 +2104,7 @@ function buildStandardStartCte(input) {
1947
2104
  const { ast, ctx, graphId, predicateIndex, requiredColumnsByAlias } = input;
1948
2105
  const alias = ast.start.alias;
1949
2106
  const kinds = ast.start.kinds;
2107
+ const cteMaterialization = ctx.dialect.capabilities.emitNotMaterializedHint ? drizzleOrm.sql`NOT MATERIALIZED ` : drizzleOrm.sql``;
1950
2108
  const kindFilter = compileKindFilter(drizzleOrm.sql.raw("kind"), kinds);
1951
2109
  const temporalFilter = input.temporalFilterPass.forAlias();
1952
2110
  const cteContext = { ...ctx, cteColumnPrefix: "" };
@@ -1961,14 +2119,19 @@ function buildStandardStartCte(input) {
1961
2119
  ...predicateClauses
1962
2120
  ];
1963
2121
  const effectiveRequiredColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(alias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
2122
+ const limitClause = input.limitOffset === void 0 ? drizzleOrm.sql`` : input.limitOffset.offset === void 0 ? drizzleOrm.sql`
2123
+ LIMIT ${input.limitOffset.limit}
2124
+ ` : drizzleOrm.sql`
2125
+ LIMIT ${input.limitOffset.limit} OFFSET ${input.limitOffset.offset}
2126
+ `;
1964
2127
  return drizzleOrm.sql`
1965
- cte_${drizzleOrm.sql.raw(alias)} AS (
2128
+ cte_${drizzleOrm.sql.raw(alias)} AS ${cteMaterialization}(
1966
2129
  SELECT ${drizzleOrm.sql.join(
1967
2130
  compileNodeSelectColumns(void 0, alias, effectiveRequiredColumns),
1968
2131
  drizzleOrm.sql`, `
1969
2132
  )}
1970
2133
  FROM ${ctx.schema.nodesTable}
1971
- WHERE ${drizzleOrm.sql.join(whereClauses, drizzleOrm.sql` AND `)}
2134
+ WHERE ${drizzleOrm.sql.join(whereClauses, drizzleOrm.sql` AND `)}${limitClause}
1972
2135
  )
1973
2136
  `;
1974
2137
  }
@@ -2032,7 +2195,7 @@ function buildStandardTraversalCte(input) {
2032
2195
  ...compileEdgeSelectColumns("e", edgeAlias, requiredEdgeColumns),
2033
2196
  ...compileNodeSelectColumns("n", nodeAlias, requiredNodeColumns)
2034
2197
  ];
2035
- const cteMaterialization = materializeCte ? drizzleOrm.sql`MATERIALIZED ` : drizzleOrm.sql``;
2198
+ const cteMaterialization = materializeCte ? drizzleOrm.sql`MATERIALIZED ` : ctx.dialect.capabilities.emitNotMaterializedHint ? drizzleOrm.sql`NOT MATERIALIZED ` : drizzleOrm.sql``;
2036
2199
  function compileTraversalBranch(branch) {
2037
2200
  const whereClauses = [
2038
2201
  ...baseWhereClauses,
@@ -2181,7 +2344,7 @@ function buildStandardProjection(input) {
2181
2344
  }
2182
2345
  const projectedFields = fields.map((field2) => {
2183
2346
  const source = compileProjectedSource(field2, dialect);
2184
- return drizzleOrm.sql`${source} AS ${quoteIdentifier(field2.outputName)}`;
2347
+ return drizzleOrm.sql`${source} AS ${quoteIdentifier2(field2.outputName)}`;
2185
2348
  });
2186
2349
  return drizzleOrm.sql.join(projectedFields, drizzleOrm.sql`, `);
2187
2350
  }
@@ -2201,23 +2364,67 @@ function compileSelectiveProjection(fields, dialect, ast, collapsedTraversalCteA
2201
2364
  const cteAlias = collapsedTraversalCteAlias ?? aliasToCte.get(field2.alias) ?? `cte_${field2.alias}`;
2202
2365
  if (field2.isSystemField) {
2203
2366
  const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
2204
- return drizzleOrm.sql`${drizzleOrm.sql.raw(cteAlias)}.${drizzleOrm.sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
2367
+ return drizzleOrm.sql`${drizzleOrm.sql.raw(cteAlias)}.${drizzleOrm.sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier2(field2.outputName)}`;
2205
2368
  }
2206
2369
  const propsColumn = `${field2.alias}_props`;
2207
2370
  const column = drizzleOrm.sql`${drizzleOrm.sql.raw(cteAlias)}.${drizzleOrm.sql.raw(propsColumn)}`;
2208
- const pointer = chunkP5CNM325_cjs.jsonPointer([field2.field]);
2371
+ const pointer = chunkUVZHTBZ5_cjs.jsonPointer([field2.field]);
2209
2372
  const extracted = compileTypedJsonExtract({
2210
2373
  column,
2211
2374
  dialect,
2212
2375
  pointer,
2213
2376
  valueType: field2.valueType
2214
2377
  });
2215
- return drizzleOrm.sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
2378
+ return drizzleOrm.sql`${extracted} AS ${quoteIdentifier2(field2.outputName)}`;
2216
2379
  });
2217
2380
  return drizzleOrm.sql.join(columns, drizzleOrm.sql`, `);
2218
2381
  }
2382
+ function buildRelevanceJoins(vectorPredicate, fulltextPredicate) {
2383
+ const hybridTargetAlias = getHybridTargetAlias(
2384
+ vectorPredicate,
2385
+ fulltextPredicate
2386
+ );
2387
+ if (hybridTargetAlias !== void 0) {
2388
+ return [
2389
+ buildHybridCandidateJoin(hybridTargetAlias),
2390
+ buildRelevanceJoin(EMBEDDINGS_CTE_ALIAS, hybridTargetAlias, "LEFT JOIN"),
2391
+ buildRelevanceJoin(FULLTEXT_CTE_ALIAS, hybridTargetAlias, "LEFT JOIN")
2392
+ ];
2393
+ }
2394
+ const joins = [];
2395
+ if (vectorPredicate) {
2396
+ joins.push(
2397
+ buildRelevanceJoin(EMBEDDINGS_CTE_ALIAS, vectorPredicate.field.alias)
2398
+ );
2399
+ }
2400
+ if (fulltextPredicate) {
2401
+ joins.push(
2402
+ buildRelevanceJoin(FULLTEXT_CTE_ALIAS, fulltextPredicate.field.alias)
2403
+ );
2404
+ }
2405
+ return joins;
2406
+ }
2407
+ function buildHybridCandidateJoin(nodeAlias) {
2408
+ const candidateCte = drizzleOrm.sql.raw(HYBRID_CANDIDATES_CTE_ALIAS);
2409
+ const node = drizzleOrm.sql.raw(`${ALIAS_CTE_PREFIX}${nodeAlias}`);
2410
+ const idColumn = drizzleOrm.sql.raw(`${nodeAlias}_id`);
2411
+ const kindColumn = drizzleOrm.sql.raw(`${nodeAlias}_kind`);
2412
+ return drizzleOrm.sql`INNER JOIN ${candidateCte} ON ${candidateCte}.node_id = ${node}.${idColumn} AND ${candidateCte}.node_kind = ${node}.${kindColumn}`;
2413
+ }
2414
+ function buildRelevanceJoin(cteAlias, nodeAlias, joinType = "INNER JOIN") {
2415
+ const cte = drizzleOrm.sql.raw(cteAlias);
2416
+ const node = drizzleOrm.sql.raw(`${ALIAS_CTE_PREFIX}${nodeAlias}`);
2417
+ const idColumn = drizzleOrm.sql.raw(`${nodeAlias}_id`);
2418
+ const kindColumn = drizzleOrm.sql.raw(`${nodeAlias}_kind`);
2419
+ return drizzleOrm.sql`${drizzleOrm.sql.raw(joinType)} ${cte} ON ${cte}.node_id = ${node}.${idColumn} AND ${cte}.node_kind = ${node}.${kindColumn}`;
2420
+ }
2219
2421
  function buildStandardFromClause(input) {
2220
- const { ast, collapsedTraversalCteAlias, vectorPredicate } = input;
2422
+ const {
2423
+ ast,
2424
+ collapsedTraversalCteAlias,
2425
+ fulltextPredicate,
2426
+ vectorPredicate
2427
+ } = input;
2221
2428
  if (collapsedTraversalCteAlias !== void 0) {
2222
2429
  return drizzleOrm.sql`FROM ${drizzleOrm.sql.raw(collapsedTraversalCteAlias)}`;
2223
2430
  }
@@ -2232,11 +2439,8 @@ function buildStandardFromClause(input) {
2232
2439
  drizzleOrm.sql`${drizzleOrm.sql.raw(joinType)} ${drizzleOrm.sql.raw(cteAlias)} ON ${drizzleOrm.sql.raw(cteAlias)}.${drizzleOrm.sql.raw(previousAlias)}_id = cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAlias)}_id AND ${drizzleOrm.sql.raw(cteAlias)}.${drizzleOrm.sql.raw(previousAlias)}_kind = cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAlias)}_kind`
2233
2440
  );
2234
2441
  }
2235
- if (vectorPredicate) {
2236
- const nodeAlias = vectorPredicate.field.alias;
2237
- joins.push(
2238
- drizzleOrm.sql`INNER JOIN cte_embeddings ON cte_embeddings.node_id = cte_${drizzleOrm.sql.raw(nodeAlias)}.${drizzleOrm.sql.raw(nodeAlias)}_id`
2239
- );
2442
+ for (const join of buildRelevanceJoins(vectorPredicate, fulltextPredicate)) {
2443
+ joins.push(join);
2240
2444
  }
2241
2445
  return joins.length === 0 ? fromClause : drizzleOrm.sql`${fromClause} ${drizzleOrm.sql.join(joins, drizzleOrm.sql` `)}`;
2242
2446
  }
@@ -2275,6 +2479,15 @@ function fieldRefKey(field2) {
2275
2479
  const pointer = field2.jsonPointer ?? "";
2276
2480
  return `${field2.alias}:${field2.path.join(".")}:${pointer}`;
2277
2481
  }
2482
+ function resolveEmbeddingFieldPath(field2) {
2483
+ if (field2.jsonPointer !== void 0 && field2.jsonPointer !== "" && field2.jsonPointer !== "/") {
2484
+ return field2.jsonPointer.replace(/^\//u, "").replaceAll("/", ".");
2485
+ }
2486
+ if (field2.path[0] === "props") {
2487
+ return field2.path.slice(1).join(".");
2488
+ }
2489
+ return field2.path.join(".");
2490
+ }
2278
2491
  function buildStandardGroupBy(input) {
2279
2492
  const { ast, dialect } = input;
2280
2493
  if (!ast.groupBy || ast.groupBy.fields.length === 0) {
@@ -2321,18 +2534,29 @@ function buildStandardHaving(input) {
2321
2534
  return drizzleOrm.sql`HAVING ${condition}`;
2322
2535
  }
2323
2536
  function buildStandardEmbeddingsCte(input) {
2324
- const { ctx, graphId, vectorPredicate } = input;
2537
+ const { ctx, graphId, nodeKinds, vectorPredicate } = input;
2325
2538
  const { dialect } = ctx;
2326
2539
  const { field: field2, metric, minScore, queryEmbedding } = vectorPredicate;
2327
- const fieldPath = field2.jsonPointer ?? (field2.path.length > 1 && field2.path[0] === "props" ? `/${field2.path.slice(1).join("/")}` : `/${field2.path.join("/")}`);
2540
+ const embeddingsTableName = ctx.schema.tables.embeddings;
2541
+ if (nodeKinds.length === 0) {
2542
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2543
+ "Vector predicate must resolve to at least one node kind"
2544
+ );
2545
+ }
2546
+ const fieldPath = resolveEmbeddingFieldPath(field2);
2328
2547
  const distanceExpr = dialect.vectorDistance(
2329
- drizzleOrm.sql.raw("embedding"),
2548
+ qualifyColumn(embeddingsTableName, "embedding"),
2330
2549
  queryEmbedding,
2331
2550
  metric
2332
2551
  );
2552
+ const scopedNodes = buildScopedNodeIdsSubquery(field2.alias);
2333
2553
  const conditions = [
2334
- drizzleOrm.sql`graph_id = ${graphId}`,
2335
- drizzleOrm.sql`field_path = ${fieldPath}`
2554
+ drizzleOrm.sql`${qualifyColumn(embeddingsTableName, "graph_id")} = ${graphId}`,
2555
+ compileKindFilter(
2556
+ qualifyColumn(embeddingsTableName, "node_kind"),
2557
+ nodeKinds
2558
+ ),
2559
+ drizzleOrm.sql`${qualifyColumn(embeddingsTableName, "field_path")} = ${fieldPath}`
2336
2560
  ];
2337
2561
  if (minScore !== void 0) {
2338
2562
  conditions.push(
@@ -2341,14 +2565,29 @@ function buildStandardEmbeddingsCte(input) {
2341
2565
  }
2342
2566
  const scoreExpr = compileVectorScoreExpression(distanceExpr, metric);
2343
2567
  return drizzleOrm.sql`
2344
- cte_embeddings AS (
2568
+ ${drizzleOrm.sql.raw(EMBEDDINGS_CTE_ALIAS)} AS (
2345
2569
  SELECT
2346
2570
  node_id,
2347
- ${distanceExpr} AS distance,
2348
- ${scoreExpr} AS score
2349
- FROM ${ctx.schema.embeddingsTable}
2350
- WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}
2351
- ORDER BY ${distanceExpr} ASC
2571
+ node_kind,
2572
+ distance,
2573
+ score,
2574
+ ROW_NUMBER() OVER (ORDER BY distance ASC) AS ord
2575
+ FROM (
2576
+ SELECT
2577
+ ${qualifyColumn(embeddingsTableName, "node_id")} AS node_id,
2578
+ ${qualifyColumn(embeddingsTableName, "node_kind")} AS node_kind,
2579
+ ${distanceExpr} AS distance,
2580
+ ${scoreExpr} AS score
2581
+ FROM ${ctx.schema.embeddingsTable}
2582
+ INNER JOIN ${scopedNodes}
2583
+ ON ${drizzleOrm.sql.raw(`${SCOPED_RELEVANCE_NODES_ALIAS}.node_id`)} =
2584
+ ${qualifyColumn(embeddingsTableName, "node_id")}
2585
+ AND ${drizzleOrm.sql.raw(`${SCOPED_RELEVANCE_NODES_ALIAS}.node_kind`)} =
2586
+ ${qualifyColumn(embeddingsTableName, "node_kind")}
2587
+ WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}
2588
+ ORDER BY distance ASC
2589
+ LIMIT ${vectorPredicate.limit}
2590
+ ) AS vec_inner
2352
2591
  )
2353
2592
  `;
2354
2593
  }
@@ -2378,37 +2617,130 @@ function compileVectorMinScoreCondition(distanceExpr, metric, minScore) {
2378
2617
  }
2379
2618
  }
2380
2619
  }
2381
- function buildStandardVectorOrderBy(input) {
2382
- const { ast, dialect } = input;
2383
- const distanceOrder = drizzleOrm.sql`cte_embeddings.distance ASC`;
2384
- const additionalOrders = [];
2385
- if (ast.orderBy && ast.orderBy.length > 0) {
2386
- const aliasToCte = buildAliasToCteMap(ast);
2387
- for (const orderSpec of ast.orderBy) {
2388
- const valueType = orderSpec.field.valueType;
2389
- if (valueType === "array" || valueType === "object") {
2390
- throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2391
- "Ordering by JSON arrays or objects is not supported"
2392
- );
2393
- }
2394
- const cteAlias = aliasToCte.get(orderSpec.field.alias) ?? `cte_${orderSpec.field.alias}`;
2395
- const field2 = compileFieldValue(
2396
- orderSpec.field,
2397
- dialect,
2398
- valueType,
2399
- cteAlias
2400
- );
2401
- const direction = drizzleOrm.sql.raw(orderSpec.direction.toUpperCase());
2402
- const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
2403
- const nullsDirection = drizzleOrm.sql.raw(nulls === "first" ? "DESC" : "ASC");
2404
- additionalOrders.push(
2405
- drizzleOrm.sql`(${field2} IS NULL) ${nullsDirection}`,
2406
- drizzleOrm.sql`${field2} ${direction}`
2620
+ function buildStandardFulltextCte(input) {
2621
+ const { ctx, fulltextPredicate, graphId, nodeKinds } = input;
2622
+ const { dialect, schema } = ctx;
2623
+ const { query, mode, language, limit, minScore } = fulltextPredicate;
2624
+ if (nodeKinds.length === 0) {
2625
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2626
+ "Fulltext predicate must resolve to at least one node kind"
2627
+ );
2628
+ }
2629
+ const tableName = schema.tables.fulltext;
2630
+ const scopedNodes = buildScopedNodeIdsSubquery(fulltextPredicate.field.alias);
2631
+ const fulltextStrategy = dialect.fulltext;
2632
+ if (fulltextStrategy === void 0) {
2633
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2634
+ `Fulltext match predicates are not supported for dialect "${dialect.name}"`
2635
+ );
2636
+ }
2637
+ const matchCondition = fulltextStrategy.matchCondition(
2638
+ tableName,
2639
+ query,
2640
+ mode,
2641
+ language
2642
+ );
2643
+ const rankExpression = fulltextStrategy.rankExpression(
2644
+ tableName,
2645
+ query,
2646
+ mode,
2647
+ language
2648
+ );
2649
+ const conditions = [
2650
+ drizzleOrm.sql`${qualifyColumn(tableName, "graph_id")} = ${graphId}`,
2651
+ compileKindFilter(qualifyColumn(tableName, "node_kind"), nodeKinds),
2652
+ matchCondition
2653
+ ];
2654
+ if (minScore !== void 0) {
2655
+ conditions.push(drizzleOrm.sql`${rankExpression} >= ${minScore}`);
2656
+ }
2657
+ return drizzleOrm.sql`
2658
+ ${drizzleOrm.sql.raw(FULLTEXT_CTE_ALIAS)} AS (
2659
+ SELECT
2660
+ node_id,
2661
+ node_kind,
2662
+ rank,
2663
+ ROW_NUMBER() OVER (ORDER BY rank DESC, node_id ASC) AS ord
2664
+ FROM (
2665
+ SELECT
2666
+ ${qualifyColumn(tableName, "node_id")} AS node_id,
2667
+ ${qualifyColumn(tableName, "node_kind")} AS node_kind,
2668
+ ${rankExpression} AS rank
2669
+ FROM ${schema.fulltextTable}
2670
+ INNER JOIN ${scopedNodes}
2671
+ ON ${drizzleOrm.sql.raw(`${SCOPED_RELEVANCE_NODES_ALIAS}.node_id`)} =
2672
+ ${qualifyColumn(tableName, "node_id")}
2673
+ AND ${drizzleOrm.sql.raw(`${SCOPED_RELEVANCE_NODES_ALIAS}.node_kind`)} =
2674
+ ${qualifyColumn(tableName, "node_kind")}
2675
+ WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}
2676
+ ORDER BY rank DESC, ${qualifyColumn(tableName, "node_id")} ASC
2677
+ LIMIT ${limit}
2678
+ ) AS fts_inner
2679
+ )
2680
+ `;
2681
+ }
2682
+ function buildStandardHybridCandidateCte() {
2683
+ return drizzleOrm.sql`
2684
+ ${drizzleOrm.sql.raw(HYBRID_CANDIDATES_CTE_ALIAS)} AS (
2685
+ SELECT node_id, node_kind FROM ${drizzleOrm.sql.raw(EMBEDDINGS_CTE_ALIAS)}
2686
+ UNION
2687
+ SELECT node_id, node_kind FROM ${drizzleOrm.sql.raw(FULLTEXT_CTE_ALIAS)}
2688
+ )
2689
+ `;
2690
+ }
2691
+ function compileUserOrderBy(ast, dialect) {
2692
+ if (!ast.orderBy || ast.orderBy.length === 0) return [];
2693
+ const aliasToCte = buildAliasToCteMap(ast);
2694
+ const fragments = [];
2695
+ for (const orderSpec of ast.orderBy) {
2696
+ const valueType = orderSpec.field.valueType;
2697
+ if (valueType === "array" || valueType === "object") {
2698
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2699
+ "Ordering by JSON arrays or objects is not supported"
2407
2700
  );
2408
2701
  }
2702
+ const cteAlias = aliasToCte.get(orderSpec.field.alias) ?? `${ALIAS_CTE_PREFIX}${orderSpec.field.alias}`;
2703
+ const field2 = compileFieldValue(
2704
+ orderSpec.field,
2705
+ dialect,
2706
+ valueType,
2707
+ cteAlias
2708
+ );
2709
+ const direction = drizzleOrm.sql.raw(orderSpec.direction.toUpperCase());
2710
+ const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
2711
+ const nullsDirection = drizzleOrm.sql.raw(nulls === "first" ? "DESC" : "ASC");
2712
+ fragments.push(
2713
+ drizzleOrm.sql`(${field2} IS NULL) ${nullsDirection}`,
2714
+ drizzleOrm.sql`${field2} ${direction}`
2715
+ );
2409
2716
  }
2410
- const allOrders = [distanceOrder, ...additionalOrders];
2411
- return drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.join(allOrders, drizzleOrm.sql`, `)}`;
2717
+ return fragments;
2718
+ }
2719
+ function buildStandardFulltextOrderBy(input) {
2720
+ const { ast, dialect } = input;
2721
+ const rankOrder = drizzleOrm.sql.raw(`${FULLTEXT_CTE_ALIAS}.rank DESC`);
2722
+ const userOrders = compileUserOrderBy(ast, dialect);
2723
+ return drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.join([rankOrder, ...userOrders], drizzleOrm.sql`, `)}`;
2724
+ }
2725
+ function buildStandardHybridRrfOrderBy(input) {
2726
+ const { ast, dialect, fusion } = input;
2727
+ const k = fusion?.k ?? DEFAULT_RRF_K;
2728
+ const vectorWeight = fusion?.weights?.vector ?? DEFAULT_RRF_WEIGHT;
2729
+ const fulltextWeight = fusion?.weights?.fulltext ?? DEFAULT_RRF_WEIGHT;
2730
+ const rrfOrder = drizzleOrm.sql.raw(
2731
+ `(COALESCE(${vectorWeight} / (${k} + ${EMBEDDINGS_CTE_ALIAS}.ord), 0) + COALESCE(${fulltextWeight} / (${k} + ${FULLTEXT_CTE_ALIAS}.ord), 0)) DESC`
2732
+ );
2733
+ const idTiebreak = drizzleOrm.sql.raw(
2734
+ `COALESCE(${FULLTEXT_CTE_ALIAS}.node_id, ${EMBEDDINGS_CTE_ALIAS}.node_id) ASC`
2735
+ );
2736
+ const userOrders = compileUserOrderBy(ast, dialect);
2737
+ return drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.join([rrfOrder, ...userOrders, idTiebreak], drizzleOrm.sql`, `)}`;
2738
+ }
2739
+ function buildStandardVectorOrderBy(input) {
2740
+ const { ast, dialect } = input;
2741
+ const distanceOrder = drizzleOrm.sql.raw(`${EMBEDDINGS_CTE_ALIAS}.distance ASC`);
2742
+ const userOrders = compileUserOrderBy(ast, dialect);
2743
+ return drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.join([distanceOrder, ...userOrders], drizzleOrm.sql`, `)}`;
2412
2744
  }
2413
2745
  function buildLimitOffsetClause(input) {
2414
2746
  const { limit, offset } = input;
@@ -2422,6 +2754,74 @@ function buildLimitOffsetClause(input) {
2422
2754
  return parts.length > 0 ? drizzleOrm.sql.join(parts, drizzleOrm.sql` `) : void 0;
2423
2755
  }
2424
2756
 
2757
+ // src/query/compiler/passes/fulltext.ts
2758
+ function runFulltextPredicatePass(ast, dialect) {
2759
+ const fulltextPredicates = extractFulltextMatchPredicates(ast.predicates);
2760
+ if (fulltextPredicates.length > 1) {
2761
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2762
+ "Multiple fulltext match predicates in a single query are not supported"
2763
+ );
2764
+ }
2765
+ const fulltextPredicate = fulltextPredicates[0];
2766
+ if (fulltextPredicate === void 0) {
2767
+ return { fulltextPredicate: void 0 };
2768
+ }
2769
+ if (!dialect.capabilities.supportsFulltext) {
2770
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2771
+ `Fulltext match predicates are not supported for dialect "${dialect.name}"`
2772
+ );
2773
+ }
2774
+ const strategy = dialect.fulltext;
2775
+ if (strategy === void 0) {
2776
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2777
+ `Dialect "${dialect.name}" advertises fulltext support but has no strategy configured`
2778
+ );
2779
+ }
2780
+ if (!strategy.supportedModes.includes(fulltextPredicate.mode)) {
2781
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2782
+ `Fulltext query mode "${fulltextPredicate.mode}" is not supported by the "${strategy.name}" strategy. Supported modes: ${strategy.supportedModes.join(", ")}.`
2783
+ );
2784
+ }
2785
+ if (!Number.isFinite(fulltextPredicate.limit) || fulltextPredicate.limit <= 0) {
2786
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2787
+ `Fulltext match limit must be a positive finite number, got ${String(fulltextPredicate.limit)}`
2788
+ );
2789
+ }
2790
+ const { minScore } = fulltextPredicate;
2791
+ if (minScore !== void 0 && !Number.isFinite(minScore)) {
2792
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2793
+ `Fulltext minScore must be a finite number, got ${String(minScore)}`
2794
+ );
2795
+ }
2796
+ return { fulltextPredicate };
2797
+ }
2798
+ function resolveFulltextAwareLimit(astLimit, fulltextPredicate) {
2799
+ if (fulltextPredicate === void 0) {
2800
+ return astLimit;
2801
+ }
2802
+ if (astLimit === void 0) {
2803
+ return fulltextPredicate.limit;
2804
+ }
2805
+ return Math.min(astLimit, fulltextPredicate.limit);
2806
+ }
2807
+
2808
+ // src/query/compiler/passes/fusion.ts
2809
+ function runFusionConfigPass(ast, vectorPredicate, fulltextPredicate) {
2810
+ const fusion = ast.fusion;
2811
+ if (fusion === void 0) return { fusion: void 0 };
2812
+ if (vectorPredicate === void 0 || fulltextPredicate === void 0) {
2813
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2814
+ `.fuseWith() requires both a .similarTo() and a .$fulltext.matches() predicate in the same query. Remove .fuseWith(), or add the missing predicate.`
2815
+ );
2816
+ }
2817
+ if (vectorPredicate.field.alias !== fulltextPredicate.field.alias) {
2818
+ throw new chunkO5PST6TB_cjs.UnsupportedPredicateError(
2819
+ `.fuseWith() requires .similarTo() and .$fulltext.matches() to target the same node alias. Got vector=${vectorPredicate.field.alias}, fulltext=${fulltextPredicate.field.alias}.`
2820
+ );
2821
+ }
2822
+ return { fusion };
2823
+ }
2824
+
2425
2825
  // src/query/compiler/passes/recursive.ts
2426
2826
  function runRecursiveTraversalSelectionPass(ast) {
2427
2827
  const variableLengthTraversal = ast.traversals.find(
@@ -2691,6 +3091,14 @@ function lowerStandardQueryToLogicalPlanNode(input) {
2691
3091
  predicate: input.vectorPredicate
2692
3092
  };
2693
3093
  }
3094
+ if (input.fulltextPredicate !== void 0) {
3095
+ currentNode = {
3096
+ id: nextPlanNodeId(),
3097
+ input: currentNode,
3098
+ op: "fulltext_match",
3099
+ predicate: input.fulltextPredicate
3100
+ };
3101
+ }
2694
3102
  return appendAggregateSortLimitAndProjectNodes(
2695
3103
  currentNode,
2696
3104
  ast,
@@ -2768,17 +3176,30 @@ function lowerComposableQueryToLogicalPlanNode(query, dialect, graphId, nextPlan
2768
3176
  nextPlanNodeId
2769
3177
  });
2770
3178
  }
3179
+ const dialectAdapter = chunk3JK66E3I_cjs.getDialect(dialect);
2771
3180
  const vectorPredicate = runVectorPredicatePass(
2772
3181
  query,
2773
- chunkMME3H4ZF_cjs.getDialect(dialect)
3182
+ dialectAdapter
2774
3183
  ).vectorPredicate;
2775
- const effectiveLimit = resolveVectorAwareLimit(query.limit, vectorPredicate);
3184
+ const fulltextPredicate = runFulltextPredicatePass(
3185
+ query,
3186
+ dialectAdapter
3187
+ ).fulltextPredicate;
3188
+ const limitAfterVector = resolveVectorAwareLimit(
3189
+ query.limit,
3190
+ vectorPredicate
3191
+ );
3192
+ const effectiveLimit = resolveFulltextAwareLimit(
3193
+ limitAfterVector,
3194
+ fulltextPredicate
3195
+ );
2776
3196
  const loweringInput = {
2777
3197
  ast: query,
2778
3198
  graphId,
2779
3199
  nextPlanNodeId,
2780
3200
  ...effectiveLimit === void 0 ? {} : { effectiveLimit },
2781
- ...vectorPredicate === void 0 ? {} : { vectorPredicate }
3201
+ ...vectorPredicate === void 0 ? {} : { vectorPredicate },
3202
+ ...fulltextPredicate === void 0 ? {} : { fulltextPredicate }
2782
3203
  };
2783
3204
  return lowerStandardQueryToLogicalPlanNode(loweringInput);
2784
3205
  }
@@ -3285,10 +3706,10 @@ function compileRecursiveProjection(ast, traversal, dialect) {
3285
3706
  drizzleOrm.sql`${drizzleOrm.sql.raw(nodeAlias)}_deleted_at`
3286
3707
  ];
3287
3708
  if (vl.depthAlias !== void 0) {
3288
- fields.push(drizzleOrm.sql`depth AS ${quoteIdentifier(vl.depthAlias)}`);
3709
+ fields.push(drizzleOrm.sql`depth AS ${quoteIdentifier2(vl.depthAlias)}`);
3289
3710
  }
3290
3711
  if (vl.pathAlias !== void 0) {
3291
- fields.push(drizzleOrm.sql`path AS ${quoteIdentifier(vl.pathAlias)}`);
3712
+ fields.push(drizzleOrm.sql`path AS ${quoteIdentifier2(vl.pathAlias)}`);
3292
3713
  }
3293
3714
  return drizzleOrm.sql.join(fields, drizzleOrm.sql`, `);
3294
3715
  }
@@ -3302,23 +3723,23 @@ function compileRecursiveSelectiveProjection(fields, ast, traversal, dialect) {
3302
3723
  }
3303
3724
  if (field2.isSystemField) {
3304
3725
  const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
3305
- return drizzleOrm.sql`${drizzleOrm.sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
3726
+ return drizzleOrm.sql`${drizzleOrm.sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier2(field2.outputName)}`;
3306
3727
  }
3307
3728
  const column = drizzleOrm.sql.raw(`${field2.alias}_props`);
3308
3729
  const extracted = compileTypedJsonExtract({
3309
3730
  column,
3310
3731
  dialect,
3311
- pointer: chunkP5CNM325_cjs.jsonPointer([field2.field]),
3732
+ pointer: chunkUVZHTBZ5_cjs.jsonPointer([field2.field]),
3312
3733
  valueType: field2.valueType
3313
3734
  });
3314
- return drizzleOrm.sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
3735
+ return drizzleOrm.sql`${extracted} AS ${quoteIdentifier2(field2.outputName)}`;
3315
3736
  });
3316
3737
  const vl = traversal.variableLength;
3317
3738
  if (vl.depthAlias !== void 0) {
3318
- columns.push(drizzleOrm.sql`depth AS ${quoteIdentifier(vl.depthAlias)}`);
3739
+ columns.push(drizzleOrm.sql`depth AS ${quoteIdentifier2(vl.depthAlias)}`);
3319
3740
  }
3320
3741
  if (vl.pathAlias !== void 0) {
3321
- columns.push(drizzleOrm.sql`path AS ${quoteIdentifier(vl.pathAlias)}`);
3742
+ columns.push(drizzleOrm.sql`path AS ${quoteIdentifier2(vl.pathAlias)}`);
3322
3743
  }
3323
3744
  return drizzleOrm.sql.join(columns, drizzleOrm.sql`, `);
3324
3745
  }
@@ -3355,43 +3776,6 @@ function compileLimitOffset(ast) {
3355
3776
  }
3356
3777
  return parts.length > 0 ? drizzleOrm.sql.join(parts, drizzleOrm.sql` `) : void 0;
3357
3778
  }
3358
- var DEFAULT_TABLE_NAMES = {
3359
- nodes: "typegraph_nodes",
3360
- edges: "typegraph_edges",
3361
- embeddings: "typegraph_node_embeddings"
3362
- };
3363
- var VALID_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_$]*$/i;
3364
- function validateTableName(name, label) {
3365
- if (!name || name.length === 0) {
3366
- throw new chunkO5PST6TB_cjs.ConfigurationError(`${label} table name cannot be empty`);
3367
- }
3368
- if (name.length > chunkMME3H4ZF_cjs.MAX_PG_IDENTIFIER_LENGTH) {
3369
- throw new chunkO5PST6TB_cjs.ConfigurationError(
3370
- `${label} table name exceeds maximum length of ${chunkMME3H4ZF_cjs.MAX_PG_IDENTIFIER_LENGTH} characters`
3371
- );
3372
- }
3373
- if (!VALID_IDENTIFIER_PATTERN.test(name)) {
3374
- throw new chunkO5PST6TB_cjs.ConfigurationError(
3375
- `${label} table name "${name}" is not a valid SQL identifier. Table names must start with a letter or underscore and contain only letters, digits, underscores, or dollar signs.`
3376
- );
3377
- }
3378
- }
3379
- function quoteIdentifier2(name) {
3380
- return `"${name.replaceAll('"', '""')}"`;
3381
- }
3382
- function createSqlSchema(names = {}) {
3383
- const tables = { ...DEFAULT_TABLE_NAMES, ...names };
3384
- validateTableName(tables.nodes, "nodes");
3385
- validateTableName(tables.edges, "edges");
3386
- validateTableName(tables.embeddings, "embeddings");
3387
- return {
3388
- tables,
3389
- nodesTable: drizzleOrm.sql.raw(quoteIdentifier2(tables.nodes)),
3390
- edgesTable: drizzleOrm.sql.raw(quoteIdentifier2(tables.edges)),
3391
- embeddingsTable: drizzleOrm.sql.raw(quoteIdentifier2(tables.embeddings))
3392
- };
3393
- }
3394
- var DEFAULT_SQL_SCHEMA = createSqlSchema();
3395
3779
 
3396
3780
  // src/query/execution/value-decoder.ts
3397
3781
  function nullToUndefined(value) {
@@ -3622,9 +4006,9 @@ function truncateToBytes(value, maxBytes) {
3622
4006
  }
3623
4007
  function projectionAlias(entityPrefix, kind, field2) {
3624
4008
  const prefix = entityPrefix === "node" ? "sg_n" : "sg_e";
3625
- const hash = chunkMME3H4ZF_cjs.fnv1aBase36(`${kind}\0${field2}`);
4009
+ const hash = chunk3JK66E3I_cjs.fnv1aBase36(`${kind}\0${field2}`);
3626
4010
  const fixedBytes = prefix.length + 1 + 1 + hash.length;
3627
- const maxKindBytes = chunkMME3H4ZF_cjs.MAX_PG_IDENTIFIER_LENGTH - fixedBytes;
4011
+ const maxKindBytes = chunk3JK66E3I_cjs.MAX_PG_IDENTIFIER_LENGTH - fixedBytes;
3628
4012
  const truncatedKind = truncateToBytes(kind, maxKindBytes);
3629
4013
  return `${prefix}_${truncatedKind}_${hash}`;
3630
4014
  }
@@ -3696,12 +4080,7 @@ async function executeSubgraph(params) {
3696
4080
  for (const row of edgeRows) {
3697
4081
  const edge = mapSubgraphEdgeRow(row, edgeProjectionPlan);
3698
4082
  insertAdjacencyEntry(adjacency, edge.fromId, edge.kind, edge);
3699
- insertAdjacencyEntry(
3700
- reverseAdjacency,
3701
- edge.toId,
3702
- edge.kind,
3703
- edge
3704
- );
4083
+ insertAdjacencyEntry(reverseAdjacency, edge.toId, edge.kind, edge);
3705
4084
  }
3706
4085
  const root = nodesMap.get(ctx.rootId);
3707
4086
  return {
@@ -3727,7 +4106,7 @@ function getSubgraphSchemaIntrospector(graph) {
3727
4106
  { schema: definition.type.schema }
3728
4107
  ])
3729
4108
  );
3730
- const introspector = chunkP5CNM325_cjs.createSchemaIntrospector(nodeKinds, edgeKinds);
4109
+ const introspector = chunkUVZHTBZ5_cjs.createSchemaIntrospector(nodeKinds, edgeKinds);
3731
4110
  introspectorCache.set(graph, introspector);
3732
4111
  return introspector;
3733
4112
  }
@@ -3871,11 +4250,11 @@ function buildProjectedPropertyColumns(alias, plan, dialect) {
3871
4250
  const extracted = compileTypedJsonExtract({
3872
4251
  column: drizzleOrm.sql.raw(`${alias}.props`),
3873
4252
  dialect,
3874
- pointer: chunkP5CNM325_cjs.jsonPointer([fieldPlan.field]),
4253
+ pointer: chunkUVZHTBZ5_cjs.jsonPointer([fieldPlan.field]),
3875
4254
  valueType: fieldPlan.typeInfo?.valueType
3876
4255
  });
3877
4256
  columns.push(
3878
- drizzleOrm.sql`CASE WHEN ${drizzleOrm.sql.raw(alias)}.kind = ${kind} THEN ${extracted} ELSE NULL END AS ${quoteIdentifier(fieldPlan.outputName)}`
4257
+ drizzleOrm.sql`CASE WHEN ${drizzleOrm.sql.raw(alias)}.kind = ${kind} THEN ${extracted} ELSE NULL END AS ${quoteIdentifier2(fieldPlan.outputName)}`
3879
4258
  );
3880
4259
  }
3881
4260
  }
@@ -3935,22 +4314,12 @@ function mapSubgraphEdgeRow(row, projectionPlan) {
3935
4314
  fromId: row.from_id,
3936
4315
  toKind: row.to_kind,
3937
4316
  toId: row.to_id
3938
- };
3939
- if (kindPlan.includeMeta) {
3940
- projectedEdge.meta = rowToEdgeMeta(row);
3941
- }
3942
- applyProjectedFields(projectedEdge, row, kindPlan);
3943
- return projectedEdge;
3944
- }
3945
-
3946
- // src/query/ast.ts
3947
- function mergeEdgeKinds(traversal) {
3948
- const inverse = traversal.inverseEdgeKinds;
3949
- if (inverse === void 0 || inverse.length === 0) return traversal.edgeKinds;
3950
- return [
3951
- ...traversal.edgeKinds,
3952
- ...inverse.filter((kind) => !traversal.edgeKinds.includes(kind))
3953
- ];
4317
+ };
4318
+ if (kindPlan.includeMeta) {
4319
+ projectedEdge.meta = rowToEdgeMeta(row);
4320
+ }
4321
+ applyProjectedFields(projectedEdge, row, kindPlan);
4322
+ return projectedEdge;
3954
4323
  }
3955
4324
  var OPERATOR_MAP = {
3956
4325
  union: "UNION",
@@ -4108,6 +4477,9 @@ function validateSqliteSetOpLeaf(ast) {
4108
4477
  if (hasVectorSimilarityPredicates(ast)) {
4109
4478
  unsupported.push("vector similarity predicates");
4110
4479
  }
4480
+ if (hasFulltextMatchPredicates(ast)) {
4481
+ unsupported.push("fulltext match predicates");
4482
+ }
4111
4483
  if (ast.groupBy !== void 0) {
4112
4484
  unsupported.push("GROUP BY");
4113
4485
  }
@@ -4157,7 +4529,42 @@ function hasVectorSimilarityInExpression(expr) {
4157
4529
  case "between":
4158
4530
  case "array_op":
4159
4531
  case "object_op":
4160
- case "aggregate_comparison": {
4532
+ case "aggregate_comparison":
4533
+ case "fulltext_match": {
4534
+ return false;
4535
+ }
4536
+ }
4537
+ }
4538
+ return false;
4539
+ }
4540
+ function hasFulltextMatchPredicates(ast) {
4541
+ return ast.predicates.some(
4542
+ (predicate2) => hasFulltextMatchInExpression(predicate2.expression)
4543
+ );
4544
+ }
4545
+ function hasFulltextMatchInExpression(expr) {
4546
+ if ("__type" in expr) {
4547
+ switch (expr.__type) {
4548
+ case "and":
4549
+ case "or": {
4550
+ return expr.predicates.some((p) => hasFulltextMatchInExpression(p));
4551
+ }
4552
+ case "not": {
4553
+ return hasFulltextMatchInExpression(expr.predicate);
4554
+ }
4555
+ case "fulltext_match": {
4556
+ return true;
4557
+ }
4558
+ case "exists":
4559
+ case "in_subquery":
4560
+ case "comparison":
4561
+ case "string_op":
4562
+ case "null_check":
4563
+ case "between":
4564
+ case "array_op":
4565
+ case "object_op":
4566
+ case "aggregate_comparison":
4567
+ case "vector_similarity": {
4161
4568
  return false;
4162
4569
  }
4163
4570
  }
@@ -4193,7 +4600,8 @@ function hasSubqueryInExpression(expr) {
4193
4600
  case "array_op":
4194
4601
  case "object_op":
4195
4602
  case "aggregate_comparison":
4196
- case "vector_similarity": {
4603
+ case "vector_similarity":
4604
+ case "fulltext_match": {
4197
4605
  return false;
4198
4606
  }
4199
4607
  }
@@ -4359,7 +4767,7 @@ function getLeftmostProjection(op) {
4359
4767
  function normalizeFieldRefKey(field2) {
4360
4768
  let pointer = field2.jsonPointer;
4361
4769
  if (pointer === void 0 && field2.path.length > 1 && field2.path[0] === "props") {
4362
- pointer = chunkP5CNM325_cjs.jsonPointer(field2.path.slice(1));
4770
+ pointer = chunkUVZHTBZ5_cjs.jsonPointer(field2.path.slice(1));
4363
4771
  }
4364
4772
  const basePath = field2.path.length > 0 && field2.path[0] === "props" ? "props" : field2.path.join(".");
4365
4773
  return `${field2.alias}:${basePath}:${pointer ?? ""}`;
@@ -4471,6 +4879,10 @@ function markPredicateFieldsAsRequired(requiredColumnsByAlias, expression) {
4471
4879
  markFieldRefAsRequired(requiredColumnsByAlias, expression.field);
4472
4880
  return;
4473
4881
  }
4882
+ case "fulltext_match": {
4883
+ markFieldRefAsRequired(requiredColumnsByAlias, expression.field);
4884
+ return;
4885
+ }
4474
4886
  case "exists": {
4475
4887
  return;
4476
4888
  }
@@ -4535,7 +4947,8 @@ function hasIdEqualityPredicate(expression, alias) {
4535
4947
  case "aggregate_comparison":
4536
4948
  case "exists":
4537
4949
  case "in_subquery":
4538
- case "vector_similarity": {
4950
+ case "vector_similarity":
4951
+ case "fulltext_match": {
4539
4952
  return false;
4540
4953
  }
4541
4954
  }
@@ -4576,10 +4989,13 @@ function resolveTraversalCteLimit(ast, predicateIndex) {
4576
4989
  );
4577
4990
  return Math.max(ast.limit, pushdownLimit);
4578
4991
  }
4579
- function canCollapseSelectiveTraversalRowset(ast, vectorPredicate) {
4992
+ function canCollapseSelectiveTraversalRowset(ast, vectorPredicate, fulltextPredicate) {
4580
4993
  if (vectorPredicate !== void 0) {
4581
4994
  return false;
4582
4995
  }
4996
+ if (fulltextPredicate !== void 0) {
4997
+ return false;
4998
+ }
4583
4999
  if (ast.traversals.length === 0) {
4584
5000
  return false;
4585
5001
  }
@@ -4619,6 +5035,8 @@ function runStandardQueryPassPipeline(ast, graphId, ctx) {
4619
5035
  collapsedTraversalCteAlias: void 0,
4620
5036
  ctx,
4621
5037
  effectiveLimit: void 0,
5038
+ fulltextPredicate: void 0,
5039
+ fusion: void 0,
4622
5040
  logicalPlan: void 0,
4623
5041
  predicateIndex: buildPredicateIndex(ast),
4624
5042
  requiredColumnsByAlias: void 0,
@@ -4640,6 +5058,39 @@ function runStandardQueryPassPipeline(ast, graphId, ctx) {
4640
5058
  }
4641
5059
  });
4642
5060
  state = vectorPass.state;
5061
+ const fulltextPass = runCompilerPass(state, {
5062
+ name: "fulltext_predicate",
5063
+ execute(currentState) {
5064
+ return runFulltextPredicatePass(
5065
+ currentState.ast,
5066
+ currentState.ctx.dialect
5067
+ ).fulltextPredicate;
5068
+ },
5069
+ update(currentState, fulltextPredicate) {
5070
+ return {
5071
+ ...currentState,
5072
+ fulltextPredicate
5073
+ };
5074
+ }
5075
+ });
5076
+ state = fulltextPass.state;
5077
+ const fusionPass = runCompilerPass(state, {
5078
+ name: "fusion_config",
5079
+ execute(currentState) {
5080
+ return runFusionConfigPass(
5081
+ currentState.ast,
5082
+ currentState.vectorPredicate,
5083
+ currentState.fulltextPredicate
5084
+ ).fusion;
5085
+ },
5086
+ update(currentState, fusion) {
5087
+ return {
5088
+ ...currentState,
5089
+ fusion
5090
+ };
5091
+ }
5092
+ });
5093
+ state = fusionPass.state;
4643
5094
  const temporalPass = runCompilerPass(state, {
4644
5095
  name: "temporal_filters",
4645
5096
  execute(currentState) {
@@ -4674,7 +5125,8 @@ function runStandardQueryPassPipeline(ast, graphId, ctx) {
4674
5125
  execute(currentState) {
4675
5126
  const shouldCollapseSelectiveTraversalRowset = canCollapseSelectiveTraversalRowset(
4676
5127
  currentState.ast,
4677
- currentState.vectorPredicate
5128
+ currentState.vectorPredicate,
5129
+ currentState.fulltextPredicate
4678
5130
  );
4679
5131
  const lastTraversal = currentState.ast.traversals.at(-1);
4680
5132
  const collapsedTraversalCteAlias = shouldCollapseSelectiveTraversalRowset && lastTraversal !== void 0 ? `cte_${lastTraversal.nodeAlias}` : void 0;
@@ -4710,9 +5162,9 @@ function runStandardQueryPassPipeline(ast, graphId, ctx) {
4710
5162
  state = traversalLimitPass.state;
4711
5163
  state = {
4712
5164
  ...state,
4713
- effectiveLimit: resolveVectorAwareLimit(
4714
- state.ast.limit,
4715
- state.vectorPredicate
5165
+ effectiveLimit: resolveFulltextAwareLimit(
5166
+ resolveVectorAwareLimit(state.ast.limit, state.vectorPredicate),
5167
+ state.fulltextPredicate
4716
5168
  )
4717
5169
  };
4718
5170
  const logicalPlanPass = runCompilerPass(state, {
@@ -4729,7 +5181,8 @@ function runStandardQueryPassPipeline(ast, graphId, ctx) {
4729
5181
  collapsedTraversalCteAlias: currentState.collapsedTraversalCteAlias
4730
5182
  },
4731
5183
  ...currentState.effectiveLimit === void 0 ? {} : { effectiveLimit: currentState.effectiveLimit },
4732
- ...currentState.vectorPredicate === void 0 ? {} : { vectorPredicate: currentState.vectorPredicate }
5184
+ ...currentState.vectorPredicate === void 0 ? {} : { vectorPredicate: currentState.vectorPredicate },
5185
+ ...currentState.fulltextPredicate === void 0 ? {} : { fulltextPredicate: currentState.fulltextPredicate }
4733
5186
  });
4734
5187
  },
4735
5188
  update(currentState, logicalPlan) {
@@ -4748,11 +5201,11 @@ function compileQuery(ast, graphId, options = "sqlite") {
4748
5201
  const options_ = typeof options === "string" ? { dialect: options } : options;
4749
5202
  const dialect = options_.dialect ?? "sqlite";
4750
5203
  const schema = options_.schema ?? DEFAULT_SQL_SCHEMA;
4751
- const adapter = chunkMME3H4ZF_cjs.getDialect(dialect);
5204
+ const adapter = resolveDialectAdapter(dialect, options_.fulltextStrategy);
4752
5205
  const ctx = {
4753
5206
  dialect: adapter,
4754
5207
  schema,
4755
- compileQuery: (subAst, subGraphId) => compileQuery(subAst, subGraphId, { dialect, schema })
5208
+ compileQuery: (subAst, subGraphId) => compileQuery(subAst, subGraphId, propagateOptions(options_))
4756
5209
  };
4757
5210
  if (hasVariableLengthTraversal(ast)) {
4758
5211
  const lowered = tryLowerSingleHopRecursiveTraversal(ast);
@@ -4788,15 +5241,29 @@ function compileSetOperation2(op, graphId, options = "sqlite") {
4788
5241
  const options_ = typeof options === "string" ? { dialect: options } : options;
4789
5242
  const dialect = options_.dialect ?? "sqlite";
4790
5243
  const schema = options_.schema ?? DEFAULT_SQL_SCHEMA;
4791
- const adapter = chunkMME3H4ZF_cjs.getDialect(dialect);
5244
+ const adapter = resolveDialectAdapter(dialect, options_.fulltextStrategy);
4792
5245
  return compileSetOperation(
4793
5246
  op,
4794
5247
  graphId,
4795
5248
  adapter,
4796
5249
  schema,
4797
- (ast, gid) => compileQuery(ast, gid, { dialect, schema })
5250
+ (ast, gid) => compileQuery(ast, gid, propagateOptions(options_))
4798
5251
  );
4799
5252
  }
5253
+ function resolveDialectAdapter(dialect, fulltextStrategy) {
5254
+ const baseAdapter = chunk3JK66E3I_cjs.getDialect(dialect);
5255
+ if (fulltextStrategy === void 0 || fulltextStrategy === baseAdapter.fulltext) {
5256
+ return baseAdapter;
5257
+ }
5258
+ return { ...baseAdapter, fulltext: fulltextStrategy };
5259
+ }
5260
+ function propagateOptions(options_) {
5261
+ return {
5262
+ dialect: options_.dialect ?? "sqlite",
5263
+ ...options_.schema === void 0 ? {} : { schema: options_.schema },
5264
+ ...options_.fulltextStrategy === void 0 ? {} : { fulltextStrategy: options_.fulltextStrategy }
5265
+ };
5266
+ }
4800
5267
  function resolveCountAggregateFastPath(ast) {
4801
5268
  if (ast.traversals.length !== 1) {
4802
5269
  return void 0;
@@ -4818,8 +5285,10 @@ function resolveCountAggregateFastPath(ast) {
4818
5285
  if (groupField.alias !== ast.start.alias || !isIdFieldRef(groupField)) {
4819
5286
  return void 0;
4820
5287
  }
4821
- let requiresCount = false;
4822
- let requiresCountDistinct = false;
5288
+ let requiresNodeCount = false;
5289
+ let requiresNodeCountDistinct = false;
5290
+ let requiresEdgeCount = false;
5291
+ let requiresEdgeCountDistinct = false;
4823
5292
  for (const projectedField of ast.projection.fields) {
4824
5293
  const source = projectedField.source;
4825
5294
  if (!isAggregateExpr(source)) {
@@ -4828,26 +5297,35 @@ function resolveCountAggregateFastPath(ast) {
4828
5297
  }
4829
5298
  continue;
4830
5299
  }
4831
- if (source.field.alias !== traversal.nodeAlias || !isIdFieldRef(source.field)) {
5300
+ if (!isIdFieldRef(source.field)) {
5301
+ return void 0;
5302
+ }
5303
+ const isNodeTarget = source.field.alias === traversal.nodeAlias;
5304
+ const isEdgeTarget = source.field.alias === traversal.edgeAlias;
5305
+ if (!isNodeTarget && !isEdgeTarget) {
4832
5306
  return void 0;
4833
5307
  }
4834
5308
  if (source.function === "count") {
4835
- requiresCount = true;
5309
+ if (isNodeTarget) requiresNodeCount = true;
5310
+ else requiresEdgeCount = true;
4836
5311
  continue;
4837
5312
  }
4838
5313
  if (source.function === "countDistinct") {
4839
- requiresCountDistinct = true;
5314
+ if (isNodeTarget) requiresNodeCountDistinct = true;
5315
+ else requiresEdgeCountDistinct = true;
4840
5316
  continue;
4841
5317
  }
4842
5318
  return void 0;
4843
5319
  }
4844
- if (!requiresCount && !requiresCountDistinct) {
5320
+ if (!requiresNodeCount && !requiresNodeCountDistinct && !requiresEdgeCount && !requiresEdgeCountDistinct) {
4845
5321
  return void 0;
4846
5322
  }
4847
5323
  return {
4848
5324
  traversal,
4849
- requiresCount,
4850
- requiresCountDistinct
5325
+ requiresNodeCount,
5326
+ requiresNodeCountDistinct,
5327
+ requiresEdgeCount,
5328
+ requiresEdgeCountDistinct
4851
5329
  };
4852
5330
  }
4853
5331
  function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredColumnsByAlias, predicateIndex, temporalFilterPass) {
@@ -4855,15 +5333,23 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
4855
5333
  if (!plan) {
4856
5334
  return void 0;
4857
5335
  }
4858
- const { traversal, requiresCount, requiresCountDistinct } = plan;
5336
+ const {
5337
+ traversal,
5338
+ requiresNodeCount,
5339
+ requiresNodeCountDistinct,
5340
+ requiresEdgeCount,
5341
+ requiresEdgeCountDistinct
5342
+ } = plan;
4859
5343
  const { dialect } = ctx;
4860
5344
  const startAlias = ast.start.alias;
4861
5345
  const previousAlias = traversal.joinFromAlias;
4862
5346
  const previousAliasIdColumn = `${previousAlias}_id`;
4863
5347
  const previousAliasKindColumn = `${previousAlias}_kind`;
4864
5348
  const countCteAlias = `cte_${traversal.nodeAlias}_counts`;
4865
- const countColumn = `${traversal.nodeAlias}_count`;
4866
- const countDistinctColumn = `${traversal.nodeAlias}_count_distinct`;
5349
+ const nodeCountColumn = `${traversal.nodeAlias}_count`;
5350
+ const nodeCountDistinctColumn = `${traversal.nodeAlias}_count_distinct`;
5351
+ const edgeCountColumn = `${traversal.edgeAlias}_count`;
5352
+ const edgeCountDistinctColumn = `${traversal.edgeAlias}_count_distinct`;
4867
5353
  const previousNodeKinds = getNodeKindsForAlias(ast, traversal.joinFromAlias);
4868
5354
  const edgeKinds = [...new Set(traversal.edgeKinds)];
4869
5355
  const nodeKinds = traversal.nodeKinds;
@@ -4889,34 +5375,68 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
4889
5375
  const targetField = traversal.direction === "out" ? "to_id" : "from_id";
4890
5376
  const joinKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
4891
5377
  const targetKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
5378
+ const hasNodePredicates = nodePredicateClauses.length > 0;
5379
+ const requiresNodeJoin = requiresNodeCount || requiresNodeCountDistinct || hasNodePredicates;
5380
+ const useInnerJoin = hasNodePredicates;
4892
5381
  const whereClauses = [
4893
5382
  drizzleOrm.sql`e.graph_id = ${graphId}`,
4894
5383
  compileKindFilter(drizzleOrm.sql.raw("e.kind"), edgeKinds),
4895
5384
  compileKindFilter(drizzleOrm.sql.raw(`e.${joinKindField}`), previousNodeKinds),
4896
5385
  compileKindFilter(drizzleOrm.sql.raw(`e.${targetKindField}`), nodeKinds),
4897
- compileKindFilter(drizzleOrm.sql.raw("n.kind"), nodeKinds),
4898
5386
  edgeTemporalFilter,
4899
- nodeTemporalFilter,
4900
- ...nodePredicateClauses,
4901
- ...edgePredicateClauses
5387
+ ...edgePredicateClauses,
5388
+ // INNER JOIN mode: node-side filters in WHERE constrain every aggregate.
5389
+ ...requiresNodeJoin && useInnerJoin ? [
5390
+ compileKindFilter(drizzleOrm.sql.raw("n.kind"), nodeKinds),
5391
+ nodeTemporalFilter,
5392
+ ...nodePredicateClauses
5393
+ ] : []
4902
5394
  ];
5395
+ const nodeJoinOnClauses = requiresNodeJoin ? useInnerJoin ? (
5396
+ // INNER JOIN: only the key conditions; filters live in WHERE.
5397
+ [
5398
+ drizzleOrm.sql`n.graph_id = e.graph_id`,
5399
+ drizzleOrm.sql`n.id = e.${drizzleOrm.sql.raw(targetField)}`,
5400
+ drizzleOrm.sql`n.kind = e.${drizzleOrm.sql.raw(targetKindField)}`
5401
+ ]
5402
+ ) : [
5403
+ drizzleOrm.sql`n.graph_id = e.graph_id`,
5404
+ drizzleOrm.sql`n.id = e.${drizzleOrm.sql.raw(targetField)}`,
5405
+ drizzleOrm.sql`n.kind = e.${drizzleOrm.sql.raw(targetKindField)}`,
5406
+ compileKindFilter(drizzleOrm.sql.raw("n.kind"), nodeKinds),
5407
+ nodeTemporalFilter
5408
+ ] : [];
4903
5409
  const aggregateColumns = [];
4904
- if (requiresCount) {
4905
- aggregateColumns.push(drizzleOrm.sql`COUNT(n.id) AS ${drizzleOrm.sql.raw(countColumn)}`);
5410
+ if (requiresNodeCount) {
5411
+ aggregateColumns.push(drizzleOrm.sql`COUNT(n.id) AS ${drizzleOrm.sql.raw(nodeCountColumn)}`);
5412
+ }
5413
+ if (requiresNodeCountDistinct) {
5414
+ aggregateColumns.push(
5415
+ drizzleOrm.sql`COUNT(DISTINCT n.id) AS ${drizzleOrm.sql.raw(nodeCountDistinctColumn)}`
5416
+ );
4906
5417
  }
4907
- if (requiresCountDistinct) {
5418
+ if (requiresEdgeCount) {
5419
+ aggregateColumns.push(drizzleOrm.sql`COUNT(e.id) AS ${drizzleOrm.sql.raw(edgeCountColumn)}`);
5420
+ }
5421
+ if (requiresEdgeCountDistinct) {
4908
5422
  aggregateColumns.push(
4909
- drizzleOrm.sql`COUNT(DISTINCT n.id) AS ${drizzleOrm.sql.raw(countDistinctColumn)}`
5423
+ drizzleOrm.sql`COUNT(DISTINCT e.id) AS ${drizzleOrm.sql.raw(edgeCountDistinctColumn)}`
4910
5424
  );
4911
5425
  }
5426
+ const startCteLimit = traversal.optional && ast.orderBy === void 0 && ast.limit !== void 0 ? { limit: ast.limit, offset: ast.offset } : void 0;
4912
5427
  const startCte = buildStandardStartCte({
4913
5428
  ast,
4914
5429
  ctx,
4915
5430
  graphId,
4916
5431
  predicateIndex,
4917
5432
  requiredColumnsByAlias,
4918
- temporalFilterPass
5433
+ temporalFilterPass,
5434
+ ...startCteLimit === void 0 ? {} : { limitOffset: startCteLimit }
4919
5435
  });
5436
+ const targetNodeJoinKeyword = useInnerJoin ? "JOIN" : "LEFT JOIN";
5437
+ const targetNodeJoin = requiresNodeJoin ? drizzleOrm.sql`
5438
+ ${drizzleOrm.sql.raw(targetNodeJoinKeyword)} ${ctx.schema.nodesTable} n ON ${drizzleOrm.sql.join(nodeJoinOnClauses, drizzleOrm.sql` AND `)}
5439
+ ` : drizzleOrm.sql``;
4920
5440
  const countCte = drizzleOrm.sql`
4921
5441
  ${drizzleOrm.sql.raw(countCteAlias)} AS (
4922
5442
  SELECT
@@ -4925,16 +5445,20 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
4925
5445
  ${drizzleOrm.sql.join(aggregateColumns, drizzleOrm.sql`, `)}
4926
5446
  FROM cte_${drizzleOrm.sql.raw(previousAlias)}
4927
5447
  JOIN ${ctx.schema.edgesTable} e ON cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAliasIdColumn)} = e.${drizzleOrm.sql.raw(joinField)}
4928
- AND cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAliasKindColumn)} = e.${drizzleOrm.sql.raw(joinKindField)}
4929
- JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
4930
- AND n.id = e.${drizzleOrm.sql.raw(targetField)}
4931
- AND n.kind = e.${drizzleOrm.sql.raw(targetKindField)}
5448
+ AND cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAliasKindColumn)} = e.${drizzleOrm.sql.raw(joinKindField)}${targetNodeJoin}
4932
5449
  WHERE ${drizzleOrm.sql.join(whereClauses, drizzleOrm.sql` AND `)}
4933
5450
  GROUP BY
4934
5451
  cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAliasIdColumn)},
4935
5452
  cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAliasKindColumn)}
4936
5453
  )
4937
5454
  `;
5455
+ function resolveCountColumn(source) {
5456
+ const isEdge = source.field.alias === traversal.edgeAlias;
5457
+ if (source.function === "countDistinct") {
5458
+ return isEdge ? edgeCountDistinctColumn : nodeCountDistinctColumn;
5459
+ }
5460
+ return isEdge ? edgeCountColumn : nodeCountColumn;
5461
+ }
4938
5462
  const projection = drizzleOrm.sql.join(
4939
5463
  ast.projection.fields.map((projectedField) => {
4940
5464
  const source = projectedField.source;
@@ -4945,12 +5469,12 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
4945
5469
  source.valueType,
4946
5470
  `cte_${source.alias}`
4947
5471
  );
4948
- return drizzleOrm.sql`${value} AS ${quoteIdentifier(projectedField.outputName)}`;
5472
+ return drizzleOrm.sql`${value} AS ${quoteIdentifier2(projectedField.outputName)}`;
4949
5473
  }
4950
- const projectedCountColumn = source.function === "countDistinct" ? countDistinctColumn : countColumn;
5474
+ const projectedCountColumn = resolveCountColumn(source);
4951
5475
  const countValue = drizzleOrm.sql`${drizzleOrm.sql.raw(countCteAlias)}.${drizzleOrm.sql.raw(projectedCountColumn)}`;
4952
5476
  const aggregateValue = traversal.optional ? drizzleOrm.sql`COALESCE(${countValue}, 0)` : countValue;
4953
- return drizzleOrm.sql`${aggregateValue} AS ${quoteIdentifier(projectedField.outputName)}`;
5477
+ return drizzleOrm.sql`${aggregateValue} AS ${quoteIdentifier2(projectedField.outputName)}`;
4954
5478
  }),
4955
5479
  drizzleOrm.sql`, `
4956
5480
  );
@@ -4962,19 +5486,29 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
4962
5486
  AND ${drizzleOrm.sql.raw(countCteAlias)}.${drizzleOrm.sql.raw(previousAliasKindColumn)} = cte_${drizzleOrm.sql.raw(previousAlias)}.${drizzleOrm.sql.raw(previousAliasKindColumn)}
4963
5487
  `;
4964
5488
  const orderBy = buildStandardOrderBy({ ast, dialect });
4965
- const limitOffset = buildLimitOffsetClause({
4966
- limit: ast.limit,
4967
- offset: ast.offset
4968
- });
5489
+ const emittedLogicalPlan = startCteLimit === void 0 ? logicalPlan : stripLimitOffsetFromPlan(logicalPlan);
5490
+ const limitOffset = startCteLimit === void 0 ? buildLimitOffsetClause({ limit: ast.limit, offset: ast.offset }) : void 0;
4969
5491
  return emitStandardQuerySql({
4970
5492
  ctes: [startCte, countCte],
4971
5493
  fromClause,
4972
5494
  ...orderBy === void 0 ? {} : { orderBy },
4973
5495
  ...limitOffset === void 0 ? {} : { limitOffset },
4974
- logicalPlan,
5496
+ logicalPlan: emittedLogicalPlan,
4975
5497
  projection
4976
5498
  });
4977
5499
  }
5500
+ function stripLimitOffsetFromPlan(plan) {
5501
+ return { ...plan, root: stripLimitOffsetFromPlanNode(plan.root) };
5502
+ }
5503
+ function stripLimitOffsetFromPlanNode(node) {
5504
+ if (node.op === "limit_offset") {
5505
+ return stripLimitOffsetFromPlanNode(node.input);
5506
+ }
5507
+ if ("input" in node) {
5508
+ return { ...node, input: stripLimitOffsetFromPlanNode(node.input) };
5509
+ }
5510
+ return node;
5511
+ }
4978
5512
  var STANDARD_QUERY_STRATEGY_HANDLERS = {
4979
5513
  cte_project: compileStandardQueryWithCteStrategy
4980
5514
  };
@@ -4983,11 +5517,37 @@ function compileStandardQuery(ast, graphId, ctx) {
4983
5517
  const handler = STANDARD_QUERY_STRATEGY_HANDLERS[strategy];
4984
5518
  return handler(ast, graphId, ctx);
4985
5519
  }
5520
+ function selectStandardOrderBy(input) {
5521
+ const {
5522
+ ast,
5523
+ collapsedTraversalCteAlias,
5524
+ dialect,
5525
+ fulltextPredicate,
5526
+ fusion,
5527
+ vectorPredicate
5528
+ } = input;
5529
+ if (vectorPredicate && fulltextPredicate) {
5530
+ return buildStandardHybridRrfOrderBy({ ast, dialect, fusion });
5531
+ }
5532
+ if (vectorPredicate) {
5533
+ return buildStandardVectorOrderBy({ ast, dialect });
5534
+ }
5535
+ if (fulltextPredicate) {
5536
+ return buildStandardFulltextOrderBy({ ast, dialect });
5537
+ }
5538
+ return buildStandardOrderBy({
5539
+ ast,
5540
+ ...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias },
5541
+ dialect
5542
+ });
5543
+ }
4986
5544
  function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
4987
5545
  const { dialect } = ctx;
4988
5546
  const {
4989
5547
  collapsedTraversalCteAlias,
4990
5548
  effectiveLimit,
5549
+ fulltextPredicate,
5550
+ fusion,
4991
5551
  logicalPlan,
4992
5552
  predicateIndex,
4993
5553
  requiredColumnsByAlias,
@@ -5008,7 +5568,7 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
5008
5568
  { phase: "standard-pass-pipeline" }
5009
5569
  );
5010
5570
  }
5011
- if (!vectorPredicate) {
5571
+ if (!vectorPredicate && !fulltextPredicate) {
5012
5572
  const fastPathSql = compileCountAggregateFastPath(
5013
5573
  ast,
5014
5574
  graphId,
@@ -5054,7 +5614,29 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
5054
5614
  );
5055
5615
  }
5056
5616
  if (vectorPredicate) {
5057
- ctes.push(buildStandardEmbeddingsCte({ ctx, graphId, vectorPredicate }));
5617
+ const nodeKinds = getNodeKindsForAlias(ast, vectorPredicate.field.alias);
5618
+ ctes.push(
5619
+ buildStandardEmbeddingsCte({
5620
+ ctx,
5621
+ graphId,
5622
+ nodeKinds,
5623
+ vectorPredicate
5624
+ })
5625
+ );
5626
+ }
5627
+ if (fulltextPredicate) {
5628
+ const nodeKinds = getNodeKindsForAlias(ast, fulltextPredicate.field.alias);
5629
+ ctes.push(
5630
+ buildStandardFulltextCte({
5631
+ ctx,
5632
+ fulltextPredicate,
5633
+ graphId,
5634
+ nodeKinds
5635
+ })
5636
+ );
5637
+ }
5638
+ if (getHybridTargetAlias(vectorPredicate, fulltextPredicate) !== void 0) {
5639
+ ctes.push(buildStandardHybridCandidateCte());
5058
5640
  }
5059
5641
  const projection = buildStandardProjection({
5060
5642
  ast,
@@ -5064,14 +5646,18 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
5064
5646
  const fromClause = buildStandardFromClause({
5065
5647
  ast,
5066
5648
  ...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias },
5067
- ...vectorPredicate === void 0 ? {} : { vectorPredicate }
5649
+ ...vectorPredicate === void 0 ? {} : { vectorPredicate },
5650
+ ...fulltextPredicate === void 0 ? {} : { fulltextPredicate }
5068
5651
  });
5069
5652
  const groupBy = buildStandardGroupBy({ ast, dialect });
5070
5653
  const having2 = buildStandardHaving({ ast, ctx });
5071
- const orderBy = vectorPredicate ? buildStandardVectorOrderBy({ ast, dialect }) : buildStandardOrderBy({
5654
+ const orderBy = selectStandardOrderBy({
5072
5655
  ast,
5073
- ...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias },
5074
- dialect
5656
+ collapsedTraversalCteAlias,
5657
+ dialect,
5658
+ fulltextPredicate,
5659
+ fusion,
5660
+ vectorPredicate
5075
5661
  });
5076
5662
  const limitOffset = buildLimitOffsetClause({
5077
5663
  limit: effectiveLimit,
@@ -5193,7 +5779,7 @@ function extractCursorValue(row, spec) {
5193
5779
  current = record[segment];
5194
5780
  }
5195
5781
  if (jsonPointer2) {
5196
- const segments = chunkP5CNM325_cjs.parseJsonPointer(jsonPointer2);
5782
+ const segments = chunkUVZHTBZ5_cjs.parseJsonPointer(jsonPointer2);
5197
5783
  for (const segment of segments) {
5198
5784
  if (current === null || current === void 0) return void 0;
5199
5785
  if (typeof current !== "object") return void 0;
@@ -5263,14 +5849,14 @@ function generateId() {
5263
5849
 
5264
5850
  // src/utils/path.ts
5265
5851
  function parseSqlitePath(path) {
5266
- const emptyPath = `${chunkMME3H4ZF_cjs.SQLITE_PATH_DELIMITER}${chunkMME3H4ZF_cjs.SQLITE_PATH_DELIMITER}`;
5852
+ const emptyPath = `${chunk3JK66E3I_cjs.SQLITE_PATH_DELIMITER}${chunk3JK66E3I_cjs.SQLITE_PATH_DELIMITER}`;
5267
5853
  if (!path || path === emptyPath) return [];
5268
5854
  const trimmed = path.slice(1, -1);
5269
5855
  if (trimmed === "") return [];
5270
- return trimmed.split(chunkMME3H4ZF_cjs.SQLITE_PATH_DELIMITER);
5856
+ return trimmed.split(chunk3JK66E3I_cjs.SQLITE_PATH_DELIMITER);
5271
5857
  }
5272
5858
  function isSqlitePath(value) {
5273
- return typeof value === "string" && value.startsWith(chunkMME3H4ZF_cjs.SQLITE_PATH_DELIMITER) && value.endsWith(chunkMME3H4ZF_cjs.SQLITE_PATH_DELIMITER);
5859
+ return typeof value === "string" && value.startsWith(chunk3JK66E3I_cjs.SQLITE_PATH_DELIMITER) && value.endsWith(chunk3JK66E3I_cjs.SQLITE_PATH_DELIMITER);
5274
5860
  }
5275
5861
  function normalizePath(value) {
5276
5862
  if (Array.isArray(value)) {
@@ -5285,12 +5871,12 @@ function normalizePath(value) {
5285
5871
  return [];
5286
5872
  }
5287
5873
  function isPostgresTextArray(value) {
5288
- return typeof value === "string" && value.startsWith(chunkMME3H4ZF_cjs.PG_ARRAY_START) && value.endsWith(chunkMME3H4ZF_cjs.PG_ARRAY_END);
5874
+ return typeof value === "string" && value.startsWith(chunk3JK66E3I_cjs.PG_ARRAY_START) && value.endsWith(chunk3JK66E3I_cjs.PG_ARRAY_END);
5289
5875
  }
5290
5876
  function parsePostgresTextArray(value) {
5291
5877
  const inner = value.slice(1, -1);
5292
5878
  if (inner === "") return [];
5293
- return inner.split(chunkMME3H4ZF_cjs.PG_PATH_ELEMENT_SEPARATOR);
5879
+ return inner.split(chunk3JK66E3I_cjs.PG_PATH_ELEMENT_SEPARATOR);
5294
5880
  }
5295
5881
 
5296
5882
  // src/query/execution/result-mapper.ts
@@ -5492,11 +6078,7 @@ function buildComparisonPredicate(field2, op, value) {
5492
6078
  };
5493
6079
  }
5494
6080
  function buildCursorFromContext(context, orderBy, direction) {
5495
- return buildCursorFromRow(
5496
- context,
5497
- orderBy,
5498
- direction
5499
- );
6081
+ return buildCursorFromRow(context, orderBy, direction);
5500
6082
  }
5501
6083
  function buildPaginatedResult(data, orderedRows, orderBy, limit, hasMore, isBackward, cursor, buildContext) {
5502
6084
  let nextCursor;
@@ -5531,7 +6113,7 @@ async function* createStreamIterable(batchSize, paginate) {
5531
6113
  }
5532
6114
  }
5533
6115
  function getStreamBatchSize(options) {
5534
- return options?.batchSize ?? chunkMME3H4ZF_cjs.DEFAULT_STREAM_BATCH_SIZE;
6116
+ return options?.batchSize ?? chunk3JK66E3I_cjs.DEFAULT_STREAM_BATCH_SIZE;
5535
6117
  }
5536
6118
 
5537
6119
  // src/query/execution/field-tracker.ts
@@ -5613,7 +6195,7 @@ function createNodeTrackingProxy(alias, kindNames, tracker, options) {
5613
6195
  return getPlaceholderForSystemField(property, options.mode);
5614
6196
  }
5615
6197
  if (property === "meta") {
5616
- for (const key of chunkMME3H4ZF_cjs.NODE_META_KEYS) {
6198
+ for (const key of chunk3JK66E3I_cjs.NODE_META_KEYS) {
5617
6199
  tracker.record(alias, `meta.${key}`, true);
5618
6200
  }
5619
6201
  return buildNodeMetaPlaceholder(options.mode);
@@ -5646,7 +6228,7 @@ function createEdgeTrackingProxy(alias, edgeKindNames, tracker, options) {
5646
6228
  return getPlaceholderForSystemField(property, options.mode);
5647
6229
  }
5648
6230
  if (property === "meta") {
5649
- for (const key of chunkMME3H4ZF_cjs.EDGE_META_KEYS) {
6231
+ for (const key of chunk3JK66E3I_cjs.EDGE_META_KEYS) {
5650
6232
  tracker.record(alias, `meta.${key}`, true);
5651
6233
  }
5652
6234
  return buildEdgeMetaPlaceholder(options.mode);
@@ -6089,59 +6671,41 @@ function validateSqlIdentifier(alias) {
6089
6671
  );
6090
6672
  }
6091
6673
  }
6092
- function throwInvalidVectorPredicatePlacement(path) {
6093
- throw new chunkO5PST6TB_cjs.ValidationError(
6094
- "Vector similarity predicates cannot be nested under OR or NOT. Use top-level AND combinations instead.",
6095
- {
6096
- issues: [
6097
- {
6098
- path,
6099
- message: "Vector similarity predicates are only supported at top-level or inside AND groups."
6100
- }
6101
- ]
6102
- },
6103
- {
6104
- suggestion: "Rewrite the predicate to keep vector similarity at top-level or combine with additional filters using AND."
6105
- }
6106
- );
6107
- }
6108
- function validateVectorPredicateExpression(expression, inDisallowedBranch, path) {
6674
+ function validateStructuralPredicatePlacement(expression, matchType, buildError, inDisallowedBranch, path) {
6675
+ if (expression.__type === matchType) {
6676
+ if (inDisallowedBranch) buildError(path);
6677
+ return;
6678
+ }
6109
6679
  switch (expression.__type) {
6110
- case "vector_similarity": {
6111
- if (inDisallowedBranch) {
6112
- throwInvalidVectorPredicatePlacement(path);
6113
- }
6114
- return;
6115
- }
6116
6680
  case "and": {
6117
- for (const [
6118
- predicateIndex,
6119
- predicate2
6120
- ] of expression.predicates.entries()) {
6121
- validateVectorPredicateExpression(
6122
- predicate2,
6681
+ for (const [index, child] of expression.predicates.entries()) {
6682
+ validateStructuralPredicatePlacement(
6683
+ child,
6684
+ matchType,
6685
+ buildError,
6123
6686
  inDisallowedBranch,
6124
- `${path}.predicates[${predicateIndex}]`
6687
+ `${path}.predicates[${index}]`
6125
6688
  );
6126
6689
  }
6127
6690
  return;
6128
6691
  }
6129
6692
  case "or": {
6130
- for (const [
6131
- predicateIndex,
6132
- predicate2
6133
- ] of expression.predicates.entries()) {
6134
- validateVectorPredicateExpression(
6135
- predicate2,
6693
+ for (const [index, child] of expression.predicates.entries()) {
6694
+ validateStructuralPredicatePlacement(
6695
+ child,
6696
+ matchType,
6697
+ buildError,
6136
6698
  true,
6137
- `${path}.predicates[${predicateIndex}]`
6699
+ `${path}.predicates[${index}]`
6138
6700
  );
6139
6701
  }
6140
6702
  return;
6141
6703
  }
6142
6704
  case "not": {
6143
- validateVectorPredicateExpression(
6705
+ validateStructuralPredicatePlacement(
6144
6706
  expression.predicate,
6707
+ matchType,
6708
+ buildError,
6145
6709
  true,
6146
6710
  `${path}.predicate`
6147
6711
  );
@@ -6155,24 +6719,108 @@ function validateVectorPredicateExpression(expression, inDisallowedBranch, path)
6155
6719
  case "object_op":
6156
6720
  case "aggregate_comparison":
6157
6721
  case "exists":
6158
- case "in_subquery": {
6722
+ case "in_subquery":
6723
+ case "vector_similarity":
6724
+ case "fulltext_match": {
6159
6725
  return;
6160
6726
  }
6161
6727
  }
6162
6728
  }
6729
+ function throwInvalidVectorPredicatePlacement(path) {
6730
+ throw new chunkO5PST6TB_cjs.ValidationError(
6731
+ "Vector similarity predicates cannot be nested under OR or NOT. Use top-level AND combinations instead.",
6732
+ {
6733
+ issues: [
6734
+ {
6735
+ path,
6736
+ message: "Vector similarity predicates are only supported at top-level or inside AND groups."
6737
+ }
6738
+ ]
6739
+ },
6740
+ {
6741
+ suggestion: "Rewrite the predicate to keep vector similarity at top-level or combine with additional filters using AND."
6742
+ }
6743
+ );
6744
+ }
6745
+ function throwInvalidFulltextPredicatePlacement(path) {
6746
+ throw new chunkO5PST6TB_cjs.ValidationError(
6747
+ "Fulltext match predicates (.matches()) cannot be nested under OR or NOT. Use top-level AND combinations instead.",
6748
+ {
6749
+ issues: [
6750
+ {
6751
+ path,
6752
+ message: "Fulltext match predicates are only supported at top-level or inside AND groups."
6753
+ }
6754
+ ]
6755
+ },
6756
+ {
6757
+ suggestion: "Restructure your query so the fulltext match appears at the top level of a whereNode() or inside an .and() combination."
6758
+ }
6759
+ );
6760
+ }
6163
6761
  function validateVectorPredicatePlacement(predicates) {
6164
- for (const [predicateIndex, predicate2] of predicates.entries()) {
6165
- validateVectorPredicateExpression(
6762
+ for (const [index, predicate2] of predicates.entries()) {
6763
+ validateStructuralPredicatePlacement(
6764
+ predicate2.expression,
6765
+ "vector_similarity",
6766
+ throwInvalidVectorPredicatePlacement,
6767
+ false,
6768
+ `predicates[${index}].expression`
6769
+ );
6770
+ }
6771
+ }
6772
+ function validateFulltextPredicatePlacement(predicates) {
6773
+ for (const [index, predicate2] of predicates.entries()) {
6774
+ validateStructuralPredicatePlacement(
6166
6775
  predicate2.expression,
6776
+ "fulltext_match",
6777
+ throwInvalidFulltextPredicatePlacement,
6167
6778
  false,
6168
- `predicates[${predicateIndex}].expression`
6779
+ `predicates[${index}].expression`
6780
+ );
6781
+ }
6782
+ }
6783
+ function validateHybridFusionOptions(options) {
6784
+ const method = options.method;
6785
+ if (method !== void 0 && method !== "rrf") {
6786
+ throw new chunkO5PST6TB_cjs.ValidationError(`Unsupported fusion method: ${method}`, {
6787
+ issues: [{ path: "fusion.method", message: `Only "rrf" is supported.` }]
6788
+ });
6789
+ }
6790
+ const { k } = options;
6791
+ if (k !== void 0 && (!Number.isFinite(k) || k <= 0)) {
6792
+ throw new chunkO5PST6TB_cjs.ValidationError(
6793
+ `Fusion k must be a positive finite number, got: ${String(k)}`,
6794
+ {
6795
+ issues: [{ path: "fusion.k", message: "Must be a positive number." }]
6796
+ }
6169
6797
  );
6170
6798
  }
6799
+ const weights = options.weights;
6800
+ if (weights !== void 0) {
6801
+ for (const key of ["vector", "fulltext"]) {
6802
+ const weight = weights[key];
6803
+ if (weight !== void 0 && (!Number.isFinite(weight) || weight < 0)) {
6804
+ throw new chunkO5PST6TB_cjs.ValidationError(
6805
+ `Fusion weight for "${key}" must be a non-negative finite number, got: ${String(weight)}`,
6806
+ {
6807
+ issues: [
6808
+ {
6809
+ path: `fusion.weights.${key}`,
6810
+ message: "Must be a non-negative number."
6811
+ }
6812
+ ]
6813
+ }
6814
+ );
6815
+ }
6816
+ }
6817
+ }
6171
6818
  }
6172
6819
 
6173
6820
  // src/query/builder/ast-builder.ts
6174
6821
  function buildQueryAst(config, state) {
6175
6822
  validateVectorPredicatePlacement(state.predicates);
6823
+ validateFulltextPredicatePlacement(state.predicates);
6176
6824
  const temporalMode = {
6177
6825
  mode: state.temporalMode
6178
6826
  };
@@ -6196,7 +6844,18 @@ function buildQueryAst(config, state) {
6196
6844
  ...state.limit !== void 0 && { limit: state.limit },
6197
6845
  ...state.offset !== void 0 && { offset: state.offset },
6198
6846
  ...state.groupBy !== void 0 && { groupBy: state.groupBy },
6199
- ...state.having !== void 0 && { having: state.having }
6847
+ ...state.having !== void 0 && { having: state.having },
6848
+ ...state.fusion !== void 0 && { fusion: state.fusion }
6849
+ };
6850
+ }
6851
+
6852
+ // src/query/builder/compile-options.ts
6853
+ function buildCompileOptions(config) {
6854
+ const fulltextStrategy = config.backend?.fulltextStrategy;
6855
+ return {
6856
+ dialect: config.dialect ?? "sqlite",
6857
+ schema: config.schema,
6858
+ ...fulltextStrategy === void 0 ? {} : { fulltextStrategy }
6200
6859
  };
6201
6860
  }
6202
6861
  function toLiteral(value) {
@@ -6298,7 +6957,8 @@ function substitutePredicateExpression(expr, bindings) {
6298
6957
  case "array_op":
6299
6958
  case "object_op":
6300
6959
  case "aggregate_comparison":
6301
- case "vector_similarity": {
6960
+ case "vector_similarity":
6961
+ case "fulltext_match": {
6302
6962
  return expr;
6303
6963
  }
6304
6964
  case "exists": {
@@ -6337,7 +6997,7 @@ function substituteParameters(ast, bindings) {
6337
6997
  };
6338
6998
  }
6339
6999
  function fillPlaceholders(params, bindings, dialect) {
6340
- const adapter = chunkMME3H4ZF_cjs.getDialect(dialect);
7000
+ const adapter = chunk3JK66E3I_cjs.getDialect(dialect);
6341
7001
  return params.map((parameter) => {
6342
7002
  if (parameter instanceof drizzleOrm.Placeholder) {
6343
7003
  const name = parameter.name;
@@ -6550,7 +7210,8 @@ function collectParameterMetadataFromExpression(expression, names, stringOpParam
6550
7210
  case "array_op":
6551
7211
  case "object_op":
6552
7212
  case "aggregate_comparison":
6553
- case "vector_similarity": {
7213
+ case "vector_similarity":
7214
+ case "fulltext_match": {
6554
7215
  return;
6555
7216
  }
6556
7217
  case "exists": {
@@ -6681,7 +7342,7 @@ var ExecutableQuery = class _ExecutableQuery {
6681
7342
  direction
6682
7343
  } : {
6683
7344
  field: fieldRef(alias, ["props"], {
6684
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
7345
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
6685
7346
  valueType: typeInfo?.valueType,
6686
7347
  elementType: typeInfo?.elementType
6687
7348
  }),
@@ -6905,10 +7566,7 @@ var ExecutableQuery = class _ExecutableQuery {
6905
7566
  * Builds compile options from the config.
6906
7567
  */
6907
7568
  #compileOptions() {
6908
- return {
6909
- dialect: this.#config.dialect ?? "sqlite",
6910
- schema: this.#config.schema
6911
- };
7569
+ return buildCompileOptions(this.#config);
6912
7570
  }
6913
7571
  #requireBackend() {
6914
7572
  const { backend } = this.#config;
@@ -7290,7 +7948,7 @@ var ExecutableQuery = class _ExecutableQuery {
7290
7948
  if (field2.jsonPointer === void 0) {
7291
7949
  return false;
7292
7950
  }
7293
- const segments = chunkP5CNM325_cjs.parseJsonPointer(field2.jsonPointer);
7951
+ const segments = chunkUVZHTBZ5_cjs.parseJsonPointer(field2.jsonPointer);
7294
7952
  if (segments.length !== 1) {
7295
7953
  return false;
7296
7954
  }
@@ -7338,7 +7996,7 @@ var ExecutableQuery = class _ExecutableQuery {
7338
7996
  aliasObject2[fieldName] = nullToUndefined(row[outputName2]);
7339
7997
  continue;
7340
7998
  }
7341
- const segments = chunkP5CNM325_cjs.parseJsonPointer(jsonPointer2);
7999
+ const segments = chunkUVZHTBZ5_cjs.parseJsonPointer(jsonPointer2);
7342
8000
  if (segments.length === 0) {
7343
8001
  throw new MissingSelectiveFieldError(alias, "orderBy");
7344
8002
  }
@@ -7434,7 +8092,7 @@ var ExecutableQuery = class _ExecutableQuery {
7434
8092
  );
7435
8093
  }
7436
8094
  const isBackward = options.last !== void 0 || options.before !== void 0;
7437
- const limit = options.first ?? options.last ?? chunkMME3H4ZF_cjs.DEFAULT_PAGINATION_LIMIT;
8095
+ const limit = options.first ?? options.last ?? chunk3JK66E3I_cjs.DEFAULT_PAGINATION_LIMIT;
7438
8096
  const cursor = options.after ?? options.before;
7439
8097
  let cursorData;
7440
8098
  if (cursor) {
@@ -7623,14 +8281,8 @@ var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
7623
8281
  this.#cachedCompiled = compiled;
7624
8282
  return compiled;
7625
8283
  }
7626
- /**
7627
- * Builds compile options from the config.
7628
- */
7629
8284
  #compileOptions() {
7630
- return {
7631
- dialect: this.#config.dialect ?? "sqlite",
7632
- schema: this.#config.schema
7633
- };
8285
+ return buildCompileOptions(this.#config);
7634
8286
  }
7635
8287
  /**
7636
8288
  * Executes the query and returns typed results.
@@ -7861,7 +8513,7 @@ var TraversalBuilder = class _TraversalBuilder {
7861
8513
  const valueType = typeInfo?.valueType;
7862
8514
  const elementType = typeInfo?.elementType;
7863
8515
  const ref = fieldRef(alias, ["props"], {
7864
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([propertyName]),
8516
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([propertyName]),
7865
8517
  valueType,
7866
8518
  elementType
7867
8519
  });
@@ -8181,7 +8833,7 @@ var QueryBuilder = class _QueryBuilder {
8181
8833
  if (source.valueType !== void 0 || source.path.length !== 1 || source.path[0] !== "props" || source.jsonPointer === void 0) {
8182
8834
  return [outputName, source];
8183
8835
  }
8184
- const segments = chunkP5CNM325_cjs.parseJsonPointer(source.jsonPointer);
8836
+ const segments = chunkUVZHTBZ5_cjs.parseJsonPointer(source.jsonPointer);
8185
8837
  if (segments.length !== 1) {
8186
8838
  return [outputName, source];
8187
8839
  }
@@ -8235,7 +8887,7 @@ var QueryBuilder = class _QueryBuilder {
8235
8887
  direction
8236
8888
  } : {
8237
8889
  field: fieldRef(alias, ["props"], {
8238
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
8890
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
8239
8891
  valueType: typeInfo?.valueType,
8240
8892
  elementType: typeInfo?.elementType
8241
8893
  }),
@@ -8310,7 +8962,7 @@ var QueryBuilder = class _QueryBuilder {
8310
8962
  __type: "field_ref",
8311
8963
  alias,
8312
8964
  path: ["props"],
8313
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
8965
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
8314
8966
  valueType: typeInfo?.valueType,
8315
8967
  elementType: typeInfo?.elementType
8316
8968
  };
@@ -8357,6 +9009,36 @@ var QueryBuilder = class _QueryBuilder {
8357
9009
  having: predicate2
8358
9010
  });
8359
9011
  }
9012
+ /**
9013
+ * Sets fusion parameters for hybrid (vector + fulltext) queries.
9014
+ *
9015
+ * Applies only when the query contains both a `.similarTo()` and a
9016
+ * `.$fulltext.matches()` predicate. Without this call, the default is
9017
+ * RRF with k=60 and equal weights. A mismatch between `.fuseWith()`
9018
+ * configuration and the predicates on the query is caught during
9019
+ * compilation, not here.
9020
+ *
9021
+ * @example
9022
+ * ```typescript
9023
+ * store.query()
9024
+ * .from("Document", "d")
9025
+ * .whereNode("d", d =>
9026
+ * d.$fulltext.matches("renewable energy", 50)
9027
+ * .and(d.embedding.similarTo(vec, 50))
9028
+ * .and(d.tenantId.eq(tenant))
9029
+ * )
9030
+ * .fuseWith({ k: 60, weights: { fulltext: 1.5 } })
9031
+ * .limit(10)
9032
+ * .execute();
9033
+ * ```
9034
+ */
9035
+ fuseWith(options) {
9036
+ validateHybridFusionOptions(options);
9037
+ return new _QueryBuilder(this.#config, {
9038
+ ...this.#state,
9039
+ fusion: options
9040
+ });
9041
+ }
8360
9042
  /**
8361
9043
  * Applies a query fragment to transform this builder.
8362
9044
  *
@@ -8408,42 +9090,14 @@ var QueryBuilder = class _QueryBuilder {
8408
9090
  property
8409
9091
  ) : void 0;
8410
9092
  const ref = fieldRef(alias, ["props"], {
8411
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([property]),
9093
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([property]),
8412
9094
  valueType: typeInfo?.valueType,
8413
9095
  elementType: typeInfo?.elementType
8414
9096
  });
8415
9097
  return this.#buildFieldBuilderForTypeInfo(ref, typeInfo);
8416
9098
  }
8417
9099
  #buildFieldBuilderForTypeInfo(ref, typeInfo) {
8418
- if (!typeInfo) {
8419
- return baseField(ref);
8420
- }
8421
- switch (typeInfo.valueType) {
8422
- case "string": {
8423
- return stringField(ref);
8424
- }
8425
- case "number": {
8426
- return numberField(ref);
8427
- }
8428
- case "boolean": {
8429
- return baseField(ref);
8430
- }
8431
- case "date": {
8432
- return dateField(ref);
8433
- }
8434
- case "array": {
8435
- return arrayField(ref);
8436
- }
8437
- case "object": {
8438
- return objectField(ref, { typeInfo });
8439
- }
8440
- case "embedding": {
8441
- return embeddingField(ref);
8442
- }
8443
- case "unknown": {
8444
- return baseField(ref);
8445
- }
8446
- }
9100
+ return buildFieldBuilderForTypeInfo(ref, typeInfo);
8447
9101
  }
8448
9102
  #createNodeAccessor(alias) {
8449
9103
  const kindNames = this.#getKindNamesForAlias(alias);
@@ -8453,6 +9107,10 @@ var QueryBuilder = class _QueryBuilder {
8453
9107
  const kindAccessor = stringField(
8454
9108
  fieldRef(alias, ["kind"], { valueType: "string" })
8455
9109
  );
9110
+ const fulltextAccessor = createFulltextAccessor(
9111
+ alias,
9112
+ () => this.#hasSearchableField(kindNames)
9113
+ );
8456
9114
  return new Proxy({}, {
8457
9115
  get: (_, property) => {
8458
9116
  if (typeof property === "symbol") return;
@@ -8460,10 +9118,15 @@ var QueryBuilder = class _QueryBuilder {
8460
9118
  if (property === "toJSON") return;
8461
9119
  if (property === "id") return idAccessor;
8462
9120
  if (property === "kind") return kindAccessor;
9121
+ if (property === "$fulltext") return fulltextAccessor;
8463
9122
  return this.#getFieldBuilderForProperty(kindNames, property, alias);
8464
9123
  }
8465
9124
  });
8466
9125
  }
9126
+ #hasSearchableField(kindNames) {
9127
+ if (!kindNames) return false;
9128
+ return this.#config.schemaIntrospector.hasSearchableField(kindNames);
9129
+ }
8467
9130
  #expandTraversalEdgeKinds(edgeKind, includeImplyingEdges) {
8468
9131
  return includeImplyingEdges ? this.#config.registry.expandImplyingEdges(edgeKind) : [edgeKind];
8469
9132
  }
@@ -8506,7 +9169,7 @@ var QueryBuilder = class _QueryBuilder {
8506
9169
  property
8507
9170
  ) : void 0;
8508
9171
  const ref = fieldRef(alias, ["props"], {
8509
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([property]),
9172
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([property]),
8510
9173
  valueType: typeInfo?.valueType,
8511
9174
  elementType: typeInfo?.elementType
8512
9175
  });
@@ -8689,14 +9352,8 @@ var UnionableQuery = class _UnionableQuery {
8689
9352
  this.#cachedCompiled = compiled;
8690
9353
  return compiled;
8691
9354
  }
8692
- /**
8693
- * Builds compile options from the config.
8694
- */
8695
9355
  #compileOptions() {
8696
- return {
8697
- dialect: this.#config.dialect ?? "sqlite",
8698
- schema: this.#config.schema
8699
- };
9356
+ return buildCompileOptions(this.#config);
8700
9357
  }
8701
9358
  /**
8702
9359
  * Executes the combined query.
@@ -8758,7 +9415,7 @@ function count(alias, field2) {
8758
9415
  __type: "field_ref",
8759
9416
  alias,
8760
9417
  path: field2 ? ["props"] : ["id"],
8761
- jsonPointer: field2 ? chunkP5CNM325_cjs.jsonPointer([field2]) : void 0,
9418
+ jsonPointer: field2 ? chunkUVZHTBZ5_cjs.jsonPointer([field2]) : void 0,
8762
9419
  valueType: field2 ? void 0 : "string"
8763
9420
  }
8764
9421
  };
@@ -8771,11 +9428,17 @@ function countDistinct(alias, field2) {
8771
9428
  __type: "field_ref",
8772
9429
  alias,
8773
9430
  path: field2 ? ["props"] : ["id"],
8774
- jsonPointer: field2 ? chunkP5CNM325_cjs.jsonPointer([field2]) : void 0,
9431
+ jsonPointer: field2 ? chunkUVZHTBZ5_cjs.jsonPointer([field2]) : void 0,
8775
9432
  valueType: field2 ? void 0 : "string"
8776
9433
  }
8777
9434
  };
8778
9435
  }
9436
+ function countEdges(edgeAlias) {
9437
+ return count(edgeAlias);
9438
+ }
9439
+ function countDistinctEdges(edgeAlias) {
9440
+ return countDistinct(edgeAlias);
9441
+ }
8779
9442
  function sum(alias, field2) {
8780
9443
  return {
8781
9444
  __type: "aggregate",
@@ -8784,7 +9447,7 @@ function sum(alias, field2) {
8784
9447
  __type: "field_ref",
8785
9448
  alias,
8786
9449
  path: ["props"],
8787
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
9450
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
8788
9451
  valueType: "number"
8789
9452
  }
8790
9453
  };
@@ -8797,7 +9460,7 @@ function avg(alias, field2) {
8797
9460
  __type: "field_ref",
8798
9461
  alias,
8799
9462
  path: ["props"],
8800
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
9463
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
8801
9464
  valueType: "number"
8802
9465
  }
8803
9466
  };
@@ -8810,7 +9473,7 @@ function min(alias, field2) {
8810
9473
  __type: "field_ref",
8811
9474
  alias,
8812
9475
  path: ["props"],
8813
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
9476
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
8814
9477
  valueType: "number"
8815
9478
  }
8816
9479
  };
@@ -8823,7 +9486,7 @@ function max(alias, field2) {
8823
9486
  __type: "field_ref",
8824
9487
  alias,
8825
9488
  path: ["props"],
8826
- jsonPointer: chunkP5CNM325_cjs.jsonPointer([field2]),
9489
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer([field2]),
8827
9490
  valueType: "number"
8828
9491
  }
8829
9492
  };
@@ -8854,7 +9517,7 @@ function field(alias, ...path) {
8854
9517
  __type: "field_ref",
8855
9518
  alias,
8856
9519
  path: ["props"],
8857
- jsonPointer: chunkP5CNM325_cjs.jsonPointer(path)
9520
+ jsonPointer: chunkUVZHTBZ5_cjs.jsonPointer(path)
8858
9521
  };
8859
9522
  }
8860
9523
  function having(aggregate, op, value) {
@@ -8912,16 +9575,12 @@ function offsetFragment(n) {
8912
9575
  }
8913
9576
 
8914
9577
  // src/query/builder/index.ts
8915
- setQueryBuilderClass(
8916
- QueryBuilder
8917
- );
8918
- setUnionableQueryClass(
8919
- UnionableQuery
8920
- );
9578
+ setQueryBuilderClass(QueryBuilder);
9579
+ setUnionableQueryClass(UnionableQuery);
8921
9580
 
8922
9581
  // src/query/builder.ts
8923
9582
  function createQueryBuilder(graphId, registry, options) {
8924
- const schemaIntrospector = chunkP5CNM325_cjs.createSchemaIntrospector(
9583
+ const schemaIntrospector = chunkUVZHTBZ5_cjs.createSchemaIntrospector(
8925
9584
  registry.nodeKinds,
8926
9585
  registry.edgeKinds
8927
9586
  );
@@ -8948,7 +9607,8 @@ function createQueryBuilder(graphId, registry, options) {
8948
9607
  temporalMode: "current",
8949
9608
  asOf: void 0,
8950
9609
  groupBy: void 0,
8951
- having: void 0
9610
+ having: void 0,
9611
+ fusion: void 0
8952
9612
  };
8953
9613
  return new QueryBuilder(config, initialState);
8954
9614
  }
@@ -8957,12 +9617,12 @@ function createQueryBuilder(graphId, registry, options) {
8957
9617
  function buildKindRegistry(graph) {
8958
9618
  const nodeTypes = extractNodeTypes(graph);
8959
9619
  const edgeTypes = extractEdgeTypes(graph);
8960
- const closures = graph.ontology.length > 0 ? chunkX33WV2UF_cjs.computeClosuresFromOntology(graph.ontology) : chunkX33WV2UF_cjs.createEmptyClosures();
8961
- return new chunkX33WV2UF_cjs.KindRegistry(nodeTypes, edgeTypes, closures);
9620
+ const closures = graph.ontology.length > 0 ? chunkHRNQ3DVJ_cjs.computeClosuresFromOntology(graph.ontology) : chunkHRNQ3DVJ_cjs.createEmptyClosures();
9621
+ return new chunkHRNQ3DVJ_cjs.KindRegistry(nodeTypes, edgeTypes, closures);
8962
9622
  }
8963
9623
  function extractNodeTypes(graph) {
8964
9624
  const result = /* @__PURE__ */ new Map();
8965
- for (const typeName of chunk5GZMPJOP_cjs.getNodeKinds(graph)) {
9625
+ for (const typeName of chunkWRWVWKQL_cjs.getNodeKinds(graph)) {
8966
9626
  const registration = graph.nodes[typeName];
8967
9627
  result.set(typeName, registration.type);
8968
9628
  }
@@ -8970,7 +9630,7 @@ function extractNodeTypes(graph) {
8970
9630
  }
8971
9631
  function extractEdgeTypes(graph) {
8972
9632
  const result = /* @__PURE__ */ new Map();
8973
- for (const typeName of chunk5GZMPJOP_cjs.getEdgeKinds(graph)) {
9633
+ for (const typeName of chunkWRWVWKQL_cjs.getEdgeKinds(graph)) {
8974
9634
  const registration = graph.edges[typeName];
8975
9635
  result.set(typeName, registration.type);
8976
9636
  }
@@ -9184,7 +9844,7 @@ function createGraphAlgorithms(params) {
9184
9844
  const ctx = {
9185
9845
  graphId: params.graphId,
9186
9846
  backend: params.backend,
9187
- dialect: chunkMME3H4ZF_cjs.getDialect(params.backend.dialect),
9847
+ dialect: chunk3JK66E3I_cjs.getDialect(params.backend.dialect),
9188
9848
  schema: params.schema ?? (params.backend.tableNames ? createSqlSchema(params.backend.tableNames) : createSqlSchema()),
9189
9849
  defaultTemporalMode: params.defaultTemporalMode
9190
9850
  };
@@ -9289,20 +9949,13 @@ function createEdgeCollection(config) {
9289
9949
  const result = {};
9290
9950
  if (options?.matchOn !== void 0)
9291
9951
  result.matchOn = options.matchOn;
9292
- if (options?.props !== void 0)
9293
- result.props = options.props;
9952
+ if (options?.props !== void 0) result.props = options.props;
9294
9953
  return result;
9295
9954
  }
9296
9955
  return {
9297
9956
  async create(from, to, props, options) {
9298
9957
  const result = await executeEdgeCreate2(
9299
- buildCreateEdgeInput(
9300
- kind,
9301
- from,
9302
- to,
9303
- props ?? {},
9304
- options
9305
- ),
9958
+ buildCreateEdgeInput(kind, from, to, props ?? {}, options),
9306
9959
  backend
9307
9960
  );
9308
9961
  return narrowEdge(result);
@@ -9397,7 +10050,7 @@ function createEdgeCollection(config) {
9397
10050
  temporalMode: mode
9398
10051
  };
9399
10052
  if (mode === "current" || mode === "asOf") {
9400
- params.asOf = options?.asOf ?? chunk5GZMPJOP_cjs.nowIso();
10053
+ params.asOf = options?.asOf ?? chunkWRWVWKQL_cjs.nowIso();
9401
10054
  }
9402
10055
  if (options?.from?.kind !== void 0)
9403
10056
  params.fromKind = options.from.kind;
@@ -9418,7 +10071,7 @@ function createEdgeCollection(config) {
9418
10071
  temporalMode: mode
9419
10072
  };
9420
10073
  if (mode === "current" || mode === "asOf") {
9421
- params.asOf = options?.asOf ?? chunk5GZMPJOP_cjs.nowIso();
10074
+ params.asOf = options?.asOf ?? chunkWRWVWKQL_cjs.nowIso();
9422
10075
  }
9423
10076
  if (options?.from?.kind !== void 0)
9424
10077
  params.fromKind = options.from.kind;
@@ -9428,10 +10081,7 @@ function createEdgeCollection(config) {
9428
10081
  return backend.countEdgesByKind(params);
9429
10082
  },
9430
10083
  async bulkCreate(items) {
9431
- const batchInputs = mapBulkEdgeInputs(
9432
- kind,
9433
- items
9434
- );
10084
+ const batchInputs = mapBulkEdgeInputs(kind, items);
9435
10085
  if (backend.capabilities.transactions && "transaction" in backend) {
9436
10086
  const results2 = await backend.transaction(
9437
10087
  async (txBackend) => executeEdgeCreateBatch2(batchInputs, txBackend)
@@ -9467,11 +10117,7 @@ function createEdgeCollection(config) {
9467
10117
  if (existing) {
9468
10118
  toUpdate.push({
9469
10119
  index: itemIndex,
9470
- input: buildUpdateEdgeInput(
9471
- item.id,
9472
- item.props ?? {},
9473
- item
9474
- ),
10120
+ input: buildUpdateEdgeInput(item.id, item.props ?? {}, item),
9475
10121
  clearDeleted: existing.deleted_at !== void 0
9476
10122
  });
9477
10123
  } else {
@@ -9510,10 +10156,7 @@ function createEdgeCollection(config) {
9510
10156
  return upsertAll(backend);
9511
10157
  },
9512
10158
  async bulkInsert(items) {
9513
- const batchInputs = mapBulkEdgeInputs(
9514
- kind,
9515
- items
9516
- );
10159
+ const batchInputs = mapBulkEdgeInputs(kind, items);
9517
10160
  if (backend.capabilities.transactions && "transaction" in backend) {
9518
10161
  await backend.transaction(async (txBackend) => {
9519
10162
  await executeEdgeCreateNoReturnBatch2(batchInputs, txBackend);
@@ -9664,11 +10307,7 @@ function createNodeCollection(config) {
9664
10307
  async getByIds(ids, options) {
9665
10308
  if (ids.length === 0) return [];
9666
10309
  if (backend.getNodes !== void 0) {
9667
- const rows = await backend.getNodes(
9668
- graphId,
9669
- kind,
9670
- ids
9671
- );
10310
+ const rows = await backend.getNodes(graphId, kind, ids);
9672
10311
  const rowMap = /* @__PURE__ */ new Map();
9673
10312
  for (const row of rows) {
9674
10313
  rowMap.set(row.id, row);
@@ -9713,7 +10352,7 @@ function createNodeCollection(config) {
9713
10352
  const mode2 = options.temporalMode ?? defaultTemporalMode;
9714
10353
  let query = createQuery().from(kind, "_n").temporal(
9715
10354
  mode2,
9716
- mode2 === "asOf" ? options.asOf ?? chunk5GZMPJOP_cjs.nowIso() : void 0
10355
+ mode2 === "asOf" ? options.asOf ?? chunkWRWVWKQL_cjs.nowIso() : void 0
9717
10356
  ).whereNode("_n", options.where).select((ctx) => ctx._n);
9718
10357
  if (options.limit !== void 0) query = query.limit(options.limit);
9719
10358
  if (options.offset !== void 0) query = query.offset(options.offset);
@@ -9728,7 +10367,7 @@ function createNodeCollection(config) {
9728
10367
  temporalMode: mode
9729
10368
  };
9730
10369
  if (mode === "current" || mode === "asOf") {
9731
- params.asOf = options?.asOf ?? chunk5GZMPJOP_cjs.nowIso();
10370
+ params.asOf = options?.asOf ?? chunkWRWVWKQL_cjs.nowIso();
9732
10371
  }
9733
10372
  if (options?.limit !== void 0) params.limit = options.limit;
9734
10373
  if (options?.offset !== void 0) params.offset = options.offset;
@@ -9744,16 +10383,12 @@ function createNodeCollection(config) {
9744
10383
  temporalMode: mode
9745
10384
  };
9746
10385
  if (mode === "current" || mode === "asOf") {
9747
- params.asOf = options?.asOf ?? chunk5GZMPJOP_cjs.nowIso();
10386
+ params.asOf = options?.asOf ?? chunkWRWVWKQL_cjs.nowIso();
9748
10387
  }
9749
10388
  return backend.countNodesByKind(params);
9750
10389
  },
9751
10390
  async upsertById(id, props, options) {
9752
- return this.upsertByIdFromRecord(
9753
- id,
9754
- props,
9755
- options
9756
- );
10391
+ return this.upsertByIdFromRecord(id, props, options);
9757
10392
  },
9758
10393
  async upsertByIdFromRecord(id, data, options) {
9759
10394
  const existing = await backend.getNode(graphId, kind, id);
@@ -9773,10 +10408,7 @@ function createNodeCollection(config) {
9773
10408
  return narrowNode(result);
9774
10409
  },
9775
10410
  async bulkCreate(items) {
9776
- const batchInputs = mapBulkNodeInputs(
9777
- kind,
9778
- items
9779
- );
10411
+ const batchInputs = mapBulkNodeInputs(kind, items);
9780
10412
  if (backend.capabilities.transactions && "transaction" in backend) {
9781
10413
  const results2 = await backend.transaction(
9782
10414
  async (txBackend) => executeNodeCreateBatch2(batchInputs, txBackend)
@@ -9799,11 +10431,7 @@ function createNodeCollection(config) {
9799
10431
  if (row !== void 0) existingMap.set(row.id, row);
9800
10432
  }
9801
10433
  } else {
9802
- const rows = await target.getNodes(
9803
- graphId,
9804
- kind,
9805
- ids
9806
- );
10434
+ const rows = await target.getNodes(graphId, kind, ids);
9807
10435
  for (const row of rows) {
9808
10436
  existingMap.set(row.id, row);
9809
10437
  }
@@ -9816,22 +10444,13 @@ function createNodeCollection(config) {
9816
10444
  if (existing) {
9817
10445
  toUpdate.push({
9818
10446
  index: itemIndex,
9819
- input: buildUpdateInput(
9820
- kind,
9821
- item.id,
9822
- item.props,
9823
- item
9824
- ),
10447
+ input: buildUpdateInput(kind, item.id, item.props, item),
9825
10448
  clearDeleted: existing.deleted_at !== void 0
9826
10449
  });
9827
10450
  } else {
9828
10451
  toCreate.push({
9829
10452
  index: itemIndex,
9830
- input: buildCreateInput(
9831
- kind,
9832
- item.props,
9833
- item
9834
- )
10453
+ input: buildCreateInput(kind, item.props, item)
9835
10454
  });
9836
10455
  }
9837
10456
  itemIndex++;
@@ -9858,10 +10477,7 @@ function createNodeCollection(config) {
9858
10477
  return upsertAll(backend);
9859
10478
  },
9860
10479
  async bulkInsert(items) {
9861
- const batchInputs = mapBulkNodeInputs(
9862
- kind,
9863
- items
9864
- );
10480
+ const batchInputs = mapBulkNodeInputs(kind, items);
9865
10481
  if (backend.capabilities.transactions && "transaction" in backend) {
9866
10482
  await backend.transaction(async (txBackend) => {
9867
10483
  await executeNodeCreateNoReturnBatch2(batchInputs, txBackend);
@@ -10394,12 +11010,12 @@ async function validateAndPrepareEdgeCreate(ctx, input, id, backend) {
10394
11010
  nodeId: input.toId
10395
11011
  });
10396
11012
  }
10397
- const validatedProps = chunkX33WV2UF_cjs.validateEdgeProps(edgeKind.schema, input.props, {
11013
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateEdgeProps(edgeKind.schema, input.props, {
10398
11014
  kind,
10399
11015
  operation: "create"
10400
11016
  });
10401
- const validFrom = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validFrom, "validFrom");
10402
- const validTo = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validTo, "validTo");
11017
+ const validFrom = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validFrom, "validFrom");
11018
+ const validTo = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validTo, "validTo");
10403
11019
  const cardinality = registration.cardinality ?? "many";
10404
11020
  const constraintContext = {
10405
11021
  graphId: ctx.graphId,
@@ -10544,12 +11160,12 @@ async function executeEdgeUpdate(ctx, input, backend) {
10544
11160
  const edgeKind = registration.type;
10545
11161
  const existingProps = JSON.parse(existing.props);
10546
11162
  const mergedProps = { ...existingProps, ...input.props };
10547
- const validatedProps = chunkX33WV2UF_cjs.validateEdgeProps(edgeKind.schema, mergedProps, {
11163
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateEdgeProps(edgeKind.schema, mergedProps, {
10548
11164
  kind: existing.kind,
10549
11165
  operation: "update",
10550
11166
  id
10551
11167
  });
10552
- const validTo = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validTo, "validTo");
11168
+ const validTo = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validTo, "validTo");
10553
11169
  const updateParams = {
10554
11170
  graphId: ctx.graphId,
10555
11171
  id,
@@ -10570,12 +11186,12 @@ async function executeEdgeUpsertUpdate(ctx, input, backend, options) {
10570
11186
  const edgeKind = registration.type;
10571
11187
  const existingProps = JSON.parse(existing.props);
10572
11188
  const mergedProps = { ...existingProps, ...input.props };
10573
- const validatedProps = chunkX33WV2UF_cjs.validateEdgeProps(edgeKind.schema, mergedProps, {
11189
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateEdgeProps(edgeKind.schema, mergedProps, {
10574
11190
  kind: existing.kind,
10575
11191
  operation: "update",
10576
11192
  id
10577
11193
  });
10578
- const validTo = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validTo, "validTo");
11194
+ const validTo = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validTo, "validTo");
10579
11195
  const updateParams = {
10580
11196
  graphId: ctx.graphId,
10581
11197
  id,
@@ -10727,7 +11343,7 @@ async function executeEdgeGetOrCreateByEndpoints(ctx, kind, fromKind, fromId, to
10727
11343
  const matchOn = options?.matchOn ?? [];
10728
11344
  const registration = getEdgeRegistration(ctx.graph, kind);
10729
11345
  const edgeKind = registration.type;
10730
- const validatedProps = chunkX33WV2UF_cjs.validateEdgeProps(edgeKind.schema, props, {
11346
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateEdgeProps(edgeKind.schema, props, {
10731
11347
  kind,
10732
11348
  operation: "create"
10733
11349
  });
@@ -10809,7 +11425,7 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
10809
11425
  validateMatchOnFields(edgeKind.schema, matchOn, kind);
10810
11426
  const validated = [];
10811
11427
  for (const item of items) {
10812
- const validatedProps = chunkX33WV2UF_cjs.validateEdgeProps(edgeKind.schema, item.props, {
11428
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateEdgeProps(edgeKind.schema, item.props, {
10813
11429
  kind,
10814
11430
  operation: "create"
10815
11431
  });
@@ -10967,15 +11583,19 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
10967
11583
  }
10968
11584
 
10969
11585
  // src/store/embedding-sync.ts
11586
+ var embeddingFieldsCache = /* @__PURE__ */ new WeakMap();
10970
11587
  function getEmbeddingFields(schema) {
10971
- if (schema.type !== "object") {
10972
- return [];
10973
- }
11588
+ const cached = embeddingFieldsCache.get(schema);
11589
+ if (cached) return cached;
11590
+ const fields = computeEmbeddingFields(schema);
11591
+ embeddingFieldsCache.set(schema, fields);
11592
+ return fields;
11593
+ }
11594
+ function computeEmbeddingFields(schema) {
11595
+ if (schema.type !== "object") return [];
10974
11596
  const def = schema.def;
10975
11597
  const shape = def.shape;
10976
- if (!shape) {
10977
- return [];
10978
- }
11598
+ if (!shape) return [];
10979
11599
  const fields = [];
10980
11600
  for (const [fieldPath, fieldSchema] of Object.entries(shape)) {
10981
11601
  const dimensions = getEmbeddingDimensionsFromField(fieldSchema);
@@ -10986,13 +11606,13 @@ function getEmbeddingFields(schema) {
10986
11606
  return fields;
10987
11607
  }
10988
11608
  function getEmbeddingDimensionsFromField(schema) {
10989
- const directDimensions = chunkP5CNM325_cjs.getEmbeddingDimensions(schema);
11609
+ const directDimensions = chunkUVZHTBZ5_cjs.getEmbeddingDimensions(schema);
10990
11610
  if (directDimensions !== void 0) {
10991
11611
  return directDimensions;
10992
11612
  }
10993
11613
  const unwrapped = unwrapToEmbedding(schema);
10994
11614
  if (unwrapped) {
10995
- return chunkP5CNM325_cjs.getEmbeddingDimensions(unwrapped);
11615
+ return chunkUVZHTBZ5_cjs.getEmbeddingDimensions(unwrapped);
10996
11616
  }
10997
11617
  return void 0;
10998
11618
  }
@@ -11000,7 +11620,7 @@ function unwrapToEmbedding(schema) {
11000
11620
  const type = schema.type;
11001
11621
  const def = schema.def;
11002
11622
  if ((type === "optional" || type === "nullable" || type === "default" || type === "readonly") && def.innerType) {
11003
- if (chunkP5CNM325_cjs.isEmbeddingSchema(def.innerType)) {
11623
+ if (chunkUVZHTBZ5_cjs.isEmbeddingSchema(def.innerType)) {
11004
11624
  return def.innerType;
11005
11625
  }
11006
11626
  return unwrapToEmbedding(def.innerType);
@@ -11062,6 +11682,101 @@ function isValidEmbeddingValue(value) {
11062
11682
  return value.every((n) => typeof n === "number" && Number.isFinite(n));
11063
11683
  }
11064
11684
 
11685
+ // src/store/fulltext-sync.ts
11686
+ var searchableFieldsCache = /* @__PURE__ */ new WeakMap();
11687
+ function getSearchableFields(schema) {
11688
+ const cached = searchableFieldsCache.get(schema);
11689
+ if (cached) return cached;
11690
+ const fields = computeSearchableFields(schema);
11691
+ searchableFieldsCache.set(schema, fields);
11692
+ return fields;
11693
+ }
11694
+ function computeSearchableFields(schema) {
11695
+ if (schema.type !== "object") return [];
11696
+ const def = schema.def;
11697
+ const shape = def.shape;
11698
+ if (!shape) return [];
11699
+ const fields = [];
11700
+ for (const [fieldPath, fieldSchema] of Object.entries(shape)) {
11701
+ const metadata = chunkUVZHTBZ5_cjs.getSearchableMetadata(fieldSchema);
11702
+ if (metadata !== void 0) {
11703
+ fields.push({ fieldPath, metadata });
11704
+ }
11705
+ }
11706
+ warnIfConflictingLanguages(fields);
11707
+ return fields;
11708
+ }
11709
+ function warnIfConflictingLanguages(fields) {
11710
+ if (fields.length < 2) return;
11711
+ const languages = new Set(fields.map((field2) => field2.metadata.language));
11712
+ if (languages.size < 2) return;
11713
+ if (typeof console === "undefined" || typeof console.warn !== "function") {
11714
+ return;
11715
+ }
11716
+ const fieldSummary = fields.map((field2) => `${field2.fieldPath}=${field2.metadata.language}`).join(", ");
11717
+ const winning = fields[0]?.metadata.language ?? chunkUVZHTBZ5_cjs.DEFAULT_SEARCHABLE_LANGUAGE;
11718
+ console.warn(
11719
+ `[typegraph] searchable() fields declare conflicting languages (${fieldSummary}). The first field's language ("${winning}") is recorded on the fulltext row; true multilingual indexing requires a dedicated node kind per language.`
11720
+ );
11721
+ }
11722
+ var FIELD_SEPARATOR = "\n";
11723
+ function resolveCombinedLanguage(fields) {
11724
+ return fields[0]?.metadata.language ?? chunkUVZHTBZ5_cjs.DEFAULT_SEARCHABLE_LANGUAGE;
11725
+ }
11726
+ function computeFulltextContent(schema, props) {
11727
+ const searchableFields = getSearchableFields(schema);
11728
+ if (searchableFields.length === 0) return void 0;
11729
+ const parts = [];
11730
+ for (const field2 of searchableFields) {
11731
+ const value = props[field2.fieldPath];
11732
+ if (typeof value === "string" && value.length > 0) {
11733
+ parts.push(value);
11734
+ }
11735
+ }
11736
+ if (parts.length === 0) return void 0;
11737
+ return {
11738
+ content: parts.join(FIELD_SEPARATOR),
11739
+ language: resolveCombinedLanguage(searchableFields)
11740
+ };
11741
+ }
11742
+ async function syncFulltext(ctx, schema, props) {
11743
+ const { backend } = ctx;
11744
+ if (!backend.upsertFulltext || !backend.deleteFulltext) {
11745
+ return;
11746
+ }
11747
+ const computed = computeFulltextContent(schema, props);
11748
+ if (computed !== void 0) {
11749
+ await backend.upsertFulltext({
11750
+ graphId: ctx.graphId,
11751
+ nodeKind: ctx.nodeKind,
11752
+ nodeId: ctx.nodeId,
11753
+ content: computed.content,
11754
+ language: computed.language
11755
+ });
11756
+ return;
11757
+ }
11758
+ if (getSearchableFields(schema).length === 0) return;
11759
+ await backend.deleteFulltext({
11760
+ graphId: ctx.graphId,
11761
+ nodeKind: ctx.nodeKind,
11762
+ nodeId: ctx.nodeId
11763
+ });
11764
+ }
11765
+ async function deleteNodeFulltext(ctx, schema) {
11766
+ const { backend } = ctx;
11767
+ if (!backend.deleteFulltext) {
11768
+ return;
11769
+ }
11770
+ if (getSearchableFields(schema).length === 0) {
11771
+ return;
11772
+ }
11773
+ await backend.deleteFulltext({
11774
+ graphId: ctx.graphId,
11775
+ nodeKind: ctx.nodeKind,
11776
+ nodeId: ctx.nodeId
11777
+ });
11778
+ }
11779
+
11065
11780
  // src/store/uniqueness.ts
11066
11781
  async function checkUniquenessConstraints(ctx, kind, id, props, constraints) {
11067
11782
  for (const constraint of constraints) {
@@ -11255,6 +11970,9 @@ function createUniquenessContext(graphId, registry, backend) {
11255
11970
  function createEmbeddingSyncContext(graphId, nodeKind, nodeId, backend) {
11256
11971
  return { graphId, nodeKind, nodeId, backend };
11257
11972
  }
11973
+ function createFulltextSyncContext(graphId, nodeKind, nodeId, backend) {
11974
+ return { graphId, nodeKind, nodeId, backend };
11975
+ }
11258
11976
  function createNodeBatchValidationBackend(graphId, registry, backend) {
11259
11977
  const nodeCache = /* @__PURE__ */ new Map();
11260
11978
  const pendingNodes = /* @__PURE__ */ new Map();
@@ -11354,12 +12072,12 @@ async function validateAndPrepareNodeCreate(ctx, input, id, backend) {
11354
12072
  const kind = input.kind;
11355
12073
  const registration = getNodeRegistration(ctx.graph, kind);
11356
12074
  const nodeKind = registration.type;
11357
- const validatedProps = chunkX33WV2UF_cjs.validateNodeProps(nodeKind.schema, input.props, {
12075
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateNodeProps(nodeKind.schema, input.props, {
11358
12076
  kind,
11359
12077
  operation: "create"
11360
12078
  });
11361
- const validFrom = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validFrom, "validFrom");
11362
- const validTo = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validTo, "validTo");
12079
+ const validFrom = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validFrom, "validFrom");
12080
+ const validTo = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validTo, "validTo");
11363
12081
  const existingNode = await backend.getNode(ctx.graphId, kind, id);
11364
12082
  if (existingNode && !existingNode.deleted_at) {
11365
12083
  throw createNodeAlreadyExistsError(kind, id);
@@ -11412,6 +12130,11 @@ async function finalizeNodeCreate(ctx, prepared, backend) {
11412
12130
  prepared.nodeKind.schema,
11413
12131
  prepared.validatedProps
11414
12132
  );
12133
+ await syncFulltext(
12134
+ createFulltextSyncContext(ctx.graphId, prepared.kind, prepared.id, backend),
12135
+ prepared.nodeKind.schema,
12136
+ prepared.validatedProps
12137
+ );
11415
12138
  }
11416
12139
  async function performNodeUpdate(ctx, input, backend, options) {
11417
12140
  const { kind, id } = input;
@@ -11423,12 +12146,12 @@ async function performNodeUpdate(ctx, input, backend, options) {
11423
12146
  const existingProps = JSON.parse(existing.props);
11424
12147
  const mergedProps = { ...existingProps, ...input.props };
11425
12148
  const nodeKind = registration.type;
11426
- const validatedProps = chunkX33WV2UF_cjs.validateNodeProps(nodeKind.schema, mergedProps, {
12149
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateNodeProps(nodeKind.schema, mergedProps, {
11427
12150
  kind,
11428
12151
  operation: "update",
11429
12152
  id
11430
12153
  });
11431
- const validTo = chunk5GZMPJOP_cjs.validateOptionalIsoDate(input.validTo, "validTo");
12154
+ const validTo = chunkWRWVWKQL_cjs.validateOptionalIsoDate(input.validTo, "validTo");
11432
12155
  await updateUniquenessEntries(
11433
12156
  createUniquenessContext(ctx.graphId, ctx.registry, backend),
11434
12157
  kind,
@@ -11452,6 +12175,11 @@ async function performNodeUpdate(ctx, input, backend, options) {
11452
12175
  nodeKind.schema,
11453
12176
  validatedProps
11454
12177
  );
12178
+ await syncFulltext(
12179
+ createFulltextSyncContext(ctx.graphId, kind, id, backend),
12180
+ nodeKind.schema,
12181
+ validatedProps
12182
+ );
11455
12183
  return rowToNode(row);
11456
12184
  }
11457
12185
  async function enforceDeleteBehavior(ctx, kind, id, mode, backend, registration) {
@@ -11668,6 +12396,10 @@ async function executeNodeDelete(ctx, kind, id, backend) {
11668
12396
  createEmbeddingSyncContext(ctx.graphId, kind, id, backend),
11669
12397
  nodeKind.schema
11670
12398
  );
12399
+ await deleteNodeFulltext(
12400
+ createFulltextSyncContext(ctx.graphId, kind, id, backend),
12401
+ nodeKind.schema
12402
+ );
11671
12403
  });
11672
12404
  }
11673
12405
  async function executeNodeHardDelete(ctx, kind, id, backend) {
@@ -11687,7 +12419,7 @@ async function executeNodeGetOrCreateByConstraint(ctx, kind, constraintName, pro
11687
12419
  const ifExists = options?.ifExists ?? "return";
11688
12420
  const registration = getNodeRegistration(ctx.graph, kind);
11689
12421
  const nodeKind = registration.type;
11690
- const validatedProps = chunkX33WV2UF_cjs.validateNodeProps(nodeKind.schema, props, {
12422
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateNodeProps(nodeKind.schema, props, {
11691
12423
  kind,
11692
12424
  operation: "create"
11693
12425
  });
@@ -11759,7 +12491,7 @@ async function executeNodeGetOrCreateByConstraint(ctx, kind, constraintName, pro
11759
12491
  async function executeNodeFindByConstraint(ctx, kind, constraintName, props, backend) {
11760
12492
  const registration = getNodeRegistration(ctx.graph, kind);
11761
12493
  const nodeKind = registration.type;
11762
- const validatedProps = chunkX33WV2UF_cjs.validateNodeProps(nodeKind.schema, props, {
12494
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateNodeProps(nodeKind.schema, props, {
11763
12495
  kind,
11764
12496
  operation: "create"
11765
12497
  });
@@ -11796,7 +12528,7 @@ async function executeNodeFindByConstraint(ctx, kind, constraintName, props, bac
11796
12528
  function validateAndComputeKeys(nodeKind, kind, constraint, items) {
11797
12529
  const validated = [];
11798
12530
  for (const item of items) {
11799
- const validatedProps = chunkX33WV2UF_cjs.validateNodeProps(nodeKind.schema, item.props, {
12531
+ const validatedProps = chunkHRNQ3DVJ_cjs.validateNodeProps(nodeKind.schema, item.props, {
11800
12532
  kind,
11801
12533
  operation: "create"
11802
12534
  });
@@ -11974,6 +12706,503 @@ async function executeNodeBulkGetOrCreateByConstraint(ctx, kind, constraintName,
11974
12706
  return results;
11975
12707
  }
11976
12708
 
12709
+ // src/store/fulltext-rebuild.ts
12710
+ var DEFAULT_PAGE_SIZE = 500;
12711
+ var DEFAULT_MAX_SKIPPED_IDS = 1e4;
12712
+ function validatePageSize(value) {
12713
+ if (value === void 0) return DEFAULT_PAGE_SIZE;
12714
+ if (!Number.isInteger(value) || value <= 0) {
12715
+ throw new chunkO5PST6TB_cjs.ValidationError(
12716
+ `pageSize must be a positive integer, got: ${String(value)}`,
12717
+ {
12718
+ issues: [{ path: "pageSize", message: "Must be a positive integer." }]
12719
+ }
12720
+ );
12721
+ }
12722
+ return value;
12723
+ }
12724
+ function validateMaxSkippedIds(value) {
12725
+ if (value === void 0) return DEFAULT_MAX_SKIPPED_IDS;
12726
+ if (!Number.isInteger(value) || value < 0) {
12727
+ throw new chunkO5PST6TB_cjs.ValidationError(
12728
+ `maxSkippedIds must be a non-negative integer, got: ${String(value)}`,
12729
+ {
12730
+ issues: [
12731
+ {
12732
+ path: "maxSkippedIds",
12733
+ message: "Must be a non-negative integer."
12734
+ }
12735
+ ]
12736
+ }
12737
+ );
12738
+ }
12739
+ return value;
12740
+ }
12741
+ function isPropsObject(value) {
12742
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12743
+ }
12744
+ async function rebuildFulltextIndex(ctx, nodeKind, options) {
12745
+ const { backend, registry } = ctx;
12746
+ if (!backend.upsertFulltext || !backend.deleteFulltext) {
12747
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
12748
+ "Backend does not support fulltext; cannot rebuild index",
12749
+ { backend: backend.dialect, capability: "fulltext" }
12750
+ );
12751
+ }
12752
+ const pageSize = validatePageSize(options.pageSize);
12753
+ const maxSkippedIds = validateMaxSkippedIds(options.maxSkippedIds);
12754
+ const targetKinds = nodeKind === void 0 ? [...registry.nodeKinds.keys()] : [nodeKind];
12755
+ const rebuiltKinds = [];
12756
+ let processed = 0;
12757
+ let upserted = 0;
12758
+ let cleared = 0;
12759
+ let skipped = 0;
12760
+ const skippedIds = [];
12761
+ for (const kind of targetKinds) {
12762
+ const nodeType = registry.getNodeType(kind);
12763
+ if (!nodeType) {
12764
+ throw new chunkO5PST6TB_cjs.ConfigurationError(`Unknown node kind: ${kind}`, { kind });
12765
+ }
12766
+ if (getSearchableFields(nodeType.schema).length === 0) {
12767
+ continue;
12768
+ }
12769
+ rebuiltKinds.push(kind);
12770
+ let cursor;
12771
+ for (; ; ) {
12772
+ const rows = await backend.findNodesByKind({
12773
+ graphId: ctx.graphId,
12774
+ kind,
12775
+ limit: pageSize,
12776
+ excludeDeleted: false,
12777
+ orderBy: "id",
12778
+ ...cursor === void 0 ? {} : { after: cursor }
12779
+ });
12780
+ if (rows.length === 0) break;
12781
+ const pageResult = processPage(nodeType.schema, rows);
12782
+ skipped += pageResult.skipped;
12783
+ const remaining = maxSkippedIds - skippedIds.length;
12784
+ if (remaining > 0 && pageResult.skippedIds.length > 0) {
12785
+ skippedIds.push(...pageResult.skippedIds.slice(0, remaining));
12786
+ }
12787
+ await backend.transaction(async (tx) => {
12788
+ if (pageResult.toUpsert.length > 0) {
12789
+ if (tx.upsertFulltextBatch) {
12790
+ await tx.upsertFulltextBatch({
12791
+ graphId: ctx.graphId,
12792
+ nodeKind: kind,
12793
+ rows: pageResult.toUpsert
12794
+ });
12795
+ } else if (tx.upsertFulltext) {
12796
+ for (const item of pageResult.toUpsert) {
12797
+ await tx.upsertFulltext({
12798
+ graphId: ctx.graphId,
12799
+ nodeKind: kind,
12800
+ nodeId: item.nodeId,
12801
+ content: item.content,
12802
+ language: item.language
12803
+ });
12804
+ }
12805
+ }
12806
+ }
12807
+ if (pageResult.toDelete.length > 0) {
12808
+ if (tx.deleteFulltextBatch) {
12809
+ await tx.deleteFulltextBatch({
12810
+ graphId: ctx.graphId,
12811
+ nodeKind: kind,
12812
+ nodeIds: pageResult.toDelete
12813
+ });
12814
+ } else if (tx.deleteFulltext) {
12815
+ for (const nodeId of pageResult.toDelete) {
12816
+ await tx.deleteFulltext({
12817
+ graphId: ctx.graphId,
12818
+ nodeKind: kind,
12819
+ nodeId
12820
+ });
12821
+ }
12822
+ }
12823
+ }
12824
+ });
12825
+ processed += rows.length;
12826
+ upserted += pageResult.toUpsert.length;
12827
+ cleared += pageResult.toDelete.length;
12828
+ const lastRow = rows.at(-1);
12829
+ if (!lastRow) break;
12830
+ cursor = lastRow.id;
12831
+ if (rows.length < pageSize) break;
12832
+ }
12833
+ }
12834
+ return {
12835
+ kinds: rebuiltKinds,
12836
+ processed,
12837
+ upserted,
12838
+ cleared,
12839
+ skipped,
12840
+ skippedIds,
12841
+ skippedTruncated: skipped > skippedIds.length
12842
+ };
12843
+ }
12844
+ function processPage(schema, rows) {
12845
+ const toUpsert = [];
12846
+ const toDelete = [];
12847
+ const skippedIds = [];
12848
+ let skipped = 0;
12849
+ for (const row of rows) {
12850
+ if (row.deleted_at !== void 0) {
12851
+ toDelete.push(row.id);
12852
+ continue;
12853
+ }
12854
+ let parsed;
12855
+ try {
12856
+ parsed = JSON.parse(row.props);
12857
+ } catch {
12858
+ skipped += 1;
12859
+ skippedIds.push(row.id);
12860
+ continue;
12861
+ }
12862
+ if (!isPropsObject(parsed)) {
12863
+ skipped += 1;
12864
+ skippedIds.push(row.id);
12865
+ continue;
12866
+ }
12867
+ const computed = computeFulltextContent(schema, parsed);
12868
+ if (computed === void 0) {
12869
+ toDelete.push(row.id);
12870
+ } else {
12871
+ toUpsert.push({
12872
+ nodeId: row.id,
12873
+ content: computed.content,
12874
+ language: computed.language
12875
+ });
12876
+ }
12877
+ }
12878
+ return { toUpsert, toDelete, skipped, skippedIds };
12879
+ }
12880
+
12881
+ // src/store/search.ts
12882
+ async function executeFulltextSearch(ctx, nodeKind, options) {
12883
+ const { backend, graphId } = ctx;
12884
+ if (!backend.fulltextSearch) {
12885
+ throw new chunkO5PST6TB_cjs.ConfigurationError("Backend does not support fulltext search", {
12886
+ backend: backend.dialect,
12887
+ capability: "fulltext"
12888
+ });
12889
+ }
12890
+ if (!Number.isInteger(options.limit) || options.limit <= 0) {
12891
+ throw new RangeError(
12892
+ `fulltextSearch.limit must be a positive integer, got: ${options.limit}`
12893
+ );
12894
+ }
12895
+ validateFulltextCallOptions(backend, {
12896
+ mode: options.mode,
12897
+ includeSnippets: options.includeSnippets,
12898
+ language: options.language
12899
+ });
12900
+ const params = {
12901
+ graphId,
12902
+ nodeKind,
12903
+ query: options.query,
12904
+ limit: options.limit,
12905
+ ...options.mode ? { mode: options.mode } : {},
12906
+ ...options.language ? { language: options.language } : {},
12907
+ ...options.minScore === void 0 ? {} : { minScore: options.minScore },
12908
+ ...options.includeSnippets === void 0 ? {} : { includeSnippets: options.includeSnippets }
12909
+ };
12910
+ const rows = await backend.fulltextSearch(params);
12911
+ if (rows.length === 0) return [];
12912
+ const ids = rows.map((r) => r.nodeId);
12913
+ const nodeMap = await fetchNodesByIds(backend, graphId, nodeKind, ids);
12914
+ const hits = [];
12915
+ let rank = 1;
12916
+ for (const row of rows) {
12917
+ const node = nodeMap.get(row.nodeId);
12918
+ if (!node) continue;
12919
+ hits.push({
12920
+ node,
12921
+ score: row.score,
12922
+ rank,
12923
+ ...row.snippet === void 0 ? {} : { snippet: row.snippet }
12924
+ });
12925
+ rank += 1;
12926
+ }
12927
+ return hits;
12928
+ }
12929
+ async function executeHybridSearch(ctx, nodeKind, options) {
12930
+ const { backend, graphId } = ctx;
12931
+ if (!backend.vectorSearch) {
12932
+ throw new chunkO5PST6TB_cjs.ConfigurationError("Backend does not support vector search", {
12933
+ backend: backend.dialect,
12934
+ capability: "vector"
12935
+ });
12936
+ }
12937
+ if (!backend.fulltextSearch) {
12938
+ throw new chunkO5PST6TB_cjs.ConfigurationError("Backend does not support fulltext search", {
12939
+ backend: backend.dialect,
12940
+ capability: "fulltext"
12941
+ });
12942
+ }
12943
+ if (!Number.isInteger(options.limit) || options.limit <= 0) {
12944
+ throw new RangeError(
12945
+ `hybridSearch.limit must be a positive integer, got: ${options.limit}`
12946
+ );
12947
+ }
12948
+ if (options.fusion !== void 0) {
12949
+ validateHybridFusionOptions(options.fusion);
12950
+ }
12951
+ validateFulltextCallOptions(backend, {
12952
+ mode: options.fulltext.mode,
12953
+ includeSnippets: options.fulltext.includeSnippets,
12954
+ language: options.fulltext.language
12955
+ });
12956
+ const fusionK = options.fusion?.k ?? DEFAULT_RRF_K;
12957
+ const vectorWeight = options.fusion?.weights?.vector ?? DEFAULT_RRF_WEIGHT;
12958
+ const fulltextWeight = options.fusion?.weights?.fulltext ?? DEFAULT_RRF_WEIGHT;
12959
+ const overFetchMultiplier = 4;
12960
+ const vectorK = options.vector.k ?? options.limit * overFetchMultiplier;
12961
+ const fulltextK = options.fulltext.k ?? options.limit * overFetchMultiplier;
12962
+ const vectorPromise = backend.vectorSearch({
12963
+ graphId,
12964
+ nodeKind,
12965
+ fieldPath: options.vector.fieldPath,
12966
+ queryEmbedding: options.vector.queryEmbedding,
12967
+ metric: options.vector.metric ?? "cosine",
12968
+ limit: vectorK,
12969
+ ...options.vector.minScore === void 0 ? {} : { minScore: options.vector.minScore }
12970
+ });
12971
+ const fulltextPromise = backend.fulltextSearch({
12972
+ graphId,
12973
+ nodeKind,
12974
+ query: options.fulltext.query,
12975
+ limit: fulltextK,
12976
+ ...options.fulltext.mode ? { mode: options.fulltext.mode } : {},
12977
+ ...options.fulltext.language ? { language: options.fulltext.language } : {},
12978
+ ...options.fulltext.minScore === void 0 ? {} : { minScore: options.fulltext.minScore },
12979
+ ...options.fulltext.includeSnippets === void 0 ? {} : { includeSnippets: options.fulltext.includeSnippets }
12980
+ });
12981
+ const [vectorRows, fulltextRows] = await Promise.all([
12982
+ vectorPromise,
12983
+ fulltextPromise
12984
+ ]);
12985
+ const fused = /* @__PURE__ */ new Map();
12986
+ for (const [index, row] of vectorRows.entries()) {
12987
+ const rank2 = index + 1;
12988
+ const contribution = vectorWeight / (fusionK + rank2);
12989
+ const entry = fused.get(row.nodeId) ?? {
12990
+ nodeId: row.nodeId,
12991
+ fusedScore: 0
12992
+ };
12993
+ entry.fusedScore += contribution;
12994
+ entry.vectorRank = rank2;
12995
+ entry.vectorScore = row.score;
12996
+ fused.set(row.nodeId, entry);
12997
+ }
12998
+ for (const [index, row] of fulltextRows.entries()) {
12999
+ const rank2 = index + 1;
13000
+ const contribution = fulltextWeight / (fusionK + rank2);
13001
+ const entry = fused.get(row.nodeId) ?? {
13002
+ nodeId: row.nodeId,
13003
+ fusedScore: 0
13004
+ };
13005
+ entry.fusedScore += contribution;
13006
+ entry.fulltextRank = rank2;
13007
+ entry.fulltextScore = row.score;
13008
+ if (row.snippet !== void 0) {
13009
+ entry.fulltextSnippet = row.snippet;
13010
+ }
13011
+ fused.set(row.nodeId, entry);
13012
+ }
13013
+ const ranked = [...fused.values()].toSorted(
13014
+ (a, b) => b.fusedScore - a.fusedScore || a.nodeId.localeCompare(b.nodeId)
13015
+ ).slice(0, options.limit);
13016
+ const ids = ranked.map((entry) => entry.nodeId);
13017
+ const nodeMap = await fetchNodesByIds(backend, graphId, nodeKind, ids);
13018
+ const hits = [];
13019
+ let rank = 1;
13020
+ for (const entry of ranked) {
13021
+ const node = nodeMap.get(entry.nodeId);
13022
+ if (!node) continue;
13023
+ const typedNode = node;
13024
+ const hit = {
13025
+ node: typedNode,
13026
+ score: entry.fusedScore,
13027
+ rank,
13028
+ ...entry.vectorRank !== void 0 && entry.vectorScore !== void 0 ? {
13029
+ vector: {
13030
+ node: typedNode,
13031
+ score: entry.vectorScore,
13032
+ rank: entry.vectorRank
13033
+ }
13034
+ } : {},
13035
+ ...entry.fulltextRank !== void 0 && entry.fulltextScore !== void 0 ? {
13036
+ fulltext: {
13037
+ node: typedNode,
13038
+ score: entry.fulltextScore,
13039
+ rank: entry.fulltextRank,
13040
+ ...entry.fulltextSnippet === void 0 ? {} : { snippet: entry.fulltextSnippet }
13041
+ }
13042
+ } : {}
13043
+ };
13044
+ hits.push(hit);
13045
+ rank += 1;
13046
+ }
13047
+ return hits;
13048
+ }
13049
+ function validateFulltextCallOptions(backend, options) {
13050
+ const strategy = backend.fulltextStrategy;
13051
+ const capabilities = backend.capabilities.fulltext;
13052
+ if (options.mode !== void 0) {
13053
+ validateFulltextMode(backend, strategy, capabilities, options.mode);
13054
+ }
13055
+ if (options.includeSnippets === true) {
13056
+ validateFulltextSnippets(backend, strategy, capabilities);
13057
+ }
13058
+ if (options.language !== void 0) {
13059
+ validateFulltextLanguageOption(
13060
+ backend,
13061
+ strategy,
13062
+ capabilities,
13063
+ options.language
13064
+ );
13065
+ }
13066
+ }
13067
+ function validateFulltextMode(backend, strategy, capabilities, mode) {
13068
+ if (strategy !== void 0) {
13069
+ if (strategy.supportedModes.includes(mode)) return;
13070
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
13071
+ `Backend "${backend.dialect}" fulltext strategy "${strategy.name}" does not support mode "${mode}". Supported modes: ${strategy.supportedModes.join(", ")}`,
13072
+ {
13073
+ backend: backend.dialect,
13074
+ strategy: strategy.name,
13075
+ capability: `fulltext.modes.${mode}`
13076
+ }
13077
+ );
13078
+ }
13079
+ if (mode === "phrase" && capabilities && !capabilities.phraseQueries) {
13080
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
13081
+ `Backend "${backend.dialect}" does not support phrase queries`,
13082
+ { backend: backend.dialect, capability: "fulltext.phraseQueries" }
13083
+ );
13084
+ }
13085
+ }
13086
+ function validateFulltextSnippets(backend, strategy, capabilities) {
13087
+ const supportsSnippets = strategy === void 0 ? capabilities?.highlighting !== false : strategy.supportsSnippets;
13088
+ if (supportsSnippets) return;
13089
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
13090
+ `Backend "${backend.dialect}" does not support snippet highlighting; drop includeSnippets or switch to a backend where highlighting is supported`,
13091
+ { backend: backend.dialect, capability: "fulltext.highlighting" }
13092
+ );
13093
+ }
13094
+ function validateFulltextLanguageOption(backend, strategy, capabilities, language) {
13095
+ if (strategy !== void 0 && !strategy.supportsLanguageOverride) {
13096
+ throw new chunkO5PST6TB_cjs.ConfigurationError(
13097
+ `Backend "${backend.dialect}" fulltext strategy "${strategy.name}" does not honor a per-query \`language\` override (its tokenizer is fixed at table-create time). Drop the option, or pick a strategy that advertises \`supportsLanguageOverride: true\`.`,
13098
+ {
13099
+ backend: backend.dialect,
13100
+ strategy: strategy.name,
13101
+ capability: "fulltext.languageOverride"
13102
+ }
13103
+ );
13104
+ }
13105
+ if (capabilities !== void 0) {
13106
+ warnIfLanguageNotAdvertised(backend.dialect, capabilities, language);
13107
+ }
13108
+ }
13109
+ function warnIfLanguageNotAdvertised(dialect, capabilities, language) {
13110
+ if (capabilities.languages.length === 0) return;
13111
+ if (capabilities.languages.includes(language)) return;
13112
+ if (typeof console === "undefined" || typeof console.warn !== "function") {
13113
+ return;
13114
+ }
13115
+ console.warn(
13116
+ `[typegraph] fulltext language "${language}" is not in the advertised list for dialect "${dialect}" (${capabilities.languages.join(", ")}). If your backend has this language installed this warning is safe to ignore.`
13117
+ );
13118
+ }
13119
+ async function fetchNodesByIds(backend, graphId, nodeKind, ids) {
13120
+ if (ids.length === 0) return /* @__PURE__ */ new Map();
13121
+ const map = /* @__PURE__ */ new Map();
13122
+ if (backend.getNodes) {
13123
+ const rows = await backend.getNodes(graphId, nodeKind, ids);
13124
+ for (const row of rows) {
13125
+ if (row.deleted_at !== void 0) continue;
13126
+ map.set(row.id, rowToNode(row));
13127
+ }
13128
+ return map;
13129
+ }
13130
+ for (const id of ids) {
13131
+ const row = await backend.getNode(graphId, nodeKind, id);
13132
+ if (row && row.deleted_at === void 0) map.set(row.id, rowToNode(row));
13133
+ }
13134
+ return map;
13135
+ }
13136
+
13137
+ // src/store/search-facade.ts
13138
+ var StoreSearch = class {
13139
+ #context;
13140
+ constructor(context) {
13141
+ this.#context = context;
13142
+ }
13143
+ /**
13144
+ * Runs a fulltext search against nodes of the given kind.
13145
+ *
13146
+ * Requires fields on the node schema declared with `searchable()`.
13147
+ * The search hits the backend's fulltext index (tsvector + GIN on
13148
+ * Postgres, FTS5 on SQLite — or whatever strategy the backend is
13149
+ * configured with) and resolves the matching node IDs back to typed
13150
+ * `Node` objects.
13151
+ */
13152
+ async fulltext(nodeKind, options) {
13153
+ return executeFulltextSearch(
13154
+ { graphId: this.#context.graphId, backend: this.#context.backend },
13155
+ nodeKind,
13156
+ options
13157
+ );
13158
+ }
13159
+ /**
13160
+ * Runs a vector + fulltext hybrid search and fuses the results with
13161
+ * Reciprocal Rank Fusion.
13162
+ *
13163
+ * RRF is rank-based, so it composes well across heterogeneous score
13164
+ * scales (cosine similarity vs ts_rank_cd vs FTS5 BM25). Default
13165
+ * over-fetch is 4× `limit` from each source — tune via `vector.k` /
13166
+ * `fulltext.k` for higher-recall corpora.
13167
+ */
13168
+ async hybrid(nodeKind, options) {
13169
+ return executeHybridSearch(
13170
+ { graphId: this.#context.graphId, backend: this.#context.backend },
13171
+ nodeKind,
13172
+ options
13173
+ );
13174
+ }
13175
+ /**
13176
+ * Rebuilds the fulltext index from existing node data.
13177
+ *
13178
+ * Use when:
13179
+ * - A node kind gained a `searchable()` field after data was already
13180
+ * written and existing rows were never indexed.
13181
+ * - The fulltext table was dropped / truncated.
13182
+ * - `language` was changed on a `searchable()` field.
13183
+ *
13184
+ * Iterates nodes with keyset pagination (stable under shared
13185
+ * timestamps and light concurrent writes), transacts per page, skips
13186
+ * kinds with no searchable fields, and cleans up stale rows for
13187
+ * soft-deleted nodes. Corrupt or non-object `props` are counted in
13188
+ * `skipped` with their node IDs surfaced via `skippedIds` so operators
13189
+ * can investigate. Concurrent hard-deletes between page fetches can
13190
+ * be missed by a single pass — run during a maintenance window for
13191
+ * full consistency.
13192
+ */
13193
+ async rebuildFulltext(nodeKind, options = {}) {
13194
+ return rebuildFulltextIndex(
13195
+ {
13196
+ graphId: this.#context.graphId,
13197
+ backend: this.#context.backend,
13198
+ registry: this.#context.registry
13199
+ },
13200
+ nodeKind,
13201
+ options
13202
+ );
13203
+ }
13204
+ };
13205
+
11977
13206
  // src/store/store.ts
11978
13207
  var Store = class {
11979
13208
  #graph;
@@ -11985,6 +13214,7 @@ var Store = class {
11985
13214
  #nodeCollections;
11986
13215
  #edgeCollections;
11987
13216
  #algorithms;
13217
+ #search;
11988
13218
  constructor(graph, backend, options) {
11989
13219
  this.#graph = graph;
11990
13220
  this.#backend = backend;
@@ -12233,6 +13463,44 @@ var Store = class {
12233
13463
  query() {
12234
13464
  return this.#createQueryForBackend(this.#backend);
12235
13465
  }
13466
+ // === Search ===
13467
+ /**
13468
+ * Search-related operations (fulltext, hybrid, and rebuild).
13469
+ *
13470
+ * All search methods live under this facade to keep the top-level
13471
+ * Store API focused on CRUD + graph traversal. The facade is
13472
+ * lazy-initialized and cached for the lifetime of the store.
13473
+ *
13474
+ * @example
13475
+ * ```typescript
13476
+ * // Fulltext
13477
+ * const hits = await store.search.fulltext("Document", {
13478
+ * query: "climate warming",
13479
+ * limit: 10,
13480
+ * includeSnippets: true,
13481
+ * });
13482
+ *
13483
+ * // Hybrid (vector + fulltext fused with RRF)
13484
+ * const ranked = await store.search.hybrid("Document", {
13485
+ * limit: 10,
13486
+ * vector: { fieldPath: "embedding", queryEmbedding: vec },
13487
+ * fulltext: { query: "climate warming" },
13488
+ * });
13489
+ *
13490
+ * // Rebuild index after schema change
13491
+ * const stats = await store.search.rebuildFulltext();
13492
+ * ```
13493
+ */
13494
+ get search() {
13495
+ if (this.#search === void 0) {
13496
+ this.#search = new StoreSearch({
13497
+ graphId: this.graphId,
13498
+ backend: this.#backend,
13499
+ registry: this.#registry
13500
+ });
13501
+ }
13502
+ return this.#search;
13503
+ }
12236
13504
  // === Batch Query Execution ===
12237
13505
  /**
12238
13506
  * Executes multiple queries over a single connection with snapshot consistency.
@@ -12306,7 +13574,7 @@ var Store = class {
12306
13574
  graphId: this.graphId,
12307
13575
  rootId,
12308
13576
  backend: this.#backend,
12309
- dialect: chunkMME3H4ZF_cjs.getDialect(this.#backend.dialect),
13577
+ dialect: chunk3JK66E3I_cjs.getDialect(this.#backend.dialect),
12310
13578
  schema: this.#schema,
12311
13579
  options
12312
13580
  });
@@ -12377,6 +13645,7 @@ var Store = class {
12377
13645
  this.#nodeCollections = void 0;
12378
13646
  this.#edgeCollections = void 0;
12379
13647
  this.#algorithms = void 0;
13648
+ this.#search = void 0;
12380
13649
  }
12381
13650
  // === Lifecycle ===
12382
13651
  /**
@@ -12446,7 +13715,7 @@ var Store = class {
12446
13715
  // === Internal: Temporal Filtering ===
12447
13716
  #matchesTemporalMode(row, options) {
12448
13717
  const mode = options?.temporalMode ?? this.#graph.defaults.temporalMode;
12449
- const asOf = options?.asOf ?? chunk5GZMPJOP_cjs.nowIso();
13718
+ const asOf = options?.asOf ?? chunkWRWVWKQL_cjs.nowIso();
12450
13719
  switch (mode) {
12451
13720
  case "current":
12452
13721
  case "asOf": {
@@ -12479,41 +13748,41 @@ function createStore(graph, backend, options) {
12479
13748
  }
12480
13749
  async function createStoreWithSchema(graph, backend, options) {
12481
13750
  const store = createStore(graph, backend, options);
12482
- const result = await chunkX33WV2UF_cjs.ensureSchema(backend, graph, options);
13751
+ const result = await chunkHRNQ3DVJ_cjs.ensureSchema(backend, graph, options);
12483
13752
  return [store, result];
12484
13753
  }
12485
13754
 
12486
13755
  Object.defineProperty(exports, "isMetaEdge", {
12487
13756
  enumerable: true,
12488
- get: function () { return chunkX33WV2UF_cjs.isMetaEdge; }
13757
+ get: function () { return chunkHRNQ3DVJ_cjs.isMetaEdge; }
12489
13758
  });
12490
13759
  Object.defineProperty(exports, "defineGraph", {
12491
13760
  enumerable: true,
12492
- get: function () { return chunk5GZMPJOP_cjs.defineGraph; }
13761
+ get: function () { return chunkWRWVWKQL_cjs.defineGraph; }
12493
13762
  });
12494
13763
  Object.defineProperty(exports, "getEdgeKinds", {
12495
13764
  enumerable: true,
12496
- get: function () { return chunk5GZMPJOP_cjs.getEdgeKinds; }
13765
+ get: function () { return chunkWRWVWKQL_cjs.getEdgeKinds; }
12497
13766
  });
12498
13767
  Object.defineProperty(exports, "getNodeKinds", {
12499
13768
  enumerable: true,
12500
- get: function () { return chunk5GZMPJOP_cjs.getNodeKinds; }
13769
+ get: function () { return chunkWRWVWKQL_cjs.getNodeKinds; }
12501
13770
  });
12502
13771
  Object.defineProperty(exports, "isEdgeType", {
12503
13772
  enumerable: true,
12504
- get: function () { return chunk5GZMPJOP_cjs.isEdgeType; }
13773
+ get: function () { return chunkWRWVWKQL_cjs.isEdgeType; }
12505
13774
  });
12506
13775
  Object.defineProperty(exports, "isEdgeTypeWithEndpoints", {
12507
13776
  enumerable: true,
12508
- get: function () { return chunk5GZMPJOP_cjs.isEdgeTypeWithEndpoints; }
13777
+ get: function () { return chunkWRWVWKQL_cjs.isEdgeTypeWithEndpoints; }
12509
13778
  });
12510
13779
  Object.defineProperty(exports, "isGraphDef", {
12511
13780
  enumerable: true,
12512
- get: function () { return chunk5GZMPJOP_cjs.isGraphDef; }
13781
+ get: function () { return chunkWRWVWKQL_cjs.isGraphDef; }
12513
13782
  });
12514
13783
  Object.defineProperty(exports, "isNodeType", {
12515
13784
  enumerable: true,
12516
- get: function () { return chunk5GZMPJOP_cjs.isNodeType; }
13785
+ get: function () { return chunkWRWVWKQL_cjs.isNodeType; }
12517
13786
  });
12518
13787
  Object.defineProperty(exports, "BackendDisposedError", {
12519
13788
  enumerable: true,
@@ -12615,37 +13884,61 @@ Object.defineProperty(exports, "isUserRecoverable", {
12615
13884
  enumerable: true,
12616
13885
  get: function () { return chunkO5PST6TB_cjs.isUserRecoverable; }
12617
13886
  });
13887
+ Object.defineProperty(exports, "fts5Strategy", {
13888
+ enumerable: true,
13889
+ get: function () { return chunk3JK66E3I_cjs.fts5Strategy; }
13890
+ });
13891
+ Object.defineProperty(exports, "tsvectorStrategy", {
13892
+ enumerable: true,
13893
+ get: function () { return chunk3JK66E3I_cjs.tsvectorStrategy; }
13894
+ });
13895
+ Object.defineProperty(exports, "DEFAULT_SEARCHABLE_LANGUAGE", {
13896
+ enumerable: true,
13897
+ get: function () { return chunkUVZHTBZ5_cjs.DEFAULT_SEARCHABLE_LANGUAGE; }
13898
+ });
12618
13899
  Object.defineProperty(exports, "MAX_JSON_POINTER_DEPTH", {
12619
13900
  enumerable: true,
12620
- get: function () { return chunkP5CNM325_cjs.MAX_JSON_POINTER_DEPTH; }
13901
+ get: function () { return chunkUVZHTBZ5_cjs.MAX_JSON_POINTER_DEPTH; }
12621
13902
  });
12622
13903
  Object.defineProperty(exports, "embedding", {
12623
13904
  enumerable: true,
12624
- get: function () { return chunkP5CNM325_cjs.embedding; }
13905
+ get: function () { return chunkUVZHTBZ5_cjs.embedding; }
12625
13906
  });
12626
13907
  Object.defineProperty(exports, "getEmbeddingDimensions", {
12627
13908
  enumerable: true,
12628
- get: function () { return chunkP5CNM325_cjs.getEmbeddingDimensions; }
13909
+ get: function () { return chunkUVZHTBZ5_cjs.getEmbeddingDimensions; }
13910
+ });
13911
+ Object.defineProperty(exports, "getSearchableMetadata", {
13912
+ enumerable: true,
13913
+ get: function () { return chunkUVZHTBZ5_cjs.getSearchableMetadata; }
12629
13914
  });
12630
13915
  Object.defineProperty(exports, "isEmbeddingSchema", {
12631
13916
  enumerable: true,
12632
- get: function () { return chunkP5CNM325_cjs.isEmbeddingSchema; }
13917
+ get: function () { return chunkUVZHTBZ5_cjs.isEmbeddingSchema; }
13918
+ });
13919
+ Object.defineProperty(exports, "isSearchableSchema", {
13920
+ enumerable: true,
13921
+ get: function () { return chunkUVZHTBZ5_cjs.isSearchableSchema; }
12633
13922
  });
12634
13923
  Object.defineProperty(exports, "joinJsonPointers", {
12635
13924
  enumerable: true,
12636
- get: function () { return chunkP5CNM325_cjs.joinJsonPointers; }
13925
+ get: function () { return chunkUVZHTBZ5_cjs.joinJsonPointers; }
12637
13926
  });
12638
13927
  Object.defineProperty(exports, "jsonPointer", {
12639
13928
  enumerable: true,
12640
- get: function () { return chunkP5CNM325_cjs.jsonPointer; }
13929
+ get: function () { return chunkUVZHTBZ5_cjs.jsonPointer; }
12641
13930
  });
12642
13931
  Object.defineProperty(exports, "normalizeJsonPointer", {
12643
13932
  enumerable: true,
12644
- get: function () { return chunkP5CNM325_cjs.normalizeJsonPointer; }
13933
+ get: function () { return chunkUVZHTBZ5_cjs.normalizeJsonPointer; }
12645
13934
  });
12646
13935
  Object.defineProperty(exports, "parseJsonPointer", {
12647
13936
  enumerable: true,
12648
- get: function () { return chunkP5CNM325_cjs.parseJsonPointer; }
13937
+ get: function () { return chunkUVZHTBZ5_cjs.parseJsonPointer; }
13938
+ });
13939
+ Object.defineProperty(exports, "searchable", {
13940
+ enumerable: true,
13941
+ get: function () { return chunkUVZHTBZ5_cjs.searchable; }
12649
13942
  });
12650
13943
  exports.DEFAULT_SQL_SCHEMA = DEFAULT_SQL_SCHEMA;
12651
13944
  exports.ExecutableAggregateQuery = ExecutableAggregateQuery;
@@ -12654,6 +13947,7 @@ exports.MAX_EXPLICIT_RECURSIVE_DEPTH = MAX_EXPLICIT_RECURSIVE_DEPTH;
12654
13947
  exports.MAX_RECURSIVE_DEPTH = MAX_RECURSIVE_DEPTH;
12655
13948
  exports.PreparedQuery = PreparedQuery;
12656
13949
  exports.QueryBuilder = QueryBuilder;
13950
+ exports.StoreSearch = StoreSearch;
12657
13951
  exports.UnionableQuery = UnionableQuery;
12658
13952
  exports.avg = avg;
12659
13953
  exports.broader = broader;
@@ -12661,6 +13955,8 @@ exports.composeFragments = composeFragments;
12661
13955
  exports.core = core;
12662
13956
  exports.count = count;
12663
13957
  exports.countDistinct = countDistinct;
13958
+ exports.countDistinctEdges = countDistinctEdges;
13959
+ exports.countEdges = countEdges;
12664
13960
  exports.createExternalRef = createExternalRef;
12665
13961
  exports.createFragment = createFragment;
12666
13962
  exports.createQueryBuilder = createQueryBuilder;