@hops-ops/distributed 4.7.0 → 4.9.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.
@@ -0,0 +1,15 @@
1
+ export type DistributedGenerationEnvelope = Readonly<{
2
+ version: 1;
3
+ generationId: string;
4
+ releaseId: string;
5
+ applicationId?: string;
6
+ clientId?: string;
7
+ schemaId?: string;
8
+ protocolId?: string;
9
+ topologyId?: string;
10
+ workerId?: string;
11
+ memberId?: string;
12
+ compatibilityId?: string;
13
+ }>;
14
+ /** Parse a generation envelope without trusting unbounded process metadata. */
15
+ export declare function parseDistributedGenerationEnvelope(value: unknown, path?: string): DistributedGenerationEnvelope;
@@ -0,0 +1,41 @@
1
+ /** Parse a generation envelope without trusting unbounded process metadata. */
2
+ export function parseDistributedGenerationEnvelope(value, path = 'generation') {
3
+ const record = object(value, path);
4
+ if (record.version !== 1)
5
+ throw new TypeError(`${path}.version must be 1`);
6
+ const parsed = {
7
+ version: 1,
8
+ generationId: identity(record.generationId, `${path}.generationId`),
9
+ releaseId: identity(record.releaseId, `${path}.releaseId`)
10
+ };
11
+ for (const key of [
12
+ 'applicationId',
13
+ 'clientId',
14
+ 'schemaId',
15
+ 'protocolId',
16
+ 'topologyId',
17
+ 'workerId',
18
+ 'memberId',
19
+ 'compatibilityId'
20
+ ]) {
21
+ if (record[key] !== undefined)
22
+ parsed[key] = identity(record[key], `${path}.${key}`);
23
+ }
24
+ return Object.freeze(parsed);
25
+ }
26
+ function object(value, path) {
27
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
28
+ throw new TypeError(`${path} must be an object`);
29
+ }
30
+ return value;
31
+ }
32
+ function identity(value, path) {
33
+ if (typeof value !== 'string' ||
34
+ value.length === 0 ||
35
+ value.length > 512 ||
36
+ value !== value.trim() ||
37
+ /[\u0000-\u001f\u007f]/.test(value)) {
38
+ throw new TypeError(`${path} must be a bounded stable identity`);
39
+ }
40
+ return value;
41
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { GqlAuth, GqlError, GqlErrorLocation, GqlResult, GraphqlVariables } from './types.js';
2
+ export { parseDistributedGenerationEnvelope, type DistributedGenerationEnvelope } from './generation.js';
2
3
  export { DISTRIBUTED_PROTOCOL_VERSION, DistributedProtocolError, compareDistributedDecimal, distributedLiveResumeExtensions, parseDistributedProtocolEnvelope, parseGraphqlResponseExtensions, type DistributedDecimalString, type DistributedCommandConsistency, type DistributedCommandMetadata, type DistributedCommandState, type DistributedIndexRevision, type DistributedLiveCursor, type DistributedLiveMetadata, type DistributedLiveResumeExtensions, type DistributedOpaqueString, type DistributedProjectionExpectation, type DistributedProjectionObservation, type DistributedProtocolValue, type DistributedProtocolEnvelope, type DistributedProtocolErrorCode, type DistributedQuerySnapshot, type DistributedRecordRevision, type DistributedTrustedPreset, type DistributedTrustedPresetCodec, type GraphqlResponseExtensions } from './protocol.js';
3
4
  export { documentToString, type GqlDocument } from './document.js';
4
5
  export { applyWsDevHeaderParams, buildAuthHeaders, wsConnectionInitPayload } from './auth-headers.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { parseDistributedGenerationEnvelope } from './generation.js';
1
2
  export { DISTRIBUTED_PROTOCOL_VERSION, DistributedProtocolError, compareDistributedDecimal, distributedLiveResumeExtensions, parseDistributedProtocolEnvelope, parseGraphqlResponseExtensions } from './protocol.js';
2
3
  export { documentToString } from './document.js';
3
4
  export { applyWsDevHeaderParams, buildAuthHeaders, wsConnectionInitPayload } from './auth-headers.js';
@@ -7,6 +7,7 @@
7
7
  * them, but never parses them as numbers or reconstructs server scopes.
8
8
  */
9
9
  import { type CommandProjectionMetadata } from './replica/projection-delta/index.js';
10
+ import { type DistributedGenerationEnvelope } from './generation.js';
10
11
  /** The only Distributed GraphQL protocol version understood by this package. */
11
12
  export declare const DISTRIBUTED_PROTOCOL_VERSION: 1;
12
13
  declare const opaqueDistributedString: unique symbol;
@@ -125,6 +126,7 @@ export type DistributedProtocolEnvelope = Readonly<Record<string, unknown> & {
125
126
  schemaHash: string;
126
127
  authorizationGeneration: string;
127
128
  cacheScope: DistributedOpaqueString;
129
+ generation?: DistributedGenerationEnvelope;
128
130
  operation?: string;
129
131
  command?: DistributedCommandMetadata;
130
132
  snapshot?: DistributedQuerySnapshot;
package/dist/protocol.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * them, but never parses them as numbers or reconstructs server scopes.
8
8
  */
9
9
  import { parseCommandProjectionMetadata } from './replica/projection-delta/index.js';
10
+ import { parseDistributedGenerationEnvelope } from './generation.js';
10
11
  /** The only Distributed GraphQL protocol version understood by this package. */
11
12
  export const DISTRIBUTED_PROTOCOL_VERSION = 1;
12
13
  /** Safe parse failure that reports structure, never hidden server values. */
@@ -82,6 +83,9 @@ export function parseDistributedProtocolEnvelope(value) {
82
83
  const schemaHash = publicString(envelope.schemaHash, 'extensions.distributed.schemaHash');
83
84
  const authorizationGeneration = publicString(envelope.authorizationGeneration, 'extensions.distributed.authorizationGeneration');
84
85
  const cacheScope = opaqueString(envelope.cacheScope, 'extensions.distributed.cacheScope');
86
+ const generation = envelope.generation === undefined
87
+ ? undefined
88
+ : parseDistributedGenerationEnvelope(envelope.generation, 'extensions.distributed.generation');
85
89
  const operation = envelope.operation === undefined
86
90
  ? undefined
87
91
  : publicString(envelope.operation, 'extensions.distributed.operation');
@@ -102,6 +106,7 @@ export function parseDistributedProtocolEnvelope(value) {
102
106
  schemaHash,
103
107
  authorizationGeneration,
104
108
  cacheScope,
109
+ ...(generation === undefined ? {} : { generation }),
105
110
  ...(operation === undefined ? {} : { operation }),
106
111
  ...(command === undefined ? {} : { command }),
107
112
  ...(snapshot === undefined ? {} : { snapshot }),
@@ -372,6 +372,14 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
372
372
  if (disposed) {
373
373
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED');
374
374
  }
375
+ try {
376
+ options.lifecycle?.assertDispatchOpen();
377
+ }
378
+ catch (error) {
379
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_RELOADING', {
380
+ cause: error
381
+ });
382
+ }
375
383
  const snapshot = authoritySnapshot();
376
384
  const scope = snapshot.scope;
377
385
  if (scope === undefined ||
@@ -1088,6 +1096,9 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
1088
1096
  return Object.freeze({
1089
1097
  commands: commands,
1090
1098
  observeResult,
1099
+ pendingCommandIds() {
1100
+ return Object.freeze([...new Set([...pending.keys(), ...unmanagedLayers])].sort());
1101
+ },
1091
1102
  dispose() {
1092
1103
  if (disposed)
1093
1104
  return;
@@ -14,6 +14,8 @@ export function commandRuntimeErrorMessage(code) {
14
14
  return 'Command response violated the generated protocol contract';
15
15
  case 'REPLICA_COMMAND_REJECTED':
16
16
  return 'Command was rejected';
17
+ case 'REPLICA_COMMAND_RELOADING':
18
+ return 'Command dispatch is paused during a coherent application reload';
17
19
  case 'REPLICA_COMMAND_SCOPE_INVALIDATED':
18
20
  return 'Command authorization scope changed';
19
21
  case 'REPLICA_COMMAND_STATUS_UNAVAILABLE':
@@ -154,7 +154,7 @@ export type ReplicaCommandCallOptions<TOutput> = PrepareReplicaCommandOptions &
154
154
  transportRetries?: number;
155
155
  onSucceeded?: (receipt: ReplicaCommandReceipt<TOutput>) => void | Promise<void>;
156
156
  }>;
157
- export type ReplicaCommandRuntimeErrorCode = 'REPLICA_COMMAND_ABORTED' | 'REPLICA_COMMAND_AUTHORITY_UNAVAILABLE' | 'REPLICA_COMMAND_DISPOSED' | 'REPLICA_COMMAND_OUTCOME_PENDING' | 'REPLICA_COMMAND_PROJECTION_FAILED' | 'REPLICA_COMMAND_PROTOCOL_INVALID' | 'REPLICA_COMMAND_REJECTED' | 'REPLICA_COMMAND_SCOPE_INVALIDATED' | 'REPLICA_COMMAND_STATUS_UNAVAILABLE' | 'REPLICA_COMMAND_TRANSPORT_AMBIGUOUS';
157
+ export type ReplicaCommandRuntimeErrorCode = 'REPLICA_COMMAND_ABORTED' | 'REPLICA_COMMAND_AUTHORITY_UNAVAILABLE' | 'REPLICA_COMMAND_DISPOSED' | 'REPLICA_COMMAND_OUTCOME_PENDING' | 'REPLICA_COMMAND_PROJECTION_FAILED' | 'REPLICA_COMMAND_PROTOCOL_INVALID' | 'REPLICA_COMMAND_REJECTED' | 'REPLICA_COMMAND_RELOADING' | 'REPLICA_COMMAND_SCOPE_INVALIDATED' | 'REPLICA_COMMAND_STATUS_UNAVAILABLE' | 'REPLICA_COMMAND_TRANSPORT_AMBIGUOUS';
158
158
  export type AnyCommandArtifact = ReplicaCommandArtifact<unknown, unknown>;
159
159
  export type CommandEntry = AnyCommandArtifact | Readonly<{
160
160
  artifact: AnyCommandArtifact;
@@ -188,6 +188,10 @@ export type ReplicaCommandRuntimeOptions = Readonly<{
188
188
  * Generated clients ship their inventory; apps do not invent board sims ad hoc.
189
189
  */
190
190
  pureFunctions?: Readonly<Record<string, ReplicaPureFunction>>;
191
+ /** Framework lifecycle fence checked immediately before command preparation. */
192
+ lifecycle?: Readonly<{
193
+ assertDispatchOpen(): void;
194
+ }>;
191
195
  }>;
192
196
  export interface ReplicaCommandRuntime<TEntries extends Readonly<Record<string, CommandEntry>>> {
193
197
  readonly commands: ReplicaBoundCommands<TEntries>;
@@ -198,6 +202,8 @@ export interface ReplicaCommandRuntime<TEntries extends Readonly<Record<string,
198
202
  * it does not normalize or confirm cache data itself.
199
203
  */
200
204
  observeResult(envelope: ReplicaResultEnvelope<unknown>): void;
205
+ /** Opaque receipt identities safe to carry across a controlled reload. */
206
+ pendingCommandIds(): readonly string[];
201
207
  dispose(): void;
202
208
  }
203
209
  export type CapturedAuthority = Readonly<{
@@ -1,4 +1,6 @@
1
1
  export { authFromPageData, type PageGraphqlData } from './auth.js';
2
+ export { distributedReloadLifecycle, registerDistributedReloadClient, validateDistributedReloadLocation, validateDistributedReloadState, type DistributedReloadLifecycle, type DistributedReloadOptions, type DistributedReloadStateDeclaration } from './lifecycle.js';
3
+ export { parseDistributedGenerationEnvelope, type DistributedGenerationEnvelope } from '../generation.js';
2
4
  export { defineDistributedSvelteKitOperation, provideDistributedSvelteKitClient, useDistributedSvelteKitClient, useDistributedSvelteKitCommands } from './context.js';
3
5
  export { bindSveltekitOperation, createPageDataSessionSource, createDistributedSvelteKit, sessionSourceFromPageData, type CreateDistributedSvelteKitOptions, type DistributedSvelteKitClient, type SveltekitBoundOperation, type SveltekitCommandRuntimeFactory, type SveltekitCommandRuntimeFactoryOptions, type SveltekitCommandRuntimeLike, type SveltekitDistributedPageData, type SveltekitPageDataSessionSource, type SveltekitPageDataSource, type SveltekitQuerySnapshot, type SveltekitQueryStore, type SveltekitReplicaAuthority, type SveltekitReplicaHydration, type SveltekitSessionSource, type UseSveltekitOperationOptions } from './replica.js';
4
6
  export { createDistributedSvelteKitServer, matchDistributedRoute, registerDistributedRoute, type CreateDistributedSvelteKitServerOptions, type DistributedRouteOperation, type DistributedRoutePlan, type DistributedRouteVariables, type DistributedSvelteKitServer, type SveltekitServerLoadEventLike } from './server-replica.js';
@@ -1,4 +1,6 @@
1
1
  export { authFromPageData } from './auth.js';
2
+ export { distributedReloadLifecycle, registerDistributedReloadClient, validateDistributedReloadLocation, validateDistributedReloadState } from './lifecycle.js';
3
+ export { parseDistributedGenerationEnvelope } from '../generation.js';
2
4
  export { defineDistributedSvelteKitOperation, provideDistributedSvelteKitClient, useDistributedSvelteKitClient, useDistributedSvelteKitCommands } from './context.js';
3
5
  export { bindSveltekitOperation, createPageDataSessionSource, createDistributedSvelteKit, sessionSourceFromPageData } from './replica.js';
4
6
  export { createDistributedSvelteKitServer, matchDistributedRoute, registerDistributedRoute } from './server-replica.js';
@@ -0,0 +1,57 @@
1
+ import type { DistributedReplica, ReplicaDehydratedState } from '../replica/index.js';
2
+ export type DistributedReloadStateDeclaration = Readonly<{
3
+ /** Stable application-owned partition name. */
4
+ key: string;
5
+ /** Changes when the serialized representation becomes incompatible. */
6
+ fingerprint: string;
7
+ capture(): unknown;
8
+ restore(value: unknown): void | Promise<void>;
9
+ }>;
10
+ export type DistributedReloadOptions = Readonly<{
11
+ /** Compiler-owned surface key; generated clients provide this automatically. */
12
+ key: string;
13
+ /** Explicitly declared serializable application state. Nothing else is captured. */
14
+ state?: readonly DistributedReloadStateDeclaration[];
15
+ /** Recover ambiguous receipts by ID; commands are never replayed by the framework. */
16
+ recoverPendingCommands?: (commandIds: readonly string[]) => void | Promise<void>;
17
+ }>;
18
+ type ReloadParticipant = Readonly<{
19
+ key: string;
20
+ prepare(): Readonly<{
21
+ replica?: ReplicaDehydratedState;
22
+ pendingCommandIds: readonly string[];
23
+ state: readonly Readonly<{
24
+ key: string;
25
+ fingerprint: string;
26
+ value: unknown;
27
+ }>[];
28
+ }>;
29
+ /** Return false when restoration is valid but must be retried later. */
30
+ restore(value: ReloadParticipantCapsule, compatible: boolean): boolean | Promise<boolean>;
31
+ }>;
32
+ type ReloadParticipantCapsule = Readonly<{
33
+ key: string;
34
+ replica?: ReplicaDehydratedState;
35
+ pendingCommandIds: readonly string[];
36
+ state: readonly Readonly<{
37
+ key: string;
38
+ fingerprint: string;
39
+ value: unknown;
40
+ }>[];
41
+ }>;
42
+ export interface DistributedReloadLifecycle {
43
+ assertDispatchOpen(): void;
44
+ register(participant: ReloadParticipant): () => void;
45
+ destroy(): void;
46
+ }
47
+ /** Validate one explicitly declared application-state partition before capture. */
48
+ export declare function validateDistributedReloadState(value: unknown, path?: string): unknown;
49
+ /** Preserve browser location only when it cannot copy an auth callback secret. */
50
+ export declare function validateDistributedReloadLocation(location: URL): string;
51
+ /** Register one generated client with the shared browser reload transaction. */
52
+ export declare function registerDistributedReloadClient(replica: DistributedReplica, runtime: Readonly<{
53
+ pendingCommandIds?(): readonly string[];
54
+ }> | undefined, options: DistributedReloadOptions): () => void;
55
+ /** Browser singleton used by every generated SvelteKit surface in one page. */
56
+ export declare function distributedReloadLifecycle(): DistributedReloadLifecycle;
57
+ export {};