@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 @@
|
|
|
1
|
+
export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './implementation.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { canonicalizeOperationVariables } from '../identity.js';
|
|
2
|
+
import { compareCodeUnits } from '../../lib/compare-code-units.js';
|
|
3
|
+
import { applyLayers, canonicalValue, compilePlans, evaluatePlan, instantiatePlans, planAffected, reason, stale, validateIndexes } from './engine.js';
|
|
4
|
+
class PurposeBuiltIndexMaintenanceRegistry {
|
|
5
|
+
#plans = new Map();
|
|
6
|
+
#nextRegistration = 0;
|
|
7
|
+
registerOperation(artifact, variables) {
|
|
8
|
+
const canonicalVariables = canonicalizeOperationVariables(artifact, variables);
|
|
9
|
+
const id = `${artifact.id}:${canonicalValue(canonicalVariables)}:${++this.#nextRegistration}`;
|
|
10
|
+
this.#plans.set(id, compilePlans(id, artifact, canonicalVariables));
|
|
11
|
+
let active = true;
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
id,
|
|
14
|
+
dispose: () => {
|
|
15
|
+
if (!active)
|
|
16
|
+
return;
|
|
17
|
+
active = false;
|
|
18
|
+
this.#plans.delete(id);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
evaluate(snapshot, layers, trustedPresets = Object.freeze([])) {
|
|
23
|
+
const applied = applyLayers(snapshot.records, layers);
|
|
24
|
+
const indexes = validateIndexes(snapshot.indexes);
|
|
25
|
+
const plansByIndex = instantiatePlans([...this.#plans.values()].flat(), indexes);
|
|
26
|
+
const decisions = [];
|
|
27
|
+
for (const [indexKey, plans] of [...plansByIndex].sort(([left], [right]) => compareCodeUnits(left, right))) {
|
|
28
|
+
const index = indexes.get(indexKey);
|
|
29
|
+
if (index === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
const affected = plans.filter((plan) => planAffected(plan, index, applied));
|
|
32
|
+
if (affected.length === 0)
|
|
33
|
+
continue;
|
|
34
|
+
const signatures = new Set(affected.map(({ signature }) => signature));
|
|
35
|
+
if (signatures.size !== 1) {
|
|
36
|
+
decisions.push(stale(indexKey, reason('conflicting_index_plan', ['indexes', indexKey], 'multiple active artifacts disagree about one semantic index plan')));
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
decisions.push(evaluatePlan(affected[0], index, applied, trustedPresets));
|
|
40
|
+
}
|
|
41
|
+
return Object.freeze(decisions);
|
|
42
|
+
}
|
|
43
|
+
clear() {
|
|
44
|
+
this.#plans.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function createReplicaIndexMaintenanceRegistry() {
|
|
48
|
+
return new PurposeBuiltIndexMaintenanceRegistry();
|
|
49
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { GraphqlVariables } from '../../types.js';
|
|
2
|
+
import type { DistributedTrustedPreset } from '../../protocol.js';
|
|
3
|
+
import type { ReplicaIndexCoverage, ReplicaIndexSemanticChange, ReplicaOperationArtifact, ReplicaValue } from '../types.js';
|
|
4
|
+
export type { ReplicaIndexDependencyChange, ReplicaIndexRecordChange, ReplicaIndexRelationshipChange, ReplicaIndexSemanticChange } from '../types.js';
|
|
5
|
+
export type ReplicaIndexMaintenanceRecord = {
|
|
6
|
+
readonly key: string;
|
|
7
|
+
readonly model: string;
|
|
8
|
+
readonly fields: Readonly<Record<string, ReplicaValue>>;
|
|
9
|
+
};
|
|
10
|
+
export type ReplicaIndexMaintenanceIndex = {
|
|
11
|
+
readonly key: string;
|
|
12
|
+
readonly records: readonly string[];
|
|
13
|
+
readonly complete: boolean;
|
|
14
|
+
readonly metadata: {
|
|
15
|
+
readonly parent?: string;
|
|
16
|
+
readonly field: string;
|
|
17
|
+
readonly arguments: Readonly<Record<string, ReplicaValue>>;
|
|
18
|
+
readonly coverage: ReplicaIndexCoverage;
|
|
19
|
+
readonly dependencies: readonly string[];
|
|
20
|
+
readonly staleReason?: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type ReplicaIndexSemanticLayer = {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly changes: readonly ReplicaIndexSemanticChange[];
|
|
26
|
+
};
|
|
27
|
+
export type ReplicaIndexMaintenanceSnapshot = {
|
|
28
|
+
readonly records: readonly ReplicaIndexMaintenanceRecord[];
|
|
29
|
+
readonly indexes: readonly ReplicaIndexMaintenanceIndex[];
|
|
30
|
+
};
|
|
31
|
+
export type ReplicaIndexMaintenanceReasonCode = 'aggregate_dependency_changed' | 'conflicting_index_plan' | 'dependency_changed' | 'duplicate_index_record' | 'index_incomplete' | 'invalid_index_metadata' | 'missing_index_record' | 'missing_order_plan' | 'missing_parent_record' | 'non_unique_order' | 'relationship_dependency_missing' | 'relationship_maintenance_revalidate' | 'relationship_mapping_unknown' | 'unsupported_cardinality' | import('../query-plan.js').ReplicaQueryPlanReasonCode;
|
|
32
|
+
export type ReplicaIndexMaintenanceReason = {
|
|
33
|
+
readonly code: ReplicaIndexMaintenanceReasonCode;
|
|
34
|
+
readonly path: readonly (string | number)[];
|
|
35
|
+
readonly message: string;
|
|
36
|
+
};
|
|
37
|
+
export type ReplicaIndexMaintenanceDecision = {
|
|
38
|
+
readonly kind: 'write';
|
|
39
|
+
readonly indexKey: string;
|
|
40
|
+
readonly records: readonly string[];
|
|
41
|
+
readonly complete: true;
|
|
42
|
+
} | {
|
|
43
|
+
readonly kind: 'stale';
|
|
44
|
+
readonly indexKey: string;
|
|
45
|
+
readonly reason: ReplicaIndexMaintenanceReason;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: 'unchanged';
|
|
48
|
+
readonly indexKey: string;
|
|
49
|
+
};
|
|
50
|
+
export type ReplicaIndexPlanRegistration = {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
dispose(): void;
|
|
53
|
+
};
|
|
54
|
+
export interface ReplicaIndexMaintenanceRegistry {
|
|
55
|
+
registerOperation<TData, TVariables extends GraphqlVariables>(artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables): ReplicaIndexPlanRegistration;
|
|
56
|
+
evaluate(snapshot: ReplicaIndexMaintenanceSnapshot, layers: readonly ReplicaIndexSemanticLayer[], trustedPresets?: readonly DistributedTrustedPreset[]): readonly ReplicaIndexMaintenanceDecision[];
|
|
57
|
+
clear(): void;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Index maintenance registry and plan evaluation; implementation in ./index-maintenance/. */
|
|
2
|
+
export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './index-maintenance/index.js';
|
|
3
|
+
export type { ReplicaIndexDependencyChange, ReplicaIndexMaintenanceDecision, ReplicaIndexMaintenanceIndex, ReplicaIndexMaintenanceReason, ReplicaIndexMaintenanceReasonCode, ReplicaIndexMaintenanceRecord, ReplicaIndexMaintenanceRegistry, ReplicaIndexMaintenanceSnapshot, ReplicaIndexPlanRegistration, ReplicaIndexRecordChange, ReplicaIndexRelationshipChange, ReplicaIndexSemanticChange, ReplicaIndexSemanticLayer } from './index-maintenance/index.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { createDistributedReplica } from './distributed-replica.js';
|
|
2
|
+
export { createReplicaDevelopmentCapability, createReplicaDiagnostics, inspectReplicaCommandArtifact, inspectReplicaOperationArtifact } from './diagnostics.js';
|
|
3
|
+
export type { ReplicaArtifactSourceLocation, ReplicaCommandArtifactInspection, ReplicaCommandEffectInspection, ReplicaDevelopmentCapability, ReplicaDiagnosticEvent, ReplicaDiagnosticEventInput, ReplicaDiagnosticFieldValueContext, ReplicaDiagnosticFieldValuePolicy, ReplicaDiagnosticIndex, ReplicaDiagnosticIndexInput, ReplicaDiagnosticLayer, ReplicaDiagnosticLayerInput, ReplicaDiagnosticReceipt, ReplicaDiagnosticReceiptExpectationInput, ReplicaDiagnosticReceiptInput, ReplicaDiagnosticRecord, ReplicaDiagnosticRecordInput, ReplicaDiagnosticReasonContext, ReplicaDiagnosticReasonPolicy, ReplicaDiagnostics, ReplicaDiagnosticsOptions, ReplicaDiagnosticsSink, ReplicaDiagnosticsSnapshot, ReplicaDiagnosticScopeInput, ReplicaDiagnosticStateInput, ReplicaOperationArtifactInspection, ReplicaOperationIndexInspection, ReplicaOperationInjectedFieldInspection } from './diagnostics.js';
|
|
4
|
+
export { createReplicaGraphqlTransport } from './graphql-transport.js';
|
|
5
|
+
export type { ReplicaGraphqlTransport, ReplicaGraphqlTransportOptions } from './graphql-transport.js';
|
|
6
|
+
export { canonicalizeOperationVariables, replicaIndexKey, replicaRecordKey } from './identity.js';
|
|
7
|
+
export { prepareReplicaCommand, ReplicaCommandContractError, verifyReplicaCommandReceipt } from './commands.js';
|
|
8
|
+
export { createReplicaCommandRuntime, ReplicaCommandRuntimeError } from './command-runtime.js';
|
|
9
|
+
export { createReplicaIndexedDbPersistence, REPLICA_OFFLINE_COMMAND_OUTBOX_SUPPORTED } from './persistence.js';
|
|
10
|
+
export type { ReplicaBoundCommand, ReplicaBoundCommands, ReplicaCommandCallOptions, ReplicaCommandProjectedOutcome, ReplicaCommandReceipt, ReplicaCommandRecoveryReceipt, ReplicaCommandRuntime, ReplicaCommandRuntimeErrorCode, ReplicaCommandRuntimeOptions, ReplicaCommandStatus, ReplicaCommandStatusArtifact, ReplicaCommandStatusRequest, ReplicaCommandTransport, ReplicaCommandTransportRequest, ReplicaCommandTransportResult } from './command-runtime.js';
|
|
11
|
+
export type { ReplicaIndexedDbFactory, ReplicaIndexedDbPersistence, ReplicaIndexedDbPersistenceOptions, ReplicaPersistenceModelPolicy, ReplicaPersistencePolicy } from './persistence.js';
|
|
12
|
+
export type { PrepareReplicaCommandOptions, ReplicaCommandArtifact, ReplicaCommandConfirmation, ReplicaCommandConfirmations, ReplicaCommandContractErrorCode, ReplicaCommandDirectProjection, ReplicaCommandEffect, ReplicaCommandEffectExpression, ReplicaCommandEffectField, ReplicaCommandEffectKey, ReplicaCommandEffectRelationship, ReplicaCommandEffects, ReplicaCommandGenerators, ReplicaCommandInputDefault, ReplicaCommandInputDefaults, ReplicaCommandRevalidationPlan, ReplicaCommandScalarCodec, ReplicaCommandShape, ReplicaCommandTypeDefinition, ReplicaCommandTypeField, ReplicaCommandVariables, ReplicaTrustedPresetDescriptor, ReplicaPreparedCommand, ReplicaPreparedCommandEffect, ReplicaPreparedConfirmation, ReplicaPreparedConfirmations, ReplicaPreparedEffectField, ReplicaPreparedEffectKey, ReplicaReceiptVerification } from './commands.js';
|
|
13
|
+
export { compareReplicaOrder, decideReplicaPaginationMaintenance, evaluateReplicaFilter } from './query-plan.js';
|
|
14
|
+
export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './index-maintenance.js';
|
|
15
|
+
export type { ReplicaIndexDependencyChange, ReplicaIndexMaintenanceDecision, ReplicaIndexMaintenanceIndex, ReplicaIndexMaintenanceReason, ReplicaIndexMaintenanceReasonCode, ReplicaIndexMaintenanceRecord, ReplicaIndexMaintenanceRegistry, ReplicaIndexMaintenanceSnapshot, ReplicaIndexPlanRegistration, ReplicaIndexRecordChange, ReplicaIndexRelationshipChange, ReplicaIndexSemanticChange, ReplicaIndexSemanticLayer } from './index-maintenance.js';
|
|
16
|
+
export type { ReplicaFilterEvaluation, ReplicaFilterEvaluationOptions, ReplicaOrderComparison, ReplicaPaginationChange, ReplicaPaginationMaintenanceDecision, ReplicaQueryPlanPath, ReplicaQueryPlanReason, ReplicaQueryPlanReasonCode, ReplicaRelationshipFilterRequest } from './query-plan.js';
|
|
17
|
+
export type { DistributedReplicaOptions, DistributedReplica, ReplicaArgumentsArtifact, ReplicaArgumentValue, ReplicaAuthoritativeScope, ReplicaBaseWriter, ReplicaBranchSemantic, ReplicaClientSurface, ReplicaCoverageArtifact, ReplicaDehydratedState, ReplicaFilterArtifact, ReplicaFilterExpression, ReplicaFilterFieldArtifact, ReplicaFilterLiteral, ReplicaFilterOperand, ReplicaFilterOperator, ReplicaIdentity, ReplicaIndexCoverage, ReplicaIndexInspection, ReplicaIndexTarget, ReplicaListValue, ReplicaLiveObserver, ReplicaLiveState, ReplicaLiteralValue, ReplicaModelArtifact, ReplicaObjectBranch, ReplicaObjectMember, ReplicaObjectSelection, ReplicaObjectValue, ReplicaOperationArtifact, ReplicaOperationSourceLocation, ReplicaOperationProtocol, ReplicaOrderArtifact, ReplicaOrderFieldArtifact, ReplicaOrderTieBreakerArtifact, ReplicaOptimisticWriter, ReplicaPaginationArtifact, ReplicaPaginationDisposition, ReplicaProtocolOperationArtifact, ReplicaRecordInspection, ReplicaRecordPatch, ReplicaRevalidationPlan, ReplicaRevalidationRelationship, ReplicaRevision, ReplicaRelationshipArtifact, ReplicaRelationshipKeyMapping, ReplicaRelationshipKind, ReplicaResultEnvelope, ReplicaRowPolicyArtifact, ReplicaRootSelection, ReplicaScalarSelection, ReplicaSelectionStorage, ReplicaSparse, ReplicaSnapshot, ReplicaStatus, ReplicaTransport, ReplicaTransportRequest, ReplicaVariableValue, ReplicaVariableCodecArtifact, ReplicaVariableEnumInputRef, ReplicaVariableFilterInputDefinition, ReplicaVariableFilterInputField, ReplicaVariableFilterInputRelationship, ReplicaVariableFilterInputTarget, ReplicaVariableInputDefinition, ReplicaVariableInputRef, ReplicaVariableListInputRef, ReplicaVariableNamedInputRef, ReplicaVariableOrderInputDefinition, ReplicaVariableOrderInputField, ReplicaVariableScalarInputRef, ReplicaValue, ReplicaWatch, ReplicaWriteSource, WatchReplicaOptions } from './types.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createDistributedReplica } from './distributed-replica.js';
|
|
2
|
+
export { createReplicaDevelopmentCapability, createReplicaDiagnostics, inspectReplicaCommandArtifact, inspectReplicaOperationArtifact } from './diagnostics.js';
|
|
3
|
+
export { createReplicaGraphqlTransport } from './graphql-transport.js';
|
|
4
|
+
export { canonicalizeOperationVariables, replicaIndexKey, replicaRecordKey } from './identity.js';
|
|
5
|
+
export { prepareReplicaCommand, ReplicaCommandContractError, verifyReplicaCommandReceipt } from './commands.js';
|
|
6
|
+
export { createReplicaCommandRuntime, ReplicaCommandRuntimeError } from './command-runtime.js';
|
|
7
|
+
export { createReplicaIndexedDbPersistence, REPLICA_OFFLINE_COMMAND_OUTBOX_SUPPORTED } from './persistence.js';
|
|
8
|
+
export { compareReplicaOrder, decideReplicaPaginationMaintenance, evaluateReplicaFilter } from './query-plan.js';
|
|
9
|
+
export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './index-maintenance.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CacheReader } from '../internal/cache-engine.js';
|
|
2
|
+
import type { GraphqlVariables } from '../types.js';
|
|
3
|
+
import type { ReplicaOperationArtifact, ReplicaSparse } from './types.js';
|
|
4
|
+
export type MaterializedReplicaResult<TData> = {
|
|
5
|
+
readonly data: ReplicaSparse<TData>;
|
|
6
|
+
readonly complete: boolean;
|
|
7
|
+
readonly stale: boolean;
|
|
8
|
+
readonly identitySignature: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function materializeReplicaOperation<TData, TVariables extends GraphqlVariables>(reader: CacheReader, artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables): MaterializedReplicaResult<TData>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { replicaIndexKey, resolveArguments } from './identity.js';
|
|
2
|
+
import { runtimeRoot } from './selection.js';
|
|
3
|
+
export function materializeReplicaOperation(reader, artifact, variables) {
|
|
4
|
+
const output = {};
|
|
5
|
+
let complete = true;
|
|
6
|
+
let stale = false;
|
|
7
|
+
const signatures = [];
|
|
8
|
+
for (const artifactRoot of artifact.roots) {
|
|
9
|
+
const root = runtimeRoot(artifactRoot);
|
|
10
|
+
const argumentsValue = resolveArguments(root.arguments, variables, root.coverage);
|
|
11
|
+
const indexKey = replicaIndexKey({ field: root.field, arguments: argumentsValue });
|
|
12
|
+
const index = reader.index(indexKey);
|
|
13
|
+
const branch = materializeBranch(reader, root, index, variables);
|
|
14
|
+
if (branch.present) {
|
|
15
|
+
defineOutputValue(output, root.responseKey, branch.value);
|
|
16
|
+
}
|
|
17
|
+
complete &&= branch.complete;
|
|
18
|
+
stale ||= branch.stale;
|
|
19
|
+
signatures.push(`${indexKey}:${branch.identitySignature}`);
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
data: Object.freeze(output),
|
|
23
|
+
complete,
|
|
24
|
+
stale,
|
|
25
|
+
identitySignature: signatures.join('|')
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function materializeBranch(reader, selection, index, variables) {
|
|
29
|
+
if (!index || !index.metadata) {
|
|
30
|
+
return {
|
|
31
|
+
present: false,
|
|
32
|
+
complete: false,
|
|
33
|
+
stale: false,
|
|
34
|
+
identitySignature: 'missing-index'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const stale = index.metadata.staleReason !== undefined;
|
|
38
|
+
/*
|
|
39
|
+
* Completeness and freshness are independent. A stale complete index still
|
|
40
|
+
* has a structurally materializable result and remains visible during
|
|
41
|
+
* stale-while-revalidate. Partial/lifecycle-invalidated indexes explicitly
|
|
42
|
+
* carry `complete=false` and remain sparse.
|
|
43
|
+
*/
|
|
44
|
+
const indexComplete = index.complete;
|
|
45
|
+
if (index.metadata.nullValue) {
|
|
46
|
+
return {
|
|
47
|
+
value: null,
|
|
48
|
+
present: true,
|
|
49
|
+
complete: indexComplete && selection.nullable,
|
|
50
|
+
stale,
|
|
51
|
+
identitySignature: `null:${index.metadata.staleReason ?? ''}`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (selection.cardinality === 'one') {
|
|
55
|
+
const key = index.records[0];
|
|
56
|
+
if (key === undefined) {
|
|
57
|
+
return {
|
|
58
|
+
present: false,
|
|
59
|
+
complete: false,
|
|
60
|
+
stale,
|
|
61
|
+
identitySignature: `missing-record:${index.metadata.staleReason ?? ''}`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const object = materializeObject(reader, selection.selection, key, variables);
|
|
65
|
+
return {
|
|
66
|
+
value: object.value,
|
|
67
|
+
present: object.value !== undefined,
|
|
68
|
+
complete: indexComplete && index.records.length === 1 && object.complete,
|
|
69
|
+
stale: stale || object.stale,
|
|
70
|
+
identitySignature: object.identitySignature
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const values = [];
|
|
74
|
+
let childrenComplete = true;
|
|
75
|
+
let childrenStale = false;
|
|
76
|
+
const signatures = [];
|
|
77
|
+
for (const key of index.records) {
|
|
78
|
+
const object = materializeObject(reader, selection.selection, key, variables);
|
|
79
|
+
if (object.value !== undefined)
|
|
80
|
+
values.push(object.value);
|
|
81
|
+
else
|
|
82
|
+
childrenComplete = false;
|
|
83
|
+
childrenComplete &&= object.complete;
|
|
84
|
+
childrenStale ||= object.stale;
|
|
85
|
+
signatures.push(object.identitySignature);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
value: Object.freeze(values),
|
|
89
|
+
present: true,
|
|
90
|
+
complete: indexComplete && childrenComplete,
|
|
91
|
+
stale: stale || childrenStale,
|
|
92
|
+
identitySignature: signatures.join(',')
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function materializeObject(reader, selection, key, variables) {
|
|
96
|
+
const record = reader.recordMeta(key);
|
|
97
|
+
if (!record) {
|
|
98
|
+
return {
|
|
99
|
+
complete: false,
|
|
100
|
+
stale: false,
|
|
101
|
+
identitySignature: `${key}:missing`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const output = {};
|
|
105
|
+
let complete = true;
|
|
106
|
+
let stale = false;
|
|
107
|
+
const nestedSignatures = [];
|
|
108
|
+
for (const member of selection.members) {
|
|
109
|
+
if (member.kind !== 'scalar')
|
|
110
|
+
continue;
|
|
111
|
+
const presence = reader.field(key, member.field);
|
|
112
|
+
if (!presence.present || (presence.value === null && !member.nullable)) {
|
|
113
|
+
complete = false;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (member.expose !== false) {
|
|
117
|
+
defineOutputValue(output, member.responseKey, presence.value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const member of selection.members) {
|
|
121
|
+
if (member.kind !== 'branch')
|
|
122
|
+
continue;
|
|
123
|
+
const branchResult = materializeNestedBranch(reader, key, record.incarnation, member, variables);
|
|
124
|
+
if (member.expose !== false && branchResult.present) {
|
|
125
|
+
defineOutputValue(output, member.responseKey, branchResult.value);
|
|
126
|
+
}
|
|
127
|
+
complete &&= branchResult.complete;
|
|
128
|
+
stale ||= branchResult.stale;
|
|
129
|
+
nestedSignatures.push(`${member.field}:${branchResult.identitySignature}`);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
value: Object.freeze(output),
|
|
133
|
+
complete,
|
|
134
|
+
stale,
|
|
135
|
+
identitySignature: `${key}@${record.incarnation}[${nestedSignatures.join('|')}]`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function materializeNestedBranch(reader, parentKey, parentIncarnation, selection, variables) {
|
|
139
|
+
const argumentsValue = resolveArguments(selection.arguments, variables, selection.coverage);
|
|
140
|
+
const indexKey = replicaIndexKey({
|
|
141
|
+
parent: parentKey,
|
|
142
|
+
field: selection.field,
|
|
143
|
+
arguments: argumentsValue
|
|
144
|
+
});
|
|
145
|
+
const index = reader.index(indexKey);
|
|
146
|
+
if (index?.metadata?.parentIncarnation !== parentIncarnation) {
|
|
147
|
+
return {
|
|
148
|
+
present: false,
|
|
149
|
+
complete: false,
|
|
150
|
+
stale: index !== undefined,
|
|
151
|
+
identitySignature: `parent-incarnation-mismatch:${parentIncarnation}`
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return materializeBranch(reader, selection, index, variables);
|
|
155
|
+
}
|
|
156
|
+
function defineOutputValue(output, key, value) {
|
|
157
|
+
Object.defineProperty(output, key, {
|
|
158
|
+
value,
|
|
159
|
+
enumerable: true,
|
|
160
|
+
configurable: false,
|
|
161
|
+
writable: false
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BaseCacheWriter, CacheValue } from '../internal/cache-engine.js';
|
|
2
|
+
import type { DistributedRecordRevision } from '../protocol.js';
|
|
3
|
+
import type { GraphqlVariables } from '../types.js';
|
|
4
|
+
import type { ReplicaOperationArtifact, ReplicaRevision, ReplicaResultEnvelope } from './types.js';
|
|
5
|
+
export type ReplicaNormalizationSummary = {
|
|
6
|
+
readonly wrote: boolean;
|
|
7
|
+
readonly partial: boolean;
|
|
8
|
+
readonly indexKeys: readonly string[];
|
|
9
|
+
};
|
|
10
|
+
export type ReplicaProtocolRecordResolution = {
|
|
11
|
+
readonly evidence: DistributedRecordRevision;
|
|
12
|
+
/** False means a same-scope newer base record already won. */
|
|
13
|
+
readonly apply: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type ReplicaNormalizationProtocol = {
|
|
16
|
+
readonly indexRevision: ReplicaRevision;
|
|
17
|
+
readonly writeIndexes: boolean;
|
|
18
|
+
readonly indexesComplete: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Missing causal record evidence may use operation-local snapshot storage.
|
|
21
|
+
* Such rows remain renderable without impersonating a shared normalized
|
|
22
|
+
* identity.
|
|
23
|
+
*/
|
|
24
|
+
readonly allowSnapshotOnlyRecords: boolean;
|
|
25
|
+
readonly record: (path: readonly string[], model: string, key: string, fields: Readonly<Record<string, CacheValue>>) => ReplicaProtocolRecordResolution | undefined;
|
|
26
|
+
};
|
|
27
|
+
export declare function normalizeReplicaResult<TData, TVariables extends GraphqlVariables>(writer: BaseCacheWriter, artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables, envelope: ReplicaResultEnvelope<TData>, protocol: ReplicaNormalizationProtocol): ReplicaNormalizationSummary;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { cloneJsonValue, coverageFromArtifact, replicaIndexKey, replicaRecordKey, resolveArguments } from './identity.js';
|
|
2
|
+
import { embeddedRecordKey, runtimeRoot } from './selection.js';
|
|
3
|
+
import { deepEqual } from '../lib/deep-equal.js';
|
|
4
|
+
export function normalizeReplicaResult(writer, artifact, variables, envelope, protocol) {
|
|
5
|
+
validateArtifact(artifact);
|
|
6
|
+
const snapshotRevision = protocol.indexRevision;
|
|
7
|
+
const errors = collectErrorPaths(envelope.errors ?? []);
|
|
8
|
+
if (errors.global || envelope.data === undefined) {
|
|
9
|
+
return Object.freeze({ wrote: false, partial: errors.global, indexKeys: [] });
|
|
10
|
+
}
|
|
11
|
+
if (envelope.data === null && (envelope.errors?.length ?? 0) > 0) {
|
|
12
|
+
// `data: null` is a valid GraphQL error result after non-null propagation.
|
|
13
|
+
// The ingress coordinator will stale the operation roots without replacing
|
|
14
|
+
// the last known-good memberships.
|
|
15
|
+
return Object.freeze({ wrote: false, partial: true, indexKeys: [] });
|
|
16
|
+
}
|
|
17
|
+
if (envelope.data === null || !isObject(envelope.data)) {
|
|
18
|
+
throw new TypeError(`operation ${artifact.id} returned a non-object GraphQL data value`);
|
|
19
|
+
}
|
|
20
|
+
let wrote = false;
|
|
21
|
+
let partial = false;
|
|
22
|
+
const indexKeys = [];
|
|
23
|
+
for (const artifactRoot of artifact.roots) {
|
|
24
|
+
const root = runtimeRoot(artifactRoot);
|
|
25
|
+
const path = [root.responseKey];
|
|
26
|
+
const hasValue = Object.prototype.hasOwnProperty.call(envelope.data, root.responseKey);
|
|
27
|
+
const blocked = pathBlocked(errors, path);
|
|
28
|
+
const argumentsValue = resolveArguments(root.arguments, variables, root.coverage);
|
|
29
|
+
const key = replicaIndexKey({ field: root.field, arguments: argumentsValue });
|
|
30
|
+
const hasErrors = pathHasErrors(errors, path);
|
|
31
|
+
const rawValue = hasValue ? envelope.data[root.responseKey] : undefined;
|
|
32
|
+
if (blocked || !hasValue || (rawValue === null && hasErrors)) {
|
|
33
|
+
wrote =
|
|
34
|
+
protocol.writeIndexes
|
|
35
|
+
? writer.markIndexStale(key, hasErrors ? 'graphql-partial-error' : 'incomplete-result', snapshotRevision) || wrote
|
|
36
|
+
: wrote;
|
|
37
|
+
indexKeys.push(key);
|
|
38
|
+
partial = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const branch = normalizeBranch(writer, artifact.id, root, rawValue, path, key, snapshotRevision, variables, errors, protocol, indexKeys);
|
|
42
|
+
const rootPartial = branch.partial ||
|
|
43
|
+
hasErrors ||
|
|
44
|
+
!protocol.indexesComplete;
|
|
45
|
+
const metadata = indexMetadata(root, argumentsValue, branch, rootPartial, hasErrors);
|
|
46
|
+
if (protocol.writeIndexes &&
|
|
47
|
+
writer.writeIndex({
|
|
48
|
+
key,
|
|
49
|
+
revision: snapshotRevision,
|
|
50
|
+
records: branch.keys,
|
|
51
|
+
complete: !rootPartial,
|
|
52
|
+
metadata
|
|
53
|
+
})) {
|
|
54
|
+
wrote = true;
|
|
55
|
+
}
|
|
56
|
+
indexKeys.push(key);
|
|
57
|
+
partial ||= rootPartial;
|
|
58
|
+
}
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
wrote,
|
|
61
|
+
partial,
|
|
62
|
+
indexKeys: Object.freeze(indexKeys)
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function normalizeBranch(writer, artifactId, selection, value, path, indexKey, snapshotRevision, variables, errors, protocol, indexKeys) {
|
|
66
|
+
if (value === null) {
|
|
67
|
+
if (!selection.nullable && !pathHasErrors(errors, path)) {
|
|
68
|
+
throw new TypeError(`operation ${artifactId} returned null for non-null field ${pathLabel(path)}`);
|
|
69
|
+
}
|
|
70
|
+
return { keys: [], nullValue: true, partial: false };
|
|
71
|
+
}
|
|
72
|
+
if (value === undefined)
|
|
73
|
+
return { keys: [], nullValue: false, partial: true };
|
|
74
|
+
if (selection.cardinality === 'one') {
|
|
75
|
+
const object = normalizeObject(writer, artifactId, selection.selection, value, path, indexKey, undefined, snapshotRevision, variables, errors, protocol, indexKeys);
|
|
76
|
+
return {
|
|
77
|
+
keys: object.key === undefined ? [] : [object.key],
|
|
78
|
+
nullValue: false,
|
|
79
|
+
partial: object.partial
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(value)) {
|
|
83
|
+
throw new TypeError(`operation ${artifactId} returned a non-list value for ${pathLabel(path)}`);
|
|
84
|
+
}
|
|
85
|
+
const keys = [];
|
|
86
|
+
let partial = false;
|
|
87
|
+
for (const [ordinal, entry] of value.entries()) {
|
|
88
|
+
const entryPath = [...path, ordinal];
|
|
89
|
+
if (entry === null || entry === undefined) {
|
|
90
|
+
if (pathHasErrors(errors, entryPath)) {
|
|
91
|
+
partial = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
throw new TypeError(`operation ${artifactId} returned null for non-null list item ${pathLabel(entryPath)}`);
|
|
95
|
+
}
|
|
96
|
+
const object = normalizeObject(writer, artifactId, selection.selection, entry, entryPath, indexKey, ordinal, snapshotRevision, variables, errors, protocol, indexKeys);
|
|
97
|
+
if (object.key === undefined)
|
|
98
|
+
partial = true;
|
|
99
|
+
else
|
|
100
|
+
keys.push(object.key);
|
|
101
|
+
partial ||= object.partial;
|
|
102
|
+
}
|
|
103
|
+
return { keys, nullValue: false, partial };
|
|
104
|
+
}
|
|
105
|
+
function normalizeObject(writer, artifactId, selection, value, path, enclosingIndexKey, ordinal, snapshotRevision, variables, errors, protocol, indexKeys) {
|
|
106
|
+
if (pathBlocked(errors, path))
|
|
107
|
+
return { partial: true };
|
|
108
|
+
if (!isObject(value)) {
|
|
109
|
+
throw new TypeError(`operation ${artifactId} returned a non-object value for ${pathLabel(path)}`);
|
|
110
|
+
}
|
|
111
|
+
const fields = Object.create(null);
|
|
112
|
+
let partial = false;
|
|
113
|
+
for (const member of selection.members) {
|
|
114
|
+
if (member.kind !== 'scalar')
|
|
115
|
+
continue;
|
|
116
|
+
const fieldPath = [...path, member.responseKey];
|
|
117
|
+
const hasValue = Object.prototype.hasOwnProperty.call(value, member.responseKey);
|
|
118
|
+
const rawValue = hasValue ? value[member.responseKey] : undefined;
|
|
119
|
+
if (pathBlocked(errors, fieldPath) ||
|
|
120
|
+
!hasValue) {
|
|
121
|
+
partial = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (rawValue === null && !member.nullable) {
|
|
125
|
+
if (pathHasErrors(errors, fieldPath)) {
|
|
126
|
+
partial = true;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
throw new TypeError(`operation ${artifactId} returned null for non-null field ${pathLabel(fieldPath)}`);
|
|
130
|
+
}
|
|
131
|
+
const next = cloneJsonValue(rawValue);
|
|
132
|
+
if (Object.prototype.hasOwnProperty.call(fields, member.field)) {
|
|
133
|
+
if (!deepEqual(fields[member.field], next)) {
|
|
134
|
+
throw new TypeError(`operation aliases disagree for ${selection.typename}.${member.field}`);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
fields[member.field] = next;
|
|
139
|
+
}
|
|
140
|
+
let key;
|
|
141
|
+
let revision;
|
|
142
|
+
let incarnation;
|
|
143
|
+
let resolution;
|
|
144
|
+
if (selection.storage.kind === 'normalized') {
|
|
145
|
+
const identity = [];
|
|
146
|
+
for (const field of selection.storage.identityFields) {
|
|
147
|
+
if (!Object.prototype.hasOwnProperty.call(fields, field) ||
|
|
148
|
+
fields[field] === null) {
|
|
149
|
+
return { partial: true };
|
|
150
|
+
}
|
|
151
|
+
identity.push(fields[field]);
|
|
152
|
+
}
|
|
153
|
+
key = replicaRecordKey({
|
|
154
|
+
id: selection.storage.model,
|
|
155
|
+
identityFields: selection.storage.identityFields
|
|
156
|
+
}, identity);
|
|
157
|
+
resolution = protocol.record(path.map(String), selection.storage.model, key, fields);
|
|
158
|
+
if (resolution === undefined) {
|
|
159
|
+
if (!protocol.allowSnapshotOnlyRecords) {
|
|
160
|
+
return { key, partial: true };
|
|
161
|
+
}
|
|
162
|
+
/*
|
|
163
|
+
* This row came from an exact authorized query, but the selected
|
|
164
|
+
* surface has no safely comparable record clock for its model.
|
|
165
|
+
* Scope the storage to this operation/index position so a later
|
|
166
|
+
* snapshot can replace it without corrupting causally normalized
|
|
167
|
+
* state shared by another operation.
|
|
168
|
+
*/
|
|
169
|
+
key = embeddedRecordKey(artifactId, enclosingIndexKey, ordinal);
|
|
170
|
+
revision = snapshotRevision;
|
|
171
|
+
incarnation = snapshotRevision;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (resolution.evidence.tombstone) {
|
|
175
|
+
throw new TypeError('live GraphQL row carries tombstone record evidence');
|
|
176
|
+
}
|
|
177
|
+
revision = resolution.evidence.revision;
|
|
178
|
+
incarnation = resolution.evidence.incarnation;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Embedded output is an operation-local replacement snapshot, not a
|
|
183
|
+
// normalized server record. Its synthetic incarnation deliberately
|
|
184
|
+
// advances with the enclosing response so removed sparse fields disappear.
|
|
185
|
+
key = embeddedRecordKey(artifactId, enclosingIndexKey, ordinal);
|
|
186
|
+
revision = snapshotRevision;
|
|
187
|
+
incarnation = snapshotRevision;
|
|
188
|
+
}
|
|
189
|
+
if (resolution?.apply !== false) {
|
|
190
|
+
writer.writeRecord({
|
|
191
|
+
key,
|
|
192
|
+
revision,
|
|
193
|
+
...(incarnation === undefined ? {} : { incarnation }),
|
|
194
|
+
fields
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const storedClock = writer.recordClock(key);
|
|
198
|
+
if (storedClock === undefined ||
|
|
199
|
+
storedClock.tombstoned ||
|
|
200
|
+
(resolution?.apply !== false &&
|
|
201
|
+
(storedClock.revision !== String(revision) ||
|
|
202
|
+
(incarnation !== undefined &&
|
|
203
|
+
storedClock.incarnation !== String(incarnation))))) {
|
|
204
|
+
return { partial: true };
|
|
205
|
+
}
|
|
206
|
+
for (const member of selection.members) {
|
|
207
|
+
if (member.kind !== 'branch')
|
|
208
|
+
continue;
|
|
209
|
+
const branchPath = [...path, member.responseKey];
|
|
210
|
+
const hasValue = Object.prototype.hasOwnProperty.call(value, member.responseKey);
|
|
211
|
+
const blocked = pathBlocked(errors, branchPath);
|
|
212
|
+
const hasErrors = pathHasErrors(errors, branchPath);
|
|
213
|
+
const rawValue = hasValue ? value[member.responseKey] : undefined;
|
|
214
|
+
const argumentsValue = resolveArguments(member.arguments, variables, member.coverage);
|
|
215
|
+
const branchIndexKey = replicaIndexKey({
|
|
216
|
+
parent: key,
|
|
217
|
+
field: member.field,
|
|
218
|
+
arguments: argumentsValue
|
|
219
|
+
});
|
|
220
|
+
indexKeys.push(branchIndexKey);
|
|
221
|
+
if (!hasValue || blocked || (rawValue === null && hasErrors)) {
|
|
222
|
+
if (protocol.writeIndexes) {
|
|
223
|
+
writer.markIndexStale(branchIndexKey, hasErrors ? 'graphql-partial-error' : 'incomplete-result', snapshotRevision);
|
|
224
|
+
}
|
|
225
|
+
partial = true;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const branch = normalizeBranch(writer, artifactId, member, rawValue, branchPath, branchIndexKey, snapshotRevision, variables, errors, protocol, indexKeys);
|
|
229
|
+
const branchPartial = branch.partial ||
|
|
230
|
+
hasErrors ||
|
|
231
|
+
!protocol.indexesComplete;
|
|
232
|
+
if (protocol.writeIndexes) {
|
|
233
|
+
writer.writeIndex({
|
|
234
|
+
key: branchIndexKey,
|
|
235
|
+
revision: snapshotRevision,
|
|
236
|
+
records: branch.keys,
|
|
237
|
+
complete: !branchPartial,
|
|
238
|
+
metadata: indexMetadata(member, argumentsValue, branch, branchPartial, hasErrors, key, storedClock.revision)
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
partial ||= branchPartial;
|
|
242
|
+
}
|
|
243
|
+
return { key, partial };
|
|
244
|
+
}
|
|
245
|
+
function indexMetadata(selection, argumentsValue, branch, partial, hasErrors, parent, parentRevision) {
|
|
246
|
+
return Object.freeze({
|
|
247
|
+
...(parent === undefined ? {} : { parent }),
|
|
248
|
+
...(parentRevision === undefined ? {} : { parentRevision: String(parentRevision) }),
|
|
249
|
+
field: selection.field,
|
|
250
|
+
arguments: argumentsValue,
|
|
251
|
+
coverage: coverageFromArtifact(selection.coverage, argumentsValue, branch.keys.length),
|
|
252
|
+
dependencies: Object.freeze([...new Set(selection.dependencies)].sort()),
|
|
253
|
+
...(partial
|
|
254
|
+
? { staleReason: hasErrors ? 'graphql-partial-error' : 'incomplete-result' }
|
|
255
|
+
: {}),
|
|
256
|
+
nullValue: branch.nullValue
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function collectErrorPaths(errors) {
|
|
260
|
+
const paths = [];
|
|
261
|
+
let global = false;
|
|
262
|
+
for (const error of errors) {
|
|
263
|
+
if (!Array.isArray(error.path) || error.path.length === 0) {
|
|
264
|
+
global = true;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
paths.push(Object.freeze([...error.path]));
|
|
268
|
+
}
|
|
269
|
+
return { global, paths };
|
|
270
|
+
}
|
|
271
|
+
function pathBlocked(errors, path) {
|
|
272
|
+
return errors.global || errors.paths.some((errorPath) => isPrefix(errorPath, path));
|
|
273
|
+
}
|
|
274
|
+
function pathHasErrors(errors, path) {
|
|
275
|
+
return (errors.global ||
|
|
276
|
+
errors.paths.some((errorPath) => isPrefix(path, errorPath) || isPrefix(errorPath, path)));
|
|
277
|
+
}
|
|
278
|
+
function isPrefix(prefix, value) {
|
|
279
|
+
return (prefix.length <= value.length &&
|
|
280
|
+
prefix.every((entry, index) => entry === value[index]));
|
|
281
|
+
}
|
|
282
|
+
function validateArtifact(artifact) {
|
|
283
|
+
if (!artifact || typeof artifact !== 'object') {
|
|
284
|
+
throw new TypeError('replica artifact is required');
|
|
285
|
+
}
|
|
286
|
+
if (typeof artifact.id !== 'string' || artifact.id.length === 0) {
|
|
287
|
+
throw new TypeError('replica artifact id must be a non-empty string');
|
|
288
|
+
}
|
|
289
|
+
if (!Array.isArray(artifact.roots) || artifact.roots.length === 0) {
|
|
290
|
+
throw new TypeError(`operation ${artifact.id} must contain at least one root selection`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function pathLabel(path) {
|
|
294
|
+
return path.map(String).join('.');
|
|
295
|
+
}
|
|
296
|
+
function isObject(value) {
|
|
297
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
298
|
+
}
|