@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.
- package/README.md +454 -0
- package/dist/auth-headers.d.ts +8 -0
- package/dist/auth-headers.js +35 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +1 -0
- package/dist/document.d.ts +8 -0
- package/dist/document.js +6 -0
- package/dist/identity.d.ts +23 -0
- package/dist/identity.js +73 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/internal/cache-engine/create.d.ts +5 -0
- package/dist/internal/cache-engine/create.js +7 -0
- package/dist/internal/cache-engine/engine.d.ts +32 -0
- package/dist/internal/cache-engine/engine.js +1287 -0
- package/dist/internal/cache-engine/errors.d.ts +5 -0
- package/dist/internal/cache-engine/errors.js +10 -0
- package/dist/internal/cache-engine/helpers.d.ts +55 -0
- package/dist/internal/cache-engine/helpers.js +545 -0
- package/dist/internal/cache-engine/index.d.ts +3 -0
- package/dist/internal/cache-engine/index.js +2 -0
- package/dist/internal/cache-engine/types.d.ts +300 -0
- package/dist/internal/cache-engine/types.js +1 -0
- package/dist/internal/cache-engine.d.ts +3 -0
- package/dist/internal/cache-engine.js +2 -0
- package/dist/lib/assert-name.d.ts +2 -0
- package/dist/lib/assert-name.js +6 -0
- package/dist/lib/compare-code-units.d.ts +2 -0
- package/dist/lib/compare-code-units.js +4 -0
- package/dist/lib/deep-equal.d.ts +2 -0
- package/dist/lib/deep-equal.js +24 -0
- package/dist/lib/freeze-record.d.ts +2 -0
- package/dist/lib/freeze-record.js +13 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/is-plain-record.d.ts +2 -0
- package/dist/lib/is-plain-record.js +8 -0
- package/dist/lib/report.d.ts +7 -0
- package/dist/lib/report.js +26 -0
- package/dist/protocol.d.ts +179 -0
- package/dist/protocol.js +558 -0
- package/dist/react/context.d.ts +20 -0
- package/dist/react/context.js +24 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +3 -0
- package/dist/react/query.d.ts +20 -0
- package/dist/react/query.js +99 -0
- package/dist/replica/command-id.d.ts +2 -0
- package/dist/replica/command-id.js +19 -0
- package/dist/replica/command-runtime/constants.d.ts +5 -0
- package/dist/replica/command-runtime/constants.js +5 -0
- package/dist/replica/command-runtime/create.d.ts +2 -0
- package/dist/replica/command-runtime/create.js +644 -0
- package/dist/replica/command-runtime/errors.d.ts +16 -0
- package/dist/replica/command-runtime/errors.js +41 -0
- package/dist/replica/command-runtime/index.d.ts +5 -0
- package/dist/replica/command-runtime/index.js +4 -0
- package/dist/replica/command-runtime/lib/binding.d.ts +4 -0
- package/dist/replica/command-runtime/lib/binding.js +64 -0
- package/dist/replica/command-runtime/lib/effects.d.ts +11 -0
- package/dist/replica/command-runtime/lib/effects.js +89 -0
- package/dist/replica/command-runtime/lib/index.d.ts +9 -0
- package/dist/replica/command-runtime/lib/index.js +9 -0
- package/dist/replica/command-runtime/lib/inventory.d.ts +10 -0
- package/dist/replica/command-runtime/lib/inventory.js +141 -0
- package/dist/replica/command-runtime/lib/output.d.ts +13 -0
- package/dist/replica/command-runtime/lib/output.js +190 -0
- package/dist/replica/command-runtime/lib/projection.d.ts +20 -0
- package/dist/replica/command-runtime/lib/projection.js +255 -0
- package/dist/replica/command-runtime/lib/status.d.ts +7 -0
- package/dist/replica/command-runtime/lib/status.js +127 -0
- package/dist/replica/command-runtime/lib/transport.d.ts +16 -0
- package/dist/replica/command-runtime/lib/transport.js +112 -0
- package/dist/replica/command-runtime/lib/util.d.ts +16 -0
- package/dist/replica/command-runtime/lib/util.js +127 -0
- package/dist/replica/command-runtime/lifecycle.d.ts +11 -0
- package/dist/replica/command-runtime/lifecycle.js +13 -0
- package/dist/replica/command-runtime/symbols.d.ts +30 -0
- package/dist/replica/command-runtime/symbols.js +30 -0
- package/dist/replica/command-runtime/types.d.ts +222 -0
- package/dist/replica/command-runtime/types.js +1 -0
- package/dist/replica/command-runtime.d.ts +3 -0
- package/dist/replica/command-runtime.js +2 -0
- package/dist/replica/commands/clone.d.ts +12 -0
- package/dist/replica/commands/clone.js +187 -0
- package/dist/replica/commands/constants.d.ts +6 -0
- package/dist/replica/commands/constants.js +6 -0
- package/dist/replica/commands/errors.d.ts +7 -0
- package/dist/replica/commands/errors.js +22 -0
- package/dist/replica/commands/implementation.d.ts +8 -0
- package/dist/replica/commands/implementation.js +8 -0
- package/dist/replica/commands/index.d.ts +5 -0
- package/dist/replica/commands/index.js +4 -0
- package/dist/replica/commands/prepare.d.ts +27 -0
- package/dist/replica/commands/prepare.js +77 -0
- package/dist/replica/commands/presets.d.ts +12 -0
- package/dist/replica/commands/presets.js +84 -0
- package/dist/replica/commands/receipt.d.ts +3 -0
- package/dist/replica/commands/receipt.js +36 -0
- package/dist/replica/commands/resolve.d.ts +11 -0
- package/dist/replica/commands/resolve.js +165 -0
- package/dist/replica/commands/types.d.ts +278 -0
- package/dist/replica/commands/types.js +2 -0
- package/dist/replica/commands/util.d.ts +21 -0
- package/dist/replica/commands/util.js +96 -0
- package/dist/replica/commands/validate.d.ts +18 -0
- package/dist/replica/commands/validate.js +477 -0
- package/dist/replica/commands.d.ts +3 -0
- package/dist/replica/commands.js +2 -0
- package/dist/replica/diagnostics/event-log.d.ts +4 -0
- package/dist/replica/diagnostics/event-log.js +463 -0
- package/dist/replica/diagnostics/implementation.d.ts +4 -0
- package/dist/replica/diagnostics/implementation.js +4 -0
- package/dist/replica/diagnostics/index.d.ts +2 -0
- package/dist/replica/diagnostics/index.js +1 -0
- package/dist/replica/diagnostics/inspect.d.ts +6 -0
- package/dist/replica/diagnostics/inspect.js +170 -0
- package/dist/replica/diagnostics/types.d.ts +295 -0
- package/dist/replica/diagnostics/types.js +2 -0
- package/dist/replica/diagnostics.d.ts +3 -0
- package/dist/replica/diagnostics.js +2 -0
- package/dist/replica/distributed-replica/clocks.d.ts +23 -0
- package/dist/replica/distributed-replica/clocks.js +156 -0
- package/dist/replica/distributed-replica/constants.d.ts +12 -0
- package/dist/replica/distributed-replica/constants.js +10 -0
- package/dist/replica/distributed-replica/helpers.d.ts +48 -0
- package/dist/replica/distributed-replica/helpers.js +445 -0
- package/dist/replica/distributed-replica/hydration.d.ts +19 -0
- package/dist/replica/distributed-replica/hydration.js +380 -0
- package/dist/replica/distributed-replica/impl-diagnostics.d.ts +22 -0
- package/dist/replica/distributed-replica/impl-diagnostics.js +189 -0
- package/dist/replica/distributed-replica/impl-fetch-live.d.ts +39 -0
- package/dist/replica/distributed-replica/impl-fetch-live.js +395 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +56 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +299 -0
- package/dist/replica/distributed-replica/impl-optimistic.d.ts +31 -0
- package/dist/replica/distributed-replica/impl-optimistic.js +172 -0
- package/dist/replica/distributed-replica/impl-protocol.d.ts +53 -0
- package/dist/replica/distributed-replica/impl-protocol.js +108 -0
- package/dist/replica/distributed-replica/impl.d.ts +44 -0
- package/dist/replica/distributed-replica/impl.js +1776 -0
- package/dist/replica/distributed-replica/index.d.ts +2 -0
- package/dist/replica/distributed-replica/index.js +4 -0
- package/dist/replica/distributed-replica/optimistic.d.ts +30 -0
- package/dist/replica/distributed-replica/optimistic.js +221 -0
- package/dist/replica/distributed-replica/types.d.ts +147 -0
- package/dist/replica/distributed-replica/types.js +1 -0
- package/dist/replica/distributed-replica/watch.d.ts +19 -0
- package/dist/replica/distributed-replica/watch.js +95 -0
- package/dist/replica/distributed-replica.d.ts +2 -0
- package/dist/replica/distributed-replica.js +2 -0
- package/dist/replica/graphql-transport.d.ts +24 -0
- package/dist/replica/graphql-transport.js +122 -0
- package/dist/replica/identity/clone.d.ts +6 -0
- package/dist/replica/identity/clone.js +54 -0
- package/dist/replica/identity/codec.d.ts +48 -0
- package/dist/replica/identity/codec.js +789 -0
- package/dist/replica/identity/constants.d.ts +3 -0
- package/dist/replica/identity/constants.js +18 -0
- package/dist/replica/identity/implementation.d.ts +5 -0
- package/dist/replica/identity/implementation.js +5 -0
- package/dist/replica/identity/index.d.ts +3 -0
- package/dist/replica/identity/index.js +3 -0
- package/dist/replica/identity/keys.d.ts +29 -0
- package/dist/replica/identity/keys.js +173 -0
- package/dist/replica/identity.d.ts +2 -0
- package/dist/replica/identity.js +2 -0
- package/dist/replica/index-maintenance/engine.d.ts +84 -0
- package/dist/replica/index-maintenance/engine.js +677 -0
- package/dist/replica/index-maintenance/implementation.d.ts +4 -0
- package/dist/replica/index-maintenance/implementation.js +4 -0
- package/dist/replica/index-maintenance/index.d.ts +2 -0
- package/dist/replica/index-maintenance/index.js +1 -0
- package/dist/replica/index-maintenance/registry.d.ts +2 -0
- package/dist/replica/index-maintenance/registry.js +49 -0
- package/dist/replica/index-maintenance/types.d.ts +58 -0
- package/dist/replica/index-maintenance/types.js +1 -0
- package/dist/replica/index-maintenance.d.ts +3 -0
- package/dist/replica/index-maintenance.js +2 -0
- package/dist/replica/index.d.ts +17 -0
- package/dist/replica/index.js +9 -0
- package/dist/replica/materialize.d.ts +10 -0
- package/dist/replica/materialize.js +163 -0
- package/dist/replica/normalize.d.ts +27 -0
- package/dist/replica/normalize.js +298 -0
- package/dist/replica/operation-binding.d.ts +14 -0
- package/dist/replica/operation-binding.js +76 -0
- package/dist/replica/persistence/idb.d.ts +20 -0
- package/dist/replica/persistence/idb.js +193 -0
- package/dist/replica/persistence/implementation.d.ts +4 -0
- package/dist/replica/persistence/implementation.js +4 -0
- package/dist/replica/persistence/index.d.ts +2 -0
- package/dist/replica/persistence/index.js +1 -0
- package/dist/replica/persistence/state.d.ts +104 -0
- package/dist/replica/persistence/state.js +734 -0
- package/dist/replica/persistence/types.d.ts +54 -0
- package/dist/replica/persistence/types.js +1 -0
- package/dist/replica/persistence.d.ts +3 -0
- package/dist/replica/persistence.js +2 -0
- package/dist/replica/query-plan/constants.d.ts +13 -0
- package/dist/replica/query-plan/constants.js +7 -0
- package/dist/replica/query-plan/filter.d.ts +39 -0
- package/dist/replica/query-plan/filter.js +544 -0
- package/dist/replica/query-plan/index.d.ts +4 -0
- package/dist/replica/query-plan/index.js +3 -0
- package/dist/replica/query-plan/order.d.ts +11 -0
- package/dist/replica/query-plan/order.js +158 -0
- package/dist/replica/query-plan/pagination.d.ts +6 -0
- package/dist/replica/query-plan/pagination.js +90 -0
- package/dist/replica/query-plan/resolve.d.ts +14 -0
- package/dist/replica/query-plan/resolve.js +163 -0
- package/dist/replica/query-plan/types.d.ts +89 -0
- package/dist/replica/query-plan/types.js +1 -0
- package/dist/replica/query-plan/util.d.ts +13 -0
- package/dist/replica/query-plan/util.js +62 -0
- package/dist/replica/query-plan.d.ts +3 -0
- package/dist/replica/query-plan.js +2 -0
- package/dist/replica/revalidation.d.ts +9 -0
- package/dist/replica/revalidation.js +86 -0
- package/dist/replica/selection.d.ts +20 -0
- package/dist/replica/selection.js +109 -0
- package/dist/replica/types.d.ts +657 -0
- package/dist/replica/types.js +1 -0
- package/dist/request.d.ts +17 -0
- package/dist/request.js +123 -0
- package/dist/sveltekit/auth.d.ts +18 -0
- package/dist/sveltekit/auth.js +9 -0
- package/dist/sveltekit/context.d.ts +28 -0
- package/dist/sveltekit/context.js +58 -0
- package/dist/sveltekit/index.d.ts +4 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/sveltekit/replica.d.ts +145 -0
- package/dist/sveltekit/replica.js +491 -0
- package/dist/sveltekit/server-replica.d.ts +52 -0
- package/dist/sveltekit/server-replica.js +176 -0
- package/dist/sveltekit/vite.d.ts +103 -0
- package/dist/sveltekit/vite.js +878 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/dist/websocket.d.ts +41 -0
- package/dist/websocket.js +187 -0
- package/package.json +95 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { createCacheEngine } from '../../internal/cache-engine.js';
|
|
2
|
+
import { matchReplicaTrustedPresetInventory } from '../commands.js';
|
|
3
|
+
import { replicaIndexKey, resolveArguments } from '../identity.js';
|
|
4
|
+
import { runtimeRoot } from '../selection.js';
|
|
5
|
+
import { freezeRecordClock } from './clocks.js';
|
|
6
|
+
import { trustedPresetInventoryFingerprint } from './helpers.js';
|
|
7
|
+
import { hydrationMetadataConsistent, parseAuthoritativeScope, parseReplicaHydration, serializeOperationProtocolGroup } from './hydration.js';
|
|
8
|
+
export function reachableConfirmedState(host) {
|
|
9
|
+
const snapshot = host.engine.extract();
|
|
10
|
+
const indexes = new Map(snapshot.indexes.map((index) => [index.key, index]));
|
|
11
|
+
const records = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
12
|
+
const indexKeys = new Set();
|
|
13
|
+
const recordKeys = new Set();
|
|
14
|
+
const clockRecordKeys = new Set();
|
|
15
|
+
const recordFields = new Map();
|
|
16
|
+
const models = new Set();
|
|
17
|
+
const rememberSelection = (selection) => {
|
|
18
|
+
if (selection.storage.kind === 'normalized') {
|
|
19
|
+
models.add(selection.storage.model);
|
|
20
|
+
}
|
|
21
|
+
for (const member of selection.members) {
|
|
22
|
+
if (member.kind === 'branch')
|
|
23
|
+
rememberSelection(member.selection);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const rememberRecord = (recordKey, selection) => {
|
|
27
|
+
recordKeys.add(recordKey);
|
|
28
|
+
clockRecordKeys.add(recordKey);
|
|
29
|
+
let selected = recordFields.get(recordKey);
|
|
30
|
+
if (selected === undefined) {
|
|
31
|
+
selected = new Set();
|
|
32
|
+
recordFields.set(recordKey, selected);
|
|
33
|
+
}
|
|
34
|
+
for (const member of selection.members) {
|
|
35
|
+
if (member.kind === 'scalar')
|
|
36
|
+
selected.add(member.field);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const visitObject = (selection, recordKey, variables) => {
|
|
40
|
+
rememberRecord(recordKey, selection);
|
|
41
|
+
for (const member of selection.members) {
|
|
42
|
+
if (member.kind !== 'branch')
|
|
43
|
+
continue;
|
|
44
|
+
visitBranch(member, recordKey, variables);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const visitBranch = (selection, parent, variables) => {
|
|
48
|
+
const key = replicaIndexKey({
|
|
49
|
+
...(parent === undefined ? {} : { parent }),
|
|
50
|
+
field: selection.field,
|
|
51
|
+
arguments: resolveArguments(selection.arguments, variables, selection.coverage)
|
|
52
|
+
});
|
|
53
|
+
const index = indexes.get(key);
|
|
54
|
+
if (index === undefined)
|
|
55
|
+
return;
|
|
56
|
+
indexKeys.add(key);
|
|
57
|
+
for (const recordKey of index.records) {
|
|
58
|
+
visitObject(selection.selection, recordKey, variables);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
for (const [key, rendered] of host.renderedOperations) {
|
|
62
|
+
for (const rootArtifact of rendered.artifact.roots) {
|
|
63
|
+
const root = runtimeRoot(rootArtifact);
|
|
64
|
+
rememberSelection(root.selection);
|
|
65
|
+
visitBranch(root, undefined, rendered.variables);
|
|
66
|
+
}
|
|
67
|
+
const group = host.operationProtocols.get(key);
|
|
68
|
+
for (const state of [group?.query, group?.live]) {
|
|
69
|
+
for (const recordKey of state?.pathRecords.values() ?? []) {
|
|
70
|
+
clockRecordKeys.add(recordKey);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
cache: Object.freeze({
|
|
76
|
+
version: 1,
|
|
77
|
+
records: Object.freeze([...recordKeys]
|
|
78
|
+
.sort()
|
|
79
|
+
.flatMap((key) => {
|
|
80
|
+
const record = records.get(key);
|
|
81
|
+
if (record === undefined)
|
|
82
|
+
return [];
|
|
83
|
+
const selected = recordFields.get(key) ?? new Set();
|
|
84
|
+
return [
|
|
85
|
+
Object.freeze({
|
|
86
|
+
...record,
|
|
87
|
+
fields: Object.freeze(Object.fromEntries(Object.entries(record.fields).filter(([field]) => selected.has(field)))),
|
|
88
|
+
links: Object.freeze({})
|
|
89
|
+
})
|
|
90
|
+
];
|
|
91
|
+
})),
|
|
92
|
+
indexes: Object.freeze([...indexKeys]
|
|
93
|
+
.sort()
|
|
94
|
+
.flatMap((key) => {
|
|
95
|
+
const index = indexes.get(key);
|
|
96
|
+
return index === undefined ? [] : [index];
|
|
97
|
+
}))
|
|
98
|
+
}),
|
|
99
|
+
recordKeys,
|
|
100
|
+
clockRecordKeys,
|
|
101
|
+
models
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function finishDehydration(host) {
|
|
105
|
+
let plansChanged = false;
|
|
106
|
+
for (const key of host.readOperationKeys) {
|
|
107
|
+
if (!host.watchRenderCounts.has(key)) {
|
|
108
|
+
host.renderedOperations.delete(key);
|
|
109
|
+
const registration = host.indexPlanRegistrations.get(key);
|
|
110
|
+
if (registration !== undefined) {
|
|
111
|
+
host.indexPlanRegistrations.delete(key);
|
|
112
|
+
registration.dispose();
|
|
113
|
+
plansChanged = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
host.readOperationKeys.clear();
|
|
118
|
+
if (plansChanged)
|
|
119
|
+
host.refreshIndexMaintenance();
|
|
120
|
+
}
|
|
121
|
+
export function dehydrateReplica(host) {
|
|
122
|
+
const scope = host.getProtocolGeneration();
|
|
123
|
+
if (scope === undefined) {
|
|
124
|
+
throw new Error('cannot dehydrate replica before the server establishes an authoritative scope');
|
|
125
|
+
}
|
|
126
|
+
const reachable = reachableConfirmedState(host);
|
|
127
|
+
const reachableIndexKeys = new Set(reachable.cache.indexes.map((index) => index.key));
|
|
128
|
+
const operationKeys = new Set(host.renderedOperations.keys());
|
|
129
|
+
for (const [key, group] of host.operationProtocols) {
|
|
130
|
+
const governsReachableState = [group.query, group.live].some((state) => state !== undefined &&
|
|
131
|
+
([...state.indexKeys].some((indexKey) => reachableIndexKeys.has(indexKey)) ||
|
|
132
|
+
[...state.pathRecords.values()].some((recordKey) => reachable.clockRecordKeys.has(recordKey))));
|
|
133
|
+
if (governsReachableState)
|
|
134
|
+
operationKeys.add(key);
|
|
135
|
+
}
|
|
136
|
+
const operations = [...operationKeys]
|
|
137
|
+
.sort()
|
|
138
|
+
.flatMap((key) => {
|
|
139
|
+
const group = host.operationProtocols.get(key);
|
|
140
|
+
if (group === undefined)
|
|
141
|
+
return [];
|
|
142
|
+
return [
|
|
143
|
+
serializeOperationProtocolGroup(key, group, host.operationGeneration(key))
|
|
144
|
+
];
|
|
145
|
+
});
|
|
146
|
+
const recordClocks = [...host.recordClocks]
|
|
147
|
+
.filter(([key]) => reachable.clockRecordKeys.has(key))
|
|
148
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
149
|
+
.map(([key, clock]) => Object.freeze([key, freezeRecordClock(clock)]));
|
|
150
|
+
const anonymousRecordClocks = [...host.anonymousRecordClocks]
|
|
151
|
+
.filter(([, value]) => reachable.models.has(value.model))
|
|
152
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
153
|
+
.map(([scopeToken, value]) => Object.freeze([
|
|
154
|
+
scopeToken,
|
|
155
|
+
Object.freeze({
|
|
156
|
+
model: value.model,
|
|
157
|
+
clock: freezeRecordClock(value.clock)
|
|
158
|
+
})
|
|
159
|
+
]));
|
|
160
|
+
const payload = Object.freeze({
|
|
161
|
+
cache: reachable.cache,
|
|
162
|
+
operations: Object.freeze(operations),
|
|
163
|
+
recordClocks: Object.freeze(recordClocks),
|
|
164
|
+
anonymousRecordClocks: Object.freeze(anonymousRecordClocks),
|
|
165
|
+
trustedPresets: host.getTrustedPresets(),
|
|
166
|
+
nextIndexRevision: host.getNextIndexRevision()
|
|
167
|
+
});
|
|
168
|
+
const state = Object.freeze({
|
|
169
|
+
version: 1,
|
|
170
|
+
scope: Object.freeze({
|
|
171
|
+
protocolVersion: 1,
|
|
172
|
+
schemaHash: scope.schemaHash,
|
|
173
|
+
cacheScope: scope.cacheScope
|
|
174
|
+
}),
|
|
175
|
+
payload
|
|
176
|
+
});
|
|
177
|
+
host.finishDehydration();
|
|
178
|
+
return state;
|
|
179
|
+
}
|
|
180
|
+
export function hydrateReplica(host, state, authoritativeScope) {
|
|
181
|
+
const rejected = (reason) => {
|
|
182
|
+
if (host.diagnostics.enabled) {
|
|
183
|
+
host.diagnosticEvent(Object.freeze({ kind: 'hydration', action: 'rejected', reason }));
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
};
|
|
187
|
+
const parsed = parseReplicaHydration(state);
|
|
188
|
+
const expectedScope = parseAuthoritativeScope(authoritativeScope);
|
|
189
|
+
if (parsed === undefined || expectedScope === undefined) {
|
|
190
|
+
return rejected('invalid');
|
|
191
|
+
}
|
|
192
|
+
if (parsed.scope.protocolVersion !== expectedScope.protocolVersion ||
|
|
193
|
+
parsed.scope.schemaHash !== expectedScope.schemaHash ||
|
|
194
|
+
parsed.scope.cacheScope !== expectedScope.cacheScope) {
|
|
195
|
+
return rejected('scope-mismatch');
|
|
196
|
+
}
|
|
197
|
+
const binding = host.getArtifactBinding();
|
|
198
|
+
if (binding !== undefined &&
|
|
199
|
+
binding.schemaHash !== parsed.scope.schemaHash) {
|
|
200
|
+
return rejected('artifact-mismatch');
|
|
201
|
+
}
|
|
202
|
+
const current = host.getProtocolGeneration();
|
|
203
|
+
if (current !== undefined &&
|
|
204
|
+
(current.protocolVersion !== parsed.scope.protocolVersion ||
|
|
205
|
+
current.schemaHash !== parsed.scope.schemaHash ||
|
|
206
|
+
current.cacheScope !== parsed.scope.cacheScope)) {
|
|
207
|
+
return rejected('active-scope-mismatch');
|
|
208
|
+
}
|
|
209
|
+
const preserveLocalCommandState = current !== undefined;
|
|
210
|
+
// Validate the private engine payload before closing transports or changing
|
|
211
|
+
// any live state. `restore` parses fully before its own transaction.
|
|
212
|
+
try {
|
|
213
|
+
createCacheEngine().restore(parsed.cache);
|
|
214
|
+
const expectedTrustedPresets = binding?.trustedPresets !== undefined
|
|
215
|
+
? binding.trustedPresets
|
|
216
|
+
: host.getCommandAuthorityContract()?.trustedPresets;
|
|
217
|
+
if (expectedTrustedPresets !== undefined) {
|
|
218
|
+
matchReplicaTrustedPresetInventory(expectedTrustedPresets, parsed.trustedPresets);
|
|
219
|
+
}
|
|
220
|
+
if (preserveLocalCommandState &&
|
|
221
|
+
trustedPresetInventoryFingerprint(host.getTrustedPresets()) !==
|
|
222
|
+
trustedPresetInventoryFingerprint(parsed.trustedPresets)) {
|
|
223
|
+
throw new TypeError('trusted presets changed within one authoritative cache scope');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return rejected('invalid');
|
|
228
|
+
}
|
|
229
|
+
if (!hydrationMetadataConsistent(parsed)) {
|
|
230
|
+
return rejected('metadata-mismatch');
|
|
231
|
+
}
|
|
232
|
+
if (preserveLocalCommandState) {
|
|
233
|
+
host.closeActiveTransports();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
host.closeAuthorizationGeneration();
|
|
237
|
+
}
|
|
238
|
+
host.queryStates.clear();
|
|
239
|
+
host.operationProtocols.clear();
|
|
240
|
+
for (const [key, group] of parsed.operationProtocols) {
|
|
241
|
+
host.operationProtocols.set(key, group);
|
|
242
|
+
}
|
|
243
|
+
host.operationGenerations.clear();
|
|
244
|
+
for (const [key, generation] of parsed.operationGenerations) {
|
|
245
|
+
host.operationGenerations.set(key, generation);
|
|
246
|
+
}
|
|
247
|
+
host.recordClocks.clear();
|
|
248
|
+
for (const [key, clock] of parsed.recordClocks) {
|
|
249
|
+
host.recordClocks.set(key, clock);
|
|
250
|
+
}
|
|
251
|
+
host.recordKeysByScope.clear();
|
|
252
|
+
for (const [scopeToken, key] of parsed.recordKeysByScope) {
|
|
253
|
+
host.recordKeysByScope.set(scopeToken, key);
|
|
254
|
+
}
|
|
255
|
+
host.anonymousRecordClocks.clear();
|
|
256
|
+
for (const [scopeToken, clock] of parsed.anonymousRecordClocks) {
|
|
257
|
+
host.anonymousRecordClocks.set(scopeToken, clock);
|
|
258
|
+
}
|
|
259
|
+
if (!preserveLocalCommandState) {
|
|
260
|
+
host.optimisticReceipts.clear();
|
|
261
|
+
host.diagnosticLayers?.clear();
|
|
262
|
+
host.setDiagnosticLayerSequence(0);
|
|
263
|
+
}
|
|
264
|
+
host.setTrustedPresets(parsed.trustedPresets);
|
|
265
|
+
host.setNextIndexRevision(parsed.nextIndexRevision);
|
|
266
|
+
host.setProtocolGeneration(parsed.scope);
|
|
267
|
+
host.setArtifactBinding(Object.freeze({
|
|
268
|
+
version: 1,
|
|
269
|
+
schemaHash: parsed.scope.schemaHash,
|
|
270
|
+
...(binding?.surfaceIdentity !== undefined
|
|
271
|
+
? { surfaceIdentity: binding.surfaceIdentity }
|
|
272
|
+
: {}),
|
|
273
|
+
...(binding?.trustedPresets !== undefined
|
|
274
|
+
? { trustedPresets: binding.trustedPresets }
|
|
275
|
+
: {})
|
|
276
|
+
}));
|
|
277
|
+
if (preserveLocalCommandState) {
|
|
278
|
+
host.engine.restoreConfirmed(parsed.cache);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
host.engine.restore(parsed.cache);
|
|
282
|
+
}
|
|
283
|
+
host.resumeLiveWatches();
|
|
284
|
+
host.syncDiagnostics();
|
|
285
|
+
if (host.diagnostics.enabled) {
|
|
286
|
+
host.diagnosticEvent(Object.freeze({
|
|
287
|
+
kind: 'hydration',
|
|
288
|
+
action: 'accepted',
|
|
289
|
+
reason: 'accepted'
|
|
290
|
+
}));
|
|
291
|
+
host.diagnosticEvent(Object.freeze({
|
|
292
|
+
kind: 'scope',
|
|
293
|
+
action: 'established',
|
|
294
|
+
generation: host.getProtocolGenerationSequence(),
|
|
295
|
+
schemaHash: parsed.scope.schemaHash
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CacheEngine } from '../../internal/cache-engine.js';
|
|
2
|
+
import { type DistributedCommandMetadata, type DistributedProjectionObservation } from '../../protocol.js';
|
|
3
|
+
import type { ReplicaDiagnosticEventInput, ReplicaDiagnosticLayerInput } from '../diagnostics.js';
|
|
4
|
+
import type { ReplicaIndexSemanticChange } from '../index-maintenance.js';
|
|
5
|
+
import type { ReplicaBaseWriter, ReplicaOptimisticWriter } from '../types.js';
|
|
6
|
+
import type { OptimisticReceiptState } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Host for optimistic layer lifecycle and receipt planning.
|
|
9
|
+
*/
|
|
10
|
+
export type OptimisticHost = {
|
|
11
|
+
readonly engine: CacheEngine;
|
|
12
|
+
readonly optimisticReceipts: Map<string, OptimisticReceiptState>;
|
|
13
|
+
readonly diagnosticLayers: Map<string, ReplicaDiagnosticLayerInput> | undefined;
|
|
14
|
+
readonly diagnostics: {
|
|
15
|
+
readonly enabled: boolean;
|
|
16
|
+
};
|
|
17
|
+
getDiagnosticLayerSequence(): number;
|
|
18
|
+
setDiagnosticLayerSequence(value: number): void;
|
|
19
|
+
diagnosticEvent(event: ReplicaDiagnosticEventInput): void;
|
|
20
|
+
syncDiagnostics(): void;
|
|
21
|
+
retireDiagnosticLayer(id: string, action: 'retired' | 'rejected', receiptState: 'projected' | 'rejected', receipt?: OptimisticReceiptState): void;
|
|
22
|
+
};
|
|
23
|
+
export declare function createOptimisticLayerOn(host: OptimisticHost, id: string, update: (writer: ReplicaOptimisticWriter) => void, semanticChanges?: readonly ReplicaIndexSemanticChange[]): void;
|
|
24
|
+
export declare function markOptimisticLayerAcceptedOn(host: OptimisticHost, id: string, receipt?: DistributedCommandMetadata): boolean;
|
|
25
|
+
export declare function confirmOptimisticLayerOn<T>(host: OptimisticHost, id: string, update: (writer: ReplicaBaseWriter) => T): T;
|
|
26
|
+
export declare function rejectOptimisticLayerOn(host: OptimisticHost, id: string): boolean;
|
|
27
|
+
export declare function planOptimisticReceipts(host: OptimisticHost, command: DistributedCommandMetadata | undefined, observations: readonly DistributedProjectionObservation[], satisfactionAdmissible: boolean): {
|
|
28
|
+
updates: Map<string, OptimisticReceiptState>;
|
|
29
|
+
satisfied: string[];
|
|
30
|
+
};
|
|
31
|
+
export declare function applyReceiptOnly(host: OptimisticHost, command: DistributedCommandMetadata | undefined): void;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { DistributedProtocolError } from '../../protocol.js';
|
|
2
|
+
import { protocolInvalid } from './clocks.js';
|
|
3
|
+
import { baseWriter } from './helpers.js';
|
|
4
|
+
import { assertReplicaOptimisticLayerId, captureReplicaOptimisticUpdate, cloneOptimisticReceipt, diagnosticReceiptCounts, expectationKey, optimisticReceiptState, replayReplicaOptimisticUpdate, sameReceipt } from './optimistic.js';
|
|
5
|
+
export function createOptimisticLayerOn(host, id, update, semanticChanges = Object.freeze([])) {
|
|
6
|
+
assertReplicaOptimisticLayerId(id);
|
|
7
|
+
if (host.engine.optimisticLayerState(id) !== undefined) {
|
|
8
|
+
throw new Error(`optimistic layer already exists: ${id}`);
|
|
9
|
+
}
|
|
10
|
+
if (typeof update !== 'function') {
|
|
11
|
+
throw new TypeError('optimistic layer update must be a function');
|
|
12
|
+
}
|
|
13
|
+
const captured = captureReplicaOptimisticUpdate(id, update, semanticChanges);
|
|
14
|
+
host.engine.createOptimisticLayer(id, (writer) => replayReplicaOptimisticUpdate(writer, captured.operations), captured.context);
|
|
15
|
+
if (host.diagnosticLayers !== undefined) {
|
|
16
|
+
const recordChanges = captured.operations.filter((operation) => operation.kind === 'write-record' ||
|
|
17
|
+
operation.kind === 'tombstone-record').length;
|
|
18
|
+
const indexChanges = captured.operations.length - recordChanges;
|
|
19
|
+
const nextSequence = host.getDiagnosticLayerSequence() + 1;
|
|
20
|
+
host.setDiagnosticLayerSequence(nextSequence);
|
|
21
|
+
const layer = Object.freeze({
|
|
22
|
+
id,
|
|
23
|
+
sequence: nextSequence,
|
|
24
|
+
state: 'optimistic',
|
|
25
|
+
recordChanges,
|
|
26
|
+
indexChanges,
|
|
27
|
+
semanticChanges: recordChanges + semanticChanges.length
|
|
28
|
+
});
|
|
29
|
+
host.diagnosticLayers.set(id, layer);
|
|
30
|
+
host.diagnosticEvent(Object.freeze({
|
|
31
|
+
kind: 'layer',
|
|
32
|
+
layer: id,
|
|
33
|
+
action: 'created',
|
|
34
|
+
recordChanges,
|
|
35
|
+
indexChanges
|
|
36
|
+
}));
|
|
37
|
+
host.diagnosticEvent(Object.freeze({
|
|
38
|
+
kind: 'receipt',
|
|
39
|
+
command: id,
|
|
40
|
+
state: 'optimistic',
|
|
41
|
+
obligations: 0,
|
|
42
|
+
observed: 0
|
|
43
|
+
}));
|
|
44
|
+
host.syncDiagnostics();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function markOptimisticLayerAcceptedOn(host, id, receipt) {
|
|
48
|
+
if (receipt !== undefined && receipt.commandId !== id) {
|
|
49
|
+
throw new TypeError('optimistic layer id must equal the causal command id');
|
|
50
|
+
}
|
|
51
|
+
const accepted = host.engine.markOptimisticLayerAccepted(id);
|
|
52
|
+
if (!accepted)
|
|
53
|
+
return false;
|
|
54
|
+
const diagnosticLayer = host.diagnosticLayers?.get(id);
|
|
55
|
+
if (diagnosticLayer !== undefined) {
|
|
56
|
+
host.diagnosticLayers.set(id, Object.freeze({ ...diagnosticLayer, state: 'accepted' }));
|
|
57
|
+
host.diagnosticEvent(Object.freeze({
|
|
58
|
+
kind: 'layer',
|
|
59
|
+
layer: id,
|
|
60
|
+
action: 'accepted',
|
|
61
|
+
recordChanges: diagnosticLayer.recordChanges,
|
|
62
|
+
indexChanges: diagnosticLayer.indexChanges
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
if (receipt === undefined) {
|
|
66
|
+
if (host.diagnostics.enabled) {
|
|
67
|
+
host.diagnosticEvent(Object.freeze({
|
|
68
|
+
kind: 'receipt',
|
|
69
|
+
command: id,
|
|
70
|
+
state: 'accepted',
|
|
71
|
+
obligations: 0,
|
|
72
|
+
observed: 0
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
host.syncDiagnostics();
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const next = optimisticReceiptState(receipt);
|
|
79
|
+
const current = host.optimisticReceipts.get(id);
|
|
80
|
+
if (current !== undefined && !sameReceipt(current, next)) {
|
|
81
|
+
throw new DistributedProtocolError('DISTRIBUTED_PROTOCOL_INVALID', 'extensions.distributed.command');
|
|
82
|
+
}
|
|
83
|
+
host.optimisticReceipts.set(id, next);
|
|
84
|
+
if (host.diagnostics.enabled) {
|
|
85
|
+
const counts = diagnosticReceiptCounts(next);
|
|
86
|
+
host.diagnosticEvent(Object.freeze({
|
|
87
|
+
kind: 'receipt',
|
|
88
|
+
command: id,
|
|
89
|
+
state: counts.obligations === 0
|
|
90
|
+
? 'accepted'
|
|
91
|
+
: 'accepted_pending_projection',
|
|
92
|
+
obligations: counts.obligations,
|
|
93
|
+
observed: counts.observed
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
host.syncDiagnostics();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
export function confirmOptimisticLayerOn(host, id, update) {
|
|
100
|
+
const result = host.engine.confirmOptimisticLayer(id, (writer) => update(baseWriter(writer)));
|
|
101
|
+
host.retireDiagnosticLayer(id, 'retired', 'projected');
|
|
102
|
+
host.optimisticReceipts.delete(id);
|
|
103
|
+
host.syncDiagnostics();
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
export function rejectOptimisticLayerOn(host, id) {
|
|
107
|
+
const rejected = host.engine.rejectOptimisticLayer(id);
|
|
108
|
+
if (rejected) {
|
|
109
|
+
host.retireDiagnosticLayer(id, 'rejected', 'rejected');
|
|
110
|
+
host.optimisticReceipts.delete(id);
|
|
111
|
+
host.syncDiagnostics();
|
|
112
|
+
}
|
|
113
|
+
return rejected;
|
|
114
|
+
}
|
|
115
|
+
export function planOptimisticReceipts(host, command, observations, satisfactionAdmissible) {
|
|
116
|
+
const updates = new Map();
|
|
117
|
+
if (command !== undefined &&
|
|
118
|
+
host.engine.optimisticLayerState(command.commandId) !== undefined) {
|
|
119
|
+
const proposed = optimisticReceiptState(command);
|
|
120
|
+
const current = host.optimisticReceipts.get(command.commandId);
|
|
121
|
+
if (current !== undefined && !sameReceipt(current, proposed)) {
|
|
122
|
+
protocolInvalid('extensions.distributed.command');
|
|
123
|
+
}
|
|
124
|
+
updates.set(command.commandId, cloneOptimisticReceipt(current ?? proposed));
|
|
125
|
+
}
|
|
126
|
+
for (const [id, receipt] of host.optimisticReceipts) {
|
|
127
|
+
if (!updates.has(id)) {
|
|
128
|
+
updates.set(id, cloneOptimisticReceipt(receipt));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const receipt of updates.values()) {
|
|
132
|
+
for (const observation of observations) {
|
|
133
|
+
if (observation.causationId !== receipt.causationId)
|
|
134
|
+
continue;
|
|
135
|
+
const key = expectationKey(observation);
|
|
136
|
+
if (receipt.expectations.has(key))
|
|
137
|
+
receipt.observed.add(key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const satisfied = satisfactionAdmissible
|
|
141
|
+
? [...updates]
|
|
142
|
+
.filter(([, receipt]) => receipt.expectations.size > 0 &&
|
|
143
|
+
[...receipt.expectations.keys()].every((key) => receipt.observed.has(key)))
|
|
144
|
+
.map(([id]) => id)
|
|
145
|
+
: [];
|
|
146
|
+
return { updates, satisfied };
|
|
147
|
+
}
|
|
148
|
+
export function applyReceiptOnly(host, command) {
|
|
149
|
+
if (command === undefined)
|
|
150
|
+
return;
|
|
151
|
+
const plan = planOptimisticReceipts(host, command, command.observations, true);
|
|
152
|
+
for (const [id, receipt] of plan.updates) {
|
|
153
|
+
host.optimisticReceipts.set(id, receipt);
|
|
154
|
+
host.engine.markOptimisticLayerAccepted(id);
|
|
155
|
+
const layer = host.diagnosticLayers?.get(id);
|
|
156
|
+
if (layer !== undefined) {
|
|
157
|
+
host.diagnosticLayers.set(id, Object.freeze({ ...layer, state: 'accepted' }));
|
|
158
|
+
}
|
|
159
|
+
if (host.diagnostics.enabled) {
|
|
160
|
+
const counts = diagnosticReceiptCounts(receipt);
|
|
161
|
+
host.diagnosticEvent(Object.freeze({
|
|
162
|
+
kind: 'receipt',
|
|
163
|
+
command: id,
|
|
164
|
+
state: counts.obligations === 0
|
|
165
|
+
? 'accepted'
|
|
166
|
+
: 'accepted_pending_projection',
|
|
167
|
+
obligations: counts.obligations,
|
|
168
|
+
observed: counts.observed
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CacheEngine } from '../../internal/cache-engine.js';
|
|
2
|
+
import type { GraphqlVariables } from '../../types.js';
|
|
3
|
+
import type { DistributedOpaqueString, DistributedProtocolEnvelope, DistributedTrustedPreset } from '../../protocol.js';
|
|
4
|
+
import type { ReplicaDiagnosticEventInput, ReplicaDiagnosticLayerInput } from '../diagnostics.js';
|
|
5
|
+
import type { ReplicaIndexPlanRegistration } from '../index-maintenance.js';
|
|
6
|
+
import type { ReplicaOperationArtifact, ReplicaWriteSource } from '../types.js';
|
|
7
|
+
import type { AnonymousRecordProtocolClock, OperationProtocolGroup, OptimisticReceiptState, ProjectedRecordFence, ProtocolGeneration, QueryState, RecordProtocolClock, RegisteredCommandAuthorityContract, RenderedOperation } from './types.js';
|
|
8
|
+
import type { ReplicaWatchState } from './watch.js';
|
|
9
|
+
/**
|
|
10
|
+
* Host for protocol generation staging, purge, and authorization closeout.
|
|
11
|
+
*/
|
|
12
|
+
export type ProtocolHost = {
|
|
13
|
+
readonly engine: CacheEngine;
|
|
14
|
+
readonly queryStates: Map<string, QueryState>;
|
|
15
|
+
readonly operationProtocols: Map<string, OperationProtocolGroup>;
|
|
16
|
+
readonly operationGenerations: Map<string, number>;
|
|
17
|
+
readonly recordClocks: Map<string, RecordProtocolClock>;
|
|
18
|
+
readonly recordKeysByScope: Map<DistributedOpaqueString, string>;
|
|
19
|
+
readonly projectedRecordFences: Map<string, ProjectedRecordFence>;
|
|
20
|
+
readonly anonymousRecordClocks: Map<DistributedOpaqueString, AnonymousRecordProtocolClock>;
|
|
21
|
+
readonly optimisticReceipts: Map<string, OptimisticReceiptState>;
|
|
22
|
+
readonly diagnosticLayers: Map<string, ReplicaDiagnosticLayerInput> | undefined;
|
|
23
|
+
readonly indexPlanRegistrations: Map<string, ReplicaIndexPlanRegistration>;
|
|
24
|
+
readonly renderedOperations: Map<string, RenderedOperation>;
|
|
25
|
+
readonly readOperationKeys: Set<string>;
|
|
26
|
+
readonly watchRenderCounts: Map<string, number>;
|
|
27
|
+
readonly watches: Map<string, Set<ReplicaWatchState<unknown, GraphqlVariables>>>;
|
|
28
|
+
readonly diagnostics: {
|
|
29
|
+
readonly enabled: boolean;
|
|
30
|
+
};
|
|
31
|
+
getProtocolGeneration(): ProtocolGeneration | undefined;
|
|
32
|
+
setProtocolGeneration(value: ProtocolGeneration | undefined): void;
|
|
33
|
+
getProtocolGenerationSequence(): number;
|
|
34
|
+
bumpProtocolGenerationSequence(): void;
|
|
35
|
+
getTrustedPresets(): readonly DistributedTrustedPreset[];
|
|
36
|
+
setTrustedPresets(value: readonly DistributedTrustedPreset[]): void;
|
|
37
|
+
getCommandAuthorityContract(): RegisteredCommandAuthorityContract | undefined;
|
|
38
|
+
setProjectionGeneration(value: number): void;
|
|
39
|
+
setNextIndexRevision(value: string): void;
|
|
40
|
+
setDiagnosticLayerSequence(value: number): void;
|
|
41
|
+
clearIndexMaintenance(): void;
|
|
42
|
+
abortAuthorization(): void;
|
|
43
|
+
closeActiveTransports(): void;
|
|
44
|
+
syncDiagnostics(): void;
|
|
45
|
+
emitState(key: string, allowFetch: boolean): void;
|
|
46
|
+
diagnosticEvent(event: ReplicaDiagnosticEventInput): void;
|
|
47
|
+
rememberRenderedOperation<TData, TVariables extends GraphqlVariables>(key: string, artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables, source: 'read' | 'watch'): void;
|
|
48
|
+
};
|
|
49
|
+
export declare function validateProtocolBinding<TData, TVariables extends GraphqlVariables>(host: ProtocolHost, artifact: ReplicaOperationArtifact<TData, TVariables>, envelope: DistributedProtocolEnvelope, source: ReplicaWriteSource): void;
|
|
50
|
+
export declare function stageProtocolGeneration(host: ProtocolHost, envelope: DistributedProtocolEnvelope): ProtocolGeneration;
|
|
51
|
+
export declare function stageTrustedPresets<TData, TVariables extends GraphqlVariables>(host: ProtocolHost, incoming: readonly DistributedTrustedPreset[], nextGeneration: ProtocolGeneration, artifact: ReplicaOperationArtifact<TData, TVariables>): readonly DistributedTrustedPreset[];
|
|
52
|
+
export declare function purgeProtocolGeneration(host: ProtocolHost): void;
|
|
53
|
+
export declare function closeAuthorizationGeneration(host: ProtocolHost): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { matchReplicaTrustedPresetInventory } from '../commands.js';
|
|
2
|
+
import { validateReplicaOperationBinding as validatedArtifactBinding } from '../operation-binding.js';
|
|
3
|
+
import { protocolInvalid } from './clocks.js';
|
|
4
|
+
import { EMPTY_CACHE_SNAPSHOT, EMPTY_TRUSTED_PRESETS } from './constants.js';
|
|
5
|
+
import { canonicalTrustedPresets, trustedPresetDescriptorFingerprint, trustedPresetInventoryFingerprint } from './helpers.js';
|
|
6
|
+
export function validateProtocolBinding(host, artifact, envelope, source) {
|
|
7
|
+
const binding = artifact.protocol;
|
|
8
|
+
if (binding === undefined) {
|
|
9
|
+
protocolInvalid('extensions.distributed');
|
|
10
|
+
}
|
|
11
|
+
if (binding.version !== 1) {
|
|
12
|
+
throw new TypeError('replica artifact protocol version is unsupported');
|
|
13
|
+
}
|
|
14
|
+
if (binding.schemaHash !== envelope.schemaHash) {
|
|
15
|
+
purgeProtocolGeneration(host);
|
|
16
|
+
protocolInvalid('extensions.distributed.schemaHash');
|
|
17
|
+
}
|
|
18
|
+
const expectedOperation = source === 'live' ? artifact.live?.id : binding.operation;
|
|
19
|
+
if (expectedOperation === undefined ||
|
|
20
|
+
envelope.operation !== expectedOperation) {
|
|
21
|
+
protocolInvalid('extensions.distributed.operation');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function stageProtocolGeneration(host, envelope) {
|
|
25
|
+
const next = {
|
|
26
|
+
protocolVersion: 1,
|
|
27
|
+
cacheScope: envelope.cacheScope,
|
|
28
|
+
schemaHash: envelope.schemaHash
|
|
29
|
+
};
|
|
30
|
+
const current = host.getProtocolGeneration();
|
|
31
|
+
if (current === undefined)
|
|
32
|
+
return next;
|
|
33
|
+
if (current.cacheScope === next.cacheScope &&
|
|
34
|
+
current.schemaHash === next.schemaHash) {
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
37
|
+
purgeProtocolGeneration(host);
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
40
|
+
export function stageTrustedPresets(host, incoming, nextGeneration, artifact) {
|
|
41
|
+
const presets = canonicalTrustedPresets(incoming);
|
|
42
|
+
try {
|
|
43
|
+
const binding = validatedArtifactBinding(artifact);
|
|
44
|
+
const operationDescriptors = binding.trustedPresets;
|
|
45
|
+
const commandDescriptors = host.getCommandAuthorityContract()?.trustedPresets;
|
|
46
|
+
if (commandDescriptors !== undefined &&
|
|
47
|
+
trustedPresetDescriptorFingerprint(operationDescriptors) !==
|
|
48
|
+
trustedPresetDescriptorFingerprint(commandDescriptors)) {
|
|
49
|
+
throw new TypeError('operation and command trusted preset contracts differ');
|
|
50
|
+
}
|
|
51
|
+
matchReplicaTrustedPresetInventory(operationDescriptors, presets);
|
|
52
|
+
if (host.getProtocolGeneration() === nextGeneration &&
|
|
53
|
+
trustedPresetInventoryFingerprint(host.getTrustedPresets()) !==
|
|
54
|
+
trustedPresetInventoryFingerprint(presets)) {
|
|
55
|
+
throw new TypeError('trusted presets changed within one authoritative cache scope');
|
|
56
|
+
}
|
|
57
|
+
return presets;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
if (host.getProtocolGeneration() !== undefined) {
|
|
61
|
+
purgeProtocolGeneration(host);
|
|
62
|
+
}
|
|
63
|
+
protocolInvalid('extensions.distributed.trustedPresets');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function purgeProtocolGeneration(host) {
|
|
67
|
+
closeAuthorizationGeneration(host);
|
|
68
|
+
host.queryStates.clear();
|
|
69
|
+
host.operationProtocols.clear();
|
|
70
|
+
host.operationGenerations.clear();
|
|
71
|
+
host.recordClocks.clear();
|
|
72
|
+
host.recordKeysByScope.clear();
|
|
73
|
+
host.projectedRecordFences.clear();
|
|
74
|
+
host.anonymousRecordClocks.clear();
|
|
75
|
+
host.optimisticReceipts.clear();
|
|
76
|
+
host.diagnosticLayers?.clear();
|
|
77
|
+
host.setDiagnosticLayerSequence(0);
|
|
78
|
+
host.clearIndexMaintenance();
|
|
79
|
+
host.indexPlanRegistrations.clear();
|
|
80
|
+
host.renderedOperations.clear();
|
|
81
|
+
host.readOperationKeys.clear();
|
|
82
|
+
host.watchRenderCounts.clear();
|
|
83
|
+
host.setTrustedPresets(EMPTY_TRUSTED_PRESETS);
|
|
84
|
+
host.setProjectionGeneration(0);
|
|
85
|
+
host.setNextIndexRevision('0');
|
|
86
|
+
host.setProtocolGeneration(undefined);
|
|
87
|
+
host.engine.restore(EMPTY_CACHE_SNAPSHOT);
|
|
88
|
+
host.syncDiagnostics();
|
|
89
|
+
for (const watches of host.watches.values()) {
|
|
90
|
+
for (const watch of watches) {
|
|
91
|
+
host.rememberRenderedOperation(watch.key, watch.artifact, watch.variables, 'watch');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const key of host.watches.keys())
|
|
95
|
+
host.emitState(key, true);
|
|
96
|
+
if (host.diagnostics.enabled) {
|
|
97
|
+
host.diagnosticEvent(Object.freeze({
|
|
98
|
+
kind: 'scope',
|
|
99
|
+
action: 'invalidated',
|
|
100
|
+
generation: host.getProtocolGenerationSequence()
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function closeAuthorizationGeneration(host) {
|
|
105
|
+
host.bumpProtocolGenerationSequence();
|
|
106
|
+
host.abortAuthorization();
|
|
107
|
+
host.closeActiveTransports();
|
|
108
|
+
}
|