@hops-ops/distributed 0.0.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 (242) hide show
  1. package/README.md +454 -0
  2. package/dist/auth-headers.d.ts +8 -0
  3. package/dist/auth-headers.js +35 -0
  4. package/dist/diagnostics.d.ts +2 -0
  5. package/dist/diagnostics.js +1 -0
  6. package/dist/document.d.ts +8 -0
  7. package/dist/document.js +6 -0
  8. package/dist/identity.d.ts +23 -0
  9. package/dist/identity.js +73 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.js +6 -0
  12. package/dist/internal/cache-engine/create.d.ts +5 -0
  13. package/dist/internal/cache-engine/create.js +7 -0
  14. package/dist/internal/cache-engine/engine.d.ts +32 -0
  15. package/dist/internal/cache-engine/engine.js +1287 -0
  16. package/dist/internal/cache-engine/errors.d.ts +5 -0
  17. package/dist/internal/cache-engine/errors.js +10 -0
  18. package/dist/internal/cache-engine/helpers.d.ts +55 -0
  19. package/dist/internal/cache-engine/helpers.js +545 -0
  20. package/dist/internal/cache-engine/index.d.ts +3 -0
  21. package/dist/internal/cache-engine/index.js +2 -0
  22. package/dist/internal/cache-engine/types.d.ts +300 -0
  23. package/dist/internal/cache-engine/types.js +1 -0
  24. package/dist/internal/cache-engine.d.ts +3 -0
  25. package/dist/internal/cache-engine.js +2 -0
  26. package/dist/lib/assert-name.d.ts +2 -0
  27. package/dist/lib/assert-name.js +6 -0
  28. package/dist/lib/compare-code-units.d.ts +2 -0
  29. package/dist/lib/compare-code-units.js +4 -0
  30. package/dist/lib/deep-equal.d.ts +2 -0
  31. package/dist/lib/deep-equal.js +24 -0
  32. package/dist/lib/freeze-record.d.ts +2 -0
  33. package/dist/lib/freeze-record.js +13 -0
  34. package/dist/lib/index.d.ts +6 -0
  35. package/dist/lib/index.js +6 -0
  36. package/dist/lib/is-plain-record.d.ts +2 -0
  37. package/dist/lib/is-plain-record.js +8 -0
  38. package/dist/lib/report.d.ts +7 -0
  39. package/dist/lib/report.js +26 -0
  40. package/dist/protocol.d.ts +179 -0
  41. package/dist/protocol.js +558 -0
  42. package/dist/react/context.d.ts +20 -0
  43. package/dist/react/context.js +24 -0
  44. package/dist/react/index.d.ts +2 -0
  45. package/dist/react/index.js +3 -0
  46. package/dist/react/query.d.ts +20 -0
  47. package/dist/react/query.js +99 -0
  48. package/dist/replica/command-id.d.ts +2 -0
  49. package/dist/replica/command-id.js +19 -0
  50. package/dist/replica/command-runtime/constants.d.ts +5 -0
  51. package/dist/replica/command-runtime/constants.js +5 -0
  52. package/dist/replica/command-runtime/create.d.ts +2 -0
  53. package/dist/replica/command-runtime/create.js +644 -0
  54. package/dist/replica/command-runtime/errors.d.ts +16 -0
  55. package/dist/replica/command-runtime/errors.js +41 -0
  56. package/dist/replica/command-runtime/index.d.ts +5 -0
  57. package/dist/replica/command-runtime/index.js +4 -0
  58. package/dist/replica/command-runtime/lib/binding.d.ts +4 -0
  59. package/dist/replica/command-runtime/lib/binding.js +64 -0
  60. package/dist/replica/command-runtime/lib/effects.d.ts +11 -0
  61. package/dist/replica/command-runtime/lib/effects.js +89 -0
  62. package/dist/replica/command-runtime/lib/index.d.ts +9 -0
  63. package/dist/replica/command-runtime/lib/index.js +9 -0
  64. package/dist/replica/command-runtime/lib/inventory.d.ts +10 -0
  65. package/dist/replica/command-runtime/lib/inventory.js +141 -0
  66. package/dist/replica/command-runtime/lib/output.d.ts +13 -0
  67. package/dist/replica/command-runtime/lib/output.js +190 -0
  68. package/dist/replica/command-runtime/lib/projection.d.ts +20 -0
  69. package/dist/replica/command-runtime/lib/projection.js +255 -0
  70. package/dist/replica/command-runtime/lib/status.d.ts +7 -0
  71. package/dist/replica/command-runtime/lib/status.js +127 -0
  72. package/dist/replica/command-runtime/lib/transport.d.ts +16 -0
  73. package/dist/replica/command-runtime/lib/transport.js +112 -0
  74. package/dist/replica/command-runtime/lib/util.d.ts +16 -0
  75. package/dist/replica/command-runtime/lib/util.js +127 -0
  76. package/dist/replica/command-runtime/lifecycle.d.ts +11 -0
  77. package/dist/replica/command-runtime/lifecycle.js +13 -0
  78. package/dist/replica/command-runtime/symbols.d.ts +30 -0
  79. package/dist/replica/command-runtime/symbols.js +30 -0
  80. package/dist/replica/command-runtime/types.d.ts +222 -0
  81. package/dist/replica/command-runtime/types.js +1 -0
  82. package/dist/replica/command-runtime.d.ts +3 -0
  83. package/dist/replica/command-runtime.js +2 -0
  84. package/dist/replica/commands/clone.d.ts +12 -0
  85. package/dist/replica/commands/clone.js +187 -0
  86. package/dist/replica/commands/constants.d.ts +6 -0
  87. package/dist/replica/commands/constants.js +6 -0
  88. package/dist/replica/commands/errors.d.ts +7 -0
  89. package/dist/replica/commands/errors.js +22 -0
  90. package/dist/replica/commands/implementation.d.ts +8 -0
  91. package/dist/replica/commands/implementation.js +8 -0
  92. package/dist/replica/commands/index.d.ts +5 -0
  93. package/dist/replica/commands/index.js +4 -0
  94. package/dist/replica/commands/prepare.d.ts +27 -0
  95. package/dist/replica/commands/prepare.js +77 -0
  96. package/dist/replica/commands/presets.d.ts +12 -0
  97. package/dist/replica/commands/presets.js +84 -0
  98. package/dist/replica/commands/receipt.d.ts +3 -0
  99. package/dist/replica/commands/receipt.js +36 -0
  100. package/dist/replica/commands/resolve.d.ts +11 -0
  101. package/dist/replica/commands/resolve.js +165 -0
  102. package/dist/replica/commands/types.d.ts +278 -0
  103. package/dist/replica/commands/types.js +2 -0
  104. package/dist/replica/commands/util.d.ts +21 -0
  105. package/dist/replica/commands/util.js +96 -0
  106. package/dist/replica/commands/validate.d.ts +18 -0
  107. package/dist/replica/commands/validate.js +477 -0
  108. package/dist/replica/commands.d.ts +3 -0
  109. package/dist/replica/commands.js +2 -0
  110. package/dist/replica/diagnostics/event-log.d.ts +4 -0
  111. package/dist/replica/diagnostics/event-log.js +463 -0
  112. package/dist/replica/diagnostics/implementation.d.ts +4 -0
  113. package/dist/replica/diagnostics/implementation.js +4 -0
  114. package/dist/replica/diagnostics/index.d.ts +2 -0
  115. package/dist/replica/diagnostics/index.js +1 -0
  116. package/dist/replica/diagnostics/inspect.d.ts +6 -0
  117. package/dist/replica/diagnostics/inspect.js +170 -0
  118. package/dist/replica/diagnostics/types.d.ts +295 -0
  119. package/dist/replica/diagnostics/types.js +2 -0
  120. package/dist/replica/diagnostics.d.ts +3 -0
  121. package/dist/replica/diagnostics.js +2 -0
  122. package/dist/replica/distributed-replica/clocks.d.ts +23 -0
  123. package/dist/replica/distributed-replica/clocks.js +156 -0
  124. package/dist/replica/distributed-replica/constants.d.ts +12 -0
  125. package/dist/replica/distributed-replica/constants.js +10 -0
  126. package/dist/replica/distributed-replica/helpers.d.ts +48 -0
  127. package/dist/replica/distributed-replica/helpers.js +445 -0
  128. package/dist/replica/distributed-replica/hydration.d.ts +19 -0
  129. package/dist/replica/distributed-replica/hydration.js +380 -0
  130. package/dist/replica/distributed-replica/impl-diagnostics.d.ts +22 -0
  131. package/dist/replica/distributed-replica/impl-diagnostics.js +189 -0
  132. package/dist/replica/distributed-replica/impl-fetch-live.d.ts +39 -0
  133. package/dist/replica/distributed-replica/impl-fetch-live.js +395 -0
  134. package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +56 -0
  135. package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +299 -0
  136. package/dist/replica/distributed-replica/impl-optimistic.d.ts +31 -0
  137. package/dist/replica/distributed-replica/impl-optimistic.js +172 -0
  138. package/dist/replica/distributed-replica/impl-protocol.d.ts +53 -0
  139. package/dist/replica/distributed-replica/impl-protocol.js +108 -0
  140. package/dist/replica/distributed-replica/impl.d.ts +44 -0
  141. package/dist/replica/distributed-replica/impl.js +1776 -0
  142. package/dist/replica/distributed-replica/index.d.ts +2 -0
  143. package/dist/replica/distributed-replica/index.js +4 -0
  144. package/dist/replica/distributed-replica/optimistic.d.ts +30 -0
  145. package/dist/replica/distributed-replica/optimistic.js +221 -0
  146. package/dist/replica/distributed-replica/types.d.ts +147 -0
  147. package/dist/replica/distributed-replica/types.js +1 -0
  148. package/dist/replica/distributed-replica/watch.d.ts +19 -0
  149. package/dist/replica/distributed-replica/watch.js +95 -0
  150. package/dist/replica/distributed-replica.d.ts +2 -0
  151. package/dist/replica/distributed-replica.js +2 -0
  152. package/dist/replica/graphql-transport.d.ts +24 -0
  153. package/dist/replica/graphql-transport.js +122 -0
  154. package/dist/replica/identity/clone.d.ts +6 -0
  155. package/dist/replica/identity/clone.js +54 -0
  156. package/dist/replica/identity/codec.d.ts +48 -0
  157. package/dist/replica/identity/codec.js +789 -0
  158. package/dist/replica/identity/constants.d.ts +3 -0
  159. package/dist/replica/identity/constants.js +18 -0
  160. package/dist/replica/identity/implementation.d.ts +5 -0
  161. package/dist/replica/identity/implementation.js +5 -0
  162. package/dist/replica/identity/index.d.ts +3 -0
  163. package/dist/replica/identity/index.js +3 -0
  164. package/dist/replica/identity/keys.d.ts +29 -0
  165. package/dist/replica/identity/keys.js +173 -0
  166. package/dist/replica/identity.d.ts +2 -0
  167. package/dist/replica/identity.js +2 -0
  168. package/dist/replica/index-maintenance/engine.d.ts +84 -0
  169. package/dist/replica/index-maintenance/engine.js +677 -0
  170. package/dist/replica/index-maintenance/implementation.d.ts +4 -0
  171. package/dist/replica/index-maintenance/implementation.js +4 -0
  172. package/dist/replica/index-maintenance/index.d.ts +2 -0
  173. package/dist/replica/index-maintenance/index.js +1 -0
  174. package/dist/replica/index-maintenance/registry.d.ts +2 -0
  175. package/dist/replica/index-maintenance/registry.js +49 -0
  176. package/dist/replica/index-maintenance/types.d.ts +58 -0
  177. package/dist/replica/index-maintenance/types.js +1 -0
  178. package/dist/replica/index-maintenance.d.ts +3 -0
  179. package/dist/replica/index-maintenance.js +2 -0
  180. package/dist/replica/index.d.ts +17 -0
  181. package/dist/replica/index.js +9 -0
  182. package/dist/replica/materialize.d.ts +10 -0
  183. package/dist/replica/materialize.js +163 -0
  184. package/dist/replica/normalize.d.ts +27 -0
  185. package/dist/replica/normalize.js +298 -0
  186. package/dist/replica/operation-binding.d.ts +14 -0
  187. package/dist/replica/operation-binding.js +76 -0
  188. package/dist/replica/persistence/idb.d.ts +20 -0
  189. package/dist/replica/persistence/idb.js +193 -0
  190. package/dist/replica/persistence/implementation.d.ts +4 -0
  191. package/dist/replica/persistence/implementation.js +4 -0
  192. package/dist/replica/persistence/index.d.ts +2 -0
  193. package/dist/replica/persistence/index.js +1 -0
  194. package/dist/replica/persistence/state.d.ts +104 -0
  195. package/dist/replica/persistence/state.js +734 -0
  196. package/dist/replica/persistence/types.d.ts +54 -0
  197. package/dist/replica/persistence/types.js +1 -0
  198. package/dist/replica/persistence.d.ts +3 -0
  199. package/dist/replica/persistence.js +2 -0
  200. package/dist/replica/query-plan/constants.d.ts +13 -0
  201. package/dist/replica/query-plan/constants.js +7 -0
  202. package/dist/replica/query-plan/filter.d.ts +39 -0
  203. package/dist/replica/query-plan/filter.js +544 -0
  204. package/dist/replica/query-plan/index.d.ts +4 -0
  205. package/dist/replica/query-plan/index.js +3 -0
  206. package/dist/replica/query-plan/order.d.ts +11 -0
  207. package/dist/replica/query-plan/order.js +158 -0
  208. package/dist/replica/query-plan/pagination.d.ts +6 -0
  209. package/dist/replica/query-plan/pagination.js +90 -0
  210. package/dist/replica/query-plan/resolve.d.ts +14 -0
  211. package/dist/replica/query-plan/resolve.js +163 -0
  212. package/dist/replica/query-plan/types.d.ts +89 -0
  213. package/dist/replica/query-plan/types.js +1 -0
  214. package/dist/replica/query-plan/util.d.ts +13 -0
  215. package/dist/replica/query-plan/util.js +62 -0
  216. package/dist/replica/query-plan.d.ts +3 -0
  217. package/dist/replica/query-plan.js +2 -0
  218. package/dist/replica/revalidation.d.ts +9 -0
  219. package/dist/replica/revalidation.js +86 -0
  220. package/dist/replica/selection.d.ts +20 -0
  221. package/dist/replica/selection.js +109 -0
  222. package/dist/replica/types.d.ts +657 -0
  223. package/dist/replica/types.js +1 -0
  224. package/dist/request.d.ts +17 -0
  225. package/dist/request.js +123 -0
  226. package/dist/sveltekit/auth.d.ts +18 -0
  227. package/dist/sveltekit/auth.js +9 -0
  228. package/dist/sveltekit/context.d.ts +28 -0
  229. package/dist/sveltekit/context.js +58 -0
  230. package/dist/sveltekit/index.d.ts +4 -0
  231. package/dist/sveltekit/index.js +4 -0
  232. package/dist/sveltekit/replica.d.ts +145 -0
  233. package/dist/sveltekit/replica.js +491 -0
  234. package/dist/sveltekit/server-replica.d.ts +52 -0
  235. package/dist/sveltekit/server-replica.js +176 -0
  236. package/dist/sveltekit/vite.d.ts +103 -0
  237. package/dist/sveltekit/vite.js +878 -0
  238. package/dist/types.d.ts +33 -0
  239. package/dist/types.js +1 -0
  240. package/dist/websocket.d.ts +41 -0
  241. package/dist/websocket.js +187 -0
  242. package/package.json +95 -0
