@nicia-ai/typegraph 0.24.1 → 0.25.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 (159) hide show
  1. package/dist/backend/postgres/index.cjs +178 -55
  2. package/dist/backend/postgres/index.cjs.map +1 -1
  3. package/dist/backend/postgres/index.d.cts +6 -7
  4. package/dist/backend/postgres/index.d.ts +6 -7
  5. package/dist/backend/postgres/index.js +151 -28
  6. package/dist/backend/postgres/index.js.map +1 -1
  7. package/dist/backend/sqlite/index.cjs +19 -17
  8. package/dist/backend/sqlite/index.d.cts +6 -7
  9. package/dist/backend/sqlite/index.d.ts +6 -7
  10. package/dist/backend/sqlite/index.js +9 -7
  11. package/dist/backend/sqlite/libsql.cjs +12 -10
  12. package/dist/backend/sqlite/libsql.cjs.map +1 -1
  13. package/dist/backend/sqlite/libsql.d.cts +4 -5
  14. package/dist/backend/sqlite/libsql.d.ts +4 -5
  15. package/dist/backend/sqlite/libsql.js +9 -7
  16. package/dist/backend/sqlite/libsql.js.map +1 -1
  17. package/dist/backend/sqlite/local.cjs +14 -12
  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 +9 -7
  22. package/dist/backend/sqlite/local.js.map +1 -1
  23. package/dist/chunk-4BOWVUGQ.js +2029 -0
  24. package/dist/chunk-4BOWVUGQ.js.map +1 -0
  25. package/dist/{chunk-SUHW2UYL.cjs → chunk-7CDXX3QZ.cjs} +2 -2
  26. package/dist/chunk-7CDXX3QZ.cjs.map +1 -0
  27. package/dist/chunk-7V335GHG.cjs +112 -0
  28. package/dist/chunk-7V335GHG.cjs.map +1 -0
  29. package/dist/chunk-7W3KAMVQ.cjs +2678 -0
  30. package/dist/chunk-7W3KAMVQ.cjs.map +1 -0
  31. package/dist/chunk-CTHNVU6K.cjs +551 -0
  32. package/dist/chunk-CTHNVU6K.cjs.map +1 -0
  33. package/dist/chunk-D2GO3RBL.cjs +12 -0
  34. package/dist/chunk-D2GO3RBL.cjs.map +1 -0
  35. package/dist/{chunk-WFMTAEEN.js → chunk-DOTEN2FG.js} +81 -7
  36. package/dist/chunk-DOTEN2FG.js.map +1 -0
  37. package/dist/chunk-E5JZSEGJ.cjs +301 -0
  38. package/dist/chunk-E5JZSEGJ.cjs.map +1 -0
  39. package/dist/chunk-EG6TJ3I5.js +286 -0
  40. package/dist/chunk-EG6TJ3I5.js.map +1 -0
  41. package/dist/chunk-EJCDOA37.js +537 -0
  42. package/dist/chunk-EJCDOA37.js.map +1 -0
  43. package/dist/{chunk-UVZHTBZ5.cjs → chunk-EK6RJ373.cjs} +7 -150
  44. package/dist/chunk-EK6RJ373.cjs.map +1 -0
  45. package/dist/{chunk-GUTQTMOV.js → chunk-FPCOUJSL.js} +2 -2
  46. package/dist/chunk-FPCOUJSL.js.map +1 -0
  47. package/dist/{chunk-3JK66E3I.cjs → chunk-JTI5WT4P.cjs} +630 -59
  48. package/dist/chunk-JTI5WT4P.cjs.map +1 -0
  49. package/dist/chunk-LVCSL6A3.js +2626 -0
  50. package/dist/chunk-LVCSL6A3.js.map +1 -0
  51. package/dist/chunk-NHQWXSTS.js +10 -0
  52. package/dist/chunk-NHQWXSTS.js.map +1 -0
  53. package/dist/chunk-NZ5PMBTG.js +105 -0
  54. package/dist/chunk-NZ5PMBTG.js.map +1 -0
  55. package/dist/{chunk-5HVDOPZU.cjs → chunk-O2P3KAUT.cjs} +416 -93
  56. package/dist/chunk-O2P3KAUT.cjs.map +1 -0
  57. package/dist/{chunk-QOLTW7OZ.js → chunk-OJ6IQVZG.js} +151 -13
  58. package/dist/chunk-OJ6IQVZG.js.map +1 -0
  59. package/dist/{chunk-EYELZHKY.js → chunk-OOS2RKRH.js} +5 -140
  60. package/dist/chunk-OOS2RKRH.js.map +1 -0
  61. package/dist/chunk-RCAJZG67.cjs +2069 -0
  62. package/dist/chunk-RCAJZG67.cjs.map +1 -0
  63. package/dist/{chunk-O5PST6TB.cjs → chunk-W5MTHMTE.cjs} +83 -6
  64. package/dist/chunk-W5MTHMTE.cjs.map +1 -0
  65. package/dist/{chunk-EY4LBX3F.js → chunk-WI5VRP3X.js} +337 -26
  66. package/dist/chunk-WI5VRP3X.js.map +1 -0
  67. package/dist/chunk-WVQXAFP3.js +79 -0
  68. package/dist/chunk-WVQXAFP3.js.map +1 -0
  69. package/dist/chunk-X3244EKP.cjs +84 -0
  70. package/dist/chunk-X3244EKP.cjs.map +1 -0
  71. package/dist/{chunk-753F33CV.js → chunk-YZZBDCLV.js} +617 -44
  72. package/dist/chunk-YZZBDCLV.js.map +1 -0
  73. package/dist/{chunk-OVJ2BE5Q.cjs → chunk-ZEMNF3QK.cjs} +171 -33
  74. package/dist/chunk-ZEMNF3QK.cjs.map +1 -0
  75. package/dist/{ddl-B_H8JM-E.d.cts → ddl-B2jjLjpR.d.cts} +762 -99
  76. package/dist/{ddl-1tFIvt9V.d.ts → ddl-DpaeAKPp.d.ts} +762 -99
  77. package/dist/extension-types-D07e_NTw.d.cts +543 -0
  78. package/dist/extension-types-D07e_NTw.d.ts +543 -0
  79. package/dist/graph-extension/index.cjs +71 -0
  80. package/dist/graph-extension/index.cjs.map +1 -0
  81. package/dist/graph-extension/index.d.cts +278 -0
  82. package/dist/graph-extension/index.d.ts +278 -0
  83. package/dist/graph-extension/index.js +6 -0
  84. package/dist/graph-extension/index.js.map +1 -0
  85. package/dist/{index-DXkEXpNz.d.ts → index-CT6nvtxL.d.cts} +114 -5
  86. package/dist/{index-DXkEXpNz.d.cts → index-DQheiVQV.d.ts} +114 -5
  87. package/dist/index.cjs +2672 -901
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +29 -10
  90. package/dist/index.d.ts +29 -10
  91. package/dist/index.js +2248 -598
  92. package/dist/index.js.map +1 -1
  93. package/dist/indexes/index.cjs +25 -23
  94. package/dist/indexes/index.d.cts +40 -22
  95. package/dist/indexes/index.d.ts +40 -22
  96. package/dist/indexes/index.js +5 -3
  97. package/dist/interchange/index.cjs +8 -7
  98. package/dist/interchange/index.cjs.map +1 -1
  99. package/dist/interchange/index.d.cts +5 -4
  100. package/dist/interchange/index.d.ts +5 -4
  101. package/dist/interchange/index.js +3 -2
  102. package/dist/interchange/index.js.map +1 -1
  103. package/dist/{manager-nIhmttD-.d.ts → manager-BKaPQbns.d.ts} +189 -6
  104. package/dist/{manager-Vkz28eAC.d.cts → manager-BpjBW1JK.d.cts} +189 -6
  105. package/dist/materialize-indexes-BB9N5uQl.d.cts +68 -0
  106. package/dist/materialize-indexes-BYpU2RKh.d.ts +68 -0
  107. package/dist/profiler/index.cjs +10 -8
  108. package/dist/profiler/index.cjs.map +1 -1
  109. package/dist/profiler/index.d.cts +8 -7
  110. package/dist/profiler/index.d.ts +8 -7
  111. package/dist/profiler/index.js +3 -1
  112. package/dist/profiler/index.js.map +1 -1
  113. package/dist/schema/index.cjs +43 -22
  114. package/dist/schema/index.cjs.map +1 -1
  115. package/dist/schema/index.d.cts +17 -8
  116. package/dist/schema/index.d.ts +17 -8
  117. package/dist/schema/index.js +9 -4
  118. package/dist/schema/index.js.map +1 -1
  119. package/dist/{sqlite-D0aceIe0.d.cts → sqlite-CU3j6rc7.d.ts} +837 -120
  120. package/dist/{sqlite-SUxbZqfy.d.ts → sqlite-Myo8e3Lr.d.cts} +837 -120
  121. package/dist/{store-U3i2tUch.d.ts → store-BAGuOHDh.d.cts} +659 -23
  122. package/dist/{store-CpvwTMxV.d.cts → store-De98Lod4.d.ts} +659 -23
  123. package/dist/types--0EslBx5.d.cts +2123 -0
  124. package/dist/{types-qYFZ9SqD.d.ts → types-BLmJq4wI.d.ts} +3 -2
  125. package/dist/types-BNWYZ-0-.d.ts +2123 -0
  126. package/dist/{types-yH4_p0XB.d.cts → types-CYga3lZp.d.cts} +3 -2
  127. package/dist/where-CluR73rs.d.ts +11 -0
  128. package/dist/where-m1uWsdua.d.cts +11 -0
  129. package/package.json +33 -73
  130. package/dist/ast-C8S37w1A.d.ts +0 -497
  131. package/dist/ast-dgLhLOg0.d.cts +0 -497
  132. package/dist/chunk-3JK66E3I.cjs.map +0 -1
  133. package/dist/chunk-3PPEHLGK.js +0 -1116
  134. package/dist/chunk-3PPEHLGK.js.map +0 -1
  135. package/dist/chunk-5HVDOPZU.cjs.map +0 -1
  136. package/dist/chunk-753F33CV.js.map +0 -1
  137. package/dist/chunk-EY4LBX3F.js.map +0 -1
  138. package/dist/chunk-EYELZHKY.js.map +0 -1
  139. package/dist/chunk-GFGVREHH.js +0 -1381
  140. package/dist/chunk-GFGVREHH.js.map +0 -1
  141. package/dist/chunk-GUTQTMOV.js.map +0 -1
  142. package/dist/chunk-LBORRUNJ.js +0 -135
  143. package/dist/chunk-LBORRUNJ.js.map +0 -1
  144. package/dist/chunk-O5PST6TB.cjs.map +0 -1
  145. package/dist/chunk-OLROXS2S.cjs +0 -147
  146. package/dist/chunk-OLROXS2S.cjs.map +0 -1
  147. package/dist/chunk-OVJ2BE5Q.cjs.map +0 -1
  148. package/dist/chunk-PAUN562Y.cjs +0 -1417
  149. package/dist/chunk-PAUN562Y.cjs.map +0 -1
  150. package/dist/chunk-PEE4EWXZ.cjs +0 -1131
  151. package/dist/chunk-PEE4EWXZ.cjs.map +0 -1
  152. package/dist/chunk-QOLTW7OZ.js.map +0 -1
  153. package/dist/chunk-SUHW2UYL.cjs.map +0 -1
  154. package/dist/chunk-UVZHTBZ5.cjs.map +0 -1
  155. package/dist/chunk-WFMTAEEN.js.map +0 -1
  156. package/dist/types-C-zfYDJB.d.ts +0 -159
  157. package/dist/types-CQx4iW5Y.d.cts +0 -159
  158. package/dist/types-Qk2l3IkQ.d.cts +0 -1254
  159. package/dist/types-Qk2l3IkQ.d.ts +0 -1254
package/dist/index.js CHANGED
@@ -1,148 +1,28 @@
1
1
  import { quoteIdentifier, EMPTY_REQUIRED_COLUMNS, addRequiredColumn, markSelectiveFieldAsRequired, markFieldRefAsRequired, NODE_COLUMNS, shouldProjectColumn, mapSelectiveSystemFieldToColumn, isIdFieldRef, isAggregateExpr, EDGE_COLUMNS } from './chunk-3EUKJEQ3.js';
2
- import { META_EDGE_BRAND, META_EDGE_SUB_CLASS_OF, META_EDGE_NARROWER, META_EDGE_BROADER, META_EDGE_RELATED_TO, META_EDGE_EQUIVALENT_TO, META_EDGE_SAME_AS, META_EDGE_DIFFERENT_FROM, META_EDGE_DISJOINT_WITH, META_EDGE_HAS_PART, META_EDGE_PART_OF, META_EDGE_INVERSE_OF, META_EDGE_IMPLIES, ensureSchema, computeClosuresFromOntology, createEmptyClosures, KindRegistry, validateNodeProps, validateEdgeProps } from './chunk-GFGVREHH.js';
3
- export { isMetaEdge } from './chunk-GFGVREHH.js';
4
- import { NODE_TYPE_BRAND, EDGE_TYPE_BRAND, nowIso, validateOptionalIsoDate, getNodeKinds, getEdgeKinds } from './chunk-LBORRUNJ.js';
5
- export { defineGraph, getEdgeKinds, getNodeKinds, isEdgeType, isEdgeTypeWithEndpoints, isGraphDef, isNodeType } from './chunk-LBORRUNJ.js';
6
- import { runOptionallyInTransaction } from './chunk-GUTQTMOV.js';
7
- import { ConfigurationError, UnsupportedPredicateError, ValidationError, CompilerInvariantError, KindNotFoundError, DatabaseOperationError, EdgeNotFoundError, NodeConstraintNotFoundError, RestrictedDeleteError, NodeNotFoundError, EndpointNotFoundError, UniquenessError, CardinalityError, EndpointError, DisjointError } from './chunk-WFMTAEEN.js';
8
- export { BackendDisposedError, CardinalityError, CompilerInvariantError, ConfigurationError, DatabaseOperationError, DisjointError, EdgeNotFoundError, EndpointError, EndpointNotFoundError, KindNotFoundError, MigrationError, NodeConstraintNotFoundError, NodeNotFoundError, RestrictedDeleteError, SchemaMismatchError, TypeGraphError, UniquenessError, UnsupportedPredicateError, ValidationError, VersionConflictError, getErrorSuggestion, isConstraintError, isSystemError, isTypeGraphError, isUserRecoverable } from './chunk-WFMTAEEN.js';
9
- import { MAX_PG_IDENTIFIER_LENGTH, DEFAULT_PAGINATION_LIMIT, getDialect, DEFAULT_STREAM_BATCH_SIZE, NODE_META_KEYS, EDGE_META_KEYS, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, fnv1aBase36 } from './chunk-753F33CV.js';
10
- export { fts5Strategy, tsvectorStrategy } from './chunk-753F33CV.js';
11
- import { jsonPointer, parseJsonPointer, createSchemaIntrospector, normalizeJsonPointer, joinJsonPointers, resolveFieldTypeInfoAtJsonPointer, getSearchableMetadata, DEFAULT_SEARCHABLE_LANGUAGE, getEmbeddingDimensions, isEmbeddingSchema } from './chunk-EYELZHKY.js';
12
- export { DEFAULT_SEARCHABLE_LANGUAGE, MAX_JSON_POINTER_DEPTH, embedding, getEmbeddingDimensions, getSearchableMetadata, isEmbeddingSchema, isSearchableSchema, joinJsonPointers, jsonPointer, normalizeJsonPointer, parseJsonPointer, searchable } from './chunk-EYELZHKY.js';
2
+ import { META_EDGE_BRAND, loadAndMergeGraphExtensionDocument, ensureSchema, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, computeClosuresFromOntology, createEmptyClosures, KindRegistry, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, validateNodeProps, validateEdgeProps, canonicalEqual, sortedReplacer } from './chunk-LVCSL6A3.js';
3
+ export { broader, core, defineEdge, defineNode, differentFrom, disjointWith, equivalentTo, hasPart, implies, inverseOf, isMetaEdge, narrower, partOf, relatedTo, sameAs, subClassOf } from './chunk-LVCSL6A3.js';
4
+ import { nowIso, isKnownKind, validateOptionalIsoDate, getNodeKinds, getEdgeKinds } from './chunk-EG6TJ3I5.js';
5
+ export { defineGraph, getEdgeKinds, getNodeKinds, isEdgeType, isEdgeTypeWithEndpoints, isGraphDef, isNodeType } from './chunk-EG6TJ3I5.js';
6
+ export { defineGraphExtension } from './chunk-NHQWXSTS.js';
7
+ import { IncompatibleChangeError, filterReservedKeys, RESERVED_NODE_KEYS, RESERVED_EDGE_KEYS, RemoveCompileTimeKindError, KindHasReferentsError, validateProjectionField } from './chunk-4BOWVUGQ.js';
8
+ export { CURRENT_GRAPH_EXTENSION_VERSION, GraphExtensionError, GraphExtensionUnresolvedEndpointError, GraphExtensionValidationError, GraphExtensionVersionUnsupportedError, IncompatibleChangeError, KindCollisionError, KindHasReferentsError, LEGACY_GRAPH_EXTENSION_VERSION, RemoveCompileTimeKindError, validateGraphExtension } from './chunk-4BOWVUGQ.js';
9
+ import { runOptionallyInTransaction } from './chunk-FPCOUJSL.js';
10
+ import { getDialect, generateIndexDDL } from './chunk-YZZBDCLV.js';
11
+ export { andWhere, fts5Strategy, notWhere, orWhere, tsvectorStrategy } from './chunk-YZZBDCLV.js';
12
+ import { MAX_PG_IDENTIFIER_LENGTH, DEFAULT_PAGINATION_LIMIT, DEFAULT_STREAM_BATCH_SIZE, NODE_META_KEYS, EDGE_META_KEYS, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, fnv1aBase36, sha256Hex } from './chunk-EJCDOA37.js';
13
+ export { defineEdgeIndex, defineNodeIndex } from './chunk-EJCDOA37.js';
14
+ import { createSchemaIntrospector, resolveFieldTypeInfoAtJsonPointer, getSearchableMetadata, DEFAULT_SEARCHABLE_LANGUAGE } from './chunk-OOS2RKRH.js';
15
+ export { DEFAULT_SEARCHABLE_LANGUAGE, getSearchableMetadata, isSearchableSchema, searchable } from './chunk-OOS2RKRH.js';
16
+ import { getEmbeddingDimensions, isEmbeddingSchema } from './chunk-WVQXAFP3.js';
17
+ export { embedding, getEmbeddingDimensions, isEmbeddingSchema } from './chunk-WVQXAFP3.js';
18
+ import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, DatabaseOperationError, EdgeNotFoundError, NodeConstraintNotFoundError, RestrictedDeleteError, NodeNotFoundError, EndpointNotFoundError, UniquenessError, CardinalityError, DisjointError } from './chunk-DOTEN2FG.js';
19
+ export { BackendDisposedError, CardinalityError, CompilerInvariantError, ConfigurationError, DatabaseOperationError, DisjointError, EagerMaterializationError, EdgeNotFoundError, EndpointError, EndpointNotFoundError, KindNotFoundError, MigrationError, NodeConstraintNotFoundError, NodeNotFoundError, RestrictedDeleteError, SchemaContentConflictError, SchemaMismatchError, StaleVersionError, TypeGraphError, UniquenessError, UnsupportedPredicateError, ValidationError, VersionConflictError, getErrorSuggestion, isConstraintError, isSystemError, isTypeGraphError, isUserRecoverable } from './chunk-DOTEN2FG.js';
20
+ import { jsonPointer, parseJsonPointer, normalizeJsonPointer, joinJsonPointers } from './chunk-NZ5PMBTG.js';
21
+ export { MAX_JSON_POINTER_DEPTH, joinJsonPointers, jsonPointer, normalizeJsonPointer, parseJsonPointer } from './chunk-NZ5PMBTG.js';
13
22
  import { z } from 'zod';
14
23
  import { sql, Placeholder } from 'drizzle-orm';
15
24
  import { nanoid } from 'nanoid';
16
25
 
