@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,41 @@
|
|
|
1
|
+
export function commandRuntimeErrorMessage(code) {
|
|
2
|
+
switch (code) {
|
|
3
|
+
case 'REPLICA_COMMAND_ABORTED':
|
|
4
|
+
return 'Caller aborted command dispatch or projection visibility wait';
|
|
5
|
+
case 'REPLICA_COMMAND_AUTHORITY_UNAVAILABLE':
|
|
6
|
+
return 'Command dispatch requires a current authoritative replica scope';
|
|
7
|
+
case 'REPLICA_COMMAND_DISPOSED':
|
|
8
|
+
return 'Command runtime is disposed';
|
|
9
|
+
case 'REPLICA_COMMAND_OUTCOME_PENDING':
|
|
10
|
+
return 'Command outcome remains pending';
|
|
11
|
+
case 'REPLICA_COMMAND_PROJECTION_FAILED':
|
|
12
|
+
return 'Command projection failed';
|
|
13
|
+
case 'REPLICA_COMMAND_PROTOCOL_INVALID':
|
|
14
|
+
return 'Command response violated the generated protocol contract';
|
|
15
|
+
case 'REPLICA_COMMAND_REJECTED':
|
|
16
|
+
return 'Command was rejected';
|
|
17
|
+
case 'REPLICA_COMMAND_SCOPE_INVALIDATED':
|
|
18
|
+
return 'Command authorization scope changed';
|
|
19
|
+
case 'REPLICA_COMMAND_STATUS_UNAVAILABLE':
|
|
20
|
+
return 'Generated command status transport is unavailable';
|
|
21
|
+
case 'REPLICA_COMMAND_TRANSPORT_AMBIGUOUS':
|
|
22
|
+
return 'Command transport outcome is ambiguous';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Redacted typed failure from the integrated generated-command lifecycle. */
|
|
26
|
+
export class ReplicaCommandRuntimeError extends Error {
|
|
27
|
+
code;
|
|
28
|
+
commandId;
|
|
29
|
+
state;
|
|
30
|
+
recovery;
|
|
31
|
+
constructor(code, options = {}) {
|
|
32
|
+
super(commandRuntimeErrorMessage(code), {
|
|
33
|
+
...(options.cause === undefined ? {} : { cause: options.cause })
|
|
34
|
+
});
|
|
35
|
+
this.name = 'ReplicaCommandRuntimeError';
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.commandId = options.commandId;
|
|
38
|
+
this.state = options.state;
|
|
39
|
+
this.recovery = options.recovery;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
|
|
2
|
+
export type { ReplicaBoundCommand, ReplicaBoundCommands, ReplicaCommandAuthorityHost, ReplicaCommandAuthorityRegistration, ReplicaCommandAuthoritySnapshot, ReplicaCommandCallOptions, ReplicaCommandDirectProjection, ReplicaCommandProjectedOutcome, ReplicaCommandReceipt, ReplicaCommandRecoveryReceipt, ReplicaCommandRuntime, ReplicaCommandRuntimeErrorCode, ReplicaCommandRuntimeOptions, ReplicaCommandStatus, ReplicaCommandStatusArtifact, ReplicaCommandStatusRequest, ReplicaCommandSurfaceContract, ReplicaCommandTransport, ReplicaCommandTransportRequest, ReplicaCommandTransportResult, ReplicaResultObservationRegistration } from './types.js';
|
|
3
|
+
export { ReplicaCommandRuntimeError } from './errors.js';
|
|
4
|
+
export { replicaCommandProjectedLifecycleOf } from './lifecycle.js';
|
|
5
|
+
export { createReplicaCommandRuntime } from './create.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
|
|
2
|
+
export { ReplicaCommandRuntimeError } from './errors.js';
|
|
3
|
+
export { replicaCommandProjectedLifecycleOf } from './lifecycle.js';
|
|
4
|
+
export { createReplicaCommandRuntime } from './create.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function defineBoundCommand(root: Record<string, unknown>, path: string, command: unknown): void;
|
|
2
|
+
export declare function commandPathSegments(path: string): readonly string[];
|
|
3
|
+
export declare function commandNamespaceCollision(path: string): never;
|
|
4
|
+
export declare function freezeCommandTree(value: Record<string, unknown>): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isPlainRecord } from '../../../lib/is-plain-record.js';
|
|
2
|
+
export function defineBoundCommand(root, path, command) {
|
|
3
|
+
const segments = commandPathSegments(path);
|
|
4
|
+
let namespace = root;
|
|
5
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
6
|
+
const segment = segments[index];
|
|
7
|
+
const leaf = index === segments.length - 1;
|
|
8
|
+
const exists = Object.prototype.hasOwnProperty.call(namespace, segment);
|
|
9
|
+
if (leaf) {
|
|
10
|
+
if (exists)
|
|
11
|
+
commandNamespaceCollision(path);
|
|
12
|
+
Object.defineProperty(namespace, segment, {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: false,
|
|
15
|
+
writable: false,
|
|
16
|
+
value: command
|
|
17
|
+
});
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (exists) {
|
|
21
|
+
const value = namespace[segment];
|
|
22
|
+
if (!isPlainRecord(value))
|
|
23
|
+
commandNamespaceCollision(path);
|
|
24
|
+
namespace = value;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const child = Object.create(null);
|
|
28
|
+
Object.defineProperty(namespace, segment, {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: false,
|
|
31
|
+
writable: false,
|
|
32
|
+
value: child
|
|
33
|
+
});
|
|
34
|
+
namespace = child;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function commandPathSegments(path) {
|
|
38
|
+
if (path.length === 0 || path.length > 512) {
|
|
39
|
+
throw new TypeError('replica command path is invalid');
|
|
40
|
+
}
|
|
41
|
+
const segments = path.split('.');
|
|
42
|
+
if (segments.length > 64 ||
|
|
43
|
+
segments.some((segment) => segment.length === 0 ||
|
|
44
|
+
segment.length > 128 ||
|
|
45
|
+
segment.trim() !== segment ||
|
|
46
|
+
/[\u0000-\u001f\u007f-\u009f]/.test(segment) ||
|
|
47
|
+
segment === '__proto__' ||
|
|
48
|
+
segment === 'prototype' ||
|
|
49
|
+
segment === 'constructor')) {
|
|
50
|
+
throw new TypeError(`replica command path ${path} is invalid`);
|
|
51
|
+
}
|
|
52
|
+
return Object.freeze(segments);
|
|
53
|
+
}
|
|
54
|
+
export function commandNamespaceCollision(path) {
|
|
55
|
+
throw new TypeError(`replica command namespace collision at ${path}`);
|
|
56
|
+
}
|
|
57
|
+
export function freezeCommandTree(value) {
|
|
58
|
+
for (const child of Object.values(value)) {
|
|
59
|
+
if (isPlainRecord(child)) {
|
|
60
|
+
freezeCommandTree(child);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
Object.freeze(value);
|
|
64
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReplicaPreparedCommand, type ReplicaPreparedCommandEffect, type ReplicaPreparedEffectKey } from '../../commands.js';
|
|
2
|
+
import type { ReplicaIndexSemanticChange } from '../../index-maintenance.js';
|
|
3
|
+
import type { ReplicaModelArtifact, ReplicaOptimisticWriter, ReplicaValue } from '../../types.js';
|
|
4
|
+
export declare function applyOptimisticEffects(writer: ReplicaOptimisticWriter, effects: readonly ReplicaPreparedCommandEffect[]): void;
|
|
5
|
+
export declare function preparedSemanticChanges<TInput, TOutput>(prepared: ReplicaPreparedCommand<TInput, TOutput>): readonly ReplicaIndexSemanticChange[];
|
|
6
|
+
export declare function modelFromKey(model: string, key: ReplicaPreparedEffectKey): ReplicaModelArtifact;
|
|
7
|
+
export declare function identityFromKey(key: ReplicaPreparedEffectKey): readonly ReplicaValue[];
|
|
8
|
+
export declare function fieldsFromEffect(model: string, key: ReplicaPreparedEffectKey, fields: readonly {
|
|
9
|
+
readonly field: string;
|
|
10
|
+
readonly value: ReplicaValue;
|
|
11
|
+
}[]): Readonly<Record<string, ReplicaValue>>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {} from '../../commands.js';
|
|
2
|
+
import { replicaRecordKey } from '../../identity.js';
|
|
3
|
+
export function applyOptimisticEffects(writer, effects) {
|
|
4
|
+
for (const effect of effects) {
|
|
5
|
+
switch (effect.kind) {
|
|
6
|
+
case 'upsert':
|
|
7
|
+
case 'patch': {
|
|
8
|
+
const model = modelFromKey(effect.model, effect.key);
|
|
9
|
+
writer.writeRecord(model, identityFromKey(effect.key), {
|
|
10
|
+
fields: fieldsFromEffect(effect.model, effect.key, effect.fields)
|
|
11
|
+
});
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
case 'delete':
|
|
15
|
+
writer.tombstoneRecord(modelFromKey(effect.model, effect.key), identityFromKey(effect.key));
|
|
16
|
+
break;
|
|
17
|
+
case 'link':
|
|
18
|
+
case 'unlink':
|
|
19
|
+
case 'invalidate_model':
|
|
20
|
+
case 'invalidate_relationship':
|
|
21
|
+
// Task 8 consumes the exact semantic context. Guessing a to-one
|
|
22
|
+
// record link for a to-many relationship would corrupt truth.
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function preparedSemanticChanges(prepared) {
|
|
28
|
+
const dependencies = Object.freeze([...prepared.revalidation.dependencies]);
|
|
29
|
+
const changes = [];
|
|
30
|
+
for (const effect of prepared.optimistic.operations) {
|
|
31
|
+
switch (effect.kind) {
|
|
32
|
+
case 'upsert':
|
|
33
|
+
case 'patch':
|
|
34
|
+
case 'delete':
|
|
35
|
+
// DistributedReplica captures ordinary writer mutations into the
|
|
36
|
+
// same layer context. Supplying them again would double-apply the
|
|
37
|
+
// semantic record operation.
|
|
38
|
+
break;
|
|
39
|
+
case 'link':
|
|
40
|
+
case 'unlink': {
|
|
41
|
+
const source = modelFromKey(effect.relationship.sourceModel, effect.source);
|
|
42
|
+
const target = modelFromKey(effect.relationship.targetModel, effect.target);
|
|
43
|
+
changes.push(Object.freeze({
|
|
44
|
+
kind: effect.kind,
|
|
45
|
+
sourceModel: effect.relationship.sourceModel,
|
|
46
|
+
field: effect.relationship.field,
|
|
47
|
+
targetModel: effect.relationship.targetModel,
|
|
48
|
+
sourceKey: replicaRecordKey(source, identityFromKey(effect.source)),
|
|
49
|
+
targetKey: replicaRecordKey(target, identityFromKey(effect.target)),
|
|
50
|
+
dependencies
|
|
51
|
+
}));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case 'invalidate_model':
|
|
55
|
+
case 'invalidate_relationship':
|
|
56
|
+
changes.push(Object.freeze({
|
|
57
|
+
kind: 'invalidate',
|
|
58
|
+
dependencies
|
|
59
|
+
}));
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze(changes);
|
|
64
|
+
}
|
|
65
|
+
export function modelFromKey(model, key) {
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
id: model,
|
|
68
|
+
identityFields: Object.freeze(key.fields.map(({ field }) => field))
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function identityFromKey(key) {
|
|
72
|
+
return Object.freeze(key.fields.map(({ value }) => value));
|
|
73
|
+
}
|
|
74
|
+
export function fieldsFromEffect(model, key, fields) {
|
|
75
|
+
const result = Object.create(null);
|
|
76
|
+
for (const field of [
|
|
77
|
+
{ field: '__typename', value: model },
|
|
78
|
+
...key.fields,
|
|
79
|
+
...fields
|
|
80
|
+
]) {
|
|
81
|
+
Object.defineProperty(result, field.field, {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
configurable: false,
|
|
84
|
+
writable: false,
|
|
85
|
+
value: field.value
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return Object.freeze(result);
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Command-runtime support modules (inventory, transport, projection, …). */
|
|
2
|
+
export * from './inventory.js';
|
|
3
|
+
export * from './binding.js';
|
|
4
|
+
export * from './effects.js';
|
|
5
|
+
export * from './transport.js';
|
|
6
|
+
export * from './status.js';
|
|
7
|
+
export * from './output.js';
|
|
8
|
+
export * from './projection.js';
|
|
9
|
+
export * from './util.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Command-runtime support modules (inventory, transport, projection, …). */
|
|
2
|
+
export * from './inventory.js';
|
|
3
|
+
export * from './binding.js';
|
|
4
|
+
export * from './effects.js';
|
|
5
|
+
export * from './transport.js';
|
|
6
|
+
export * from './status.js';
|
|
7
|
+
export * from './output.js';
|
|
8
|
+
export * from './projection.js';
|
|
9
|
+
export * from './util.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReplicaTrustedPresetDescriptor } from '../../commands.js';
|
|
2
|
+
import type { AnyCommandArtifact, CommandEntry, ReplicaCommandStatusArtifact, ReplicaCommandSurfaceContract } from '../types.js';
|
|
3
|
+
export declare function normalizeInventory<TEntries extends Readonly<Record<string, CommandEntry>>>(entries: TEntries): readonly {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly artifact: AnyCommandArtifact;
|
|
6
|
+
}[];
|
|
7
|
+
export declare function commandSurfaceContract(artifacts: readonly AnyCommandArtifact[], surfacePresets: readonly ReplicaTrustedPresetDescriptor[] | undefined): ReplicaCommandSurfaceContract;
|
|
8
|
+
export declare function commandStatusArtifact(value: ReplicaCommandStatusArtifact, contract: ReplicaCommandSurfaceContract): ReplicaCommandStatusArtifact;
|
|
9
|
+
export declare function normalizePresetDescriptors(value: readonly ReplicaTrustedPresetDescriptor[], path: string): readonly ReplicaTrustedPresetDescriptor[];
|
|
10
|
+
export declare function samePresetDescriptors(left: readonly ReplicaTrustedPresetDescriptor[], right: readonly ReplicaTrustedPresetDescriptor[]): boolean;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isDistributedTrustedPresetCodec } from '../../../protocol.js';
|
|
2
|
+
import {} from '../../commands.js';
|
|
3
|
+
import { SHA256 } from '../constants.js';
|
|
4
|
+
import { compareCodeUnits } from '../../../lib/compare-code-units.js';
|
|
5
|
+
import { commandNamespaceCollision, commandPathSegments } from './binding.js';
|
|
6
|
+
import { cloneSurface, sameSurface } from './util.js';
|
|
7
|
+
export function normalizeInventory(entries) {
|
|
8
|
+
if (entries === null || typeof entries !== 'object' || Array.isArray(entries)) {
|
|
9
|
+
throw new TypeError('replica command inventory must be an object');
|
|
10
|
+
}
|
|
11
|
+
const names = new Set();
|
|
12
|
+
const inventory = Object.entries(entries).map(([key, entry]) => {
|
|
13
|
+
commandPathSegments(key);
|
|
14
|
+
const artifact = 'artifact' in entry ? entry.artifact : entry;
|
|
15
|
+
if (names.has(artifact.name)) {
|
|
16
|
+
throw new TypeError(`duplicate replica command artifact ${artifact.name}`);
|
|
17
|
+
}
|
|
18
|
+
names.add(artifact.name);
|
|
19
|
+
return Object.freeze({ key, artifact });
|
|
20
|
+
});
|
|
21
|
+
const sortedPaths = inventory.map(({ key }) => key).sort(compareCodeUnits);
|
|
22
|
+
for (let index = 1; index < sortedPaths.length; index += 1) {
|
|
23
|
+
const previous = sortedPaths[index - 1];
|
|
24
|
+
const current = sortedPaths[index];
|
|
25
|
+
if (current.startsWith(`${previous}.`)) {
|
|
26
|
+
commandNamespaceCollision(current);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Object.freeze(inventory);
|
|
30
|
+
}
|
|
31
|
+
export function commandSurfaceContract(artifacts, surfacePresets) {
|
|
32
|
+
if (artifacts.length === 0) {
|
|
33
|
+
throw new TypeError('replica command inventory must not be empty');
|
|
34
|
+
}
|
|
35
|
+
const first = artifacts[0];
|
|
36
|
+
const protocol = first.protocol;
|
|
37
|
+
if (protocol.surface === undefined) {
|
|
38
|
+
throw new TypeError('generated command protocol requires a client surface');
|
|
39
|
+
}
|
|
40
|
+
const trustedPresets = normalizePresetDescriptors(protocol.trustedPresets, 'artifact.protocol.trustedPresets');
|
|
41
|
+
const commandPresets = new Map();
|
|
42
|
+
for (const artifact of artifacts) {
|
|
43
|
+
if (artifact.protocol.version !== 1 ||
|
|
44
|
+
artifact.protocol.schemaHash !== protocol.schemaHash ||
|
|
45
|
+
artifact.protocol.protocolHash !== protocol.protocolHash ||
|
|
46
|
+
!sameSurface(artifact.protocol.surface, protocol.surface) ||
|
|
47
|
+
!samePresetDescriptors(normalizePresetDescriptors(artifact.protocol.trustedPresets, 'artifact.protocol.trustedPresets'), trustedPresets)) {
|
|
48
|
+
throw new TypeError('replica command inventory spans incompatible client surfaces');
|
|
49
|
+
}
|
|
50
|
+
for (const descriptor of artifact.trustedPresets ?? []) {
|
|
51
|
+
const previous = commandPresets.get(descriptor.name);
|
|
52
|
+
if (previous !== undefined && previous.codec !== descriptor.codec) {
|
|
53
|
+
throw new TypeError(`trusted preset ${descriptor.name} has conflicting codecs`);
|
|
54
|
+
}
|
|
55
|
+
commandPresets.set(descriptor.name, Object.freeze({ name: descriptor.name, codec: descriptor.codec }));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (surfacePresets !== undefined &&
|
|
59
|
+
!samePresetDescriptors(normalizePresetDescriptors(surfacePresets, 'status.protocol.trustedPresets'), trustedPresets)) {
|
|
60
|
+
throw new TypeError('generated command status inventory does not match its client surface');
|
|
61
|
+
}
|
|
62
|
+
const surfaceByName = new Map(trustedPresets.map((descriptor) => [descriptor.name, descriptor]));
|
|
63
|
+
for (const descriptor of commandPresets.values()) {
|
|
64
|
+
if (surfaceByName.get(descriptor.name)?.codec !== descriptor.codec) {
|
|
65
|
+
throw new TypeError(`command trusted preset ${descriptor.name} is absent from the client surface`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
protocolVersion: 1,
|
|
70
|
+
schemaHash: protocol.schemaHash,
|
|
71
|
+
protocolHash: protocol.protocolHash,
|
|
72
|
+
surface: cloneSurface(protocol.surface),
|
|
73
|
+
trustedPresets
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function commandStatusArtifact(value, contract) {
|
|
77
|
+
if (value === null ||
|
|
78
|
+
typeof value !== 'object' ||
|
|
79
|
+
typeof value.name !== 'string' ||
|
|
80
|
+
value.name.trim().length === 0 ||
|
|
81
|
+
typeof value.document !== 'string' ||
|
|
82
|
+
value.document.trim().length === 0 ||
|
|
83
|
+
typeof value.operationHash !== 'string' ||
|
|
84
|
+
!SHA256.test(value.operationHash) ||
|
|
85
|
+
value.protocol === null ||
|
|
86
|
+
typeof value.protocol !== 'object' ||
|
|
87
|
+
value.protocol.version !== 1 ||
|
|
88
|
+
value.protocol.operation !== value.operationHash ||
|
|
89
|
+
value.protocol.schemaHash !== contract.schemaHash ||
|
|
90
|
+
value.protocol.protocolHash !== contract.protocolHash ||
|
|
91
|
+
!sameSurface(value.protocol.surface, contract.surface)) {
|
|
92
|
+
throw new TypeError('generated command status artifact is invalid');
|
|
93
|
+
}
|
|
94
|
+
const trustedPresets = normalizePresetDescriptors(value.protocol.trustedPresets, 'status.protocol.trustedPresets');
|
|
95
|
+
if (!samePresetDescriptors(trustedPresets, contract.trustedPresets)) {
|
|
96
|
+
throw new TypeError('generated command status inventory does not match its client surface');
|
|
97
|
+
}
|
|
98
|
+
return Object.freeze({
|
|
99
|
+
name: value.name,
|
|
100
|
+
document: value.document,
|
|
101
|
+
operationHash: value.operationHash,
|
|
102
|
+
protocol: Object.freeze({
|
|
103
|
+
version: 1,
|
|
104
|
+
schemaHash: contract.schemaHash,
|
|
105
|
+
protocolHash: contract.protocolHash,
|
|
106
|
+
surface: cloneSurface(contract.surface),
|
|
107
|
+
operation: value.operationHash,
|
|
108
|
+
trustedPresets
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export function normalizePresetDescriptors(value, path) {
|
|
113
|
+
if (!Array.isArray(value)) {
|
|
114
|
+
throw new TypeError(`${path} must be an array`);
|
|
115
|
+
}
|
|
116
|
+
const names = new Set();
|
|
117
|
+
const result = value.map((descriptor, index) => {
|
|
118
|
+
if (descriptor === null ||
|
|
119
|
+
typeof descriptor !== 'object' ||
|
|
120
|
+
typeof descriptor.name !== 'string' ||
|
|
121
|
+
descriptor.name.length === 0 ||
|
|
122
|
+
descriptor.name.length > 128 ||
|
|
123
|
+
descriptor.name.trim() !== descriptor.name ||
|
|
124
|
+
/[\u0000-\u001f\u007f-\u009f]/.test(descriptor.name) ||
|
|
125
|
+
names.has(descriptor.name) ||
|
|
126
|
+
!isDistributedTrustedPresetCodec(descriptor.codec)) {
|
|
127
|
+
throw new TypeError(`${path}[${index}] is invalid`);
|
|
128
|
+
}
|
|
129
|
+
names.add(descriptor.name);
|
|
130
|
+
return Object.freeze({
|
|
131
|
+
name: descriptor.name,
|
|
132
|
+
codec: descriptor.codec
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
return Object.freeze(result.sort(({ name: left }, { name: right }) => compareCodeUnits(left, right)));
|
|
136
|
+
}
|
|
137
|
+
export function samePresetDescriptors(left, right) {
|
|
138
|
+
return (left.length === right.length &&
|
|
139
|
+
left.every((descriptor, index) => descriptor.name === right[index]?.name &&
|
|
140
|
+
descriptor.codec === right[index]?.codec));
|
|
141
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DistributedCommandMetadata } from '../../../protocol.js';
|
|
2
|
+
import { type ReplicaCommandArtifact } from '../../commands.js';
|
|
3
|
+
import type { ReplicaValue } from '../../types.js';
|
|
4
|
+
export declare function projectionExpectationFingerprint(value: DistributedCommandMetadata['expects'][number]): string;
|
|
5
|
+
export declare function projectionObservationFingerprint(value: DistributedCommandMetadata['observations'][number]): string;
|
|
6
|
+
export declare function recordRevisionFingerprint(value: DistributedCommandMetadata['records'][number]): string;
|
|
7
|
+
export declare function tupleFingerprint(parts: readonly string[]): string;
|
|
8
|
+
export declare function sameStringMultiset(left: readonly string[], right: readonly string[]): boolean;
|
|
9
|
+
export declare function isStringSubset(subset: readonly string[], superset: readonly string[]): boolean;
|
|
10
|
+
export declare function commandOutput<TInput, TOutput>(artifact: ReplicaCommandArtifact<TInput, TOutput>, data: Readonly<Record<string, unknown>> | null | undefined, field: string): unknown;
|
|
11
|
+
export declare function cloneOutputShape(shape: ReplicaCommandArtifact<unknown, unknown>['output'], value: unknown, path: string, depth: number): unknown;
|
|
12
|
+
export declare function cloneOutputScalar(codec: string | undefined, value: unknown, path: string): ReplicaValue;
|
|
13
|
+
export declare function cloneOutputJson(value: unknown, path: string, active: Set<object>, depth: number): ReplicaValue;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {} from '../../../protocol.js';
|
|
2
|
+
import {} from '../../commands.js';
|
|
3
|
+
import { MAX_OUTPUT_DEPTH } from '../constants.js';
|
|
4
|
+
import { ReplicaCommandRuntimeError } from '../errors.js';
|
|
5
|
+
import { compareCodeUnits } from '../../../lib/compare-code-units.js';
|
|
6
|
+
import { isPlainRecord } from '../../../lib/is-plain-record.js';
|
|
7
|
+
import { comparePropertyKeys, outputInvalid } from './util.js';
|
|
8
|
+
export function projectionExpectationFingerprint(value) {
|
|
9
|
+
return tupleFingerprint([
|
|
10
|
+
value.projection,
|
|
11
|
+
value.model,
|
|
12
|
+
value.scopeToken
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
15
|
+
export function projectionObservationFingerprint(value) {
|
|
16
|
+
return tupleFingerprint([
|
|
17
|
+
value.causationId,
|
|
18
|
+
value.projection,
|
|
19
|
+
value.model,
|
|
20
|
+
value.scopeToken
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
export function recordRevisionFingerprint(value) {
|
|
24
|
+
return tupleFingerprint([
|
|
25
|
+
value.model,
|
|
26
|
+
value.scopeToken,
|
|
27
|
+
value.incarnation,
|
|
28
|
+
value.revision,
|
|
29
|
+
value.tombstone ? '1' : '0',
|
|
30
|
+
...(value.path ?? [])
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
export function tupleFingerprint(parts) {
|
|
34
|
+
return parts.map((part) => `${part.length}:${part}`).join('');
|
|
35
|
+
}
|
|
36
|
+
export function sameStringMultiset(left, right) {
|
|
37
|
+
if (left.length !== right.length)
|
|
38
|
+
return false;
|
|
39
|
+
const sortedLeft = [...left].sort(compareCodeUnits);
|
|
40
|
+
const sortedRight = [...right].sort(compareCodeUnits);
|
|
41
|
+
return sortedLeft.every((value, index) => value === sortedRight[index]);
|
|
42
|
+
}
|
|
43
|
+
export function isStringSubset(subset, superset) {
|
|
44
|
+
const remaining = new Map();
|
|
45
|
+
for (const value of superset) {
|
|
46
|
+
remaining.set(value, (remaining.get(value) ?? 0) + 1);
|
|
47
|
+
}
|
|
48
|
+
for (const value of subset) {
|
|
49
|
+
const count = remaining.get(value) ?? 0;
|
|
50
|
+
if (count === 0)
|
|
51
|
+
return false;
|
|
52
|
+
remaining.set(value, count - 1);
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
export function commandOutput(artifact, data, field) {
|
|
57
|
+
if (data === undefined ||
|
|
58
|
+
data === null ||
|
|
59
|
+
!Object.prototype.hasOwnProperty.call(data, field) ||
|
|
60
|
+
Reflect.ownKeys(data).some((key) => key !== field)) {
|
|
61
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID');
|
|
62
|
+
}
|
|
63
|
+
return cloneOutputShape(artifact.output, data[field], `data.${field}`, 0);
|
|
64
|
+
}
|
|
65
|
+
export function cloneOutputShape(shape, value, path, depth) {
|
|
66
|
+
if (depth > MAX_OUTPUT_DEPTH)
|
|
67
|
+
outputInvalid(path);
|
|
68
|
+
if (shape.kind !== 'object')
|
|
69
|
+
outputInvalid(path);
|
|
70
|
+
if (!isPlainRecord(value))
|
|
71
|
+
outputInvalid(path);
|
|
72
|
+
const known = new Set(shape.definition.fields.map(({ name }) => name));
|
|
73
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
74
|
+
if (typeof key !== 'string' || !known.has(key))
|
|
75
|
+
outputInvalid(`${path}.${String(key)}`);
|
|
76
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
77
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
78
|
+
outputInvalid(`${path}.${key}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const output = {};
|
|
82
|
+
for (const field of shape.definition.fields) {
|
|
83
|
+
const present = Object.prototype.hasOwnProperty.call(value, field.name) &&
|
|
84
|
+
value[field.name] !== undefined;
|
|
85
|
+
if (!present) {
|
|
86
|
+
outputInvalid(`${path}.${field.name}`);
|
|
87
|
+
}
|
|
88
|
+
const fieldValue = value[field.name];
|
|
89
|
+
if (fieldValue === null) {
|
|
90
|
+
if (!field.nullable)
|
|
91
|
+
outputInvalid(`${path}.${field.name}`);
|
|
92
|
+
output[field.name] = null;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const cloneItem = (item, itemPath) => field.nested === undefined
|
|
96
|
+
? cloneOutputScalar(field.codec, item, itemPath)
|
|
97
|
+
: cloneOutputShape({ kind: 'object', definition: field.nested }, item, itemPath, depth + 1);
|
|
98
|
+
if (field.list) {
|
|
99
|
+
if (!Array.isArray(fieldValue))
|
|
100
|
+
outputInvalid(`${path}.${field.name}`);
|
|
101
|
+
output[field.name] = Object.freeze(fieldValue.map((item, index) => {
|
|
102
|
+
if (item === null) {
|
|
103
|
+
if (!field.itemNullable) {
|
|
104
|
+
outputInvalid(`${path}.${field.name}[${index}]`);
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return cloneItem(item, `${path}.${field.name}[${index}]`);
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
output[field.name] = cloneItem(fieldValue, `${path}.${field.name}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return Object.freeze(output);
|
|
116
|
+
}
|
|
117
|
+
export function cloneOutputScalar(codec, value, path) {
|
|
118
|
+
switch (codec) {
|
|
119
|
+
case 'string':
|
|
120
|
+
case 'string_unvalidated_timestamp':
|
|
121
|
+
case 'base64':
|
|
122
|
+
if (typeof value !== 'string')
|
|
123
|
+
outputInvalid(path);
|
|
124
|
+
return value;
|
|
125
|
+
case 'boolean':
|
|
126
|
+
if (typeof value !== 'boolean')
|
|
127
|
+
outputInvalid(path);
|
|
128
|
+
return value;
|
|
129
|
+
case 'int32':
|
|
130
|
+
if (typeof value !== 'number' ||
|
|
131
|
+
!Number.isInteger(value) ||
|
|
132
|
+
value < -2_147_483_648 ||
|
|
133
|
+
value > 2_147_483_647) {
|
|
134
|
+
outputInvalid(path);
|
|
135
|
+
}
|
|
136
|
+
return Object.is(value, -0) ? 0 : value;
|
|
137
|
+
case 'json_number_precision_limited':
|
|
138
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
139
|
+
outputInvalid(path);
|
|
140
|
+
}
|
|
141
|
+
return Object.is(value, -0) ? 0 : value;
|
|
142
|
+
case 'float64':
|
|
143
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
144
|
+
outputInvalid(path);
|
|
145
|
+
}
|
|
146
|
+
return Object.is(value, -0) ? 0 : value;
|
|
147
|
+
case 'json':
|
|
148
|
+
return cloneOutputJson(value, path, new Set(), 0);
|
|
149
|
+
default:
|
|
150
|
+
outputInvalid(`${path}.codec`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function cloneOutputJson(value, path, active, depth) {
|
|
154
|
+
if (depth > MAX_OUTPUT_DEPTH)
|
|
155
|
+
outputInvalid(path);
|
|
156
|
+
if (value === null ||
|
|
157
|
+
typeof value === 'string' ||
|
|
158
|
+
typeof value === 'boolean') {
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
if (typeof value === 'number') {
|
|
162
|
+
if (!Number.isFinite(value))
|
|
163
|
+
outputInvalid(path);
|
|
164
|
+
return Object.is(value, -0) ? 0 : value;
|
|
165
|
+
}
|
|
166
|
+
if (typeof value !== 'object' || active.has(value))
|
|
167
|
+
outputInvalid(path);
|
|
168
|
+
active.add(value);
|
|
169
|
+
if (Array.isArray(value)) {
|
|
170
|
+
const output = Object.freeze(value.map((item, index) => cloneOutputJson(item, `${path}[${index}]`, active, depth + 1)));
|
|
171
|
+
active.delete(value);
|
|
172
|
+
return output;
|
|
173
|
+
}
|
|
174
|
+
if (!isPlainRecord(value))
|
|
175
|
+
outputInvalid(path);
|
|
176
|
+
const output = {};
|
|
177
|
+
for (const key of Reflect.ownKeys(value).sort(comparePropertyKeys)) {
|
|
178
|
+
if (typeof key !== 'string')
|
|
179
|
+
outputInvalid(path);
|
|
180
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
181
|
+
if (descriptor === undefined ||
|
|
182
|
+
!('value' in descriptor) ||
|
|
183
|
+
descriptor.value === undefined) {
|
|
184
|
+
outputInvalid(`${path}.${key}`);
|
|
185
|
+
}
|
|
186
|
+
output[key] = cloneOutputJson(descriptor.value, `${path}.${key}`, active, depth + 1);
|
|
187
|
+
}
|
|
188
|
+
active.delete(value);
|
|
189
|
+
return Object.freeze(output);
|
|
190
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DistributedCommandMetadata } from '../../../protocol.js';
|
|
2
|
+
import { type ReplicaPreparedCommand } from '../../commands.js';
|
|
3
|
+
import type { CapturedAuthority, CommandStatusTracker, PendingProjection, ReplicaCommandAuthorityHost, ReplicaCommandProjectedOutcome, ReplicaCommandStatus } from '../types.js';
|
|
4
|
+
export declare function confirmDirectProjection<TInput, TOutput>(replica: ReplicaCommandAuthorityHost, prepared: ReplicaPreparedCommand<TInput, TOutput>, output: TOutput, metadata: DistributedCommandMetadata): void;
|
|
5
|
+
export declare function pendingProjection(authority: CapturedAuthority, metadata: DistributedCommandMetadata, prepared: ReplicaPreparedCommand<unknown, unknown>): PendingProjection;
|
|
6
|
+
/**
|
|
7
|
+
* Generated fact commands converge without application polling. Durable status
|
|
8
|
+
* is the only completion signal; timers merely schedule reads and never infer a
|
|
9
|
+
* successful outcome.
|
|
10
|
+
*/
|
|
11
|
+
export declare function monitorPendingProjection(controller: PendingProjection, readStatus: () => Promise<ReplicaCommandStatus>, retained: () => boolean, reportError: ((error: unknown) => void) | undefined): void;
|
|
12
|
+
export declare function reportBackgroundErrorSafely(reportError: ((error: unknown) => void) | undefined, error: unknown): void;
|
|
13
|
+
export declare function projectionAuthorityAborted(controller: PendingProjection): boolean;
|
|
14
|
+
export declare function waitForProjectionPoll(delay: number, signals: readonly AbortSignal[]): Promise<void>;
|
|
15
|
+
export declare function settleProjectionSuccess(controller: PendingProjection): void;
|
|
16
|
+
export declare function callerProjectedPromise(controller: PendingProjection, signal: AbortSignal | undefined): Promise<ReplicaCommandProjectedOutcome<unknown>>;
|
|
17
|
+
export declare function attachAuthorityAbort(controller: PendingProjection, onSettled: () => void): void;
|
|
18
|
+
export declare function settleProjectionFailure(controller: PendingProjection, error: unknown): void;
|
|
19
|
+
export declare function settleTrackedProjection(tracker: CommandStatusTracker, pending: Map<string, PendingProjection>): void;
|
|
20
|
+
export declare function failTrackedProjection(tracker: CommandStatusTracker, pending: Map<string, PendingProjection>, error: unknown): void;
|