@@ -0,0 +1,677 @@
1
+ import { replicaIndexKey, resolveArguments } from '../identity.js';
2
+ import { compareReplicaOrder, decideReplicaPaginationMaintenance, evaluateReplicaFilter } from '../query-plan.js';
3
+ import { compareCodeUnits } from '../../lib/compare-code-units.js';
4
+ export function formatReplicaIndexStaleReason(reasonValue) {
5
+ const path = reasonValue.path.length === 0
6
+ ? ''
7
+ : `:${reasonValue.path.map(String).join('.')}`;
8
+ return `query-plan:${reasonValue.code}${path}`;
9
+ }
10
+ export function compilePlans(registration, artifact, variables) {
11
+ const plans = [];
12
+ const trustedPresets = artifact.protocol.trustedPresets;
13
+ for (const [index, root] of artifact.roots.entries()) {
14
+ const path = ['roots', index, root.field];
15
+ const targetModel = selectionModel(root.selection);
16
+ const kind = targetModel === undefined ? 'snapshot' : 'entity';
17
+ const argumentsValue = resolveArguments(root.arguments, variables, root.coverage);
18
+ plans.push(plan({
19
+ registration,
20
+ kind,
21
+ exactIndexKey: replicaIndexKey({
22
+ field: root.field,
23
+ arguments: argumentsValue
24
+ }),
25
+ targetModel,
26
+ field: root.field,
27
+ arguments: argumentsValue,
28
+ dependencies: root.dependencies,
29
+ cardinality: root.cardinality,
30
+ filter: root.filter,
31
+ order: root.order,
32
+ pagination: root.pagination,
33
+ coverage: root.coverage,
34
+ variables,
35
+ trustedPresets,
36
+ path
37
+ }));
38
+ compileSelectionPlans(plans, registration, root.selection, targetModel, variables, trustedPresets, path);
39
+ }
40
+ return Object.freeze(plans);
41
+ }
42
+ export function compileSelectionPlans(output, registration, selection, parentModel, variables, trustedPresets, path) {
43
+ for (const [index, member] of selection.members.entries()) {
44
+ if (member.kind !== 'branch')
45
+ continue;
46
+ compileBranchPlan(output, registration, member, parentModel, variables, trustedPresets, [...path, 'members', index, member.field]);
47
+ }
48
+ }
49
+ export function compileBranchPlan(output, registration, branch, parentModel, variables, trustedPresets, path) {
50
+ const argumentsValue = resolveArguments(branch.arguments, variables, branch.coverage);
51
+ const targetModel = selectionModel(branch.selection);
52
+ output.push(plan({
53
+ registration,
54
+ kind: branch.semantic === 'relationship' ? 'relationship' : 'snapshot',
55
+ parentModel,
56
+ targetModel,
57
+ field: branch.field,
58
+ arguments: argumentsValue,
59
+ dependencies: branch.dependencies,
60
+ cardinality: branch.cardinality,
61
+ filter: branch.filter,
62
+ order: branch.order,
63
+ pagination: branch.pagination,
64
+ coverage: branch.coverage,
65
+ relationship: branch.relationship,
66
+ variables,
67
+ trustedPresets,
68
+ path
69
+ }));
70
+ compileSelectionPlans(output, registration, branch.selection, targetModel ?? parentModel, variables, trustedPresets, path);
71
+ }
72
+ export function plan(value) {
73
+ const signature = canonicalValue({
74
+ kind: value.kind,
75
+ parentModel: value.parentModel ?? null,
76
+ targetModel: value.targetModel ?? null,
77
+ field: value.field,
78
+ arguments: value.arguments,
79
+ dependencies: [...value.dependencies].sort(compareCodeUnits),
80
+ cardinality: value.cardinality,
81
+ filter: value.filter ?? null,
82
+ order: value.order ?? null,
83
+ pagination: value.pagination ?? null,
84
+ coverage: value.coverage ?? null,
85
+ relationship: value.relationship ?? null,
86
+ trustedPresets: value.trustedPresets
87
+ });
88
+ return Object.freeze({
89
+ ...value,
90
+ arguments: cloneRecord(value.arguments),
91
+ dependencies: Object.freeze([...new Set(value.dependencies)].sort(compareCodeUnits)),
92
+ trustedPresets: Object.freeze(value.trustedPresets.map((descriptor) => Object.freeze({ name: descriptor.name, codec: descriptor.codec }))),
93
+ path: Object.freeze([...value.path]),
94
+ signature
95
+ });
96
+ }
97
+ export function selectionModel(selection) {
98
+ return selection.storage.kind === 'normalized'
99
+ ? selection.storage.model
100
+ : undefined;
101
+ }
102
+ export function instantiatePlans(plans, indexes) {
103
+ const result = new Map();
104
+ for (const value of plans) {
105
+ if (value.exactIndexKey !== undefined) {
106
+ if (indexes.has(value.exactIndexKey)) {
107
+ appendPlan(result, value.exactIndexKey, value);
108
+ }
109
+ continue;
110
+ }
111
+ for (const index of indexes.values()) {
112
+ const parent = index.metadata.parent;
113
+ if (parent === undefined ||
114
+ index.metadata.field !== value.field ||
115
+ replicaIndexKey({
116
+ parent,
117
+ field: value.field,
118
+ arguments: value.arguments
119
+ }) !== index.key ||
120
+ (value.parentModel !== undefined &&
121
+ !recordKeyMatchesModel(parent, value.parentModel))) {
122
+ continue;
123
+ }
124
+ appendPlan(result, index.key, value);
125
+ }
126
+ }
127
+ return result;
128
+ }
129
+ export function appendPlan(output, key, value) {
130
+ const current = output.get(key) ?? [];
131
+ if (current.some((candidate) => candidate.registration === value.registration &&
132
+ candidate.signature === value.signature)) {
133
+ return;
134
+ }
135
+ current.push(value);
136
+ output.set(key, current);
137
+ }
138
+ export function applyLayers(base, layers) {
139
+ const records = new Map();
140
+ for (const input of base) {
141
+ validateName(input.key, 'record key');
142
+ validateName(input.model, 'record model');
143
+ if (records.has(input.key)) {
144
+ throw new TypeError(`duplicate maintenance record: ${input.key}`);
145
+ }
146
+ records.set(input.key, {
147
+ key: input.key,
148
+ model: input.model,
149
+ fields: { ...input.fields }
150
+ });
151
+ }
152
+ const baseRecords = new Map([...records].map(([key, record]) => [
153
+ key,
154
+ {
155
+ key: record.key,
156
+ model: record.model,
157
+ fields: { ...record.fields }
158
+ }
159
+ ]));
160
+ const recordChanges = [];
161
+ const relationshipChanges = [];
162
+ const invalidatedDependencies = new Set();
163
+ const allDependencies = new Set();
164
+ const layerIds = new Set();
165
+ for (const layer of layers) {
166
+ validateName(layer.id, 'semantic layer id');
167
+ if (layerIds.has(layer.id)) {
168
+ throw new TypeError(`duplicate semantic layer: ${layer.id}`);
169
+ }
170
+ layerIds.add(layer.id);
171
+ for (const change of layer.changes) {
172
+ if (change.kind === 'invalidate') {
173
+ for (const dependency of validateDependencies(change.dependencies)) {
174
+ invalidatedDependencies.add(dependency);
175
+ allDependencies.add(dependency);
176
+ }
177
+ continue;
178
+ }
179
+ if (isRelationshipChange(change)) {
180
+ validateName(change.sourceModel, 'relationship source model');
181
+ validateName(change.field, 'relationship field');
182
+ validateName(change.targetModel, 'relationship target model');
183
+ validateName(change.sourceKey, 'relationship source key');
184
+ validateName(change.targetKey, 'relationship target key');
185
+ for (const dependency of validateDependencies(change.dependencies)) {
186
+ allDependencies.add(dependency);
187
+ }
188
+ relationshipChanges.push(change);
189
+ continue;
190
+ }
191
+ validateName(change.model, 'record change model');
192
+ validateName(change.key, 'record change key');
193
+ for (const dependency of validateDependencies(change.dependencies ?? [])) {
194
+ allDependencies.add(dependency);
195
+ }
196
+ recordChanges.push(change);
197
+ if (change.kind === 'delete') {
198
+ records.delete(change.key);
199
+ continue;
200
+ }
201
+ const current = records.get(change.key);
202
+ if (current !== undefined && current.model !== change.model) {
203
+ throw new TypeError(`record model changed for ${change.key}`);
204
+ }
205
+ records.set(change.key, {
206
+ key: change.key,
207
+ model: change.model,
208
+ fields: { ...(current?.fields ?? {}), ...change.fields }
209
+ });
210
+ }
211
+ }
212
+ return {
213
+ baseRecords,
214
+ records,
215
+ recordChanges: Object.freeze(recordChanges),
216
+ relationshipChanges: Object.freeze(relationshipChanges),
217
+ invalidatedDependencies,
218
+ allDependencies
219
+ };
220
+ }
221
+ export function validateIndexes(input) {
222
+ const indexes = new Map();
223
+ for (const index of input) {
224
+ validateName(index.key, 'index key');
225
+ if (indexes.has(index.key)) {
226
+ throw new TypeError(`duplicate maintenance index: ${index.key}`);
227
+ }
228
+ indexes.set(index.key, index);
229
+ }
230
+ return indexes;
231
+ }
232
+ export function planAffected(planValue, index, changes) {
233
+ if (planValue.kind === 'snapshot' &&
234
+ intersects(planValue.dependencies, changes.allDependencies)) {
235
+ return true;
236
+ }
237
+ if (intersects(planValue.dependencies, changes.invalidatedDependencies)) {
238
+ return true;
239
+ }
240
+ if (planValue.targetModel !== undefined &&
241
+ changes.recordChanges.some(({ model }) => model === planValue.targetModel)) {
242
+ return true;
243
+ }
244
+ if (planValue.kind === 'relationship' &&
245
+ planValue.parentModel !== undefined &&
246
+ changes.recordChanges.some((change) => parentRecordChangeAffectsRelationship(planValue, index, change))) {
247
+ return true;
248
+ }
249
+ return changes.relationshipChanges.some((change) => relationshipChangeMatches(planValue, index, change));
250
+ }
251
+ export function evaluatePlan(planValue, index, changes, trustedPresets) {
252
+ if (planValue.kind === 'snapshot') {
253
+ return stale(index.key, reason('aggregate_dependency_changed', planValue.path, 'an aggregate or embedded query snapshot dependency changed'));
254
+ }
255
+ if (intersects(planValue.dependencies, changes.invalidatedDependencies)) {
256
+ return stale(index.key, reason('dependency_changed', planValue.path, 'a declared dependency changed without locally provable row evidence'));
257
+ }
258
+ /*
259
+ * Freshness and structure are independent. Revalidation deliberately marks
260
+ * a complete index stale while retaining its last authoritative membership.
261
+ * Pending semantic layers must keep rebasing over that membership; the
262
+ * derived write preserves the stale metadata until the network result lands.
263
+ */
264
+ if (!index.complete) {
265
+ return stale(index.key, reason('index_incomplete', ['indexes', index.key], 'only a structurally complete index can be maintained locally'));
266
+ }
267
+ if (planValue.cardinality !== 'many') {
268
+ const targetChanges = changes.recordChanges.filter((change) => change.model === planValue.targetModel);
269
+ if (changes.relationshipChanges.length === 0 &&
270
+ targetChanges.length > 0 &&
271
+ targetChanges.every((change) => change.kind === 'upsert' && index.records.includes(change.key))) {
272
+ // Updating fields on the already-selected singular identity cannot
273
+ // change that index's membership. The sparse record dependency will
274
+ // still publish selected field changes to the UI.
275
+ return unchanged(index.key);
276
+ }
277
+ return stale(index.key, reason('unsupported_cardinality', planValue.path, 'local query-index maintenance is limited to collection fields'));
278
+ }
279
+ if (index.metadata.field !== planValue.field ||
280
+ !sameValue(index.metadata.arguments, planValue.arguments)) {
281
+ return stale(index.key, reason('invalid_index_metadata', ['indexes', index.key], 'stored index metadata does not match its compiler plan'));
282
+ }
283
+ const duplicate = firstDuplicate(index.records);
284
+ if (duplicate !== undefined) {
285
+ return stale(index.key, reason('duplicate_index_record', ['indexes', index.key], `stored index contains duplicate record ${duplicate}`));
286
+ }
287
+ if (planValue.kind === 'relationship' &&
288
+ planValue.relationship?.maintenance !== 'local') {
289
+ return stale(index.key, reason('relationship_maintenance_revalidate', planValue.path, 'the compiler marked this relationship as revalidation-only'));
290
+ }
291
+ return maintainEntityIndex(planValue, index, changes, trustedPresets);
292
+ }
293
+ export function maintainEntityIndex(planValue, index, changes, trustedPresets) {
294
+ const current = [...index.records];
295
+ const members = new Set(current);
296
+ const candidates = new Set();
297
+ for (const change of changes.recordChanges) {
298
+ if (change.model === planValue.targetModel)
299
+ candidates.add(change.key);
300
+ }
301
+ const relationshipOverrides = new Map();
302
+ if (planValue.kind === 'relationship') {
303
+ const parentChanged = changes.recordChanges.some((change) => parentRecordChangeAffectsRelationship(planValue, index, change));
304
+ if (parentChanged) {
305
+ return stale(index.key, reason('relationship_mapping_unknown', planValue.path, 'a parent-key dependency changed and sparse target coverage cannot prove the new relationship'));
306
+ }
307
+ for (const change of changes.relationshipChanges) {
308
+ if (!relationshipChangeMatches(planValue, index, change))
309
+ continue;
310
+ const missing = planValue.relationship.dependencies.filter((dependency) => !change.dependencies.includes(dependency));
311
+ if (missing.length > 0) {
312
+ return stale(index.key, reason('relationship_dependency_missing', planValue.path, `relationship change omits declared dependencies: ${missing.join(', ')}`));
313
+ }
314
+ candidates.add(change.targetKey);
315
+ relationshipOverrides.set(change.targetKey, change.kind === 'link');
316
+ }
317
+ }
318
+ let inserted = false;
319
+ let removed = false;
320
+ for (const key of candidates) {
321
+ const record = changes.records.get(key);
322
+ let related = true;
323
+ if (planValue.kind === 'relationship') {
324
+ const override = relationshipOverrides.get(key);
325
+ if (override !== undefined) {
326
+ related = override;
327
+ }
328
+ else if (members.has(key)) {
329
+ related = true;
330
+ }
331
+ else {
332
+ const relation = relationshipMembership(planValue, index, record, changes.records);
333
+ if ('reason' in relation)
334
+ return stale(index.key, relation.reason);
335
+ related = relation.related;
336
+ }
337
+ }
338
+ if (record === undefined || !related) {
339
+ if (members.delete(key))
340
+ removed = true;
341
+ continue;
342
+ }
343
+ const filter = evaluateMembership(planValue, record, trustedPresets);
344
+ if ('reason' in filter)
345
+ return stale(index.key, filter.reason);
346
+ if (filter.matches) {
347
+ if (!members.has(key)) {
348
+ members.add(key);
349
+ inserted = true;
350
+ }
351
+ }
352
+ else if (members.delete(key)) {
353
+ removed = true;
354
+ }
355
+ }
356
+ let activeOrderChanged = false;
357
+ if (planValue.order !== undefined) {
358
+ for (const key of candidates) {
359
+ if (!members.has(key))
360
+ continue;
361
+ const before = changes.baseRecords.get(key);
362
+ const after = changes.records.get(key);
363
+ if (before === undefined || after === undefined)
364
+ continue;
365
+ const compared = compareReplicaOrder(planValue.order, before.fields, after.fields, planValue.variables);
366
+ if (compared.result === 'unknown') {
367
+ return stale(index.key, compared.reason);
368
+ }
369
+ if (compared.result !== 'equal') {
370
+ activeOrderChanged = true;
371
+ break;
372
+ }
373
+ }
374
+ }
375
+ let records = current.filter((key) => members.has(key));
376
+ for (const key of members) {
377
+ if (!records.includes(key))
378
+ records.push(key);
379
+ }
380
+ const needsOrder = inserted || activeOrderChanged;
381
+ if (needsOrder) {
382
+ if (planValue.order === undefined && inserted) {
383
+ return stale(index.key, reason('missing_order_plan', planValue.path, 'an inserted record cannot be positioned without a compiler order plan'));
384
+ }
385
+ if (planValue.order !== undefined) {
386
+ const sorted = sortRecords(records, planValue.order, changes.records, planValue.variables, planValue.path);
387
+ if ('reason' in sorted)
388
+ return stale(index.key, sorted.reason);
389
+ records = [...sorted.records];
390
+ }
391
+ }
392
+ const reordered = !inserted &&
393
+ !removed &&
394
+ (activeOrderChanged || !sameStringList(records, current));
395
+ const changeKind = inserted
396
+ ? 'insert'
397
+ : removed
398
+ ? 'delete'
399
+ : reordered
400
+ ? 'reorder'
401
+ : 'stable_update';
402
+ if (planValue.pagination === undefined) {
403
+ if (changeKind === 'stable_update' &&
404
+ sameStringList(records, current)) {
405
+ // An uncertified collection can still prove a membership/order no-op;
406
+ // that stable update needs no index write or revalidation.
407
+ return unchanged(index.key);
408
+ }
409
+ return stale(index.key, reason('invalid_artifact', planValue.path, 'collection plan is missing its pagination maintenance contract'));
410
+ }
411
+ const coverageReason = certifyOffsetCoverage(planValue, index, current.length, changeKind);
412
+ if (coverageReason !== undefined) {
413
+ return stale(index.key, coverageReason);
414
+ }
415
+ const pagination = decideReplicaPaginationMaintenance(planValue.pagination, index.metadata.coverage, { kind: changeKind });
416
+ if (pagination.decision === 'revalidate') {
417
+ return stale(index.key, pagination.reason);
418
+ }
419
+ if (planValue.pagination.kind === 'offset' &&
420
+ index.metadata.coverage.kind === 'offset' &&
421
+ index.metadata.coverage.offset === 0 &&
422
+ index.metadata.coverage.limit !== undefined &&
423
+ records.length > index.metadata.coverage.limit) {
424
+ // Locality above is possible only because the base first page was
425
+ // non-full, so this is the complete optimistic ordered set. Preserve the
426
+ // operation's exact window when stacked optimistic inserts cross its
427
+ // limit.
428
+ records = records.slice(0, index.metadata.coverage.limit);
429
+ }
430
+ return sameStringList(records, current)
431
+ ? unchanged(index.key)
432
+ : write(index.key, records);
433
+ }
434
+ export function certifyOffsetCoverage(planValue, index, confirmedRecords, changeKind) {
435
+ if (planValue.pagination?.kind !== 'offset' ||
436
+ changeKind === 'stable_update') {
437
+ return undefined;
438
+ }
439
+ const artifact = planValue.coverage;
440
+ const coverage = index.metadata.coverage;
441
+ if (artifact?.kind !== 'offset' || coverage.kind !== 'offset') {
442
+ return reason('invalid_index_metadata', ['indexes', index.key, 'coverage'], 'offset pagination locality requires its exact compiler coverage contract');
443
+ }
444
+ const expectedOffset = artifact.offsetArgument === undefined
445
+ ? 0
446
+ : planValue.arguments[artifact.offsetArgument];
447
+ const configuredLimit = artifact.limitArgument === undefined
448
+ ? artifact.defaultLimit
449
+ : planValue.arguments[artifact.limitArgument];
450
+ const expectedLimit = typeof configuredLimit === 'number' &&
451
+ typeof artifact.maxLimit === 'number'
452
+ ? Math.min(configuredLimit, artifact.maxLimit)
453
+ : configuredLimit;
454
+ if (typeof expectedOffset !== 'number' ||
455
+ !Number.isSafeInteger(expectedOffset) ||
456
+ expectedOffset < 0 ||
457
+ (typeof expectedLimit !== 'number' &&
458
+ expectedLimit !== undefined) ||
459
+ (typeof expectedLimit === 'number' &&
460
+ (!Number.isSafeInteger(expectedLimit) || expectedLimit < 0)) ||
461
+ coverage.offset !== expectedOffset ||
462
+ coverage.limit !== expectedLimit ||
463
+ coverage.returned !== confirmedRecords ||
464
+ coverage.hasNext === true) {
465
+ return reason('invalid_index_metadata', ['indexes', index.key, 'coverage'], 'offset coverage does not match the request, confirmed index size, or observed page boundary');
466
+ }
467
+ return undefined;
468
+ }
469
+ export function parentRecordChangeAffectsRelationship(planValue, index, change) {
470
+ if (change.model !== planValue.parentModel ||
471
+ change.key !== index.metadata.parent) {
472
+ return false;
473
+ }
474
+ if (change.kind === 'delete')
475
+ return true;
476
+ const relationship = planValue.relationship;
477
+ if (relationship === undefined) {
478
+ // A forged relationship branch without its compiler-owned mapping must
479
+ // be evaluated and rejected instead of silently ignoring a parent change.
480
+ return true;
481
+ }
482
+ const mapping = relationship.keyMapping;
483
+ const mappingFields = mapping.kind === 'embedded' ? Object.freeze([]) : mapping.local;
484
+ if (mappingFields.some((field) => Object.prototype.hasOwnProperty.call(change.fields, field))) {
485
+ return true;
486
+ }
487
+ const dependencies = new Set(change.dependencies ?? []);
488
+ return relationship.dependencies.some((dependency) => dependencies.has(dependency));
489
+ }
490
+ export function relationshipMembership(planValue, index, target, records) {
491
+ if (target === undefined)
492
+ return { related: false };
493
+ const relationship = planValue.relationship;
494
+ const parentKey = index.metadata.parent;
495
+ if (relationship === undefined || parentKey === undefined) {
496
+ return {
497
+ reason: reason('relationship_mapping_unknown', planValue.path, 'relationship plan or parent identity is unavailable')
498
+ };
499
+ }
500
+ if (relationship.keyMapping.kind !== 'direct') {
501
+ return {
502
+ reason: reason('relationship_mapping_unknown', planValue.path, 'a non-member through/opaque relationship requires explicit link evidence')
503
+ };
504
+ }
505
+ const parent = records.get(parentKey);
506
+ if (parent === undefined) {
507
+ return {
508
+ reason: reason('missing_parent_record', planValue.path, 'relationship parent record is absent')
509
+ };
510
+ }
511
+ for (let indexValue = 0; indexValue < relationship.keyMapping.local.length; indexValue += 1) {
512
+ const local = relationship.keyMapping.local[indexValue];
513
+ const remote = relationship.keyMapping.remote[indexValue];
514
+ if (!Object.prototype.hasOwnProperty.call(parent.fields, local) ||
515
+ !Object.prototype.hasOwnProperty.call(target.fields, remote)) {
516
+ return {
517
+ reason: reason('missing_field', [...planValue.path, 'relationship', local, remote], 'relationship key fields are not completely present')
518
+ };
519
+ }
520
+ if (!sameValue(parent.fields[local], target.fields[remote])) {
521
+ return { related: false };
522
+ }
523
+ }
524
+ return { related: true };
525
+ }
526
+ export function evaluateMembership(planValue, record, trustedPresets) {
527
+ if (planValue.filter === undefined)
528
+ return { matches: true };
529
+ const evaluated = evaluateReplicaFilter(planValue.filter, record.fields, planValue.variables, {
530
+ trustedPresets: {
531
+ descriptors: planValue.trustedPresets,
532
+ values: trustedPresets
533
+ }
534
+ });
535
+ return evaluated.result === 'unknown'
536
+ ? { reason: evaluated.reason }
537
+ : { matches: evaluated.result === 'match' };
538
+ }
539
+ export function sortRecords(keys, order, records, variables, path) {
540
+ let unknown;
541
+ const sorted = [...keys].sort((leftKey, rightKey) => {
542
+ if (unknown !== undefined || leftKey === rightKey)
543
+ return 0;
544
+ const left = records.get(leftKey);
545
+ const right = records.get(rightKey);
546
+ if (left === undefined || right === undefined) {
547
+ unknown = reason('missing_index_record', path, `ordered index references missing record ${left === undefined ? leftKey : rightKey}`);
548
+ return 0;
549
+ }
550
+ const compared = compareReplicaOrder(order, left.fields, right.fields, variables);
551
+ if (compared.result === 'unknown') {
552
+ unknown = compared.reason;
553
+ return 0;
554
+ }
555
+ if (compared.result === 'equal') {
556
+ unknown = reason('non_unique_order', path, `distinct records ${leftKey} and ${rightKey} have no deterministic order`);
557
+ return 0;
558
+ }
559
+ return compared.result === 'less' ? -1 : 1;
560
+ });
561
+ return unknown === undefined
562
+ ? { records: Object.freeze(sorted) }
563
+ : { reason: unknown };
564
+ }
565
+ export function relationshipChangeMatches(planValue, index, change) {
566
+ return (planValue.kind === 'relationship' &&
567
+ planValue.parentModel === change.sourceModel &&
568
+ planValue.field === change.field &&
569
+ planValue.targetModel === change.targetModel &&
570
+ index.metadata.parent === change.sourceKey);
571
+ }
572
+ export function isRelationshipChange(change) {
573
+ return change.kind === 'link' || change.kind === 'unlink';
574
+ }
575
+ export function validateDependencies(value) {
576
+ if (!Array.isArray(value))
577
+ throw new TypeError('dependencies must be an array');
578
+ const seen = new Set();
579
+ for (const dependency of value) {
580
+ validateName(dependency, 'dependency');
581
+ if (seen.has(dependency)) {
582
+ throw new TypeError(`duplicate dependency: ${dependency}`);
583
+ }
584
+ seen.add(dependency);
585
+ }
586
+ return value;
587
+ }
588
+ export function intersects(left, right) {
589
+ return left.some((value) => right.has(value));
590
+ }
591
+ export function recordKeyMatchesModel(key, model) {
592
+ return key.startsWith(`record:${encodeURIComponent(model)}:`);
593
+ }
594
+ export function firstDuplicate(values) {
595
+ const seen = new Set();
596
+ for (const value of values) {
597
+ if (seen.has(value))
598
+ return value;
599
+ seen.add(value);
600
+ }
601
+ return undefined;
602
+ }
603
+ export function write(indexKey, records) {
604
+ return Object.freeze({
605
+ kind: 'write',
606
+ indexKey,
607
+ records: Object.freeze([...records]),
608
+ complete: true
609
+ });
610
+ }
611
+ export function stale(indexKey, reasonValue) {
612
+ return Object.freeze({
613
+ kind: 'stale',
614
+ indexKey,
615
+ reason: reasonValue
616
+ });
617
+ }
618
+ export function unchanged(indexKey) {
619
+ return Object.freeze({ kind: 'unchanged', indexKey });
620
+ }
621
+ export function reason(code, path, message) {
622
+ return Object.freeze({
623
+ code,
624
+ path: Object.freeze([...path]),
625
+ message
626
+ });
627
+ }
628
+ export function sameStringList(left, right) {
629
+ return (left.length === right.length &&
630
+ left.every((value, index) => value === right[index]));
631
+ }
632
+ export function sameValue(left, right) {
633
+ return canonicalValue(left) === canonicalValue(right);
634
+ }
635
+ export function cloneRecord(value) {
636
+ return Object.freeze(Object.fromEntries(Object.entries(value)
637
+ .sort(([left], [right]) => compareCodeUnits(left, right))
638
+ .map(([key, entry]) => [key, cloneValue(entry)])));
639
+ }
640
+ export function cloneValue(value) {
641
+ if (Array.isArray(value))
642
+ return Object.freeze(value.map(cloneValue));
643
+ if (value !== null && typeof value === 'object') {
644
+ return cloneRecord(value);
645
+ }
646
+ return value;
647
+ }
648
+ export function canonicalValue(value) {
649
+ if (value === null)
650
+ return 'null';
651
+ if (typeof value === 'string')
652
+ return JSON.stringify(value);
653
+ if (typeof value === 'boolean')
654
+ return value ? 'true' : 'false';
655
+ if (typeof value === 'number') {
656
+ if (!Number.isFinite(value))
657
+ throw new TypeError('canonical values must be finite');
658
+ return Object.is(value, -0) ? '0' : JSON.stringify(value);
659
+ }
660
+ if (Array.isArray(value)) {
661
+ return `[${value.map(canonicalValue).join(',')}]`;
662
+ }
663
+ if (typeof value === 'object') {
664
+ const entries = Object.entries(value)
665
+ .filter(([, entry]) => entry !== undefined)
666
+ .sort(([left], [right]) => compareCodeUnits(left, right));
667
+ return `{${entries
668
+ .map(([key, entry]) => `${JSON.stringify(key)}:${canonicalValue(entry)}`)
669
+ .join(',')}}`;
670
+ }
671
+ throw new TypeError('canonical values must be JSON-compatible');
672
+ }
673
+ export function validateName(value, description) {
674
+ if (typeof value !== 'string' || value.length === 0) {
675
+ throw new TypeError(`${description} must be a non-empty string`);
676
+ }
677
+ }
@@ -0,0 +1,4 @@
1
+ /** Re-export surface for index-maintenance modules (body-split). */
2
+ export * from './types.js';
3
+ export * from './engine.js';
4
+ export * from './registry.js';
@@ -0,0 +1,4 @@
1
+ /** Re-export surface for index-maintenance modules (body-split). */
2
+ export * from './types.js';
3
+ export * from './engine.js';
4
+ export * from './registry.js';
@@ -0,0 +1,2 @@
1
+ export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './implementation.js';
2
+ export type { ReplicaIndexDependencyChange, ReplicaIndexMaintenanceDecision, ReplicaIndexMaintenanceIndex, ReplicaIndexMaintenanceReason, ReplicaIndexMaintenanceReasonCode, ReplicaIndexMaintenanceRecord, ReplicaIndexMaintenanceRegistry, ReplicaIndexMaintenanceSnapshot, ReplicaIndexPlanRegistration, ReplicaIndexRecordChange, ReplicaIndexRelationshipChange, ReplicaIndexSemanticChange, ReplicaIndexSemanticLayer } from './implementation.js';