17
- // src/store/reserved-keys.ts
18
- var RESERVED_NODE_KEYS = /* @__PURE__ */ new Set([
19
- "id",
20
- "kind",
21
- "meta"
22
- ]);
23
- var RESERVED_EDGE_KEYS = /* @__PURE__ */ new Set([
24
- "id",
25
- "kind",
26
- "meta",
27
- "fromKind",
28
- "fromId",
29
- "toKind",
30
- "toId"
31
- ]);
32
- var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
33
- "__proto__",
34
- "constructor",
35
- "prototype"
36
- ]);
37
- var RESERVED_PROPERTY_PREFIX = "$";
38
- function isReservedPropertyName(name) {
39
- return name.startsWith(RESERVED_PROPERTY_PREFIX);
40
- }
41
- function assertSchemaKeysAreFree(entityKind, name, keys, reservedStructuralKeys) {
42
- const structuralConflicts = keys.filter(
43
- (key) => reservedStructuralKeys.has(key)
44
- );
45
- if (structuralConflicts.length > 0) {
46
- const label = entityKind.toLowerCase();
47
- throw new ConfigurationError(
48
- `${entityKind} "${name}" schema contains reserved property names: ${structuralConflicts.join(", ")}`,
49
- {
50
- [`${label}Type`]: name,
51
- conflicts: structuralConflicts,
52
- reservedKeys: [...reservedStructuralKeys]
53
- },
54
- {
55
- suggestion: `Rename the conflicting properties. Reserved names (${[...reservedStructuralKeys].join(", ")}) are added automatically to all ${label}s.`
56
- }
57
- );
58
- }
59
- const prefixConflicts = keys.filter((key) => isReservedPropertyName(key));
60
- if (prefixConflicts.length > 0) {
61
- const label = entityKind.toLowerCase();
62
- throw new ConfigurationError(
63
- `${entityKind} "${name}" schema uses the reserved "${RESERVED_PROPERTY_PREFIX}" prefix on: ${prefixConflicts.join(", ")}`,
64
- {
65
- [`${label}Type`]: name,
66
- conflicts: prefixConflicts,
67
- reservedPrefix: RESERVED_PROPERTY_PREFIX
68
- },
69
- {
70
- suggestion: `Property names starting with "${RESERVED_PROPERTY_PREFIX}" are reserved for TypeGraph accessors (e.g. $fulltext). Rename each field.`
71
- }
72
- );
73
- }
74
- }
75
- function validateProjectionField(field2, entityType, kind) {
76
- const reserved = entityType === "node" ? RESERVED_NODE_KEYS : RESERVED_EDGE_KEYS;
77
- if (reserved.has(field2)) {
78
- throw new ConfigurationError(
79
- `Projection field "${field2}" on ${entityType} kind "${kind}" conflicts with a reserved structural key`,
80
- { field: field2, kind, entityType, reservedKeys: [...reserved] },
81
- {
82
- suggestion: `Remove "${field2}" from the projection. Structural fields (${[...reserved].join(", ")}) are included automatically when relevant.`
83
- }
84
- );
85
- }
86
- if (PROTOTYPE_POLLUTION_KEYS.has(field2)) {
87
- throw new ConfigurationError(
88
- `Projection field "${field2}" on ${entityType} kind "${kind}" is not allowed`,
89
- { field: field2, kind, entityType },
90
- {
91
- suggestion: `"${field2}" cannot be used as a projection field name.`
92
- }
93
- );
94
- }
95
- }
96
- function filterReservedKeys(props, reservedKeys) {
97
- const filtered = {};
98
- for (const [key, value] of Object.entries(props)) {
99
- if (!reservedKeys.has(key)) {
100
- filtered[key] = value;
101
- }
102
- }
103
- return filtered;
104
- }
105
-
106
- // src/core/node.ts
107
- function validateSchemaKeys(schema, name) {
108
- assertSchemaKeysAreFree(
109
- "Node",
110
- name,
111
- Object.keys(schema.shape),
112
- RESERVED_NODE_KEYS
113
- );
114
- }
115
- function defineNode(name, options) {
116
- validateSchemaKeys(options.schema, name);
117
- return Object.freeze({
118
- [NODE_TYPE_BRAND]: true,
119
- kind: name,
120
- schema: options.schema,
121
- description: options.description
122
- });
123
- }
124
- var EMPTY_SCHEMA = z.object({});
125
- function validateSchemaKeys2(schema, name) {
126
- assertSchemaKeysAreFree(
127
- "Edge",
128
- name,
129
- Object.keys(schema.shape),
130
- RESERVED_EDGE_KEYS
131
- );
132
- }
133
- function defineEdge(name, options) {
134
- const schema = options?.schema ?? EMPTY_SCHEMA;
135
- validateSchemaKeys2(schema, name);
136
- return Object.freeze({
137
- [EDGE_TYPE_BRAND]: true,
138
- kind: name,
139
- schema,
140
- description: options?.description,
141
- from: options?.from,
142
- to: options?.to
143
- });
144
- }
145
-
146
26
  // src/core/meta-edge.ts
