@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,2 @@
1
+ import type { DistributedReplica as DistributedReplicaApi, DistributedReplicaOptions } from '../types.js';
2
+ export declare function createDistributedReplica(options?: DistributedReplicaOptions): DistributedReplicaApi;
@@ -0,0 +1,4 @@
1
+ import { DistributedReplicaImpl } from './impl.js';
2
+ export function createDistributedReplica(options = {}) {
3
+ return new DistributedReplicaImpl(options);
4
+ }
@@ -0,0 +1,30 @@
1
+ import { type CacheValue, type OptimisticCacheWriter, type RecordLink } from '../../internal/cache-engine.js';
2
+ import type { DistributedCommandMetadata, DistributedOpaqueString } from '../../protocol.js';
3
+ import type { ReplicaIndexSemanticChange } from '../index-maintenance.js';
4
+ import type { ReplicaOptimisticWriter, ReplicaRecordPatch } from '../types.js';
5
+ import type { CapturedReplicaOptimisticOperation, CapturedReplicaOptimisticUpdate, OptimisticReceiptState } from './types.js';
6
+ export declare function optimisticReceiptState(command: DistributedCommandMetadata): OptimisticReceiptState;
7
+ export declare function cloneOptimisticReceipt(receipt: OptimisticReceiptState): OptimisticReceiptState;
8
+ export declare function sameReceipt(left: OptimisticReceiptState, right: OptimisticReceiptState): boolean;
9
+ export declare function diagnosticReceiptExpectations(receipt: OptimisticReceiptState): readonly {
10
+ readonly projection: string;
11
+ readonly model: string;
12
+ readonly observed: boolean;
13
+ }[];
14
+ export declare function diagnosticReceiptCounts(receipt: OptimisticReceiptState | undefined): Readonly<{
15
+ obligations: number;
16
+ observed: number;
17
+ }>;
18
+ export declare function expectationKey(value: {
19
+ readonly projection: string;
20
+ readonly model: string;
21
+ readonly scopeToken: DistributedOpaqueString;
22
+ }): string;
23
+ export declare function captureReplicaOptimisticUpdate(id: string, update: (writer: ReplicaOptimisticWriter) => void, semanticChanges: readonly ReplicaIndexSemanticChange[]): CapturedReplicaOptimisticUpdate;
24
+ export declare function replayReplicaOptimisticUpdate(writer: OptimisticCacheWriter, operations: readonly CapturedReplicaOptimisticOperation[]): void;
25
+ export declare function cloneOptimisticFields(fields: ReplicaRecordPatch['fields']): Readonly<Record<string, CacheValue>>;
26
+ export declare function cloneOptimisticLinks(links: ReplicaRecordPatch['links']): Readonly<Record<string, RecordLink>>;
27
+ export declare function assertPlainReplicaRecord(value: object, description: string): void;
28
+ export declare function assertReplicaOptimisticLayerId(id: string): void;
29
+ export declare function assertReplicaName(value: string, description: string): void;
30
+ export declare function assertReplicaOptimisticUpdateSynchronous(result: unknown): void;
@@ -0,0 +1,221 @@
1
+ import {} from '../../internal/cache-engine.js';
2
+ import { cloneJsonValue, replicaRecordKey } from '../identity.js';
3
+ import { protocolInvalid } from './clocks.js';
4
+ import { indexKeyFromTarget, metadataFromTarget } from './helpers.js';
5
+ export function optimisticReceiptState(command) {
6
+ const expectations = new Map();
7
+ for (const expectation of command.expects) {
8
+ const key = expectationKey(expectation);
9
+ if (expectations.has(key)) {
10
+ protocolInvalid('extensions.distributed.command.expects');
11
+ }
12
+ expectations.set(key, true);
13
+ }
14
+ const observed = new Set();
15
+ for (const observation of command.observations) {
16
+ if (observation.causationId !== command.causationId) {
17
+ protocolInvalid('extensions.distributed.command.observations');
18
+ }
19
+ const key = expectationKey(observation);
20
+ if (!expectations.has(key)) {
21
+ protocolInvalid('extensions.distributed.command.observations');
22
+ }
23
+ observed.add(key);
24
+ }
25
+ return {
26
+ causationId: command.causationId,
27
+ expectations,
28
+ observed
29
+ };
30
+ }
31
+ export function cloneOptimisticReceipt(receipt) {
32
+ return {
33
+ causationId: receipt.causationId,
34
+ expectations: new Map(receipt.expectations),
35
+ observed: new Set(receipt.observed)
36
+ };
37
+ }
38
+ export function sameReceipt(left, right) {
39
+ return (left.causationId === right.causationId &&
40
+ left.expectations.size === right.expectations.size &&
41
+ [...left.expectations.keys()].every((key) => right.expectations.has(key)));
42
+ }
43
+ export function diagnosticReceiptExpectations(receipt) {
44
+ return Object.freeze([...receipt.expectations.keys()]
45
+ .map((key) => {
46
+ const parsed = JSON.parse(key);
47
+ if (!Array.isArray(parsed) ||
48
+ parsed.length !== 3 ||
49
+ typeof parsed[0] !== 'string' ||
50
+ typeof parsed[1] !== 'string') {
51
+ throw new TypeError('invalid internal projection expectation');
52
+ }
53
+ return Object.freeze({
54
+ projection: parsed[0],
55
+ model: parsed[1],
56
+ observed: receipt.observed.has(key)
57
+ });
58
+ })
59
+ .sort((left, right) => `${left.projection}\0${left.model}`.localeCompare(`${right.projection}\0${right.model}`)));
60
+ }
61
+ export function diagnosticReceiptCounts(receipt) {
62
+ return Object.freeze({
63
+ obligations: receipt?.expectations.size ?? 0,
64
+ observed: receipt?.observed.size ?? 0
65
+ });
66
+ }
67
+ export function expectationKey(value) {
68
+ return JSON.stringify([value.projection, value.model, value.scopeToken]);
69
+ }
70
+ export function captureReplicaOptimisticUpdate(id, update, semanticChanges) {
71
+ if (!Array.isArray(semanticChanges)) {
72
+ throw new TypeError('optimistic semantic changes must be an array');
73
+ }
74
+ const suppliedChanges = cloneJsonValue(semanticChanges);
75
+ const operations = [];
76
+ const changes = [];
77
+ let active = true;
78
+ const assertActive = () => {
79
+ if (!active)
80
+ throw new Error('replica optimistic writer is no longer active');
81
+ };
82
+ const writer = Object.freeze({
83
+ writeRecord(model, identity, patch) {
84
+ assertActive();
85
+ const key = replicaRecordKey(model, identity);
86
+ const fields = cloneOptimisticFields(patch.fields);
87
+ const links = cloneOptimisticLinks(patch.links);
88
+ if (Object.keys(fields).length === 0 && Object.keys(links).length === 0) {
89
+ return;
90
+ }
91
+ operations.push(Object.freeze({
92
+ kind: 'write-record',
93
+ write: Object.freeze({ key, fields, links })
94
+ }));
95
+ changes.push(Object.freeze({
96
+ kind: 'upsert',
97
+ model: model.id,
98
+ key,
99
+ fields
100
+ }));
101
+ },
102
+ tombstoneRecord(model, identity) {
103
+ assertActive();
104
+ const key = replicaRecordKey(model, identity);
105
+ operations.push(Object.freeze({ kind: 'tombstone-record', key }));
106
+ changes.push(Object.freeze({
107
+ kind: 'delete',
108
+ model: model.id,
109
+ key
110
+ }));
111
+ },
112
+ writeIndex(target, records) {
113
+ assertActive();
114
+ if (!Array.isArray(records)) {
115
+ throw new TypeError('index records must be an array');
116
+ }
117
+ const metadata = cloneJsonValue(metadataFromTarget(target));
118
+ operations.push(Object.freeze({
119
+ kind: 'write-index',
120
+ write: Object.freeze({
121
+ key: indexKeyFromTarget(target),
122
+ records: Object.freeze([...records]),
123
+ complete: target.complete ?? false,
124
+ metadata
125
+ })
126
+ }));
127
+ },
128
+ deleteIndex(target) {
129
+ assertActive();
130
+ operations.push(Object.freeze({
131
+ kind: 'delete-index',
132
+ key: indexKeyFromTarget(target)
133
+ }));
134
+ }
135
+ });
136
+ try {
137
+ const result = update(writer);
138
+ assertReplicaOptimisticUpdateSynchronous(result);
139
+ }
140
+ finally {
141
+ active = false;
142
+ }
143
+ changes.push(...suppliedChanges);
144
+ return Object.freeze({
145
+ operations: Object.freeze(operations),
146
+ context: cloneJsonValue({
147
+ id,
148
+ changes
149
+ })
150
+ });
151
+ }
152
+ export function replayReplicaOptimisticUpdate(writer, operations) {
153
+ for (const operation of operations) {
154
+ if (operation.kind === 'write-record') {
155
+ writer.writeRecord(operation.write);
156
+ }
157
+ else if (operation.kind === 'tombstone-record') {
158
+ writer.tombstoneRecord(operation.key);
159
+ }
160
+ else if (operation.kind === 'write-index') {
161
+ writer.writeIndex(operation.write);
162
+ }
163
+ else {
164
+ writer.deleteIndex(operation.key);
165
+ }
166
+ }
167
+ }
168
+ export function cloneOptimisticFields(fields) {
169
+ if (fields === undefined)
170
+ return Object.freeze({});
171
+ assertPlainReplicaRecord(fields, 'record fields');
172
+ return Object.freeze(Object.fromEntries(Object.entries(fields).map(([name, value]) => {
173
+ assertReplicaName(name, 'record field');
174
+ return [name, cloneJsonValue(value)];
175
+ })));
176
+ }
177
+ export function cloneOptimisticLinks(links) {
178
+ if (links === undefined)
179
+ return Object.freeze({});
180
+ assertPlainReplicaRecord(links, 'record links');
181
+ return Object.freeze(Object.fromEntries(Object.entries(links).map(([name, value]) => {
182
+ assertReplicaName(name, 'record link');
183
+ if (value === null)
184
+ return [name, null];
185
+ if (typeof value === 'string') {
186
+ assertReplicaName(value, 'record key');
187
+ return [name, value];
188
+ }
189
+ if (!Array.isArray(value)) {
190
+ throw new TypeError('record link must be a key, key array, or null');
191
+ }
192
+ const keys = value.map((key) => {
193
+ assertReplicaName(key, 'record key');
194
+ return key;
195
+ });
196
+ return [name, Object.freeze(keys)];
197
+ })));
198
+ }
199
+ export function assertPlainReplicaRecord(value, description) {
200
+ const prototype = Object.getPrototypeOf(value);
201
+ if (Array.isArray(value) ||
202
+ (prototype !== Object.prototype && prototype !== null)) {
203
+ throw new TypeError(`${description} must be a plain object`);
204
+ }
205
+ }
206
+ export function assertReplicaOptimisticLayerId(id) {
207
+ assertReplicaName(id, 'optimistic layer id');
208
+ }
209
+ export function assertReplicaName(value, description) {
210
+ if (typeof value !== 'string' || value.length === 0) {
211
+ throw new TypeError(`${description} must be a non-empty string`);
212
+ }
213
+ }
214
+ export function assertReplicaOptimisticUpdateSynchronous(result) {
215
+ if (result !== null &&
216
+ (typeof result === 'object' || typeof result === 'function') &&
217
+ typeof result.then === 'function') {
218
+ void Promise.resolve(result).catch(() => undefined);
219
+ throw new TypeError('optimistic layer update must be synchronous');
220
+ }
221
+ }
@@ -0,0 +1,147 @@
1
+ import type { GqlError, GraphqlVariables } from '../../types.js';
2
+ import type { CacheEngineSnapshot, CacheValue, OptimisticIndexWrite, OptimisticRecordWrite } from '../../internal/cache-engine.js';
3
+ import type { DistributedDecimalString, DistributedLiveCursor, DistributedOpaqueString, DistributedTrustedPreset } from '../../protocol.js';
4
+ import type { ReplicaTrustedPresetDescriptor } from '../commands.js';
5
+ import type { ValidatedReplicaOperationBinding } from '../operation-binding.js';
6
+ import type { ReplicaLiveState, ReplicaOperationArtifact, ReplicaValue } from '../types.js';
7
+ export type QueryState = {
8
+ fetching: boolean;
9
+ errors: readonly GqlError[];
10
+ live: ReplicaLiveState;
11
+ };
12
+ export type LiveEntry = {
13
+ count: number;
14
+ unsubscribe: () => void;
15
+ active: boolean;
16
+ protocolGeneration: number;
17
+ operationGeneration?: number;
18
+ };
19
+ export type ProtocolGeneration = {
20
+ protocolVersion: 1;
21
+ cacheScope: DistributedOpaqueString;
22
+ schemaHash: string;
23
+ };
24
+ export type RenderedOperation = {
25
+ readonly artifact: ReplicaOperationArtifact<unknown, GraphqlVariables>;
26
+ readonly variables: GraphqlVariables;
27
+ };
28
+ export type SerializedOperationProtocolState = {
29
+ readonly operation: string;
30
+ readonly snapshotScope?: string;
31
+ readonly indexClocks: readonly (readonly [
32
+ string,
33
+ Readonly<{
34
+ scopeToken: string;
35
+ position: string;
36
+ }>
37
+ ])[];
38
+ readonly indexRevision?: string;
39
+ readonly indexKeys: readonly string[];
40
+ readonly pathRecords: readonly (readonly [string, string])[];
41
+ readonly cursors: readonly DistributedLiveCursor[];
42
+ };
43
+ export type SerializedOperationProtocolGroup = {
44
+ readonly key: string;
45
+ readonly query?: SerializedOperationProtocolState;
46
+ readonly live?: SerializedOperationProtocolState;
47
+ readonly active?: OperationProtocolSource;
48
+ readonly generation: number;
49
+ };
50
+ export type ReplicaDehydratedPayloadV1 = {
51
+ readonly cache: CacheEngineSnapshot;
52
+ readonly operations: readonly SerializedOperationProtocolGroup[];
53
+ readonly recordClocks: readonly (readonly [string, RecordProtocolClock])[];
54
+ readonly anonymousRecordClocks: readonly (readonly [
55
+ string,
56
+ AnonymousRecordProtocolClock
57
+ ])[];
58
+ readonly trustedPresets: readonly DistributedTrustedPreset[];
59
+ readonly nextIndexRevision: string;
60
+ };
61
+ export type ParsedReplicaHydration = {
62
+ readonly scope: ProtocolGeneration;
63
+ readonly cache: CacheEngineSnapshot;
64
+ readonly operationProtocols: Map<string, OperationProtocolGroup>;
65
+ readonly operationGenerations: Map<string, number>;
66
+ readonly recordClocks: Map<string, RecordProtocolClock>;
67
+ readonly recordKeysByScope: Map<DistributedOpaqueString, string>;
68
+ readonly anonymousRecordClocks: Map<DistributedOpaqueString, AnonymousRecordProtocolClock>;
69
+ readonly trustedPresets: readonly DistributedTrustedPreset[];
70
+ readonly nextIndexRevision: string;
71
+ };
72
+ export type RegisteredCommandAuthorityContract = {
73
+ readonly schemaHash: string;
74
+ readonly protocolHash: string;
75
+ readonly surfaceIdentity: string;
76
+ readonly trustedPresets: readonly ReplicaTrustedPresetDescriptor[];
77
+ readonly fingerprint: string;
78
+ };
79
+ export type ReplicaArtifactBinding = {
80
+ version: 1;
81
+ schemaHash: string;
82
+ surfaceIdentity?: string;
83
+ trustedPresets?: readonly ReplicaTrustedPresetDescriptor[];
84
+ };
85
+ export type ValidatedArtifactBinding = ValidatedReplicaOperationBinding;
86
+ export type RecordProtocolClock = {
87
+ scopeToken: DistributedOpaqueString;
88
+ incarnation: DistributedDecimalString;
89
+ revision: DistributedDecimalString;
90
+ tombstone: boolean;
91
+ };
92
+ export type ProjectedRecordFence = {
93
+ readonly fields: Readonly<Record<string, ReplicaValue>>;
94
+ readonly clock: RecordProtocolClock;
95
+ readonly projectionGeneration: number;
96
+ };
97
+ export type AnonymousRecordProtocolClock = {
98
+ model: string;
99
+ clock: RecordProtocolClock;
100
+ };
101
+ export type IndexProtocolClock = {
102
+ scopeToken: DistributedOpaqueString;
103
+ position: DistributedDecimalString;
104
+ };
105
+ export type OperationProtocolState = {
106
+ operation: string;
107
+ snapshotScope?: DistributedOpaqueString;
108
+ indexClocks: Map<string, IndexProtocolClock>;
109
+ indexRevision?: string;
110
+ indexKeys: Set<string>;
111
+ pathRecords: Map<string, string>;
112
+ cursors: readonly DistributedLiveCursor[];
113
+ };
114
+ export type OperationProtocolSource = 'query' | 'live';
115
+ export type OperationProtocolGroup = {
116
+ query?: OperationProtocolState;
117
+ live?: OperationProtocolState;
118
+ active?: OperationProtocolSource;
119
+ };
120
+ export type OptimisticReceiptState = {
121
+ causationId: DistributedOpaqueString;
122
+ expectations: ReadonlyMap<string, true>;
123
+ observed: Set<string>;
124
+ };
125
+ export type IndexDisposition = 'fresh' | 'equal' | 'higher' | 'lower' | 'incomparable';
126
+ export type SharedIndexDisposition = {
127
+ readonly compared: boolean;
128
+ readonly disposition?: 'equal' | 'higher' | 'lower';
129
+ readonly indexRevision?: string;
130
+ };
131
+ export type CapturedReplicaOptimisticOperation = {
132
+ readonly kind: 'write-record';
133
+ readonly write: OptimisticRecordWrite;
134
+ } | {
135
+ readonly kind: 'tombstone-record';
136
+ readonly key: string;
137
+ } | {
138
+ readonly kind: 'write-index';
139
+ readonly write: OptimisticIndexWrite;
140
+ } | {
141
+ readonly kind: 'delete-index';
142
+ readonly key: string;
143
+ };
144
+ export type CapturedReplicaOptimisticUpdate = {
145
+ readonly operations: readonly CapturedReplicaOptimisticOperation[];
146
+ readonly context: CacheValue;
147
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { GraphqlVariables } from '../../types.js';
2
+ import type { MaterializedReplicaResult } from '../materialize.js';
3
+ import type { ReplicaOperationArtifact, ReplicaSnapshot, ReplicaWatch, WatchReplicaOptions } from '../types.js';
4
+ import type { DistributedReplicaImpl } from './impl.js';
5
+ export declare class ReplicaWatchState<TData, TVariables extends GraphqlVariables> implements ReplicaWatch<TData> {
6
+ #private;
7
+ readonly key: string;
8
+ readonly artifact: ReplicaOperationArtifact<TData, TVariables>;
9
+ readonly variables: TVariables;
10
+ readonly liveRequested: boolean;
11
+ materialized: MaterializedReplicaResult<TData>;
12
+ constructor(owner: DistributedReplicaImpl, artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables, options: WatchReplicaOptions);
13
+ get(): ReplicaSnapshot<TData>;
14
+ subscribe(listener: (snapshot: ReplicaSnapshot<TData>) => void): () => void;
15
+ refresh(): Promise<void>;
16
+ destroy(): void;
17
+ _cacheChanged(materialized: MaterializedReplicaResult<TData>): void;
18
+ _stateChanged(allowFetch: boolean): void;
19
+ }
@@ -0,0 +1,95 @@
1
+ import { canonicalizeOperationVariables } from '../identity.js';
2
+ import { operationKey, snapshotEqual, snapshotFrom, deepEqual } from './helpers.js';
3
+ export class ReplicaWatchState {
4
+ key;
5
+ artifact;
6
+ variables;
7
+ liveRequested;
8
+ materialized;
9
+ #owner;
10
+ #listeners = new Set();
11
+ #snapshot;
12
+ #identitySignature;
13
+ #destroyed = false;
14
+ #unregister;
15
+ constructor(owner, artifact, variables, options) {
16
+ this.#owner = owner;
17
+ this.artifact = artifact;
18
+ this.variables = canonicalizeOperationVariables(artifact, variables);
19
+ this.key = operationKey(artifact, this.variables);
20
+ this.liveRequested = options.live === true;
21
+ this.materialized = owner._materialize(artifact, this.variables);
22
+ this.#identitySignature = this.materialized.identitySignature;
23
+ this.#snapshot = snapshotFrom(this.materialized, owner._state(this.key));
24
+ this.#unregister = owner._register(this);
25
+ }
26
+ get() {
27
+ return this.#snapshot;
28
+ }
29
+ subscribe(listener) {
30
+ if (this.#destroyed)
31
+ throw new Error('replica watch is destroyed');
32
+ if (typeof listener !== 'function')
33
+ throw new TypeError('replica listener must be a function');
34
+ this.#listeners.add(listener);
35
+ try {
36
+ listener(this.#snapshot);
37
+ }
38
+ catch (error) {
39
+ this.#listeners.delete(listener);
40
+ this.#owner._reportObserverErrors([error]);
41
+ }
42
+ return () => this.#listeners.delete(listener);
43
+ }
44
+ refresh() {
45
+ if (this.#destroyed)
46
+ return Promise.reject(new Error('replica watch is destroyed'));
47
+ return this.#owner._fetch(this, true);
48
+ }
49
+ destroy() {
50
+ if (this.#destroyed)
51
+ return;
52
+ this.#destroyed = true;
53
+ this.#unregister();
54
+ this.#listeners.clear();
55
+ }
56
+ _cacheChanged(materialized) {
57
+ if (this.#destroyed)
58
+ return;
59
+ this.materialized = materialized;
60
+ this.#sync(true);
61
+ }
62
+ _stateChanged(allowFetch) {
63
+ if (this.#destroyed)
64
+ return;
65
+ this.#sync(allowFetch);
66
+ }
67
+ #sync(allowFetch) {
68
+ const state = this.#owner._state(this.key);
69
+ const nextRaw = snapshotFrom(this.materialized, state);
70
+ const keepData = this.#identitySignature === this.materialized.identitySignature &&
71
+ deepEqual(this.#snapshot.data, nextRaw.data);
72
+ this.#identitySignature = this.materialized.identitySignature;
73
+ const next = keepData
74
+ ? Object.freeze({
75
+ ...nextRaw,
76
+ data: this.#snapshot.data
77
+ })
78
+ : nextRaw;
79
+ if (!snapshotEqual(this.#snapshot, next)) {
80
+ this.#snapshot = next;
81
+ const errors = [];
82
+ for (const listener of this.#listeners) {
83
+ try {
84
+ listener(next);
85
+ }
86
+ catch (error) {
87
+ errors.push(error);
88
+ }
89
+ }
90
+ this.#owner._reportObserverErrors(errors);
91
+ }
92
+ if (allowFetch)
93
+ void this.#owner._fetch(this, false);
94
+ }
95
+ }
@@ -0,0 +1,2 @@
1
+ /** Public entry for createDistributedReplica; implementation lives in ./distributed-replica/. */
2
+ export { createDistributedReplica } from './distributed-replica/index.js';
@@ -0,0 +1,2 @@
1
+ /** Public entry for createDistributedReplica; implementation lives in ./distributed-replica/. */
2
+ export { createDistributedReplica } from './distributed-replica/index.js';
@@ -0,0 +1,24 @@
1
+ import { type FetchLike } from '../request.js';
2
+ import type { GqlAuth } from '../types.js';
3
+ import { type WebSocketConstructor } from '../websocket.js';
4
+ import type { ReplicaCommandTransport } from './command-runtime.js';
5
+ import type { ReplicaTransport } from './types.js';
6
+ export type ReplicaGraphqlTransportOptions = Readonly<{
7
+ /** GraphQL HTTP endpoint. Same-origin paths and absolute URLs are supported. */
8
+ getUrl: () => string;
9
+ /** One injected credential source shared by HTTP, command, and WebSocket work. */
10
+ getAuth: () => GqlAuth | Promise<GqlAuth>;
11
+ /** Request-scoped SvelteKit fetch on the server, or a test/browser override. */
12
+ fetch?: FetchLike;
13
+ /** Browser/test WebSocket constructor override. */
14
+ webSocket?: WebSocketConstructor;
15
+ }>;
16
+ /**
17
+ * One concrete GraphQL transport for generated replica reads, live operations,
18
+ * command dispatch, and command-status recovery.
19
+ *
20
+ * The transport is intentionally framework-neutral. Framework adapters inject
21
+ * their request/session lifecycle but never duplicate GraphQL protocol logic.
22
+ */
23
+ export type ReplicaGraphqlTransport = ReplicaTransport & ReplicaCommandTransport;
24
+ export declare function createReplicaGraphqlTransport(options: ReplicaGraphqlTransportOptions): ReplicaGraphqlTransport;
@@ -0,0 +1,122 @@
1
+ import { requestGraphql } from '../request.js';
2
+ import { subscribe as subscribeGraphqlWs } from '../websocket.js';
3
+ export function createReplicaGraphqlTransport(options) {
4
+ if (typeof options.getUrl !== 'function') {
5
+ throw new TypeError('replica GraphQL transport requires getUrl');
6
+ }
7
+ if (typeof options.getAuth !== 'function') {
8
+ throw new TypeError('replica GraphQL transport requires getAuth');
9
+ }
10
+ const execute = async (document, variables, extensions, signal) => {
11
+ const auth = await options.getAuth();
12
+ const result = await requestGraphql(options.getUrl(), document, auth, variables, {
13
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
14
+ extensions,
15
+ ...(signal === undefined ? {} : { signal })
16
+ });
17
+ return Object.freeze({
18
+ ...(result.data === undefined ? {} : { data: result.data }),
19
+ ...(result.errors === undefined ? {} : { errors: result.errors }),
20
+ ...(result.extensions === undefined
21
+ ? {}
22
+ : { extensions: result.extensions }),
23
+ status: result.status
24
+ });
25
+ };
26
+ return Object.freeze({
27
+ async fetch(request) {
28
+ const auth = await options.getAuth();
29
+ const result = await requestGraphql(options.getUrl(), request.document, auth, request.variables, {
30
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
31
+ ...(request.extensions === undefined
32
+ ? {}
33
+ : { extensions: request.extensions }),
34
+ ...(request.signal === undefined
35
+ ? {}
36
+ : { signal: request.signal })
37
+ });
38
+ return Object.freeze({
39
+ ...(result.data === undefined ? {} : { data: result.data }),
40
+ ...(result.errors === undefined ? {} : { errors: result.errors }),
41
+ ...(result.extensions === undefined
42
+ ? {}
43
+ : { extensions: result.extensions })
44
+ });
45
+ },
46
+ subscribe(request, observer) {
47
+ return subscribeReplicaOperation(options, request, observer);
48
+ },
49
+ dispatch(request) {
50
+ return execute(request.document, request.variables, request.extensions, request.signal);
51
+ },
52
+ status(request) {
53
+ return execute(request.document, request.variables, request.extensions, request.signal);
54
+ }
55
+ });
56
+ }
57
+ function subscribeReplicaOperation(options, request, observer) {
58
+ let unsubscribe = () => undefined;
59
+ let closed = false;
60
+ const close = () => {
61
+ if (closed)
62
+ return;
63
+ closed = true;
64
+ request.signal?.removeEventListener('abort', close);
65
+ unsubscribe();
66
+ };
67
+ const fail = (error) => {
68
+ if (closed)
69
+ return;
70
+ try {
71
+ observer.error(error);
72
+ }
73
+ finally {
74
+ close();
75
+ }
76
+ };
77
+ if (request.signal?.aborted) {
78
+ return close;
79
+ }
80
+ request.signal?.addEventListener('abort', close, { once: true });
81
+ void Promise.resolve()
82
+ .then(() => options.getAuth())
83
+ .then((auth) => {
84
+ if (closed || request.signal?.aborted)
85
+ return;
86
+ unsubscribe = subscribeGraphqlWs(request.document, auth, {
87
+ onNext: (result) => {
88
+ if (!closed)
89
+ observer.next(result);
90
+ },
91
+ onError: fail,
92
+ onComplete: () => {
93
+ if (closed)
94
+ return;
95
+ try {
96
+ observer.complete();
97
+ }
98
+ finally {
99
+ close();
100
+ }
101
+ }
102
+ }, {
103
+ httpUrl: options.getUrl(),
104
+ variables: request.variables,
105
+ ...(request.extensions === undefined
106
+ ? {}
107
+ : { extensions: request.extensions }),
108
+ ...(request.resume === undefined
109
+ ? {}
110
+ : { resume: request.resume }),
111
+ ...(options.webSocket === undefined
112
+ ? {}
113
+ : { webSocket: options.webSocket })
114
+ });
115
+ if (closed || request.signal?.aborted)
116
+ unsubscribe();
117
+ })
118
+ .catch((error) => {
119
+ fail(error);
120
+ });
121
+ return close;
122
+ }