147
27
  function metaEdge(name, options = {}) {
148
28
  const properties = {
@@ -187,200 +67,6 @@ function createExternalRef(table) {
187
67
  return (id) => ({ table, id });
188
68
  }
189
69
 
190
- // src/ontology/core-meta-edges.ts
191
- function createMetaEdge(name, properties) {
192
- return Object.freeze({
193
- [META_EDGE_BRAND]: true,
194
- name,
195
- properties: {
196
- transitive: properties.transitive ?? false,
197
- symmetric: properties.symmetric ?? false,
198
- reflexive: properties.reflexive ?? false,
199
- inverse: properties.inverse,
200
- inference: properties.inference,
201
- description: properties.description
202
- }
203
- });
204
- }
205
- var subClassOfMetaEdge = createMetaEdge(META_EDGE_SUB_CLASS_OF, {
206
- transitive: true,
207
- inference: "subsumption",
208
- description: "Type inheritance (Podcast subClassOf Media)"
209
- });
210
- function subClassOf(child, parent) {
211
- return {
212
- metaEdge: subClassOfMetaEdge,
213
- from: child,
214
- to: parent
215
- };
216
- }
217
- var broaderMetaEdge = createMetaEdge(META_EDGE_BROADER, {
218
- transitive: true,
219
- inverse: META_EDGE_NARROWER,
220
- inference: "hierarchy",
221
- description: "Broader concept (ML broader AI)"
222
- });
223
- function broader(narrowerConcept, broaderConcept) {
224
- return {
225
- metaEdge: broaderMetaEdge,
226
- from: narrowerConcept,
227
- to: broaderConcept
228
- };
229
- }
230
- var narrowerMetaEdge = createMetaEdge(META_EDGE_NARROWER, {
231
- transitive: true,
232
- inverse: META_EDGE_BROADER,
233
- inference: "hierarchy",
234
- description: "Narrower concept (AI narrower ML)"
235
- });
236
- function narrower(broaderConcept, narrowerConcept) {
237
- return {
238
- metaEdge: narrowerMetaEdge,
239
- from: broaderConcept,
240
- to: narrowerConcept
241
- };
242
- }
243
- var relatedToMetaEdge = createMetaEdge(META_EDGE_RELATED_TO, {
244
- symmetric: true,
245
- inference: "association",
246
- description: "Non-hierarchical association"
247
- });
248
- function relatedTo(conceptA, conceptB) {
249
- return {
250
- metaEdge: relatedToMetaEdge,
251
- from: conceptA,
252
- to: conceptB
253
- };
254
- }
255
- var equivalentToMetaEdge = createMetaEdge(META_EDGE_EQUIVALENT_TO, {
256
- symmetric: true,
257
- transitive: true,
258
- inference: "substitution",
259
- description: "Same class, different representation"
260
- });
261
- function equivalentTo(kindA, kindBOrIri) {
262
- return {
263
- metaEdge: equivalentToMetaEdge,
264
- from: kindA,
265
- to: kindBOrIri
266
- };
267
- }
268
- var sameAsMetaEdge = createMetaEdge(META_EDGE_SAME_AS, {
269
- symmetric: true,
270
- transitive: true,
271
- inference: "substitution",
272
- description: "Same individual (for deduplication)"
273
- });
274
- function sameAs(kindA, kindBOrIri) {
275
- return {
276
- metaEdge: sameAsMetaEdge,
277
- from: kindA,
278
- to: kindBOrIri
279
- };
280
- }
281
- var differentFromMetaEdge = createMetaEdge(META_EDGE_DIFFERENT_FROM, {
282
- symmetric: true,
283
- inference: "constraint",
284
- description: "Explicitly different individuals"
285
- });
286
- function differentFrom(kindA, kindB) {
287
- return {
288
- metaEdge: differentFromMetaEdge,
289
- from: kindA,
290
- to: kindB
291
- };
292
- }
293
- var disjointWithMetaEdge = createMetaEdge(META_EDGE_DISJOINT_WITH, {
294
- symmetric: true,
295
- inference: "constraint",
296
- description: "Mutually exclusive types"
297
- });
298
- function disjointWith(kindA, kindB) {
299
- return {
300
- metaEdge: disjointWithMetaEdge,
301
- from: kindA,
302
- to: kindB
303
- };
304
- }
305
- var partOfMetaEdge = createMetaEdge(META_EDGE_PART_OF, {
306
- transitive: true,
307
- inverse: META_EDGE_HAS_PART,
308
- inference: "composition",
309
- description: "X is part of Y"
310
- });
311
- function partOf(part, whole) {
312
- return {
313
- metaEdge: partOfMetaEdge,
314
- from: part,
315
- to: whole
316
- };
317
- }
318
- var hasPartMetaEdge = createMetaEdge(META_EDGE_HAS_PART, {
319
- transitive: true,
320
- inverse: META_EDGE_PART_OF,
321
- inference: "composition",
322
- description: "Y has part X"
323
- });
324
- function hasPart(whole, part) {
325
- return {
326
- metaEdge: hasPartMetaEdge,
327
- from: whole,
328
- to: part
329
- };
330
- }
331
- var inverseOfMetaEdge = createMetaEdge(META_EDGE_INVERSE_OF, {
332
- symmetric: true,
333
- inference: "none",
334
- description: "Edge A is inverse of edge B"
335
- });
336
- var impliesMetaEdge = createMetaEdge(META_EDGE_IMPLIES, {
337
- transitive: true,
338
- inference: "none",
339
- description: "Edge A implies edge B exists"
340
- });
341
- function inverseOf(edgeA, edgeB) {
342
- return {
343
- metaEdge: inverseOfMetaEdge,
344
- from: edgeA,
345
- to: edgeB
346
- };
347
- }
348
- function implies(edgeA, edgeB) {
349
- return {
350
- metaEdge: impliesMetaEdge,
351
- from: edgeA,
352
- to: edgeB
353
- };
354
- }
355
- var core = {
356
- // Meta-edges
357
- subClassOfMetaEdge,
358
- broaderMetaEdge,
359
- narrowerMetaEdge,
360
- relatedToMetaEdge,
361
- equivalentToMetaEdge,
362
- sameAsMetaEdge,
363
- differentFromMetaEdge,
364
- disjointWithMetaEdge,
365
- partOfMetaEdge,
366
- hasPartMetaEdge,
367
- inverseOfMetaEdge,
368
- impliesMetaEdge,
369
- // Relation factories
370
- subClassOf,
371
- broader,
372
- narrower,
373
- relatedTo,
374
- equivalentTo,
375
- sameAs,
376
- differentFrom,
377
- disjointWith,
378
- partOf,
379
- hasPart,
380
- inverseOf,
381
- implies
382
- };
383
-
384
70
  // src/query/subquery-utils.ts
385
71
  function normalizeValueType(valueType) {
386
72
  if (valueType === void 0 || valueType === "unknown") {
@@ -1024,8 +710,8 @@ function compileFieldJsonValue(field2, dialect, cteAlias, pointerOverride, cteCo
1024
710
  }
1025
711
  function resolveLiteralValueTypes(literals) {
1026
712
  const resolved = /* @__PURE__ */ new Set();
1027
- for (const literal2 of literals) {
1028
- const valueType = normalizeValueType2(literal2.valueType);
713
+ for (const literal3 of literals) {
714
+ const valueType = normalizeValueType2(literal3.valueType);
1029
715
  if (valueType) {
1030
716
  resolved.add(valueType);
1031
717
  }
@@ -1053,12 +739,12 @@ function resolveComparisonValueType(field2, right) {
1053
739
  }
1054
740
  return fieldType;
1055
741
  }
1056
- function resolvePredicateValueType(preferred, literal2) {
742
+ function resolvePredicateValueType(preferred, literal3) {
1057
743
  const normalizedPreferred = normalizeValueType2(preferred);
1058
744
  if (normalizedPreferred) {
1059
745
  return normalizedPreferred;
1060
746
  }
1061
- return literal2 ? normalizeValueType2(literal2.valueType) : void 0;
747
+ return literal3 ? normalizeValueType2(literal3.valueType) : void 0;
1062
748
  }
1063
749
  function convertValueForSql(value, dialect) {
1064
750
  return dialect.bindValue(value);
@@ -1928,7 +1614,8 @@ var DEFAULT_TABLE_NAMES = {
1928
1614
  nodes: "typegraph_nodes",
1929
1615
  edges: "typegraph_edges",
1930
1616
  embeddings: "typegraph_node_embeddings",
1931
- fulltext: "typegraph_node_fulltext"
1617
+ fulltext: "typegraph_node_fulltext",
1618
+ uniques: "typegraph_node_uniques"
1932
1619
  };
1933
1620
  var VALID_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_$]*$/i;
1934
1621
  function validateTableName(name, label) {
@@ -1955,6 +1642,7 @@ function createSqlSchema(names = {}) {
1955
1642
  validateTableName(tables.edges, "edges");
1956
1643
  validateTableName(tables.embeddings, "embeddings");
1957
1644
  validateTableName(tables.fulltext, "fulltext");
1645
+ validateTableName(tables.uniques, "uniques");
1958
1646
  return {
1959
1647
  tables,
1960
1648
  nodesTable: sql.raw(quoteIdentifier2(tables.nodes)),
@@ -4256,147 +3944,676 @@ function mapSubgraphEdgeRow(row, projectionPlan) {
4256
3944
  applyProjectedFields(projectedEdge, row, kindPlan);
4257
3945
  return projectedEdge;
4258
3946
  }
4259
- var OPERATOR_MAP = {
4260
- union: "UNION",
4261
- unionAll: "UNION ALL",
4262
- intersect: "INTERSECT",
4263
- except: "EXCEPT"
4264
- };
4265
- function compileSetOperationWithStandardParenthesizedStrategy(op, graphId, dialect, logicalPlan, _schema, compileQuery2) {
4266
- return compileSetOperationStandard(
4267
- op,
4268
- graphId,
4269
- dialect,
4270
- logicalPlan,
4271
- compileQuery2
4272
- );
4273
- }
4274
- function compileSetOperationWithSqliteCompoundStrategy(op, graphId, dialect, logicalPlan, schema, _compileQuery) {
4275
- return compileSetOperationForSqlite(
4276
- op,
4277
- graphId,
4278
- dialect,
4279
- logicalPlan,
4280
- schema
4281
- );
4282
- }
4283
- var SET_OPERATION_STRATEGY_HANDLERS = {
4284
- standard_parenthesized: compileSetOperationWithStandardParenthesizedStrategy,
4285
- sqlite_compound: compileSetOperationWithSqliteCompoundStrategy
4286
- };
4287
- function runSetOperationPassPipeline(op, graphId, dialect) {
4288
- let state = {
4289
- dialect,
4290
- graphId,
4291
- logicalPlan: void 0,
4292
- op
3947
+
3948
+ // src/graph-extension/classify.ts
3949
+ function classifyModifications(existing, next) {
3950
+ const incompatible = [];
3951
+ const requireEmptyMutable = /* @__PURE__ */ new Map();
3952
+ const recordIncompatible = (entry2) => {
3953
+ incompatible.push(entry2);
4293
3954
  };
4294
- const logicalPlanPass = runCompilerPass(state, {
4295
- name: "logical_plan",
4296
- execute(currentState) {
4297
- return lowerSetOperationToLogicalPlan({
4298
- dialect: currentState.dialect.name,
4299
- graphId: currentState.graphId,
4300
- op: currentState.op
3955
+ const recordRequireEmpty = (entity, entry2) => {
3956
+ const key = `${entity}:${entry2.kind}`;
3957
+ const existingEntry = requireEmptyMutable.get(key);
3958
+ if (existingEntry === void 0) {
3959
+ requireEmptyMutable.set(key, {
3960
+ entity,
3961
+ kindName: entry2.kind,
3962
+ changes: [entry2]
4301
3963
  });
4302
- },
4303
- update(currentState, logicalPlan) {
4304
- return {
4305
- ...currentState,
4306
- logicalPlan
4307
- };
3964
+ } else {
3965
+ existingEntry.changes.push(entry2);
4308
3966
  }
4309
- });
4310
- state = logicalPlanPass.state;
4311
- return state;
4312
- }
4313
- function compileSetOperation(op, graphId, dialect, schema, compileQuery2) {
4314
- const passState = runSetOperationPassPipeline(op, graphId, dialect);
4315
- const { logicalPlan } = passState;
4316
- if (logicalPlan === void 0) {
4317
- throw new CompilerInvariantError(
4318
- "Logical plan pass did not initialize plan state"
3967
+ };
3968
+ for (const [name, nextNode] of Object.entries(next.nodes ?? {})) {
3969
+ const existingNode = existing.nodes?.[name];
3970
+ if (existingNode === void 0) continue;
3971
+ classifyNode(name, existingNode, nextNode, {
3972
+ recordIncompatible,
3973
+ recordRequireEmpty: (entry2) => {
3974
+ recordRequireEmpty("node", entry2);
3975
+ }
3976
+ });
3977
+ }
3978
+ for (const [name, nextEdge] of Object.entries(next.edges ?? {})) {
3979
+ const existingEdge = existing.edges?.[name];
3980
+ if (existingEdge === void 0) continue;
3981
+ classifyEdge(name, existingEdge, nextEdge, {
3982
+ recordIncompatible,
3983
+ recordRequireEmpty: (entry2) => {
3984
+ recordRequireEmpty("edge", entry2);
3985
+ }
3986
+ });
3987
+ }
3988
+ const requireEmpty = [];
3989
+ for (const value of requireEmptyMutable.values()) {
3990
+ requireEmpty.push(
3991
+ Object.freeze({
3992
+ entity: value.entity,
3993
+ kindName: value.kindName,
3994
+ changes: Object.freeze([...value.changes])
3995
+ })
4319
3996
  );
4320
3997
  }
4321
- const strategy = dialect.capabilities.setOperationStrategy;
4322
- const handler = SET_OPERATION_STRATEGY_HANDLERS[strategy];
4323
- return handler(op, graphId, dialect, logicalPlan, schema, compileQuery2);
3998
+ return { incompatible, requireEmpty };
4324
3999
  }
4325
- function compileSetOperationStandard(op, graphId, dialect, logicalPlan, compileQuery2) {
4326
- const coreSql = compileSetOperationCoreStandard(
4327
- op,
4328
- graphId,
4329
- dialect,
4330
- compileQuery2
4331
- );
4332
- const suffixClauses = buildSetOperationSuffixClauses(op, dialect);
4333
- return emitSetOperationQuerySql({
4334
- baseQuery: coreSql,
4335
- logicalPlan,
4336
- ...suffixClauses.length === 0 ? {} : { suffixClauses }
4337
- });
4000
+ function buildIncompatibleChangeError(classification, nonEmpty, graphId) {
4001
+ const promoted = [];
4002
+ for (const entry2 of nonEmpty) {
4003
+ promoted.push(...entry2.changes);
4004
+ }
4005
+ const all = [...classification.incompatible, ...promoted];
4006
+ if (all.length === 0) return void 0;
4007
+ return new IncompatibleChangeError(all, graphId);
4338
4008
  }
4339
- function compileSetOperationCoreStandard(op, graphId, dialect, compileQuery2) {
4340
- const left = compileComposableQueryStandard(
4341
- op.left,
4342
- graphId,
4343
- dialect,
4344
- compileQuery2
4345
- );
4346
- const right = compileComposableQueryStandard(
4347
- op.right,
4348
- graphId,
4349
- dialect,
4350
- compileQuery2
4351
- );
4352
- const opSql = sql.raw(OPERATOR_MAP[op.operator]);
4353
- return sql`(${left}) ${opSql} (${right})`;
4009
+ function classifyNode(kind, existing, next, recorders) {
4010
+ classifyProperties(kind, existing.properties, next.properties, recorders);
4011
+ classifyUnique(kind, existing.unique ?? [], next.unique ?? [], recorders);
4354
4012
  }
4355
- function compileComposableQueryStandard(query, graphId, dialect, compileQuery2) {
4356
- if ("__type" in query) {
4357
- return compileSetOperationCoreStandard(
4358
- query,
4359
- graphId,
4360
- dialect,
4361
- compileQuery2
4362
- );
4013
+ function classifyEdge(kind, existing, next, recorders) {
4014
+ classifyProperties(
4015
+ kind,
4016
+ existing.properties ?? {},
4017
+ next.properties ?? {},
4018
+ recorders
4019
+ );
4020
+ classifyEdgeEndpoints(kind, existing, next, recorders);
4021
+ }
4022
+ function classifyEdgeEndpoints(kind, existing, next, recorders) {
4023
+ const fromExisting = new Set(existing.from);
4024
+ const toExisting = new Set(existing.to);
4025
+ const fromNext = new Set(next.from);
4026
+ const toNext = new Set(next.to);
4027
+ for (const removed of [...fromExisting].filter(
4028
+ (entry2) => !fromNext.has(entry2)
4029
+ )) {
4030
+ recorders.recordRequireEmpty({
4031
+ kind,
4032
+ type: "TIGHTEN_EDGE_ENDPOINTS",
4033
+ detail: `removed "${removed}" from \`from\``
4034
+ });
4363
4035
  }
4364
- return compileQuery2(query, graphId);
4365
- }
4366
- function runSqliteSetOperationValidationPass(op) {
4367
- const leaves = [];
4368
- collectLeafQueries(op, leaves, "q");
4369
- for (const leaf of leaves) {
4370
- validateSqliteSetOpLeaf(leaf.ast);
4036
+ for (const removed of [...toExisting].filter((entry2) => !toNext.has(entry2))) {
4037
+ recorders.recordRequireEmpty({
4038
+ kind,
4039
+ type: "TIGHTEN_EDGE_ENDPOINTS",
4040
+ detail: `removed "${removed}" from \`to\``
4041
+ });
4371
4042
  }
4372
- return { leaves };
4373
4043
  }
4374
- function compileSetOperationForSqlite(op, graphId, dialect, logicalPlan, schema) {
4375
- const { leaves } = runSqliteSetOperationValidationPass(op);
4376
- const allCtes = [];
4377
- const ctx = {
4378
- dialect,
4379
- schema,
4380
- compileQuery: () => {
4381
- throw new CompilerInvariantError(
4382
- "compileQuery is not available in set-operation CTE compilation"
4383
- );
4044
+ function classifyProperties(kind, existing, next, recorders) {
4045
+ const existingNames = new Set(Object.keys(existing));
4046
+ const nextNames = new Set(Object.keys(next));
4047
+ for (const name of existingNames) {
4048
+ if (!nextNames.has(name)) {
4049
+ recorders.recordIncompatible({
4050
+ kind,
4051
+ field: name,
4052
+ type: "REMOVE_PROPERTY"
4053
+ });
4384
4054
  }
4385
- };
4386
- for (const leaf of leaves) {
4387
- const cte = compilePrefixedStartCte(leaf.ast, leaf.prefix, graphId, ctx);
4388
- allCtes.push(cte);
4389
4055
  }
4390
- const selectStatements = [];
4391
- for (const leaf of leaves) {
4392
- const select = compilePrefixedSelect(leaf.ast, leaf.prefix, dialect);
4393
- selectStatements.push(select);
4056
+ for (const name of nextNames) {
4057
+ if (!existingNames.has(name)) {
4058
+ const property = next[name];
4059
+ if (property.optional === true) continue;
4060
+ recorders.recordRequireEmpty({
4061
+ kind,
4062
+ field: name,
4063
+ type: "ADD_REQUIRED_PROPERTY"
4064
+ });
4065
+ }
4394
4066
  }
4395
- const compoundSelect = buildCompoundSelect(op, leaves, selectStatements);
4396
- const suffixClauses = buildSetOperationSuffixClauses(op, dialect);
4397
- return emitSetOperationQuerySql({
4398
- baseQuery: compoundSelect,
4399
- ...allCtes.length === 0 ? {} : { ctes: allCtes },
4067
+ for (const name of existingNames) {
4068
+ if (!nextNames.has(name)) continue;
4069
+ classifyProperty(kind, name, existing[name], next[name], recorders);
4070
+ }
4071
+ }
4072
+ function classifyProperty(kind, field2, existing, next, recorders) {
4073
+ if (existing.type !== next.type) {
4074
+ recorders.recordIncompatible({
4075
+ kind,
4076
+ field: field2,
4077
+ type: "TYPE_CHANGE",
4078
+ detail: `${existing.type} \u2192 ${next.type}`
4079
+ });
4080
+ return;
4081
+ }
4082
+ const wasOptional = existing.optional === true;
4083
+ const isOptional = next.optional === true;
4084
+ if (!wasOptional && isOptional) ; else if (wasOptional && !isOptional) {
4085
+ recorders.recordRequireEmpty({
4086
+ kind,
4087
+ field: field2,
4088
+ type: "TIGHTEN_OPTIONALITY",
4089
+ detail: "optional: true \u2192 false"
4090
+ });
4091
+ }
4092
+ switch (existing.type) {
4093
+ case "string": {
4094
+ classifyString(
4095
+ kind,
4096
+ field2,
4097
+ existing,
4098
+ next,
4099
+ recorders
4100
+ );
4101
+ break;
4102
+ }
4103
+ case "number": {
4104
+ classifyNumber(
4105
+ kind,
4106
+ field2,
4107
+ existing,
4108
+ next,
4109
+ recorders
4110
+ );
4111
+ break;
4112
+ }
4113
+ case "enum": {
4114
+ classifyEnum(
4115
+ kind,
4116
+ field2,
4117
+ existing,
4118
+ next,
4119
+ recorders
4120
+ );
4121
+ break;
4122
+ }
4123
+ case "array": {
4124
+ classifyArray(
4125
+ kind,
4126
+ field2,
4127
+ existing,
4128
+ next,
4129
+ recorders
4130
+ );
4131
+ break;
4132
+ }
4133
+ case "object": {
4134
+ classifyObject(
4135
+ kind,
4136
+ field2,
4137
+ existing,
4138
+ next,
4139
+ recorders
4140
+ );
4141
+ break;
4142
+ }
4143
+ }
4144
+ }
4145
+ function classifyString(kind, field2, existing, next, recorders) {
4146
+ if (existing.minLength !== next.minLength && next.minLength !== void 0 && (existing.minLength === void 0 || next.minLength > existing.minLength)) {
4147
+ recorders.recordRequireEmpty({
4148
+ kind,
4149
+ field: field2,
4150
+ type: "TIGHTEN_CONSTRAINT",
4151
+ detail: `minLength: ${existing.minLength ?? "(none)"} \u2192 ${next.minLength}`
4152
+ });
4153
+ }
4154
+ if (existing.maxLength !== next.maxLength && next.maxLength !== void 0 && (existing.maxLength === void 0 || next.maxLength < existing.maxLength)) {
4155
+ recorders.recordRequireEmpty({
4156
+ kind,
4157
+ field: field2,
4158
+ type: "TIGHTEN_CONSTRAINT",
4159
+ detail: `maxLength: ${existing.maxLength ?? "(none)"} \u2192 ${next.maxLength}`
4160
+ });
4161
+ }
4162
+ if (existing.pattern !== next.pattern) {
4163
+ if (existing.pattern === void 0 && next.pattern !== void 0) {
4164
+ recorders.recordRequireEmpty({
4165
+ kind,
4166
+ field: field2,
4167
+ type: "ADD_PATTERN",
4168
+ detail: next.pattern
4169
+ });
4170
+ } else if (existing.pattern !== void 0 && next.pattern === void 0) ; else if (existing.pattern !== void 0 && next.pattern !== void 0 && existing.pattern !== next.pattern) {
4171
+ recorders.recordRequireEmpty({
4172
+ kind,
4173
+ field: field2,
4174
+ type: "CHANGE_PATTERN",
4175
+ detail: `${existing.pattern} \u2192 ${next.pattern}`
4176
+ });
4177
+ }
4178
+ }
4179
+ if (existing.format !== next.format) {
4180
+ if (existing.format === void 0 && next.format !== void 0) {
4181
+ recorders.recordRequireEmpty({
4182
+ kind,
4183
+ field: field2,
4184
+ type: "ADD_FORMAT",
4185
+ detail: next.format
4186
+ });
4187
+ } else if (existing.format !== void 0 && next.format === void 0) ; else {
4188
+ recorders.recordRequireEmpty({
4189
+ kind,
4190
+ field: field2,
4191
+ type: "CHANGE_FORMAT",
4192
+ detail: `${existing.format} \u2192 ${next.format}`
4193
+ });
4194
+ }
4195
+ }
4196
+ }
4197
+ function classifyNumber(kind, field2, existing, next, recorders) {
4198
+ if (next.min !== void 0 && (existing.min === void 0 || next.min > existing.min)) {
4199
+ recorders.recordRequireEmpty({
4200
+ kind,
4201
+ field: field2,
4202
+ type: "TIGHTEN_CONSTRAINT",
4203
+ detail: `min: ${existing.min ?? "(none)"} \u2192 ${next.min}`
4204
+ });
4205
+ }
4206
+ if (next.max !== void 0 && (existing.max === void 0 || next.max < existing.max)) {
4207
+ recorders.recordRequireEmpty({
4208
+ kind,
4209
+ field: field2,
4210
+ type: "TIGHTEN_CONSTRAINT",
4211
+ detail: `max: ${existing.max ?? "(none)"} \u2192 ${next.max}`
4212
+ });
4213
+ }
4214
+ if (existing.int !== next.int && next.int === true && existing.int !== true) {
4215
+ recorders.recordRequireEmpty({
4216
+ kind,
4217
+ field: field2,
4218
+ type: "TIGHTEN_INT",
4219
+ detail: "int: false \u2192 true"
4220
+ });
4221
+ }
4222
+ }
4223
+ function classifyEnum(kind, field2, existing, next, recorders) {
4224
+ const nextValues = new Set(next.values);
4225
+ const removed = existing.values.filter((value) => !nextValues.has(value));
4226
+ if (removed.length > 0) {
4227
+ recorders.recordRequireEmpty({
4228
+ kind,
4229
+ field: field2,
4230
+ type: "TIGHTEN_ENUM",
4231
+ detail: `removed values: ${removed.join(", ")}`
4232
+ });
4233
+ }
4234
+ }
4235
+ function classifyArray(kind, field2, existing, next, recorders) {
4236
+ if (existing.items.type !== next.items.type) {
4237
+ recorders.recordIncompatible({
4238
+ kind,
4239
+ field: `${field2}.items`,
4240
+ type: "TYPE_CHANGE",
4241
+ detail: `${existing.items.type} \u2192 ${next.items.type}`
4242
+ });
4243
+ return;
4244
+ }
4245
+ classifyProperty(
4246
+ kind,
4247
+ `${field2}.items`,
4248
+ existing.items,
4249
+ next.items,
4250
+ recorders
4251
+ );
4252
+ }
4253
+ function classifyObject(kind, field2, existing, next, recorders) {
4254
+ classifyProperties(kind, existing.properties, next.properties, {
4255
+ recordIncompatible: (entry2) => {
4256
+ recorders.recordIncompatible({
4257
+ ...entry2,
4258
+ field: `${field2}.${entry2.field ?? ""}`
4259
+ });
4260
+ },
4261
+ recordRequireEmpty: (entry2) => {
4262
+ recorders.recordRequireEmpty({
4263
+ ...entry2,
4264
+ field: `${field2}.${entry2.field ?? ""}`
4265
+ });
4266
+ }
4267
+ });
4268
+ }
4269
+ function classifyUnique(kind, existing, next, recorders) {
4270
+ const existingByName = new Map(
4271
+ existing.map((constraint) => [constraint.name, constraint])
4272
+ );
4273
+ const nextByName = new Map(
4274
+ next.map((constraint) => [constraint.name, constraint])
4275
+ );
4276
+ for (const [name, constraint] of nextByName) {
4277
+ if (!existingByName.has(name)) {
4278
+ recorders.recordRequireEmpty({
4279
+ kind,
4280
+ type: "ADD_UNIQUE_ON_POPULATED",
4281
+ detail: `unique constraint "${name}" on [${constraint.fields.join(", ")}]`
4282
+ });
4283
+ }
4284
+ }
4285
+ for (const [name, constraint] of existingByName) {
4286
+ const newOne = nextByName.get(name);
4287
+ if (newOne === void 0) continue;
4288
+ const { name: _existingName, ...existingShape } = constraint;
4289
+ const { name: _nextName, ...nextShape } = newOne;
4290
+ if (!canonicalEqual(existingShape, nextShape)) {
4291
+ recorders.recordRequireEmpty({
4292
+ kind,
4293
+ type: "ADD_UNIQUE_ON_POPULATED",
4294
+ detail: `unique constraint "${name}" reshaped`
4295
+ });
4296
+ }
4297
+ }
4298
+ }
4299
+
4300
+ // src/graph-extension/remove.ts
4301
+ function planRemovals(graph, names) {
4302
+ const document = graph.extension;
4303
+ const namesSet = new Set(names);
4304
+ if (namesSet.size === 0) {
4305
+ return { document, removedNodeKinds: [], removedEdgeKinds: [] };
4306
+ }
4307
+ const { nodes: runtimeNodeNames, edges: runtimeEdgeNames } = extensionKindNames(document);
4308
+ const compileTimeNodeNames = new Set(
4309
+ Object.keys(graph.nodes).filter((name) => !runtimeNodeNames.has(name))
4310
+ );
4311
+ const compileTimeEdgeNames = new Set(
4312
+ Object.keys(graph.edges).filter((name) => !runtimeEdgeNames.has(name))
4313
+ );
4314
+ for (const name of namesSet) {
4315
+ if (compileTimeNodeNames.has(name)) {
4316
+ throw new RemoveCompileTimeKindError(name, "node", graph.id);
4317
+ }
4318
+ if (compileTimeEdgeNames.has(name)) {
4319
+ throw new RemoveCompileTimeKindError(name, "edge", graph.id);
4320
+ }
4321
+ }
4322
+ const removedNodeKinds = [...namesSet].filter(
4323
+ (name) => runtimeNodeNames.has(name)
4324
+ );
4325
+ const explicitlyRemovedEdgeKinds = [...namesSet].filter(
4326
+ (name) => runtimeEdgeNames.has(name)
4327
+ );
4328
+ const referentsByKind = buildCompileTimeReferentIndex(
4329
+ graph,
4330
+ runtimeEdgeNames
4331
+ );
4332
+ for (const kindName of removedNodeKinds) {
4333
+ const referents = referentsByKind.get(kindName);
4334
+ if (referents !== void 0 && referents.length > 0) {
4335
+ throw new KindHasReferentsError(kindName, referents, graph.id);
4336
+ }
4337
+ }
4338
+ if (document === void 0) {
4339
+ return {
4340
+ document: void 0,
4341
+ removedNodeKinds: [],
4342
+ removedEdgeKinds: []
4343
+ };
4344
+ }
4345
+ const removedNodeKindsSet = new Set(removedNodeKinds);
4346
+ const explicitlyRemovedEdgeKindsSet = new Set(explicitlyRemovedEdgeKinds);
4347
+ const cascadeEdges = /* @__PURE__ */ new Set();
4348
+ const updatedEdges = {};
4349
+ for (const [edgeName, edgeDocument] of Object.entries(document.edges ?? {})) {
4350
+ if (explicitlyRemovedEdgeKindsSet.has(edgeName)) {
4351
+ cascadeEdges.add(edgeName);
4352
+ continue;
4353
+ }
4354
+ const newFrom = edgeDocument.from.filter(
4355
+ (kind) => !removedNodeKindsSet.has(kind)
4356
+ );
4357
+ const newTo = edgeDocument.to.filter(
4358
+ (kind) => !removedNodeKindsSet.has(kind)
4359
+ );
4360
+ if (newFrom.length === 0 || newTo.length === 0) {
4361
+ cascadeEdges.add(edgeName);
4362
+ continue;
4363
+ }
4364
+ updatedEdges[edgeName] = {
4365
+ ...edgeDocument,
4366
+ from: newFrom,
4367
+ to: newTo
4368
+ };
4369
+ }
4370
+ const updatedNodes = {};
4371
+ for (const [nodeName, nodeDocument] of Object.entries(document.nodes ?? {})) {
4372
+ if (removedNodeKindsSet.has(nodeName)) continue;
4373
+ updatedNodes[nodeName] = nodeDocument;
4374
+ }
4375
+ const survivingOntology = (document.ontology ?? []).filter(
4376
+ (relation) => !removedNodeKindsSet.has(relation.from) && !removedNodeKindsSet.has(relation.to)
4377
+ );
4378
+ const allRemovedKinds = /* @__PURE__ */ new Set([
4379
+ ...removedNodeKindsSet,
4380
+ ...cascadeEdges,
4381
+ ...explicitlyRemovedEdgeKindsSet
4382
+ ]);
4383
+ const survivingIndexes = (document.indexes ?? []).filter(
4384
+ (index) => !allRemovedKinds.has(index.kind)
4385
+ );
4386
+ const newDocument = Object.freeze({
4387
+ ...document.version === void 0 ? {} : { version: document.version },
4388
+ ...Object.keys(updatedNodes).length === 0 ? {} : { nodes: updatedNodes },
4389
+ ...Object.keys(updatedEdges).length === 0 ? {} : { edges: updatedEdges },
4390
+ ...survivingOntology.length === 0 ? {} : { ontology: survivingOntology },
4391
+ ...survivingIndexes.length === 0 ? {} : { indexes: survivingIndexes }
4392
+ });
4393
+ return {
4394
+ document: newDocument,
4395
+ removedNodeKinds,
4396
+ removedEdgeKinds: [
4397
+ ...explicitlyRemovedEdgeKinds,
4398
+ ...[...cascadeEdges].filter(
4399
+ (name) => !explicitlyRemovedEdgeKindsSet.has(name)
4400
+ )
4401
+ ]
4402
+ };
4403
+ }
4404
+ function stripGraphExtension(graph) {
4405
+ const document = graph.extension;
4406
+ if (document === void 0) return graph;
4407
+ const { nodes: runtimeNodeNames, edges: runtimeEdgeNames } = extensionKindNames(document);
4408
+ const runtimeOntologyKeys = buildGraphExtensionOntologyKeySet(document);
4409
+ const compileNodes = {};
4410
+ for (const [name, registration] of Object.entries(graph.nodes)) {
4411
+ if (runtimeNodeNames.has(name)) continue;
4412
+ compileNodes[name] = registration;
4413
+ }
4414
+ const compileEdges = {};
4415
+ for (const [name, registration] of Object.entries(graph.edges)) {
4416
+ if (runtimeEdgeNames.has(name)) continue;
4417
+ compileEdges[name] = registration;
4418
+ }
4419
+ const compileOntology = graph.ontology.filter(
4420
+ (relation) => !runtimeOntologyKeys.has(compileTimeOntologyKey(relation))
4421
+ );
4422
+ const compileIndexes = (graph.indexes ?? []).filter(
4423
+ (index) => index.origin !== "runtime"
4424
+ );
4425
+ return Object.freeze({
4426
+ ...graph,
4427
+ nodes: compileNodes,
4428
+ edges: compileEdges,
4429
+ ontology: compileOntology,
4430
+ indexes: compileIndexes.length === 0 ? void 0 : compileIndexes,
4431
+ extension: void 0
4432
+ });
4433
+ }
4434
+ function buildCompileTimeReferentIndex(graph, runtimeEdgeNames) {
4435
+ const index = /* @__PURE__ */ new Map();
4436
+ const append = (kindName, referent) => {
4437
+ const existing = index.get(kindName);
4438
+ if (existing === void 0) index.set(kindName, [referent]);
4439
+ else existing.push(referent);
4440
+ };
4441
+ for (const [edgeName, registration] of Object.entries(graph.edges)) {
4442
+ if (runtimeEdgeNames.has(edgeName)) continue;
4443
+ const reg = registration;
4444
+ const referent = {
4445
+ type: "compile-time-edge",
4446
+ name: edgeName
4447
+ };
4448
+ const seen = /* @__PURE__ */ new Set();
4449
+ for (const endpoint of reg.from ?? []) {
4450
+ if (seen.has(endpoint.kind)) continue;
4451
+ seen.add(endpoint.kind);
4452
+ append(endpoint.kind, referent);
4453
+ }
4454
+ for (const endpoint of reg.to ?? []) {
4455
+ if (seen.has(endpoint.kind)) continue;
4456
+ seen.add(endpoint.kind);
4457
+ append(endpoint.kind, referent);
4458
+ }
4459
+ }
4460
+ const runtimeOntologyKeys = buildGraphExtensionOntologyKeySet(
4461
+ graph.extension
4462
+ );
4463
+ for (const relation of graph.ontology) {
4464
+ if (runtimeOntologyKeys.has(compileTimeOntologyKey(relation))) continue;
4465
+ const fromName = getTypeName(relation.from);
4466
+ const toName = getTypeName(relation.to);
4467
+ const referent = {
4468
+ type: "compile-time-ontology",
4469
+ name: `${relation.metaEdge.name}(${fromName}, ${toName})`
4470
+ };
4471
+ append(fromName, referent);
4472
+ if (toName !== fromName) append(toName, referent);
4473
+ }
4474
+ return index;
4475
+ }
4476
+ var OPERATOR_MAP = {
4477
+ union: "UNION",
4478
+ unionAll: "UNION ALL",
4479
+ intersect: "INTERSECT",
4480
+ except: "EXCEPT"
4481
+ };
4482
+ function compileSetOperationWithStandardParenthesizedStrategy(op, graphId, dialect, logicalPlan, _schema, compileQuery2) {
4483
+ return compileSetOperationStandard(
4484
+ op,
4485
+ graphId,
4486
+ dialect,
4487
+ logicalPlan,
4488
+ compileQuery2
4489
+ );
4490
+ }
4491
+ function compileSetOperationWithSqliteCompoundStrategy(op, graphId, dialect, logicalPlan, schema, _compileQuery) {
4492
+ return compileSetOperationForSqlite(
4493
+ op,
4494
+ graphId,
4495
+ dialect,
4496
+ logicalPlan,
4497
+ schema
4498
+ );
4499
+ }
4500
+ var SET_OPERATION_STRATEGY_HANDLERS = {
4501
+ standard_parenthesized: compileSetOperationWithStandardParenthesizedStrategy,
4502
+ sqlite_compound: compileSetOperationWithSqliteCompoundStrategy
4503
+ };
4504
+ function runSetOperationPassPipeline(op, graphId, dialect) {
4505
+ let state = {
4506
+ dialect,
4507
+ graphId,
4508
+ logicalPlan: void 0,
4509
+ op
4510
+ };
4511
+ const logicalPlanPass = runCompilerPass(state, {
4512
+ name: "logical_plan",
4513
+ execute(currentState) {
4514
+ return lowerSetOperationToLogicalPlan({
4515
+ dialect: currentState.dialect.name,
4516
+ graphId: currentState.graphId,
4517
+ op: currentState.op
4518
+ });
4519
+ },
4520
+ update(currentState, logicalPlan) {
4521
+ return {
4522
+ ...currentState,
4523
+ logicalPlan
4524
+ };
4525
+ }
4526
+ });
4527
+ state = logicalPlanPass.state;
4528
+ return state;
4529
+ }
4530
+ function compileSetOperation(op, graphId, dialect, schema, compileQuery2) {
4531
+ const passState = runSetOperationPassPipeline(op, graphId, dialect);
4532
+ const { logicalPlan } = passState;
4533
+ if (logicalPlan === void 0) {
4534
+ throw new CompilerInvariantError(
4535
+ "Logical plan pass did not initialize plan state"
4536
+ );
4537
+ }
4538
+ const strategy = dialect.capabilities.setOperationStrategy;
4539
+ const handler = SET_OPERATION_STRATEGY_HANDLERS[strategy];
4540
+ return handler(op, graphId, dialect, logicalPlan, schema, compileQuery2);
4541
+ }
4542
+ function compileSetOperationStandard(op, graphId, dialect, logicalPlan, compileQuery2) {
4543
+ const coreSql = compileSetOperationCoreStandard(
4544
+ op,
4545
+ graphId,
4546
+ dialect,
4547
+ compileQuery2
4548
+ );
4549
+ const suffixClauses = buildSetOperationSuffixClauses(op, dialect);
4550
+ return emitSetOperationQuerySql({
4551
+ baseQuery: coreSql,
4552
+ logicalPlan,
4553
+ ...suffixClauses.length === 0 ? {} : { suffixClauses }
4554
+ });
4555
+ }
4556
+ function compileSetOperationCoreStandard(op, graphId, dialect, compileQuery2) {
4557
+ const left = compileComposableQueryStandard(
4558
+ op.left,
4559
+ graphId,
4560
+ dialect,
4561
+ compileQuery2
4562
+ );
4563
+ const right = compileComposableQueryStandard(
4564
+ op.right,
4565
+ graphId,
4566
+ dialect,
4567
+ compileQuery2
4568
+ );
4569
+ const opSql = sql.raw(OPERATOR_MAP[op.operator]);
4570
+ return sql`(${left}) ${opSql} (${right})`;
4571
+ }
4572
+ function compileComposableQueryStandard(query, graphId, dialect, compileQuery2) {
4573
+ if ("__type" in query) {
4574
+ return compileSetOperationCoreStandard(
4575
+ query,
4576
+ graphId,
4577
+ dialect,
4578
+ compileQuery2
4579
+ );
4580
+ }
4581
+ return compileQuery2(query, graphId);
4582
+ }
4583
+ function runSqliteSetOperationValidationPass(op) {
4584
+ const leaves = [];
4585
+ collectLeafQueries(op, leaves, "q");
4586
+ for (const leaf of leaves) {
4587
+ validateSqliteSetOpLeaf(leaf.ast);
4588
+ }
4589
+ return { leaves };
4590
+ }
4591
+ function compileSetOperationForSqlite(op, graphId, dialect, logicalPlan, schema) {
4592
+ const { leaves } = runSqliteSetOperationValidationPass(op);
4593
+ const allCtes = [];
4594
+ const ctx = {
4595
+ dialect,
4596
+ schema,
4597
+ compileQuery: () => {
4598
+ throw new CompilerInvariantError(
4599
+ "compileQuery is not available in set-operation CTE compilation"
4600
+ );
4601
+ }
4602
+ };
4603
+ for (const leaf of leaves) {
4604
+ const cte = compilePrefixedStartCte(leaf.ast, leaf.prefix, graphId, ctx);
4605
+ allCtes.push(cte);
4606
+ }
4607
+ const selectStatements = [];
4608
+ for (const leaf of leaves) {
4609
+ const select = compilePrefixedSelect(leaf.ast, leaf.prefix, dialect);
4610
+ selectStatements.push(select);
4611
+ }
4612
+ const compoundSelect = buildCompoundSelect(op, leaves, selectStatements);
4613
+ const suffixClauses = buildSetOperationSuffixClauses(op, dialect);
4614
+ return emitSetOperationQuerySql({
4615
+ baseQuery: compoundSelect,
4616
+ ...allCtes.length === 0 ? {} : { ctes: allCtes },
4400
4617
  logicalPlan,
4401
4618
  ...suffixClauses.length === 0 ? {} : { suffixClauses }
4402
4619
  });
@@ -8148,6 +8365,45 @@ var ExecutableQuery = class _ExecutableQuery {
8148
8365
  }
8149
8366
  };
8150
8367
 
8368
+ // src/query/builder/dynamic.ts
8369
+ function describeKindContext(entity, kindNames) {
8370
+ if (kindNames === void 0 || kindNames.length === 0) {
8371
+ return `${entity} (kind unresolved)`;
8372
+ }
8373
+ const list = kindNames.map((k) => `"${k}"`).join(" | ");
8374
+ return `${entity} kind${kindNames.length > 1 ? "s" : ""} ${list}`;
8375
+ }
8376
+ function createDynamicFieldBuilder(introspector, alias, name, kindNames, entity) {
8377
+ const typeInfo = kindNames === void 0 ? void 0 : entity === "node" ? introspector.getSharedFieldTypeInfo(kindNames, name) : introspector.getSharedEdgeFieldTypeInfo(kindNames, name);
8378
+ const where = describeKindContext(entity, kindNames);
8379
+ if (typeInfo === void 0) {
8380
+ throw new Error(`Property "${name}" is not declared on ${where}.`);
8381
+ }
8382
+ const ref = fieldRef(alias, ["props"], {
8383
+ jsonPointer: jsonPointer([name]),
8384
+ valueType: typeInfo.valueType,
8385
+ elementType: typeInfo.elementType
8386
+ });
8387
+ const base = buildFieldBuilderForTypeInfo(ref, typeInfo);
8388
+ const expect = (asserted) => {
8389
+ if (typeInfo.valueType !== asserted) {
8390
+ throw new TypeError(
8391
+ `Property "${name}" on ${where} is ${typeInfo.valueType}, not ${asserted}.`
8392
+ );
8393
+ }
8394
+ return base;
8395
+ };
8396
+ return {
8397
+ ...base,
8398
+ string: () => expect("string"),
8399
+ number: () => expect("number"),
8400
+ date: () => expect("date"),
8401
+ array: () => expect("array"),
8402
+ object: () => expect("object"),
8403
+ embedding: () => expect("embedding")
8404
+ };
8405
+ }
8406
+
8151
8407
  // src/query/builder/executable-aggregate-query.ts
8152
8408
  var NOT_COMPUTED2 = /* @__PURE__ */ Symbol("NOT_COMPUTED");
8153
8409
  var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
@@ -8478,6 +8734,21 @@ var TraversalBuilder = class _TraversalBuilder {
8478
8734
  }
8479
8735
  }
8480
8736
  };
8737
+ if (this.#state.dynamicEdgeAliases.has(alias)) {
8738
+ return {
8739
+ id: idAccessor,
8740
+ kind: kindAccessor,
8741
+ fromId: fromIdAccessor,
8742
+ toId: toIdAccessor,
8743
+ field: (name) => createDynamicFieldBuilder(
8744
+ this.#config.schemaIntrospector,
8745
+ alias,
8746
+ name,
8747
+ allEdgeKinds,
8748
+ "edge"
8749
+ )
8750
+ };
8751
+ }
8481
8752
  return new Proxy({}, {
8482
8753
  get: (_, property) => {
8483
8754
  if (typeof property === "symbol") return;
@@ -8495,6 +8766,35 @@ var TraversalBuilder = class _TraversalBuilder {
8495
8766
  validateSqlIdentifier(alias);
8496
8767
  const includeSubClasses = options?.includeSubClasses ?? false;
8497
8768
  const kinds = includeSubClasses ? this.#config.registry.expandSubClasses(kind) : [kind];
8769
+ const newState = this.#stateWithTraversal(alias, kinds);
8770
+ return new QueryBuilderClass(this.#config, newState);
8771
+ }
8772
+ /**
8773
+ * String-keyed sibling of `to` for runtime-declared target kinds.
8774
+ * Throws `KindNotFoundError` if the kind is not registered.
8775
+ */
8776
+ toDynamic(kind, alias, options) {
8777
+ validateSqlIdentifier(alias);
8778
+ if (!this.#config.registry.hasNodeType(kind)) {
8779
+ throw new KindNotFoundError(kind, "node", {
8780
+ graphId: this.#config.graphId
8781
+ });
8782
+ }
8783
+ this.#assertValidEndpoint(kind);
8784
+ const includeSubClasses = options?.includeSubClasses ?? false;
8785
+ const kinds = includeSubClasses ? this.#config.registry.expandSubClasses(kind) : [kind];
8786
+ const baseState = this.#stateWithTraversal(alias, kinds);
8787
+ const newState = {
8788
+ ...baseState,
8789
+ dynamicNodeAliases: /* @__PURE__ */ new Set([...baseState.dynamicNodeAliases, alias])
8790
+ };
8791
+ return new QueryBuilderClass(this.#config, newState);
8792
+ }
8793
+ /**
8794
+ * Builds the next `QueryBuilderState` after appending a traversal
8795
+ * targeting `alias` with `kinds`. Shared by `to` and `toDynamic`.
8796
+ */
8797
+ #stateWithTraversal(alias, kinds) {
8498
8798
  const traversalBase = {
8499
8799
  edgeAlias: this.#edgeAlias,
8500
8800
  edgeKinds: this.#edgeKinds,
@@ -8520,14 +8820,53 @@ var TraversalBuilder = class _TraversalBuilder {
8520
8820
  }
8521
8821
  }
8522
8822
  } : baseTraversal;
8523
- const newState = {
8823
+ return {
8524
8824
  ...this.#state,
8525
8825
  traversals: [...this.#state.traversals, traversal],
8526
8826
  predicates: [...this.#state.predicates, ...this.#pendingEdgePredicates],
8527
8827
  currentAlias: alias
8528
- // Update current alias to this traversal's target
8529
8828
  };
8530
- return new QueryBuilderClass(this.#config, newState);
8829
+ }
8830
+ /**
8831
+ * Asserts a runtime-declared target kind is a valid endpoint for the
8832
+ * current traversal. Mirrors the compile-time `ValidEdgeTargets`
8833
+ * constraint enforced by `to(...)` — without this check `toDynamic`
8834
+ * would silently accept e.g. `traverseDynamic("authoredBy") +
8835
+ * toDynamic("Document")` where authoredBy.to=[Author] and produce an
8836
+ * empty result set.
8837
+ *
8838
+ * Permissive on unions: if the target is assignable to any valid
8839
+ * endpoint across the edges being traversed, accept. Subclass
8840
+ * relationships are honored via `registry.isAssignableTo`.
8841
+ */
8842
+ #assertValidEndpoint(targetKind) {
8843
+ const expectedKinds = /* @__PURE__ */ new Set();
8844
+ const collectFromEdge = (edgeName, side) => {
8845
+ const edgeType = this.#config.registry.getEdgeType(edgeName);
8846
+ const endpoints = edgeType?.[side];
8847
+ if (!endpoints) return;
8848
+ for (const endpoint of endpoints) {
8849
+ expectedKinds.add(endpoint.kind);
8850
+ }
8851
+ };
8852
+ const forwardSide = this.#direction === "out" ? "to" : "from";
8853
+ const inverseSide = this.#direction === "out" ? "from" : "to";
8854
+ for (const edgeName of this.#edgeKinds) {
8855
+ collectFromEdge(edgeName, forwardSide);
8856
+ }
8857
+ for (const edgeName of this.#inverseEdgeKinds) {
8858
+ collectFromEdge(edgeName, inverseSide);
8859
+ }
8860
+ if (expectedKinds.size === 0) return;
8861
+ for (const expected of expectedKinds) {
8862
+ if (this.#config.registry.isAssignableTo(targetKind, expected)) return;
8863
+ }
8864
+ throw new EndpointError({
8865
+ edgeKind: this.#edgeKinds[0] ?? "(unknown)",
8866
+ endpoint: forwardSide,
8867
+ actualKind: targetKind,
8868
+ expectedKinds: [...expectedKinds]
8869
+ });
8531
8870
  }
8532
8871
  };
8533
8872
 
@@ -8647,6 +8986,30 @@ var QueryBuilder = class _QueryBuilder {
8647
8986
  };
8648
8987
  return new _QueryBuilder(this.#config, newState);
8649
8988
  }
8989
+ /**
8990
+ * String-keyed sibling of `from` for runtime-declared kinds. Throws
8991
+ * `KindNotFoundError` if the kind is not registered. Predicates use
8992
+ * the `n.field("name").number().gte(...)` discriminator.
8993
+ */
8994
+ fromDynamic(kind, alias, options) {
8995
+ validateSqlIdentifier(alias);
8996
+ if (!this.#config.registry.hasNodeType(kind)) {
8997
+ throw new KindNotFoundError(kind, "node", {
8998
+ graphId: this.#config.graphId
8999
+ });
9000
+ }
9001
+ const includeSubClasses = options?.includeSubClasses ?? false;
9002
+ const kinds = includeSubClasses ? this.#config.registry.expandSubClasses(kind) : [kind];
9003
+ const newState = {
9004
+ ...this.#state,
9005
+ startAlias: alias,
9006
+ currentAlias: alias,
9007
+ startKinds: kinds,
9008
+ includeSubClasses,
9009
+ dynamicNodeAliases: /* @__PURE__ */ new Set([...this.#state.dynamicNodeAliases, alias])
9010
+ };
9011
+ return new _QueryBuilder(this.#config, newState);
9012
+ }
8650
9013
  /**
8651
9014
  * Adds a WHERE clause for a node.
8652
9015
  */
@@ -8714,6 +9077,13 @@ var QueryBuilder = class _QueryBuilder {
8714
9077
  false
8715
9078
  );
8716
9079
  }
9080
+ /**
9081
+ * String-keyed sibling of `traverse` for runtime-declared edge kinds.
9082
+ * Throws `KindNotFoundError` if the edge kind is not registered.
9083
+ */
9084
+ traverseDynamic(edgeKind, edgeAlias, options) {
9085
+ return this.#beginDynamicTraversal(edgeKind, edgeAlias, false, options);
9086
+ }
8717
9087
  optionalTraverse(edgeKind, edgeAlias, options) {
8718
9088
  validateSqlIdentifier(edgeAlias);
8719
9089
  const direction = options?.direction ?? "out";
@@ -8737,6 +9107,55 @@ var QueryBuilder = class _QueryBuilder {
8737
9107
  true
8738
9108
  );
8739
9109
  }
9110
+ /**
9111
+ * String-keyed sibling of `optionalTraverse` for runtime-declared edge
9112
+ * kinds. LEFT JOIN semantics — non-matching rows produce a null edge
9113
+ * alias instead of dropping. Throws `KindNotFoundError` if the edge
9114
+ * kind is not registered.
9115
+ */
9116
+ optionalTraverseDynamic(edgeKind, edgeAlias, options) {
9117
+ return this.#beginDynamicTraversal(edgeKind, edgeAlias, true, options);
9118
+ }
9119
+ /**
9120
+ * Shared body for `traverseDynamic` and `optionalTraverseDynamic`.
9121
+ * The only difference is the `optional` flag passed to the
9122
+ * `TraversalBuilder` constructor.
9123
+ */
9124
+ #beginDynamicTraversal(edgeKind, edgeAlias, optional, options) {
9125
+ validateSqlIdentifier(edgeAlias);
9126
+ if (!this.#config.registry.hasEdgeType(edgeKind)) {
9127
+ throw new KindNotFoundError(edgeKind, "edge", {
9128
+ graphId: this.#config.graphId
9129
+ });
9130
+ }
9131
+ const direction = options?.direction ?? "out";
9132
+ const expansion = options?.expand ?? this.#config.defaultTraversalExpansion;
9133
+ const includeImplyingEdges = expansion === "implying" || expansion === "all";
9134
+ const includeInverseEdges = expansion === "inverse" || expansion === "all";
9135
+ const fromAlias = options?.from ?? this.#state.currentAlias;
9136
+ const edgeKinds = this.#expandTraversalEdgeKinds(
9137
+ edgeKind,
9138
+ includeImplyingEdges
9139
+ );
9140
+ const inverseEdgeKinds = includeInverseEdges ? this.#expandInverseTraversalEdgeKinds(edgeKinds, includeImplyingEdges) : [];
9141
+ const newState = {
9142
+ ...this.#state,
9143
+ dynamicEdgeAliases: /* @__PURE__ */ new Set([
9144
+ ...this.#state.dynamicEdgeAliases,
9145
+ edgeAlias
9146
+ ])
9147
+ };
9148
+ return new TraversalBuilder(
9149
+ this.#config,
9150
+ newState,
9151
+ edgeKinds,
9152
+ edgeAlias,
9153
+ direction,
9154
+ fromAlias,
9155
+ inverseEdgeKinds,
9156
+ optional
9157
+ );
9158
+ }
8740
9159
  /**
8741
9160
  * Selects fields to return.
8742
9161
  */
@@ -9046,6 +9465,20 @@ var QueryBuilder = class _QueryBuilder {
9046
9465
  alias,
9047
9466
  () => this.#hasSearchableField(kindNames)
9048
9467
  );
9468
+ if (this.#state.dynamicNodeAliases.has(alias)) {
9469
+ return {
9470
+ id: idAccessor,
9471
+ kind: kindAccessor,
9472
+ $fulltext: fulltextAccessor,
9473
+ field: (name) => createDynamicFieldBuilder(
9474
+ this.#config.schemaIntrospector,
9475
+ alias,
9476
+ name,
9477
+ kindNames,
9478
+ "node"
9479
+ )
9480
+ };
9481
+ }
9049
9482
  return new Proxy({}, {
9050
9483
  get: (_, property) => {
9051
9484
  if (typeof property === "symbol") return;
@@ -9124,6 +9557,21 @@ var QueryBuilder = class _QueryBuilder {
9124
9557
  const toIdAccessor = stringField(
9125
9558
  fieldRef(alias, ["to_id"], { valueType: "string" })
9126
9559
  );
9560
+ if (this.#state.dynamicEdgeAliases.has(alias)) {
9561
+ return {
9562
+ id: idAccessor,
9563
+ kind: kindAccessor,
9564
+ fromId: fromIdAccessor,
9565
+ toId: toIdAccessor,
9566
+ field: (name) => createDynamicFieldBuilder(
9567
+ this.#config.schemaIntrospector,
9568
+ alias,
9569
+ name,
9570
+ edgeKindNames,
9571
+ "edge"
9572
+ )
9573
+ };
9574
+ }
9127
9575
  return new Proxy({}, {
9128
9576
  get: (_, property) => {
9129
9577
  if (typeof property === "symbol") return;
@@ -9543,7 +9991,9 @@ function createQueryBuilder(graphId, registry, options) {
9543
9991
  asOf: void 0,
9544
9992
  groupBy: void 0,
9545
9993
  having: void 0,
9546
- fusion: void 0
9994
+ fusion: void 0,
9995
+ dynamicNodeAliases: /* @__PURE__ */ new Set(),
9996
+ dynamicEdgeAliases: /* @__PURE__ */ new Set()
9547
9997
  };
9548
9998
  return new QueryBuilder(config, initialState);
9549
9999
  }
@@ -10071,17 +10521,17 @@ function createEdgeCollection(config) {
10071
10521
  }
10072
10522
  const results = Array.from({ length: items.length });
10073
10523
  if (toCreate.length > 0) {
10074
- const createInputs = toCreate.map((entry) => entry.input);
10524
+ const createInputs = toCreate.map((entry2) => entry2.input);
10075
10525
  const created = await executeEdgeCreateBatch2(createInputs, target);
10076
- for (const [index, entry] of toCreate.entries()) {
10077
- results[entry.index] = narrowEdge(created[index]);
10526
+ for (const [index, entry2] of toCreate.entries()) {
10527
+ results[entry2.index] = narrowEdge(created[index]);
10078
10528
  }
10079
10529
  }
10080
- for (const entry of toUpdate) {
10081
- const result = await executeEdgeUpsertUpdate2(entry.input, target, {
10082
- clearDeleted: entry.clearDeleted
10530
+ for (const entry2 of toUpdate) {
10531
+ const result = await executeEdgeUpsertUpdate2(entry2.input, target, {
10532
+ clearDeleted: entry2.clearDeleted
10083
10533
  });
10084
- results[entry.index] = narrowEdge(result);
10534
+ results[entry2.index] = narrowEdge(result);
10085
10535
  }
10086
10536
  return results;
10087
10537
  };
@@ -10392,17 +10842,17 @@ function createNodeCollection(config) {
10392
10842
  }
10393
10843
  const results = Array.from({ length: items.length });
10394
10844
  if (toCreate.length > 0) {
10395
- const createInputs = toCreate.map((entry) => entry.input);
10845
+ const createInputs = toCreate.map((entry2) => entry2.input);
10396
10846
  const created = await executeNodeCreateBatch2(createInputs, target);
10397
- for (const [index, entry] of toCreate.entries()) {
10398
- results[entry.index] = narrowNode(created[index]);
10847
+ for (const [index, entry2] of toCreate.entries()) {
10848
+ results[entry2.index] = narrowNode(created[index]);
10399
10849
  }
10400
10850
  }
10401
- for (const entry of toUpdate) {
10402
- const result = await executeNodeUpsertUpdate2(entry.input, target, {
10403
- clearDeleted: entry.clearDeleted
10851
+ for (const entry2 of toUpdate) {
10852
+ const result = await executeNodeUpsertUpdate2(entry2.input, target, {
10853
+ clearDeleted: entry2.clearDeleted
10404
10854
  });
10405
- results[entry.index] = narrowNode(result);
10855
+ results[entry2.index] = narrowNode(result);
10406
10856
  }
10407
10857
  return results;
10408
10858
  };
@@ -10498,51 +10948,614 @@ function createNodeCollection(config) {
10498
10948
  }
10499
10949
  };
10500
10950
  }
10501
-
10502
- // src/store/collection-factory.ts
10503
- function createNodeCollectionsProxy(graph, graphId, registry, backend, operations) {
10504
- const collectionCache = /* @__PURE__ */ new Map();
10505
- return new Proxy({}, {
10506
- get: (_, kind) => {
10507
- if (!Object.hasOwn(graph.nodes, kind)) {
10508
- throw new KindNotFoundError(kind, "node");
10509
- }
10510
- const cached = collectionCache.get(kind);
10511
- if (cached !== void 0) {
10512
- return cached;
10951
+
10952
+ // src/store/collection-factory.ts
10953
+ function createNodeCollectionsProxy(graph, graphId, registry, backend, operations) {
10954
+ const collectionCache = /* @__PURE__ */ new Map();
10955
+ return new Proxy({}, {
10956
+ get: (_, kind) => {
10957
+ if (!Object.hasOwn(graph.nodes, kind)) {
10958
+ throw new KindNotFoundError(kind, "node");
10959
+ }
10960
+ const cached = collectionCache.get(kind);
10961
+ if (cached !== void 0) {
10962
+ return cached;
10963
+ }
10964
+ const collection = createNodeCollection({
10965
+ graphId,
10966
+ kind,
10967
+ backend,
10968
+ ...operations
10969
+ });
10970
+ collectionCache.set(kind, collection);
10971
+ return collection;
10972
+ }
10973
+ });
10974
+ }
10975
+ function createEdgeCollectionsProxy(graph, graphId, registry, backend, operations) {
10976
+ const collectionCache = /* @__PURE__ */ new Map();
10977
+ return new Proxy({}, {
10978
+ get: (_, kind) => {
10979
+ if (!Object.hasOwn(graph.edges, kind)) {
10980
+ throw new KindNotFoundError(kind, "edge");
10981
+ }
10982
+ const cached = collectionCache.get(kind);
10983
+ if (cached !== void 0) {
10984
+ return cached;
10985
+ }
10986
+ const collection = createEdgeCollection({
10987
+ graphId,
10988
+ kind,
10989
+ backend,
10990
+ ...operations
10991
+ });
10992
+ collectionCache.set(kind, collection);
10993
+ return collection;
10994
+ }
10995
+ });
10996
+ }
10997
+
10998
+ // src/store/introspect.ts
10999
+ function introspectSchema(graph, context) {
11000
+ const extension = graph.extension;
11001
+ const { nodes: runtimeNodeNames, edges: runtimeEdgeNames } = extensionKindNames(extension);
11002
+ const deprecated = graph.deprecatedKinds;
11003
+ const kinds = [];
11004
+ for (const [name, registration] of Object.entries(graph.nodes)) {
11005
+ const reg = registration;
11006
+ kinds.push({
11007
+ name,
11008
+ origin: runtimeNodeNames.has(name) ? "runtime" : "compile-time",
11009
+ description: reg.type.description,
11010
+ annotations: reg.type.annotations,
11011
+ deprecated: deprecated.has(name),
11012
+ properties: serializeSchemaProperties(reg.type.schema),
11013
+ unique: (reg.unique ?? []).map((constraint) => ({
11014
+ name: constraint.name,
11015
+ fields: [...constraint.fields],
11016
+ scope: constraint.scope,
11017
+ collation: constraint.collation
11018
+ }))
11019
+ });
11020
+ }
11021
+ const edges = [];
11022
+ for (const [name, registration] of Object.entries(graph.edges)) {
11023
+ const reg = registration;
11024
+ const edgeType = reg.type;
11025
+ edges.push({
11026
+ name,
11027
+ origin: runtimeEdgeNames.has(name) ? "runtime" : "compile-time",
11028
+ description: edgeType.description,
11029
+ from: reg.from.map((entry2) => entry2.kind),
11030
+ to: reg.to.map((entry2) => entry2.kind),
11031
+ cardinality: reg.cardinality ?? "many",
11032
+ endpointExistence: reg.endpointExistence ?? "notDeleted",
11033
+ properties: serializeSchemaProperties(edgeType.schema),
11034
+ annotations: edgeType.annotations,
11035
+ deprecated: deprecated.has(name)
11036
+ });
11037
+ }
11038
+ const runtimeOntologyKeys = buildGraphExtensionOntologyKeySet(extension);
11039
+ const ontology = graph.ontology.map((relation) => ({
11040
+ metaEdge: relation.metaEdge.name,
11041
+ from: getTypeName(relation.from),
11042
+ to: getTypeName(relation.to),
11043
+ origin: runtimeOntologyKeys.has(compileTimeOntologyKey(relation)) ? "runtime" : "compile-time"
11044
+ }));
11045
+ return {
11046
+ graphId: context.graphId,
11047
+ schemaVersion: context.schemaVersion,
11048
+ schemaHash: context.schemaHash,
11049
+ kinds,
11050
+ edges,
11051
+ ontology,
11052
+ deprecatedKinds: new Set(deprecated),
11053
+ extension
11054
+ };
11055
+ }
11056
+
11057
+ // src/store/materialize-shared.ts
11058
+ async function ensureFocusedStatusTable(backend, ensureFocused) {
11059
+ if (ensureFocused !== void 0) {
11060
+ await ensureFocused();
11061
+ return;
11062
+ }
11063
+ await backend.bootstrapTables?.();
11064
+ }
11065
+ async function runMaterialization(items, options, runOne) {
11066
+ if (options.stopOnError === true) {
11067
+ const results = [];
11068
+ for (const item of items) {
11069
+ const entry2 = await runOne(item);
11070
+ results.push(entry2);
11071
+ if (entry2.status === "failed") break;
11072
+ }
11073
+ return results;
11074
+ }
11075
+ return Promise.all(items.map((item) => runOne(item)));
11076
+ }
11077
+
11078
+ // src/store/materialize-indexes.ts
11079
+ async function materializeIndexes(context, options = {}) {
11080
+ const { graph, graphId, backend, schemaVersion } = context;
11081
+ if (backend.executeDdl === void 0) {
11082
+ throw new ConfigurationError(
11083
+ "store.materializeIndexes() requires a backend with `executeDdl` support. The bundled SQLite and Postgres backends provide it; a custom backend without `executeDdl` cannot run index DDL.",
11084
+ { code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
11085
+ );
11086
+ }
11087
+ if (backend.getIndexMaterialization === void 0 || backend.recordIndexMaterialization === void 0) {
11088
+ throw new ConfigurationError(
11089
+ "store.materializeIndexes() requires a backend with index materialization status primitives. The bundled SQLite and Postgres backends provide them; a custom backend must implement `getIndexMaterialization` and `recordIndexMaterialization`.",
11090
+ { code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
11091
+ );
11092
+ }
11093
+ const declared = graph.indexes ?? [];
11094
+ const kindFilter = options.kinds === void 0 ? void 0 : new Set(options.kinds);
11095
+ if (kindFilter !== void 0) {
11096
+ for (const name of kindFilter) {
11097
+ if (!isKnownKind(graph, name)) {
11098
+ throw new KindNotFoundError(
11099
+ name,
11100
+ Object.hasOwn(graph.edges, name) ? "edge" : "node",
11101
+ {
11102
+ graphId,
11103
+ suggestion: "Only kinds declared on the graph (compile-time or runtime) can be passed to materializeIndexes."
11104
+ }
11105
+ );
11106
+ }
11107
+ }
11108
+ }
11109
+ const candidates = declared.filter(
11110
+ (index) => kindFilter === void 0 ? true : kindFilter.has(index.kind)
11111
+ );
11112
+ if (candidates.length === 0) {
11113
+ return { results: [] };
11114
+ }
11115
+ await ensureFocusedStatusTable(
11116
+ backend,
11117
+ backend.ensureIndexMaterializationsTable
11118
+ );
11119
+ const dialect = backend.dialect;
11120
+ const tableNames = backend.tableNames;
11121
+ const ddlOptions = {
11122
+ ifNotExists: true,
11123
+ concurrent: dialect === "postgres",
11124
+ ...tableNames?.nodes === void 0 ? {} : { nodesTableName: tableNames.nodes },
11125
+ ...tableNames?.edges === void 0 ? {} : { edgesTableName: tableNames.edges }
11126
+ };
11127
+ const statusKeys = candidates.map(
11128
+ (declaration) => statusKeyFor(declaration, graphId)
11129
+ );
11130
+ const existingByStatusKey = await preloadMaterializations(
11131
+ backend,
11132
+ statusKeys
11133
+ );
11134
+ const materializeEntry = (declaration) => declaration.entity === "vector" ? materializeVectorIndex(
11135
+ declaration,
11136
+ backend,
11137
+ graphId,
11138
+ schemaVersion,
11139
+ existingByStatusKey
11140
+ ) : materializeRelationalIndex(
11141
+ declaration,
11142
+ backend,
11143
+ dialect,
11144
+ ddlOptions,
11145
+ graphId,
11146
+ schemaVersion,
11147
+ existingByStatusKey
11148
+ );
11149
+ if (options.stopOnError === true) {
11150
+ const results = [];
11151
+ for (const declaration of candidates) {
11152
+ const entry2 = await materializeEntry(declaration);
11153
+ results.push(entry2);
11154
+ if (entry2.status === "failed") break;
11155
+ }
11156
+ return { results };
11157
+ }
11158
+ const buckets = /* @__PURE__ */ new Map();
11159
+ for (const declaration of candidates) {
11160
+ const key = parallelBucketKey(declaration);
11161
+ const list = buckets.get(key);
11162
+ if (list === void 0) buckets.set(key, [declaration]);
11163
+ else list.push(declaration);
11164
+ }
11165
+ const bucketResults = await Promise.all(
11166
+ [...buckets.values()].map(async (group) => {
11167
+ const out = [];
11168
+ for (const declaration of group) {
11169
+ out.push(await materializeEntry(declaration));
11170
+ }
11171
+ return out;
11172
+ })
11173
+ );
11174
+ const byDeclaration = /* @__PURE__ */ new Map();
11175
+ let bucketIndex = 0;
11176
+ for (const group of buckets.values()) {
11177
+ const entries = bucketResults[bucketIndex];
11178
+ for (const [declarationIndex, declaration] of group.entries()) {
11179
+ byDeclaration.set(declaration, entries[declarationIndex]);
11180
+ }
11181
+ bucketIndex += 1;
11182
+ }
11183
+ return {
11184
+ results: candidates.map((declaration) => byDeclaration.get(declaration))
11185
+ };
11186
+ }
11187
+ function parallelBucketKey(declaration) {
11188
+ return declaration.entity;
11189
+ }
11190
+ async function materializeRelationalIndex(declaration, backend, dialect, ddlOptions, graphId, schemaVersion, existingByStatusKey) {
11191
+ const ddl = generateIndexDDL(declaration, dialect, ddlOptions);
11192
+ const targetTable = declaration.entity === "node" ? ddlOptions.nodesTableName ?? "typegraph_nodes" : ddlOptions.edgesTableName ?? "typegraph_edges";
11193
+ const signature = await computeIndexSignature(
11194
+ dialect,
11195
+ targetTable,
11196
+ declaration
11197
+ );
11198
+ return materializeOne(declaration, backend, graphId, schemaVersion, {
11199
+ statusKey: declaration.name,
11200
+ signature,
11201
+ driftLabel: "Index",
11202
+ run: () => backend.executeDdl(ddl),
11203
+ existingByStatusKey
11204
+ });
11205
+ }
11206
+ async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey) {
11207
+ if (declaration.indexType === "none") {
11208
+ return skippedEntry(
11209
+ declaration,
11210
+ "indexType: 'none' opts out of automatic materialization"
11211
+ );
11212
+ }
11213
+ const vectorCapability = backend.capabilities.vector;
11214
+ if (vectorCapability?.supported !== true || backend.createVectorIndex === void 0) {
11215
+ return skippedEntry(
11216
+ declaration,
11217
+ `Backend (${backend.dialect}) does not support vector indexes in its current configuration`
11218
+ );
11219
+ }
11220
+ if (!vectorCapability.indexTypes.includes(declaration.indexType)) {
11221
+ return skippedEntry(
11222
+ declaration,
11223
+ `Backend (${backend.dialect}) does not support index type "${declaration.indexType}" for vector indexes; supported: ${vectorCapability.indexTypes.join(", ") || "(none)"}`
11224
+ );
11225
+ }
11226
+ const embeddingsTable = backend.tableNames?.embeddings ?? "typegraph_node_embeddings";
11227
+ const signature = await computeIndexSignature(
11228
+ backend.dialect,
11229
+ embeddingsTable,
11230
+ declaration
11231
+ );
11232
+ const params = {
11233
+ graphId,
11234
+ nodeKind: declaration.kind,
11235
+ fieldPath: declaration.fieldPath,
11236
+ dimensions: declaration.dimensions,
11237
+ metric: declaration.metric,
11238
+ indexType: declaration.indexType,
11239
+ indexParams: {
11240
+ m: declaration.indexParams.m,
11241
+ efConstruction: declaration.indexParams.efConstruction,
11242
+ ...declaration.indexParams.lists === void 0 ? {} : { lists: declaration.indexParams.lists }
11243
+ },
11244
+ concurrent: backend.dialect === "postgres"
11245
+ };
11246
+ return materializeOne(declaration, backend, graphId, schemaVersion, {
11247
+ // Compound status-table key for vector entries. Pgvector creates
11248
+ // one physical index per (graphId, kind, field) — so the
11249
+ // per-deployment status table needs to disambiguate entries that
11250
+ // SHARE a declaration name but belong to different graphs.
11251
+ // Applied uniformly to auto-derived AND explicit declarations.
11252
+ statusKey: vectorStatusKey(graphId, declaration.name),
11253
+ signature,
11254
+ driftLabel: "Vector index",
11255
+ run: () => backend.createVectorIndex(params),
11256
+ existingByStatusKey
11257
+ });
11258
+ }
11259
+ async function materializeOne(declaration, backend, graphId, schemaVersion, action) {
11260
+ const recordIndexMaterialization = backend.recordIndexMaterialization;
11261
+ const { statusKey, signature, driftLabel, run, existingByStatusKey } = action;
11262
+ const statusOverride = statusKey === declaration.name ? void 0 : { statusName: statusKey };
11263
+ const existing = existingByStatusKey.get(statusKey);
11264
+ if (existing?.materializedAt !== void 0) {
11265
+ if (existing.signature === signature) {
11266
+ return entry(declaration, "alreadyMaterialized");
11267
+ }
11268
+ const error = new Error(
11269
+ `${driftLabel} "${declaration.name}" already materialized with a different signature (recorded by graph "${existing.graphId}" at version ${existing.schemaVersion}). Drop the index manually and retry, or rename the new declaration.`
11270
+ );
11271
+ await recordIndexMaterialization(
11272
+ buildAttempt({
11273
+ declaration,
11274
+ graphId,
11275
+ signature,
11276
+ schemaVersion,
11277
+ materializedAt: void 0,
11278
+ error,
11279
+ ...statusOverride
11280
+ })
11281
+ );
11282
+ return entry(declaration, "failed", error);
11283
+ }
11284
+ try {
11285
+ await run();
11286
+ const attemptedAt = nowIso();
11287
+ await recordIndexMaterialization(
11288
+ buildAttempt({
11289
+ declaration,
11290
+ graphId,
11291
+ signature,
11292
+ schemaVersion,
11293
+ materializedAt: attemptedAt,
11294
+ error: void 0,
11295
+ attemptedAt,
11296
+ ...statusOverride
11297
+ })
11298
+ );
11299
+ return entry(declaration, "created");
11300
+ } catch (error_) {
11301
+ const error = error_ instanceof Error ? error_ : new Error(String(error_));
11302
+ await recordIndexMaterialization(
11303
+ buildAttempt({
11304
+ declaration,
11305
+ graphId,
11306
+ signature,
11307
+ schemaVersion,
11308
+ materializedAt: void 0,
11309
+ error,
11310
+ ...statusOverride
11311
+ })
11312
+ );
11313
+ return entry(declaration, "failed", error);
11314
+ }
11315
+ }
11316
+ function entry(declaration, status, error) {
11317
+ return {
11318
+ indexName: declaration.name,
11319
+ entity: declaration.entity,
11320
+ kind: declaration.kind,
11321
+ status,
11322
+ ...error === void 0 ? {} : { error }
11323
+ };
11324
+ }
11325
+ function skippedEntry(declaration, reason) {
11326
+ return {
11327
+ indexName: declaration.name,
11328
+ entity: "vector",
11329
+ kind: declaration.kind,
11330
+ status: "skipped",
11331
+ reason
11332
+ };
11333
+ }
11334
+ function vectorStatusKey(graphId, declarationName) {
11335
+ return `${graphId}::${declarationName}`;
11336
+ }
11337
+ function statusKeyFor(declaration, graphId) {
11338
+ return declaration.entity === "vector" ? vectorStatusKey(graphId, declaration.name) : declaration.name;
11339
+ }
11340
+ async function preloadMaterializations(backend, statusKeys) {
11341
+ const map = /* @__PURE__ */ new Map();
11342
+ if (statusKeys.length === 0) return map;
11343
+ if (backend.getIndexMaterializations !== void 0) {
11344
+ const rows2 = await backend.getIndexMaterializations(statusKeys);
11345
+ for (const row of rows2) map.set(row.indexName, row);
11346
+ return map;
11347
+ }
11348
+ const getOne = backend.getIndexMaterialization;
11349
+ const rows = await Promise.all(statusKeys.map((key) => getOne(key)));
11350
+ for (const [index, row] of rows.entries()) {
11351
+ if (row !== void 0) map.set(statusKeys[index], row);
11352
+ }
11353
+ return map;
11354
+ }
11355
+ function buildAttempt(args) {
11356
+ return {
11357
+ indexName: args.statusName ?? args.declaration.name,
11358
+ graphId: args.graphId,
11359
+ entity: args.declaration.entity,
11360
+ kind: args.declaration.kind,
11361
+ signature: args.signature,
11362
+ schemaVersion: args.schemaVersion,
11363
+ attemptedAt: args.attemptedAt ?? nowIso(),
11364
+ materializedAt: args.materializedAt,
11365
+ error: args.error?.message
11366
+ };
11367
+ }
11368
+ async function computeIndexSignature(dialect, targetTableName, declaration) {
11369
+ const hashable = { dialect, targetTableName, declaration };
11370
+ const json = JSON.stringify(hashable, sortedReplacer);
11371
+ return sha256Hex(json, 16);
11372
+ }
11373
+
11374
+ // src/store/materialize-removals.ts
11375
+ var ENTITY_KINDS = ["node", "edge"];
11376
+ function buildPendingKindRemoval(args) {
11377
+ return { ...args, removedAt: void 0, error: void 0 };
11378
+ }
11379
+ async function materializeRemovals(context, options = {}) {
11380
+ const { backend, graphId } = context;
11381
+ if (backend.executeDdl === void 0 || backend.recordKindRemoval === void 0 || backend.getPendingKindRemovals === void 0) {
11382
+ throw new ConfigurationError(
11383
+ "store.materializeRemovals() requires a backend with kind-removal primitives (executeDdl, recordKindRemoval, getPendingKindRemovals). The bundled SQLite and Postgres backends provide them.",
11384
+ { code: "MATERIALIZE_REMOVALS_BACKEND_UNSUPPORTED" }
11385
+ );
11386
+ }
11387
+ await ensureFocusedStatusTable(backend, backend.ensureKindRemovalsTable);
11388
+ await reconcilePendingRemovals(context);
11389
+ const pending = await backend.getPendingKindRemovals(graphId);
11390
+ const kindFilter = options.kinds === void 0 ? void 0 : new Set(options.kinds);
11391
+ const candidates = pending.filter(
11392
+ (row) => kindFilter === void 0 ? true : kindFilter.has(row.kindName)
11393
+ );
11394
+ if (candidates.length === 0) return { results: [] };
11395
+ const tableNames = backend.tableNames;
11396
+ const nodesTable = tableNames?.nodes ?? "typegraph_nodes";
11397
+ const edgesTable = tableNames?.edges ?? "typegraph_edges";
11398
+ const embeddingsTable = tableNames?.embeddings ?? "typegraph_node_embeddings";
11399
+ const fulltextTable = tableNames?.fulltext ?? "typegraph_node_fulltext";
11400
+ const uniquesTable = tableNames?.uniques ?? "typegraph_node_uniques";
11401
+ const ctx = {
11402
+ backend,
11403
+ graphId,
11404
+ nodesTable,
11405
+ edgesTable,
11406
+ embeddingsTable,
11407
+ fulltextTable,
11408
+ uniquesTable
11409
+ };
11410
+ return {
11411
+ results: await runMaterialization(
11412
+ candidates,
11413
+ options,
11414
+ (removal) => materializeOne2(removal, ctx)
11415
+ )
11416
+ };
11417
+ }
11418
+ async function reconcilePendingRemovals(context) {
11419
+ const { backend, graphId } = context;
11420
+ const recordKindRemoval = backend.recordKindRemoval;
11421
+ if (recordKindRemoval === void 0) return;
11422
+ const activeRow = await backend.getActiveSchema(graphId);
11423
+ if (activeRow === void 0 || activeRow.version <= 1) return;
11424
+ if (backend.ensureReconciliationMarkersTable !== void 0) {
11425
+ await backend.ensureReconciliationMarkersTable();
11426
+ }
11427
+ const marker = backend.getReconciliationMarker === void 0 ? void 0 : await backend.getReconciliationMarker(graphId);
11428
+ if (marker !== void 0 && marker >= activeRow.version) return;
11429
+ const allRemovals = await (backend.getAllKindRemovals === void 0 ? backend.getPendingKindRemovals(graphId) : backend.getAllKindRemovals(graphId));
11430
+ const recorded = new Set(
11431
+ allRemovals.map(
11432
+ (row) => kindRemovalKey(row.entity, row.kindName, row.schemaVersion)
11433
+ )
11434
+ );
11435
+ const reconciliations = [];
11436
+ const stopAtVersion = marker ?? 1;
11437
+ let currentRow = activeRow;
11438
+ let currentSchema = parseSerializedSchema(activeRow.schema_doc);
11439
+ while (currentRow.version > stopAtVersion) {
11440
+ const priorRow = await backend.getSchemaVersion(
11441
+ graphId,
11442
+ currentRow.version - 1
11443
+ );
11444
+ if (priorRow === void 0) break;
11445
+ const priorSchema = parseSerializedSchema(priorRow.schema_doc);
11446
+ for (const entity of ENTITY_KINDS) {
11447
+ const priorEntries = priorSchema[entity === "node" ? "nodes" : "edges"];
11448
+ const currentEntries = currentSchema[entity === "node" ? "nodes" : "edges"];
11449
+ for (const kindName of Object.keys(priorEntries)) {
11450
+ if (kindName in currentEntries) continue;
11451
+ if (recorded.has(kindRemovalKey(entity, kindName, currentRow.version))) {
11452
+ continue;
11453
+ }
11454
+ reconciliations.push({
11455
+ kindName,
11456
+ entity,
11457
+ schemaVersion: currentRow.version
11458
+ });
10513
11459
  }
10514
- const collection = createNodeCollection({
10515
- graphId,
10516
- kind,
10517
- backend,
10518
- ...operations
10519
- });
10520
- collectionCache.set(kind, collection);
10521
- return collection;
10522
11460
  }
10523
- });
11461
+ currentRow = priorRow;
11462
+ currentSchema = priorSchema;
11463
+ }
11464
+ if (reconciliations.length > 0) {
11465
+ const attemptedAt = nowIso();
11466
+ await Promise.all(
11467
+ reconciliations.map(
11468
+ (entry2) => recordKindRemoval(
11469
+ buildPendingKindRemoval({
11470
+ graphId,
11471
+ kindName: entry2.kindName,
11472
+ entity: entry2.entity,
11473
+ schemaVersion: entry2.schemaVersion,
11474
+ attemptedAt
11475
+ })
11476
+ )
11477
+ )
11478
+ );
11479
+ }
11480
+ if (backend.setReconciliationMarker !== void 0) {
11481
+ await backend.setReconciliationMarker(graphId, activeRow.version);
11482
+ }
10524
11483
  }
10525
- function createEdgeCollectionsProxy(graph, graphId, registry, backend, operations) {
10526
- const collectionCache = /* @__PURE__ */ new Map();
10527
- return new Proxy({}, {
10528
- get: (_, kind) => {
10529
- if (!Object.hasOwn(graph.edges, kind)) {
10530
- throw new KindNotFoundError(kind, "edge");
10531
- }
10532
- const cached = collectionCache.get(kind);
10533
- if (cached !== void 0) {
10534
- return cached;
10535
- }
10536
- const collection = createEdgeCollection({
10537
- graphId,
10538
- kind,
10539
- backend,
10540
- ...operations
10541
- });
10542
- collectionCache.set(kind, collection);
10543
- return collection;
11484
+ function kindRemovalKey(entity, kindName, version) {
11485
+ return `${entity}|${kindName}|${version}`;
11486
+ }
11487
+ async function materializeOne2(row, ctx) {
11488
+ const executeDdl = ctx.backend.executeDdl;
11489
+ const recordKindRemoval = ctx.backend.recordKindRemoval;
11490
+ try {
11491
+ const deletes = [];
11492
+ const graphLit = literal2(ctx.graphId);
11493
+ const kindLit = literal2(row.kindName);
11494
+ if (row.entity === "node") {
11495
+ deletes.push(
11496
+ executeDdl(
11497
+ `DELETE FROM ${quote(ctx.nodesTable)} WHERE graph_id = ${graphLit} AND kind = ${kindLit}`
11498
+ ),
11499
+ executeDdl(
11500
+ `DELETE FROM ${quote(ctx.edgesTable)} WHERE graph_id = ${graphLit} AND (from_kind = ${kindLit} OR to_kind = ${kindLit})`
11501
+ ),
11502
+ executeDdl(
11503
+ `DELETE FROM ${quote(ctx.embeddingsTable)} WHERE graph_id = ${graphLit} AND node_kind = ${kindLit}`
11504
+ ),
11505
+ executeDdl(
11506
+ `DELETE FROM ${quote(ctx.fulltextTable)} WHERE graph_id = ${graphLit} AND node_kind = ${kindLit}`
11507
+ ),
11508
+ executeDdl(
11509
+ `DELETE FROM ${quote(ctx.uniquesTable)} WHERE graph_id = ${graphLit} AND node_kind = ${kindLit}`
11510
+ )
11511
+ );
11512
+ } else {
11513
+ deletes.push(
11514
+ executeDdl(
11515
+ `DELETE FROM ${quote(ctx.edgesTable)} WHERE graph_id = ${graphLit} AND kind = ${kindLit}`
11516
+ )
11517
+ );
10544
11518
  }
10545
- });
11519
+ await Promise.all(deletes);
11520
+ const removedAt = nowIso();
11521
+ await recordKindRemoval({
11522
+ graphId: ctx.graphId,
11523
+ kindName: row.kindName,
11524
+ entity: row.entity,
11525
+ schemaVersion: row.schemaVersion,
11526
+ attemptedAt: removedAt,
11527
+ removedAt,
11528
+ error: void 0
11529
+ });
11530
+ return {
11531
+ kind: row.kindName,
11532
+ entity: row.entity,
11533
+ status: "removed"
11534
+ };
11535
+ } catch (error_) {
11536
+ const error = error_ instanceof Error ? error_ : new Error(String(error_));
11537
+ await recordKindRemoval({
11538
+ graphId: ctx.graphId,
11539
+ kindName: row.kindName,
11540
+ entity: row.entity,
11541
+ schemaVersion: row.schemaVersion,
11542
+ attemptedAt: nowIso(),
11543
+ removedAt: void 0,
11544
+ error: error.message
11545
+ });
11546
+ return {
11547
+ kind: row.kindName,
11548
+ entity: row.entity,
11549
+ status: "failed",
11550
+ error
11551
+ };
11552
+ }
11553
+ }
11554
+ function quote(identifier) {
11555
+ return `"${identifier.replaceAll('"', '""')}"`;
11556
+ }
11557
+ function literal2(value) {
11558
+ return `'${value.replaceAll("'", "''")}'`;
10546
11559
  }
10547
11560
 
10548
11561
  // src/constraints/index.ts
@@ -11389,13 +12402,13 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
11389
12402
  });
11390
12403
  }
11391
12404
  const uniqueEndpoints = /* @__PURE__ */ new Map();
11392
- for (const entry of validated) {
11393
- if (!uniqueEndpoints.has(entry.endpointKey)) {
11394
- uniqueEndpoints.set(entry.endpointKey, {
11395
- fromKind: entry.fromKind,
11396
- fromId: entry.fromId,
11397
- toKind: entry.toKind,
11398
- toId: entry.toId
12405
+ for (const entry2 of validated) {
12406
+ if (!uniqueEndpoints.has(entry2.endpointKey)) {
12407
+ uniqueEndpoints.set(entry2.endpointKey, {
12408
+ fromKind: entry2.fromKind,
12409
+ fromId: entry2.fromId,
12410
+ toKind: entry2.toKind,
12411
+ toId: entry2.toId
11399
12412
  });
11400
12413
  }
11401
12414
  }
@@ -11417,29 +12430,29 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
11417
12430
  const toFetch = [];
11418
12431
  const duplicateOf = [];
11419
12432
  const seenKeys = /* @__PURE__ */ new Map();
11420
- for (const [index, entry] of validated.entries()) {
11421
- const previousIndex = seenKeys.get(entry.compositeKey);
12433
+ for (const [index, entry2] of validated.entries()) {
12434
+ const previousIndex = seenKeys.get(entry2.compositeKey);
11422
12435
  if (previousIndex !== void 0) {
11423
12436
  duplicateOf.push({ index, sourceIndex: previousIndex });
11424
12437
  continue;
11425
12438
  }
11426
- seenKeys.set(entry.compositeKey, index);
11427
- const candidateRows = rowsByEndpoint.get(entry.endpointKey) ?? [];
12439
+ seenKeys.set(entry2.compositeKey, index);
12440
+ const candidateRows = rowsByEndpoint.get(entry2.endpointKey) ?? [];
11428
12441
  const { liveRow, deletedRow } = findMatchingEdge(
11429
12442
  candidateRows,
11430
12443
  matchOn,
11431
- entry.validatedProps
12444
+ entry2.validatedProps
11432
12445
  );
11433
12446
  if (liveRow === void 0 && deletedRow === void 0) {
11434
12447
  toCreate.push({
11435
12448
  index,
11436
12449
  input: {
11437
12450
  kind,
11438
- fromKind: entry.fromKind,
11439
- fromId: entry.fromId,
11440
- toKind: entry.toKind,
11441
- toId: entry.toId,
11442
- props: entry.validatedProps
12451
+ fromKind: entry2.fromKind,
12452
+ fromId: entry2.fromId,
12453
+ toKind: entry2.toKind,
12454
+ toId: entry2.toId,
12455
+ props: entry2.validatedProps
11443
12456
  }
11444
12457
  });
11445
12458
  } else {
@@ -11451,32 +12464,32 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
11451
12464
  index,
11452
12465
  row: bestRow,
11453
12466
  isDeleted: liveRow === void 0,
11454
- validatedProps: entry.validatedProps,
11455
- fromKind: entry.fromKind,
11456
- fromId: entry.fromId,
11457
- toKind: entry.toKind,
11458
- toId: entry.toId
12467
+ validatedProps: entry2.validatedProps,
12468
+ fromKind: entry2.fromKind,
12469
+ fromId: entry2.fromId,
12470
+ toKind: entry2.toKind,
12471
+ toId: entry2.toId
11459
12472
  });
11460
12473
  }
11461
12474
  }
11462
12475
  const results = Array.from({ length: items.length });
11463
12476
  if (toCreate.length > 0) {
11464
- const createInputs = toCreate.map((entry) => entry.input);
12477
+ const createInputs = toCreate.map((entry2) => entry2.input);
11465
12478
  const createdEdges = await executeEdgeCreateBatch(
11466
12479
  ctx,
11467
12480
  createInputs,
11468
12481
  backend
11469
12482
  );
11470
- for (const [batchIndex, entry] of toCreate.entries()) {
11471
- results[entry.index] = {
12483
+ for (const [batchIndex, entry2] of toCreate.entries()) {
12484
+ results[entry2.index] = {
11472
12485
  edge: createdEdges[batchIndex],
11473
12486
  action: "created"
11474
12487
  };
11475
12488
  }
11476
12489
  }
11477
- for (const entry of toFetch) {
11478
- if (entry.isDeleted) {
11479
- const effectiveValidTo = entry.row.valid_to;
12490
+ for (const entry2 of toFetch) {
12491
+ if (entry2.isDeleted) {
12492
+ const effectiveValidTo = entry2.row.valid_to;
11480
12493
  const constraintContext = {
11481
12494
  graphId: ctx.graphId,
11482
12495
  registry: ctx.registry,
@@ -11486,28 +12499,28 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
11486
12499
  constraintContext,
11487
12500
  kind,
11488
12501
  cardinality,
11489
- entry.fromKind,
11490
- entry.fromId,
11491
- entry.toKind,
11492
- entry.toId,
12502
+ entry2.fromKind,
12503
+ entry2.fromId,
12504
+ entry2.toKind,
12505
+ entry2.toId,
11493
12506
  effectiveValidTo
11494
12507
  );
11495
12508
  const edge = await executeEdgeUpsertUpdate(
11496
12509
  ctx,
11497
- { id: entry.row.id, props: entry.validatedProps },
12510
+ { id: entry2.row.id, props: entry2.validatedProps },
11498
12511
  backend,
11499
12512
  { clearDeleted: true }
11500
12513
  );
11501
- results[entry.index] = { edge, action: "resurrected" };
12514
+ results[entry2.index] = { edge, action: "resurrected" };
11502
12515
  } else if (ifExists === "update") {
11503
12516
  const edge = await executeEdgeUpsertUpdate(
11504
12517
  ctx,
11505
- { id: entry.row.id, props: entry.validatedProps },
12518
+ { id: entry2.row.id, props: entry2.validatedProps },
11506
12519
  backend
11507
12520
  );
11508
- results[entry.index] = { edge, action: "updated" };
12521
+ results[entry2.index] = { edge, action: "updated" };
11509
12522
  } else {
11510
- results[entry.index] = { edge: rowToEdge(entry.row), action: "found" };
12523
+ results[entry2.index] = { edge: rowToEdge(entry2.row), action: "found" };
11511
12524
  }
11512
12525
  }
11513
12526
  for (const { index, sourceIndex } of duplicateOf) {
@@ -12480,7 +13493,7 @@ function validateAndComputeKeys(nodeKind, kind, constraint, items) {
12480
13493
  function collectUniqueKeys(validated) {
12481
13494
  return [
12482
13495
  ...new Set(
12483
- validated.map((entry) => entry.key).filter((key) => key !== void 0)
13496
+ validated.map((entry2) => entry2.key).filter((key) => key !== void 0)
12484
13497
  )
12485
13498
  ];
12486
13499
  }
@@ -12586,21 +13599,21 @@ async function executeNodeBulkGetOrCreateByConstraint(ctx, kind, constraintName,
12586
13599
  }
12587
13600
  const results = Array.from({ length: items.length });
12588
13601
  if (toCreate.length > 0) {
12589
- const createInputs = toCreate.map((entry) => entry.input);
13602
+ const createInputs = toCreate.map((entry2) => entry2.input);
12590
13603
  const createdNodes = await executeNodeCreateBatch(
12591
13604
  ctx,
12592
13605
  createInputs,
12593
13606
  backend
12594
13607
  );
12595
- for (const [batchIndex, entry] of toCreate.entries()) {
12596
- results[entry.index] = {
13608
+ for (const [batchIndex, entry2] of toCreate.entries()) {
13609
+ results[entry2.index] = {
12597
13610
  node: createdNodes[batchIndex],
12598
13611
  action: "created"
12599
13612
  };
12600
13613
  }
12601
13614
  }
12602
- for (const entry of toFetch) {
12603
- const { index, concreteKind, validatedProps, isSoftDeleted, nodeId } = entry;
13615
+ for (const entry2 of toFetch) {
13616
+ const { index, concreteKind, validatedProps, isSoftDeleted, nodeId } = entry2;
12604
13617
  const existingRow = await backend.getNode(
12605
13618
  ctx.graphId,
12606
13619
  concreteKind,
@@ -12845,7 +13858,7 @@ async function executeFulltextSearch(ctx, nodeKind, options) {
12845
13858
  };
12846
13859
  const rows = await backend.fulltextSearch(params);
12847
13860
  if (rows.length === 0) return [];
12848
- const ids = rows.map((r) => r.nodeId);
13861
+ const ids = rows.map((row) => row.nodeId);
12849
13862
  const nodeMap = await fetchNodesByIds(backend, graphId, nodeKind, ids);
12850
13863
  const hits = [];
12851
13864
  let rank = 1;
@@ -12862,6 +13875,41 @@ async function executeFulltextSearch(ctx, nodeKind, options) {
12862
13875
  }
12863
13876
  return hits;
12864
13877
  }
13878
+ async function executeVectorSearch(ctx, nodeKind, options) {
13879
+ const { backend, graphId } = ctx;
13880
+ if (!backend.vectorSearch) {
13881
+ throw new ConfigurationError("Backend does not support vector search", {
13882
+ backend: backend.dialect,
13883
+ capability: "vector"
13884
+ });
13885
+ }
13886
+ if (!Number.isInteger(options.limit) || options.limit <= 0) {
13887
+ throw new RangeError(
13888
+ `vectorSearch.limit must be a positive integer, got: ${options.limit}`
13889
+ );
13890
+ }
13891
+ const rows = await backend.vectorSearch({
13892
+ graphId,
13893
+ nodeKind,
13894
+ fieldPath: options.fieldPath,
13895
+ queryEmbedding: options.queryEmbedding,
13896
+ metric: options.metric ?? "cosine",
13897
+ limit: options.limit,
13898
+ ...options.minScore === void 0 ? {} : { minScore: options.minScore }
13899
+ });
13900
+ if (rows.length === 0) return [];
13901
+ const ids = rows.map((row) => row.nodeId);
13902
+ const nodeMap = await fetchNodesByIds(backend, graphId, nodeKind, ids);
13903
+ const hits = [];
13904
+ let rank = 1;
13905
+ for (const row of rows) {
13906
+ const node = nodeMap.get(row.nodeId);
13907
+ if (!node) continue;
13908
+ hits.push({ node, score: row.score, rank });
13909
+ rank += 1;
13910
+ }
13911
+ return hits;
13912
+ }
12865
13913
  async function executeHybridSearch(ctx, nodeKind, options) {
12866
13914
  const { backend, graphId } = ctx;
12867
13915
  if (!backend.vectorSearch) {
@@ -12922,58 +13970,58 @@ async function executeHybridSearch(ctx, nodeKind, options) {
12922
13970
  for (const [index, row] of vectorRows.entries()) {
12923
13971
  const rank2 = index + 1;
12924
13972
  const contribution = vectorWeight / (fusionK + rank2);
12925
- const entry = fused.get(row.nodeId) ?? {
13973
+ const entry2 = fused.get(row.nodeId) ?? {
12926
13974
  nodeId: row.nodeId,
12927
13975
  fusedScore: 0
12928
13976
  };
12929
- entry.fusedScore += contribution;
12930
- entry.vectorRank = rank2;
12931
- entry.vectorScore = row.score;
12932
- fused.set(row.nodeId, entry);
13977
+ entry2.fusedScore += contribution;
13978
+ entry2.vectorRank = rank2;
13979
+ entry2.vectorScore = row.score;
13980
+ fused.set(row.nodeId, entry2);
12933
13981
  }
12934
13982
  for (const [index, row] of fulltextRows.entries()) {
12935
13983
  const rank2 = index + 1;
12936
13984
  const contribution = fulltextWeight / (fusionK + rank2);
12937
- const entry = fused.get(row.nodeId) ?? {
13985
+ const entry2 = fused.get(row.nodeId) ?? {
12938
13986
  nodeId: row.nodeId,
12939
13987
  fusedScore: 0
12940
13988
  };
12941
- entry.fusedScore += contribution;
12942
- entry.fulltextRank = rank2;
12943
- entry.fulltextScore = row.score;
13989
+ entry2.fusedScore += contribution;
13990
+ entry2.fulltextRank = rank2;
13991
+ entry2.fulltextScore = row.score;
12944
13992
  if (row.snippet !== void 0) {
12945
- entry.fulltextSnippet = row.snippet;
13993
+ entry2.fulltextSnippet = row.snippet;
12946
13994
  }
12947
- fused.set(row.nodeId, entry);
13995
+ fused.set(row.nodeId, entry2);
12948
13996
  }
12949
13997
  const ranked = [...fused.values()].toSorted(
12950
13998
  (a, b) => b.fusedScore - a.fusedScore || a.nodeId.localeCompare(b.nodeId)
12951
13999
  ).slice(0, options.limit);
12952
- const ids = ranked.map((entry) => entry.nodeId);
14000
+ const ids = ranked.map((entry2) => entry2.nodeId);
12953
14001
  const nodeMap = await fetchNodesByIds(backend, graphId, nodeKind, ids);
12954
14002
  const hits = [];
12955
14003
  let rank = 1;
12956
- for (const entry of ranked) {
12957
- const node = nodeMap.get(entry.nodeId);
14004
+ for (const entry2 of ranked) {
14005
+ const node = nodeMap.get(entry2.nodeId);
12958
14006
  if (!node) continue;
12959
14007
  const typedNode = node;
12960
14008
  const hit = {
12961
14009
  node: typedNode,
12962
- score: entry.fusedScore,
14010
+ score: entry2.fusedScore,
12963
14011
  rank,
12964
- ...entry.vectorRank !== void 0 && entry.vectorScore !== void 0 ? {
14012
+ ...entry2.vectorRank !== void 0 && entry2.vectorScore !== void 0 ? {
12965
14013
  vector: {
12966
14014
  node: typedNode,
12967
- score: entry.vectorScore,
12968
- rank: entry.vectorRank
14015
+ score: entry2.vectorScore,
14016
+ rank: entry2.vectorRank
12969
14017
  }
12970
14018
  } : {},
12971
- ...entry.fulltextRank !== void 0 && entry.fulltextScore !== void 0 ? {
14019
+ ...entry2.fulltextRank !== void 0 && entry2.fulltextScore !== void 0 ? {
12972
14020
  fulltext: {
12973
14021
  node: typedNode,
12974
- score: entry.fulltextScore,
12975
- rank: entry.fulltextRank,
12976
- ...entry.fulltextSnippet === void 0 ? {} : { snippet: entry.fulltextSnippet }
14022
+ score: entry2.fulltextScore,
14023
+ rank: entry2.fulltextRank,
14024
+ ...entry2.fulltextSnippet === void 0 ? {} : { snippet: entry2.fulltextSnippet }
12977
14025
  }
12978
14026
  } : {}
12979
14027
  };
@@ -13086,12 +14134,32 @@ var StoreSearch = class {
13086
14134
  * `Node` objects.
13087
14135
  */
13088
14136
  async fulltext(nodeKind, options) {
14137
+ this.#assertKindRegistered(nodeKind);
13089
14138
  return executeFulltextSearch(
13090
14139
  { graphId: this.#context.graphId, backend: this.#context.backend },
13091
14140
  nodeKind,
13092
14141
  options
13093
14142
  );
13094
14143
  }
14144
+ /**
14145
+ * Runs a vector similarity search against nodes of the given kind.
14146
+ *
14147
+ * Requires a field on the node schema declared with `embedding()`,
14148
+ * either at compile time or via a graph extension (the auto-derived
14149
+ * `VectorIndexDeclaration` flows through `materializeIndexes()` on
14150
+ * the same path either way).
14151
+ *
14152
+ * Pure vector — no fulltext leg, no fusion. For combined
14153
+ * vector+fulltext ranking, use `hybrid`.
14154
+ */
14155
+ async vector(nodeKind, options) {
14156
+ this.#assertKindRegistered(nodeKind);
14157
+ return executeVectorSearch(
14158
+ { graphId: this.#context.graphId, backend: this.#context.backend },
14159
+ nodeKind,
14160
+ options
14161
+ );
14162
+ }
13095
14163
  /**
13096
14164
  * Runs a vector + fulltext hybrid search and fuses the results with
13097
14165
  * Reciprocal Rank Fusion.
@@ -13102,6 +14170,7 @@ var StoreSearch = class {
13102
14170
  * `fulltext.k` for higher-recall corpora.
13103
14171
  */
13104
14172
  async hybrid(nodeKind, options) {
14173
+ this.#assertKindRegistered(nodeKind);
13105
14174
  return executeHybridSearch(
13106
14175
  { graphId: this.#context.graphId, backend: this.#context.backend },
13107
14176
  nodeKind,
@@ -13127,6 +14196,7 @@ var StoreSearch = class {
13127
14196
  * full consistency.
13128
14197
  */
13129
14198
  async rebuildFulltext(nodeKind, options = {}) {
14199
+ if (nodeKind !== void 0) this.#assertKindRegistered(nodeKind);
13130
14200
  return rebuildFulltextIndex(
13131
14201
  {
13132
14202
  graphId: this.#context.graphId,
@@ -13137,27 +14207,62 @@ var StoreSearch = class {
13137
14207
  options
13138
14208
  );
13139
14209
  }
14210
+ #assertKindRegistered(kind) {
14211
+ if (this.#context.registry.hasNodeType(kind)) return;
14212
+ throw new KindNotFoundError(kind, "node", {
14213
+ graphId: this.#context.graphId,
14214
+ suggestion: "Compile-time kinds come from defineGraph; extension kinds appear after store.evolve() returns. Check store.introspect() for the registered set."
14215
+ });
14216
+ }
13140
14217
  };
13141
14218
 
13142
14219
  // src/store/store.ts
13143
- var Store = class {
14220
+ var UNKNOWN_SCHEMA_METADATA = Object.freeze({
14221
+ schemaVersion: void 0,
14222
+ schemaHash: void 0
14223
+ });
14224
+ var CAUGHT_UP_VERB_DETAILS = {
14225
+ evolve: { phrase: "evolve", code: "EVOLVE_BEFORE_INITIALIZE" },
14226
+ materialize: {
14227
+ phrase: "materialize indexes on",
14228
+ code: "MATERIALIZE_BEFORE_INITIALIZE"
14229
+ },
14230
+ remove: { phrase: "remove kinds on", code: "REMOVE_BEFORE_INITIALIZE" },
14231
+ deprecate: {
14232
+ phrase: "deprecate kinds on",
14233
+ code: "DEPRECATE_BEFORE_INITIALIZE"
14234
+ },
14235
+ undeprecate: {
14236
+ phrase: "undeprecate kinds on",
14237
+ code: "DEPRECATE_BEFORE_INITIALIZE"
14238
+ }
14239
+ };
14240
+ var Store = class _Store {
13144
14241
  #graph;
13145
14242
  #backend;
13146
14243
  #registry;
13147
14244
  #hooks;
13148
14245
  #schema;
14246
+ #schemaMetadata;
13149
14247
  #defaultTraversalExpansion;
14248
+ // Stored verbatim so `evolve()` can construct the next Store with
14249
+ // identical options. Reconstructing from the individual private
14250
+ // fields would silently drop any future StoreOptions field a
14251
+ // maintainer adds without also threading it through evolve.
14252
+ #options;
13150
14253
  #nodeCollections;
13151
14254
  #edgeCollections;
13152
14255
  #algorithms;
13153
14256
  #search;
13154
- constructor(graph, backend, options) {
14257
+ constructor(graph, backend, options, schemaMetadata) {
13155
14258
  this.#graph = graph;
13156
14259
  this.#backend = backend;
13157
14260
  this.#registry = buildKindRegistry(graph);
13158
14261
  this.#hooks = options?.hooks ?? {};
13159
14262
  this.#schema = options?.schema ?? (backend.tableNames ? createSqlSchema(backend.tableNames) : void 0);
13160
14263
  this.#defaultTraversalExpansion = options?.queryDefaults?.traversalExpansion ?? "inverse";
14264
+ this.#options = options;
14265
+ this.#schemaMetadata = schemaMetadata ?? UNKNOWN_SCHEMA_METADATA;
13161
14266
  }
13162
14267
  // === Accessors ===
13163
14268
  /** The graph definition */
@@ -13271,23 +14376,125 @@ var Store = class {
13271
14376
  *
13272
14377
  * Use this for runtime string-keyed access when the kind is not known at
13273
14378
  * compile time (e.g., iterating all kinds, resolving from edge metadata,
13274
- * dynamic admin UIs).
14379
+ * dynamic admin UIs). For the post-evolve "I just added this kind, give
14380
+ * me the collection" pattern, prefer `getNodeCollectionOrThrow` — it
14381
+ * throws `KindNotFoundError` instead of forcing a null-check.
13275
14382
  */
13276
14383
  getNodeCollection(kind) {
13277
14384
  if (!Object.hasOwn(this.#graph.nodes, kind)) return void 0;
13278
14385
  return this.nodes[kind];
13279
14386
  }
14387
+ /**
14388
+ * Returns the node collection for the given kind. Throws
14389
+ * `KindNotFoundError` when the kind is not registered.
14390
+ *
14391
+ * The dominant graph-extension-kind access pattern: `await store.evolve(...)`
14392
+ * returns a new store, the caller immediately operates on the new
14393
+ * kind, and the null-check the optional variant requires is busywork.
14394
+ */
14395
+ getNodeCollectionOrThrow(kind) {
14396
+ const collection = this.getNodeCollection(kind);
14397
+ if (collection === void 0) {
14398
+ throw new KindNotFoundError(kind, "node", {
14399
+ graphId: this.graphId
14400
+ });
14401
+ }
14402
+ return collection;
14403
+ }
13280
14404
  /**
13281
14405
  * Returns the edge collection for the given kind, or undefined if the kind
13282
14406
  * is not registered in this graph.
13283
14407
  *
13284
14408
  * Use this for runtime string-keyed access when the kind is not known at
13285
- * compile time.
14409
+ * compile time. For post-evolve access, prefer `getEdgeCollectionOrThrow`.
13286
14410
  */
13287
14411
  getEdgeCollection(kind) {
13288
14412
  if (!Object.hasOwn(this.#graph.edges, kind)) return void 0;
13289
14413
  return this.edges[kind];
13290
14414
  }
14415
+ /**
14416
+ * Returns the edge collection for the given kind. Throws
14417
+ * `KindNotFoundError` when the kind is not registered.
14418
+ */
14419
+ getEdgeCollectionOrThrow(kind) {
14420
+ const collection = this.getEdgeCollection(kind);
14421
+ if (collection === void 0) {
14422
+ throw new KindNotFoundError(kind, "edge", {
14423
+ graphId: this.graphId
14424
+ });
14425
+ }
14426
+ return collection;
14427
+ }
14428
+ // === Dynamic Props Schema Access ===
14429
+ /**
14430
+ * Returns the Zod props schema for the given node kind, or `undefined`
14431
+ * if the kind is not registered. Identity-preserving: returns the
14432
+ * exact instance used by `.create()` / `.update()`, so a `parse()`
14433
+ * surfaces the same underlying Zod issues that `ValidationError`
14434
+ * wraps (operation-level checks like uniqueness and endpoints stay
14435
+ * in `collection.create`).
14436
+ */
14437
+ getNodePropsSchema(kind) {
14438
+ if (!Object.hasOwn(this.#graph.nodes, kind)) return void 0;
14439
+ return this.#graph.nodes[kind].type.schema;
14440
+ }
14441
+ /**
14442
+ * Returns the Zod props schema for the given node kind. Throws
14443
+ * `KindNotFoundError` when the kind is not registered.
14444
+ */
14445
+ getNodePropsSchemaOrThrow(kind) {
14446
+ const schema = this.getNodePropsSchema(kind);
14447
+ if (schema === void 0) {
14448
+ throw new KindNotFoundError(kind, "node", {
14449
+ graphId: this.graphId
14450
+ });
14451
+ }
14452
+ return schema;
14453
+ }
14454
+ /**
14455
+ * Returns the Zod props schema for the given edge kind, or `undefined`
14456
+ * if the kind is not registered. Symmetric with `getNodePropsSchema`.
14457
+ */
14458
+ getEdgePropsSchema(kind) {
14459
+ if (!Object.hasOwn(this.#graph.edges, kind)) return void 0;
14460
+ return this.#graph.edges[kind].type.schema;
14461
+ }
14462
+ /**
14463
+ * Returns the Zod props schema for the given edge kind. Throws
14464
+ * `KindNotFoundError` when the kind is not registered.
14465
+ */
14466
+ getEdgePropsSchemaOrThrow(kind) {
14467
+ const schema = this.getEdgePropsSchema(kind);
14468
+ if (schema === void 0) {
14469
+ throw new KindNotFoundError(kind, "edge", {
14470
+ graphId: this.graphId
14471
+ });
14472
+ }
14473
+ return schema;
14474
+ }
14475
+ /**
14476
+ * Returns a unified read of the merged schema — every compile-time
14477
+ * and graph-extension kind, every edge, every ontology relation,
14478
+ * with explicit `origin: "compile-time" | "runtime"` markers — plus
14479
+ * the persisted `extension` for round-tripping.
14480
+ *
14481
+ * Pure synchronous read built from the in-memory graph and the
14482
+ * already-merged graph-extension document. `schemaVersion` and `schemaHash`
14483
+ * are populated when the loader cached them at construction or after
14484
+ * `evolve` returns; consumers needing a fresh read should call
14485
+ * `backend.getActiveSchema(graphId)`.
14486
+ *
14487
+ * The return shape is the canonical schema-introspection surface:
14488
+ * the prior standalone `store.deprecatedKinds` accessor is replaced
14489
+ * by `introspect().deprecatedKinds`.
14490
+ */
14491
+ introspect() {
14492
+ return introspectSchema(this.#graph, {
14493
+ graphId: this.graphId,
14494
+ schemaVersion: this.#schemaMetadata.schemaVersion,
14495
+ schemaHash: this.#schemaMetadata.schemaHash
14496
+ });
14497
+ }
13291
14498
  /**
13292
14499
  * Node operations bound to this store instance.
13293
14500
  */
@@ -13639,6 +14846,415 @@ var Store = class {
13639
14846
  async refreshStatistics() {
13640
14847
  await this.#backend.refreshStatistics();
13641
14848
  }
14849
+ /**
14850
+ * Evolves the graph at runtime by merging a graph extension into the
14851
+ * current schema, atomically committing a new schema version, and
14852
+ * returning a fresh `Store` constructed against the merged graph.
14853
+ *
14854
+ * The `Store` is immutable — its registry, collections, and operation
14855
+ * contexts close over the graph at construction time — so callers
14856
+ * must use the returned store (or pass a `ref` to be re-pointed) for
14857
+ * any work involving the new kinds.
14858
+ *
14859
+ * **Cost for purely additive extensions is proportional to schema
14860
+ * document size, not row count.** The commit is a single CAS write.
14861
+ * Tightening changes against existing graph-extension-declared kinds run
14862
+ * row-count probes for those affected kinds so populated kinds can
14863
+ * be rejected without a backfill.
14864
+ *
14865
+ * **Concurrent evolve recovery.** On `StaleVersionError`, refetch the
14866
+ * current active schema (or dereference your `StoreRef`),
14867
+ * reconstruct your `Store`, and re-call `evolve(extension)` against
14868
+ * the new store. Re-validation may now surface deterministic errors
14869
+ * (e.g., another caller just added a kind that collides with yours,
14870
+ * or redeclared one of yours with a different shape). Don't loop
14871
+ * blindly — surface the error.
14872
+ *
14873
+ * @param extension - Graph extension produced by
14874
+ * `defineGraphExtension(...)`.
14875
+ * @param options.ref - Optional handle whose `current` is overwritten
14876
+ * atomically with the schema commit. Long-lived consumers
14877
+ * (request handlers, background workers) that dereference through
14878
+ * the ref see the new kinds on the *next* call.
14879
+ *
14880
+ * @throws {GraphExtensionValidationError} when the extension is
14881
+ * structurally invalid.
14882
+ * @throws {KindCollisionError} when an extension kind shadows a
14883
+ * compile-time kind (code `KIND_COLLISION`).
14884
+ * @throws {IncompatibleChangeError} when a redeclared kind narrows
14885
+ * in a way the existing rows can't satisfy
14886
+ * (code `INCOMPATIBLE_CHANGE`).
14887
+ * @throws {GraphExtensionUnresolvedEndpointError} when an edge
14888
+ * endpoint references a kind that exists in neither the extension
14889
+ * nor the host graph
14890
+ * (code `GRAPH_EXTENSION_UNRESOLVED_ENDPOINT`).
14891
+ * @throws {StaleVersionError} when another writer has advanced the
14892
+ * schema since this store was constructed; recovery as above.
14893
+ * @throws {SchemaContentConflictError} when a row already exists at
14894
+ * the target version with a different content hash.
14895
+ */
14896
+ async evolve(extension, options) {
14897
+ const { activeRow, baseline } = await this.#loadCaughtUp("evolve");
14898
+ const merged = mergeGraphExtension(baseline, extension);
14899
+ if (merged === baseline) {
14900
+ if (baseline === this.#graph) {
14901
+ if (options?.ref !== void 0) options.ref.current = this;
14902
+ if (options?.eager !== void 0) {
14903
+ await this.#runEagerOrThrow(this, options.eager);
14904
+ }
14905
+ return this;
14906
+ }
14907
+ const caughtUp = this.#cloneWithGraph(
14908
+ baseline,
14909
+ options?.ref,
14910
+ schemaMetadataFromRow(activeRow)
14911
+ );
14912
+ if (options?.eager !== void 0) {
14913
+ await this.#runEagerOrThrow(caughtUp, options.eager);
14914
+ }
14915
+ return caughtUp;
14916
+ }
14917
+ const baselineDocument = baseline.extension ?? Object.freeze({});
14918
+ const classification = classifyModifications(baselineDocument, extension);
14919
+ if (classification.incompatible.length > 0) {
14920
+ throw new IncompatibleChangeError(
14921
+ classification.incompatible,
14922
+ this.graphId
14923
+ );
14924
+ }
14925
+ if (classification.requireEmpty.length > 0) {
14926
+ const nonEmpty = await this.#probeEmptyKinds(classification.requireEmpty);
14927
+ const error = buildIncompatibleChangeError(
14928
+ classification,
14929
+ nonEmpty,
14930
+ this.graphId
14931
+ );
14932
+ if (error !== void 0) throw error;
14933
+ }
14934
+ const committed = await commitNewSchemaVersion(
14935
+ this.#backend,
14936
+ merged,
14937
+ activeRow.version
14938
+ );
14939
+ const evolved = this.#cloneWithGraph(
14940
+ merged,
14941
+ options?.ref,
14942
+ schemaMetadataFromRow(committed)
14943
+ );
14944
+ if (options?.eager !== void 0) {
14945
+ await this.#runEagerOrThrow(evolved, options.eager);
14946
+ }
14947
+ return evolved;
14948
+ }
14949
+ async #runEagerOrThrow(store, eager) {
14950
+ const result = await store.materializeIndexes(eager);
14951
+ if (result.results.some((entry2) => entry2.status === "failed")) {
14952
+ throw new EagerMaterializationError(result, this.graphId);
14953
+ }
14954
+ }
14955
+ /**
14956
+ * Marks the named node and edge kinds as soft-deprecated. Surfaces in
14957
+ * `store.introspect().deprecatedKinds` for introspection (codegen,
14958
+ * UI tooling, lints) but does not gate reads, writes, or queries —
14959
+ * deprecation is a signal, not a removal.
14960
+ *
14961
+ * Atomically commits a new schema version through the same primitive
14962
+ * `evolve()` uses, so concurrent deprecate/evolve calls produce
14963
+ * `StaleVersionError | SchemaContentConflictError` race losers that
14964
+ * the caller refetches and retries. Idempotent: re-deprecating a
14965
+ * kind that's already marked is a no-op (no version bump).
14966
+ *
14967
+ * @param names - Node or edge kind names to mark as deprecated.
14968
+ * @param options.ref - Optional handle to be re-pointed atomically
14969
+ * with the schema commit, mirroring `evolve()`.
14970
+ *
14971
+ * @throws {ConfigurationError} on `DEPRECATE_BEFORE_INITIALIZE` (no
14972
+ * schema yet) or `DEPRECATE_UNKNOWN_KIND` (name not on the graph).
14973
+ * @throws {StaleVersionError} when another writer has advanced the
14974
+ * schema since this store was constructed; refetch and retry per
14975
+ * the same recipe `evolve()` documents.
14976
+ * @throws {SchemaContentConflictError} when a row already exists at
14977
+ * the target version with a different content hash.
14978
+ */
14979
+ async deprecateKinds(names, options) {
14980
+ return this.#updateDeprecatedKinds("add", names, options);
14981
+ }
14982
+ /**
14983
+ * Reverses `deprecateKinds(...)` for the named kinds. Same race +
14984
+ * idempotency semantics: removing a name that isn't currently
14985
+ * deprecated is a no-op for that name (the call as a whole is a
14986
+ * no-op only if every name was already absent).
14987
+ *
14988
+ * @param names - Node or edge kind names to remove from the
14989
+ * deprecated set.
14990
+ * @param options.ref - Optional handle to be re-pointed atomically
14991
+ * with the schema commit.
14992
+ *
14993
+ * @throws {ConfigurationError} on `DEPRECATE_BEFORE_INITIALIZE`.
14994
+ * @throws {StaleVersionError} on a CAS race with another writer.
14995
+ * @throws {SchemaContentConflictError} on a same-version content
14996
+ * conflict.
14997
+ */
14998
+ async undeprecateKinds(names, options) {
14999
+ return this.#updateDeprecatedKinds("remove", names, options);
15000
+ }
15001
+ /**
15002
+ * Runs `CREATE INDEX` DDL for the indexes declared on this graph and
15003
+ * records per-deployment status in `typegraph_index_materializations`.
15004
+ * The status table is per-database (two replicas of the same
15005
+ * `schema_doc` are still two different databases for DDL purposes),
15006
+ * so the same call against two replicas materializes independently.
15007
+ *
15008
+ * Idempotent. Re-running the verb against an already-materialized
15009
+ * index is a no-op (`status: "alreadyMaterialized"`). Postgres uses
15010
+ * `CREATE INDEX CONCURRENTLY` so live tables never take an
15011
+ * `AccessExclusiveLock`. SQLite is single-writer regardless.
15012
+ *
15013
+ * Best-effort by default: a failed index records `status: "failed"`
15014
+ * with the error and the loop continues. Pass `{ stopOnError: true }`
15015
+ * to halt on the first failure.
15016
+ *
15017
+ * @param options.kinds - Restrict to indexes whose `kind` is in this
15018
+ * set. Throws `ConfigurationError` (`code:
15019
+ * "MATERIALIZE_UNKNOWN_KIND"`) if any name doesn't match a known
15020
+ * compile-time or extension kind.
15021
+ * @param options.stopOnError - Halt on first failure. Default false.
15022
+ *
15023
+ * @throws {ConfigurationError} `MATERIALIZE_BACKEND_UNSUPPORTED` if
15024
+ * the backend lacks `executeDdl`, `getIndexMaterialization`, or
15025
+ * `recordIndexMaterialization`; `MATERIALIZE_UNKNOWN_KIND` for an
15026
+ * unknown kind name; `MATERIALIZE_BEFORE_INITIALIZE` if the store
15027
+ * has no schema yet.
15028
+ */
15029
+ async materializeIndexes(options) {
15030
+ const { activeRow, baseline } = await this.#loadCaughtUp("materialize");
15031
+ return materializeIndexes(
15032
+ {
15033
+ graph: baseline,
15034
+ graphId: this.graphId,
15035
+ backend: this.#backend,
15036
+ schemaVersion: activeRow.version
15037
+ },
15038
+ options ?? {}
15039
+ );
15040
+ }
15041
+ /**
15042
+ * Removes extension kinds from the schema with cascading edge and
15043
+ * ontology cleanup. Two-phase by design:
15044
+ *
15045
+ * 1. **Schema commit (this method).** Validates the removal,
15046
+ * rebuilds the persisted extension without the named kinds
15047
+ * (and without extension edges that lose their last endpoint),
15048
+ * CAS-commits the new schema version, and queues
15049
+ * per-deployment data-cleanup status. Millisecond budget.
15050
+ * 2. **Data cleanup (`materializeRemovals`).** Deletes the orphan
15051
+ * rows from the nodes/edges tables. Bounded by row count.
15052
+ *
15053
+ * Pass `{ eager: {} }` to run the data-cleanup pass inline with
15054
+ * default options, or `{ eager: { ... } }` to scope it; otherwise
15055
+ * call `materializeRemovals()` later.
15056
+ *
15057
+ * Idempotent: removing a name that doesn't exist is a no-op (no
15058
+ * version bump). Removing an extension kind referenced by a compile-
15059
+ * time edge or ontology relation throws `KindHasReferentsError`.
15060
+ * Removing a compile-time kind throws `RemoveCompileTimeKindError` —
15061
+ * compile-time kinds are removed by recompiling and redeploying.
15062
+ *
15063
+ * @throws {RemoveCompileTimeKindError} when `names` includes a
15064
+ * compile-time kind.
15065
+ * @throws {KindHasReferentsError} when an extension kind being
15066
+ * removed is referenced by a compile-time declaration.
15067
+ * @throws {StaleVersionError} on a CAS race with another writer.
15068
+ * @throws {SchemaContentConflictError} on a same-version content
15069
+ * conflict.
15070
+ */
15071
+ async removeKinds(names, options) {
15072
+ const { activeRow, baseline } = await this.#loadCaughtUp("remove");
15073
+ const plan = planRemovals(baseline, names);
15074
+ if (plan.removedNodeKinds.length === 0 && plan.removedEdgeKinds.length === 0) {
15075
+ if (baseline === this.#graph) {
15076
+ if (options?.ref !== void 0) options.ref.current = this;
15077
+ return this;
15078
+ }
15079
+ return this.#cloneWithGraph(
15080
+ baseline,
15081
+ options?.ref,
15082
+ schemaMetadataFromRow(activeRow)
15083
+ );
15084
+ }
15085
+ const compileTimeGraph = stripGraphExtension(this.#graph);
15086
+ const merged = plan.document === void 0 ? compileTimeGraph : mergeGraphExtension(compileTimeGraph, plan.document);
15087
+ const finalGraph = applyDeprecatedKinds(merged, baseline.deprecatedKinds);
15088
+ const committedRow = await commitNewSchemaVersion(
15089
+ this.#backend,
15090
+ finalGraph,
15091
+ activeRow.version
15092
+ );
15093
+ const recordKindRemoval = this.#backend.recordKindRemoval;
15094
+ if (recordKindRemoval !== void 0) {
15095
+ if (this.#backend.ensureKindRemovalsTable !== void 0) {
15096
+ await this.#backend.ensureKindRemovalsTable();
15097
+ }
15098
+ const attemptedAt = nowIso();
15099
+ const newSchemaVersion = committedRow.version;
15100
+ const queue = (kindName, entity) => recordKindRemoval(
15101
+ buildPendingKindRemoval({
15102
+ graphId: this.graphId,
15103
+ kindName,
15104
+ entity,
15105
+ schemaVersion: newSchemaVersion,
15106
+ attemptedAt
15107
+ })
15108
+ );
15109
+ await Promise.all([
15110
+ ...plan.removedNodeKinds.map((name) => queue(name, "node")),
15111
+ ...plan.removedEdgeKinds.map((name) => queue(name, "edge"))
15112
+ ]);
15113
+ }
15114
+ const evolved = this.#cloneWithGraph(
15115
+ finalGraph,
15116
+ options?.ref,
15117
+ schemaMetadataFromRow(committedRow)
15118
+ );
15119
+ if (options?.eager !== void 0) {
15120
+ const kinds = [...plan.removedNodeKinds, ...plan.removedEdgeKinds];
15121
+ await evolved.materializeRemovals({
15122
+ ...options.eager,
15123
+ kinds: options.eager.kinds ?? kinds
15124
+ });
15125
+ }
15126
+ return evolved;
15127
+ }
15128
+ /**
15129
+ * Runs the data-cleanup phase for any kinds removed via
15130
+ * `removeKinds()` whose data has not yet been deleted on this
15131
+ * deployment. Safe to call repeatedly; idempotent.
15132
+ */
15133
+ async materializeRemovals(options) {
15134
+ return materializeRemovals(
15135
+ { graphId: this.graphId, backend: this.#backend },
15136
+ options ?? {}
15137
+ );
15138
+ }
15139
+ async #updateDeprecatedKinds(direction, names, options) {
15140
+ const verb = direction === "add" ? "deprecate" : "undeprecate";
15141
+ const { activeRow, storedSchema, baseline } = await this.#loadCaughtUp(verb);
15142
+ const nextSet = new Set(baseline.deprecatedKinds);
15143
+ if (direction === "add") {
15144
+ for (const name of names) {
15145
+ if (!isKnownKind(baseline, name)) {
15146
+ throw new KindNotFoundError(
15147
+ name,
15148
+ isKnownEdgeKind(baseline, name) ? "edge" : "node",
15149
+ {
15150
+ graphId: this.graphId,
15151
+ suggestion: "Only kinds declared on the graph (compile-time or runtime) can be deprecated."
15152
+ }
15153
+ );
15154
+ }
15155
+ nextSet.add(name);
15156
+ }
15157
+ } else {
15158
+ for (const name of names) nextSet.delete(name);
15159
+ }
15160
+ if (setsEqual(nextSet, baseline.deprecatedKinds)) {
15161
+ if (baseline === this.#graph) {
15162
+ if (options?.ref !== void 0) options.ref.current = this;
15163
+ return this;
15164
+ }
15165
+ return this.#cloneWithGraph(
15166
+ baseline,
15167
+ options?.ref,
15168
+ schemaMetadataFromRow(activeRow)
15169
+ );
15170
+ }
15171
+ const merged = applyDeprecatedKinds(baseline, nextSet);
15172
+ const result = await ensureSchema(this.#backend, merged, {
15173
+ preloaded: { activeRow, storedSchema },
15174
+ autoMigrate: true
15175
+ });
15176
+ const metadata = result.status === "migrated" || result.status === "initialized" ? schemaMetadataFromRow(result.committedRow) : schemaMetadataFromRow(activeRow);
15177
+ return this.#cloneWithGraph(merged, options?.ref, metadata);
15178
+ }
15179
+ #catchUpToStored(storedSchema) {
15180
+ const compileTimeGraph = stripGraphExtension(this.#graph);
15181
+ const withGraphExtension = storedSchema.extension === void 0 ? compileTimeGraph : mergeGraphExtension(compileTimeGraph, storedSchema.extension);
15182
+ return applyDeprecatedKinds(
15183
+ withGraphExtension,
15184
+ storedSchema.deprecatedKinds
15185
+ );
15186
+ }
15187
+ /**
15188
+ * Loads the active schema row, parses it, and catches the in-memory
15189
+ * graph up to the persisted state — the shared preamble for
15190
+ * `evolve`, `materializeIndexes`, and `(un)deprecateKinds`.
15191
+ *
15192
+ * Throws `ConfigurationError` with a verb-specific code when the
15193
+ * graph has not been initialized yet. The catch-up step replays the
15194
+ * persisted graph-extension document and deprecation set on top of the
15195
+ * compile-time graph so we diff against the same baseline another
15196
+ * writer would; the CAS guard inside `commitSchemaVersion` still
15197
+ * serializes the actual commit.
15198
+ */
15199
+ async #loadCaughtUp(verb) {
15200
+ const activeRow = await loadActiveSchemaWithBootstrap(
15201
+ this.#backend,
15202
+ this.graphId
15203
+ );
15204
+ if (activeRow === void 0) {
15205
+ const { phrase, code } = CAUGHT_UP_VERB_DETAILS[verb];
15206
+ throw new ConfigurationError(
15207
+ `Cannot ${phrase} graph "${this.graphId}": no schema has been initialized. Call createStoreWithSchema first.`,
15208
+ { code }
15209
+ );
15210
+ }
15211
+ const storedSchema = parseSerializedSchema(activeRow.schema_doc);
15212
+ const baseline = this.#catchUpToStored(storedSchema);
15213
+ return { activeRow, storedSchema, baseline };
15214
+ }
15215
+ /**
15216
+ * Probes each `requireEmpty` entry for any rows. Returns the set of
15217
+ * composite keys (`${entity}:${kindName}`) that have at least one
15218
+ * row — those entries need to be promoted from "allowed-on-empty"
15219
+ * to incompatible. Dispatches to `countNodesByKind` for `node`
15220
+ * entries and `countEdgesByKind` for `edge` entries (a single-
15221
+ * primitive probe would always return 0 for the wrong-entity case
15222
+ * and silently bypass the gate).
15223
+ *
15224
+ * Probes run in parallel; each entry is independent. Race window
15225
+ * (probe → CAS commit): another writer can insert a row into a
15226
+ * previously-empty kind. The schema commit still succeeds (CAS
15227
+ * guards on schema version, not row count); the new schema rejects
15228
+ * the inserted row at next read, which the operator inspects and
15229
+ * either deletes or reverts. Rare in practice; tighter elimination
15230
+ * would require `SELECT FOR UPDATE` on the rows table, too
15231
+ * heavyweight for a millisecond-budget operation.
15232
+ */
15233
+ async #probeEmptyKinds(requireEmpty) {
15234
+ const probes = requireEmpty.map(
15235
+ async (entry2) => {
15236
+ const count2 = entry2.entity === "node" ? await this.#backend.countNodesByKind({
15237
+ graphId: this.graphId,
15238
+ kind: entry2.kindName
15239
+ }) : await this.#backend.countEdgesByKind({
15240
+ graphId: this.graphId,
15241
+ kind: entry2.kindName
15242
+ });
15243
+ return [entry2, count2];
15244
+ }
15245
+ );
15246
+ const results = await Promise.all(probes);
15247
+ const nonEmpty = /* @__PURE__ */ new Set();
15248
+ for (const [entry2, count2] of results) {
15249
+ if (count2 > 0) nonEmpty.add(entry2);
15250
+ }
15251
+ return nonEmpty;
15252
+ }
15253
+ #cloneWithGraph(graph, ref, schemaMetadata = this.#schemaMetadata) {
15254
+ const next = new _Store(graph, this.#backend, this.#options, schemaMetadata);
15255
+ if (ref !== void 0) ref.current = next;
15256
+ return next;
15257
+ }
13642
15258
  /**
13643
15259
  * Closes the store and releases underlying resources.
13644
15260
  *
@@ -13737,12 +15353,46 @@ var Store = class {
13737
15353
  function createStore(graph, backend, options) {
13738
15354
  return new Store(graph, backend, options);
13739
15355
  }
15356
+ function isKnownEdgeKind(graph, name) {
15357
+ return Object.hasOwn(graph.edges, name);
15358
+ }
15359
+ function setsEqual(a, b) {
15360
+ if (a.size !== b.size) return false;
15361
+ for (const value of a) if (!b.has(value)) return false;
15362
+ return true;
15363
+ }
15364
+ function schemaMetadataFromRow(row) {
15365
+ if (row === void 0) return UNKNOWN_SCHEMA_METADATA;
15366
+ return Object.freeze({
15367
+ schemaVersion: row.version,
15368
+ schemaHash: row.schema_hash
15369
+ });
15370
+ }
15371
+ function schemaMetadataForResult(activeRow, result) {
15372
+ if (result.status === "initialized" || result.status === "migrated") {
15373
+ return schemaMetadataFromRow(result.committedRow);
15374
+ }
15375
+ return schemaMetadataFromRow(activeRow);
15376
+ }
13740
15377
  async function createStoreWithSchema(graph, backend, options) {
13741
- const store = createStore(graph, backend, options);
13742
- const result = await ensureSchema(backend, graph, options);
15378
+ const {
15379
+ graph: merged,
15380
+ activeRow,
15381
+ storedSchema
15382
+ } = await loadAndMergeGraphExtensionDocument(backend, graph);
15383
+ const result = await ensureSchema(backend, merged, {
15384
+ ...options,
15385
+ preloaded: { activeRow, storedSchema }
15386
+ });
15387
+ const store = new Store(
15388
+ merged,
15389
+ backend,
15390
+ options,
15391
+ schemaMetadataForResult(activeRow, result)
15392
+ );
13743
15393
  return [store, result];
13744
15394
  }
13745
15395
 
13746
- export { DEFAULT_SQL_SCHEMA, ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, StoreSearch, UnionableQuery, avg, broader, composeFragments, core, count, countDistinct, countDistinctEdges, countEdges, createExternalRef, createFragment, createQueryBuilder, createSqlSchema, createStore, createStoreWithSchema, defineEdge, defineNode, defineSubgraphProject, differentFrom, disjointWith, equivalentTo, exists, externalRef, field, fieldRef, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inSubquery, inverseOf, isExternalRefSchema, isParameterRef, limitFragment, max, metaEdge, min, narrower, notExists, notInSubquery, offsetFragment, orderByFragment, param, partOf, relatedTo, sameAs, subClassOf, sum };
15396
+ export { DEFAULT_SQL_SCHEMA, ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, StoreSearch, UnionableQuery, avg, composeFragments, count, countDistinct, countDistinctEdges, countEdges, createExternalRef, createFragment, createQueryBuilder, createSqlSchema, createStore, createStoreWithSchema, defineSubgraphProject, exists, externalRef, field, fieldRef, generateId, getExternalRefTable, having, havingEq, havingGt, havingGte, havingLt, havingLte, inSubquery, isExternalRefSchema, isParameterRef, limitFragment, max, metaEdge, min, notExists, notInSubquery, offsetFragment, orderByFragment, param, sum };
13747
15397
  //# sourceMappingURL=index.js.map
13748
15398
  //# sourceMappingURL=index.js.map