@zlink-systems/framework 0.30.0 → 0.30.1
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/LICENSE +150 -68
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Actors/ZLinkActorClient.d.ts +2 -1
- package/dist/contracts/Channels/Calls.d.ts +2 -1
- package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
- package/dist/contracts/Handlers/Attributes.d.ts +5 -13
- package/dist/contracts/Handlers/Attributes.js +7 -11
- package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
- package/dist/contracts/Handlers/JsonContract.js +4 -153
- package/dist/contracts/Spots/Contracts.d.ts +2 -1
- package/dist/nest-integration.d.ts +1 -1
- package/dist/nest-integration.js +2 -1
- package/dist/runtime/actors/actor-client.d.ts +1 -1
- package/dist/runtime/actors/actor-client.js +14 -12
- package/dist/runtime/actors/actor-handoff.d.ts +1 -1
- package/dist/runtime/actors/actor-handoff.js +7 -4
- package/dist/runtime/actors/actor-join-relocation.d.ts +1 -0
- package/dist/runtime/actors/actor-local-native-join.js +9 -12
- package/dist/runtime/actors/actor-remote-wire.js +15 -0
- package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
- package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
- package/dist/runtime/actors/index.d.ts +2 -1
- package/dist/runtime/actors/index.js +18 -6
- package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
- package/dist/runtime/admission.d.ts +1 -1
- package/dist/runtime/admission.js +15 -5
- package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +6 -8
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +39 -22
- package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +2 -1
- package/dist/runtime/application-jobs/application-job-queue-scope.js +37 -22
- package/dist/runtime/application-jobs/contracts.d.ts +10 -1
- package/dist/runtime/backend/contracts/index.d.ts +18 -9
- package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
- package/dist/runtime/backend/mesh-completion-table.js +149 -49
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
- package/dist/runtime/backend/mesh-dispatch-pump.js +32 -11
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
- package/dist/runtime/backend/node/node-backend-adapter-support.js +16 -9
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
- package/dist/runtime/backend/node/node-raw-binding-port.js +42 -22
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +13 -5
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +69 -51
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -6
- package/dist/runtime/backend/raw-binding-port.d.ts +2 -2
- package/dist/runtime/backend/runtime-message.d.ts +1 -0
- package/dist/runtime/backend/runtime-message.js +5 -0
- package/dist/runtime/backend/runtime-values.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.js +2 -1
- package/dist/runtime/channels/channel-clients.d.ts +1 -0
- package/dist/runtime/channels/channel-clients.js +58 -21
- package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +3 -2
- package/dist/runtime/channels/channel-dispatch-pipeline.js +20 -11
- package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
- package/dist/runtime/channels/channel-dispatch-services.js +45 -0
- package/dist/runtime/channels/channel-dispatchers.d.ts +1 -1
- package/dist/runtime/channels/channel-dispatchers.js +27 -26
- package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
- package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
- package/dist/runtime/channels/channel-envelope.d.ts +1 -0
- package/dist/runtime/channels/channel-envelope.js +83 -8
- package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
- package/dist/runtime/channels/channel-outbound-operations.js +8 -8
- package/dist/runtime/channels/channel-runtime-manager.d.ts +6 -6
- package/dist/runtime/channels/channel-runtime-manager.js +12 -12
- package/dist/runtime/channels/channel-transports.d.ts +18 -15
- package/dist/runtime/channels/channel-transports.js +48 -31
- package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
- package/dist/runtime/channels/local-spot-route-transport.js +2 -1
- package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
- package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
- package/dist/runtime/channels/source-spot-router.d.ts +2 -2
- package/dist/runtime/channels/source-spot-router.js +4 -4
- package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
- package/dist/runtime/channels/spot-node-route-transport.js +4 -4
- package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
- package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
- package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
- package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
- package/dist/runtime/execution/serial-execution-queue.d.ts +1 -0
- package/dist/runtime/execution/serial-execution-queue.js +1 -0
- package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
- package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
- package/dist/runtime/foundation/durable-service-request.js +98 -0
- package/dist/runtime/foundation/operation-identity.d.ts +1 -0
- package/dist/runtime/foundation/operation-identity.js +5 -1
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +22 -5
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +356 -232
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +1 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +1 -0
- package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
- package/dist/runtime/foundation/service-relocation-runtime.js +13 -1
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -0
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +4 -4
- package/dist/runtime/foundation/service-stateful-runtime.js +44 -103
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -5
- package/dist/runtime/foundation/service-topology-registry.d.ts +12 -2
- package/dist/runtime/foundation/service-topology-registry.js +61 -27
- package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +3 -3
- package/dist/runtime/foundation/service-wire-m6a-codec.js +36 -38
- package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
- package/dist/runtime/handlers/handler-instance-scope.js +54 -11
- package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
- package/dist/runtime/host/application-job-queue.d.ts +19 -7
- package/dist/runtime/host/application-job-queue.js +150 -42
- package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
- package/dist/runtime/host/entry-actor-runtime.js +3 -0
- package/dist/runtime/host/index.d.ts +7 -1
- package/dist/runtime/host/index.js +142 -118
- package/dist/runtime/host/remote-bound-session-relay.js +2 -18
- package/dist/runtime/host/route-mesh-runtime.js +1 -1
- package/dist/runtime/host/service-relocation-control.d.ts +1 -0
- package/dist/runtime/host/service-relocation-control.js +10 -4
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +6 -8
- package/dist/runtime/host/service-relocation-host-runtime.js +213 -241
- package/dist/runtime/host/spot-address-transport.js +3 -2
- package/dist/runtime/host/spot-runtime-options-factory.js +1 -0
- package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
- package/dist/runtime/locations/actor-location-claims.js +34 -19
- package/dist/runtime/locations/lifecycle.d.ts +1 -2
- package/dist/runtime/locations/lifecycle.js +2 -5
- package/dist/runtime/locations/runtime.d.ts +1 -1
- package/dist/runtime/locations/runtime.js +24 -23
- package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
- package/dist/runtime/messaging/framework-json-v1.js +38 -294
- package/dist/runtime/messaging/packet-name.d.ts +1 -1
- package/dist/runtime/messaging/packet-name.js +8 -24
- package/dist/runtime/messaging/request-result.d.ts +2 -0
- package/dist/runtime/messaging/request-result.js +54 -0
- package/dist/runtime/messaging/submission-result.d.ts +1 -0
- package/dist/runtime/messaging/submission-result.js +5 -2
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +15 -0
- package/dist/runtime/protocol/service_wire_codec.generated.js +144 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +9 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +14 -1
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
- package/dist/runtime/spots/index.d.ts +3 -0
- package/dist/runtime/spots/index.js +9 -2
- package/dist/runtime/spots/spot-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-activation.js +17 -8
- package/dist/runtime/spots/spot-entry-activation.js +6 -2
- package/dist/runtime/spots/spot-handler-registry.d.ts +3 -1
- package/dist/runtime/spots/spot-handler-registry.js +18 -8
- package/dist/runtime/spots/spot-node-autoconnect.js +4 -3
- package/dist/runtime/spots/spot-outbound.d.ts +3 -0
- package/dist/runtime/spots/spot-outbound.js +24 -15
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
- package/dist/runtime/spots/spot-runtime-ports.d.ts +1 -0
- package/dist/runtime/spots/spot-subscription-dispatch.js +0 -3
- package/dist/runtime/spots/spot-timer.js +1 -0
- package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
- package/dist/runtime/streams/stream-session-runtime.js +13 -27
- package/package.json +2 -2
- package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
- package/src/contracts/Channels/Calls.ts +2 -1
- package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
- package/src/contracts/Handlers/Attributes.ts +14 -21
- package/src/contracts/Handlers/JsonContract.ts +8 -216
- package/src/contracts/Spots/Contracts.ts +2 -1
- package/src/nest-integration.ts +1 -0
- package/src/runtime/actors/actor-client.ts +40 -16
- package/src/runtime/actors/actor-handoff.ts +9 -4
- package/src/runtime/actors/actor-join-relocation.ts +1 -0
- package/src/runtime/actors/actor-local-native-join.ts +17 -26
- package/src/runtime/actors/actor-remote-wire.ts +17 -0
- package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
- package/src/runtime/actors/actor-routed-json-request.ts +10 -2
- package/src/runtime/actors/index.ts +19 -5
- package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
- package/src/runtime/admission.ts +18 -5
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
- package/src/runtime/application-jobs/application-job-queue-scope.ts +51 -22
- package/src/runtime/application-jobs/contracts.ts +14 -1
- package/src/runtime/backend/contracts/index.ts +53 -14
- package/src/runtime/backend/mesh-completion-table.ts +156 -57
- package/src/runtime/backend/mesh-dispatch-pump.ts +40 -10
- package/src/runtime/backend/node/node-backend-adapter-support.ts +17 -8
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
- package/src/runtime/backend/node/node-raw-binding-port.ts +57 -30
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +128 -65
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +21 -6
- package/src/runtime/backend/raw-binding-port.ts +7 -2
- package/src/runtime/backend/runtime-message.ts +6 -0
- package/src/runtime/backend/runtime-values.ts +1 -0
- package/src/runtime/channels/channel-clients.ts +90 -41
- package/src/runtime/channels/channel-dispatch-pipeline.ts +69 -34
- package/src/runtime/channels/channel-dispatch-services.ts +69 -1
- package/src/runtime/channels/channel-dispatchers.ts +29 -27
- package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
- package/src/runtime/channels/channel-envelope.ts +133 -8
- package/src/runtime/channels/channel-outbound-operations.ts +22 -8
- package/src/runtime/channels/channel-runtime-manager.ts +29 -12
- package/src/runtime/channels/channel-transports.ts +128 -60
- package/src/runtime/channels/local-spot-route-transport.ts +3 -1
- package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
- package/src/runtime/channels/source-spot-router.ts +8 -4
- package/src/runtime/channels/spot-node-route-transport.ts +8 -4
- package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
- package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
- package/src/runtime/execution/serial-execution-queue.ts +2 -0
- package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
- package/src/runtime/foundation/durable-service-request.ts +137 -0
- package/src/runtime/foundation/operation-identity.ts +5 -1
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +484 -293
- package/src/runtime/foundation/service-relocation-object-owner.ts +2 -0
- package/src/runtime/foundation/service-relocation-runtime.ts +18 -1
- package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
- package/src/runtime/foundation/service-stateful-runtime.ts +67 -125
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -5
- package/src/runtime/foundation/service-topology-registry.ts +91 -36
- package/src/runtime/foundation/service-wire-m6a-codec.ts +57 -38
- package/src/runtime/handlers/handler-instance-scope.ts +67 -16
- package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
- package/src/runtime/host/application-job-queue.ts +175 -41
- package/src/runtime/host/entry-actor-runtime.ts +4 -0
- package/src/runtime/host/index.ts +201 -156
- package/src/runtime/host/remote-bound-session-relay.ts +2 -23
- package/src/runtime/host/route-mesh-runtime.ts +1 -1
- package/src/runtime/host/service-relocation-control.ts +11 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +315 -291
- package/src/runtime/host/spot-address-transport.ts +4 -2
- package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
- package/src/runtime/locations/actor-location-claims.ts +52 -30
- package/src/runtime/locations/lifecycle.ts +8 -26
- package/src/runtime/locations/runtime.ts +28 -24
- package/src/runtime/messaging/framework-json-v1.ts +5 -325
- package/src/runtime/messaging/packet-name.ts +5 -25
- package/src/runtime/messaging/request-result.ts +58 -0
- package/src/runtime/messaging/submission-result.ts +5 -1
- package/src/runtime/protocol/service_wire_codec.generated.ts +131 -1
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +43 -23
- package/src/runtime/spots/index.ts +14 -2
- package/src/runtime/spots/spot-activation.ts +28 -10
- package/src/runtime/spots/spot-entry-activation.ts +9 -2
- package/src/runtime/spots/spot-handler-registry.ts +41 -8
- package/src/runtime/spots/spot-node-autoconnect.ts +6 -2
- package/src/runtime/spots/spot-outbound.ts +36 -16
- package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
- package/src/runtime/spots/spot-runtime-ports.ts +1 -0
- package/src/runtime/spots/spot-subscription-dispatch.ts +0 -3
- package/src/runtime/spots/spot-timer.ts +5 -1
- package/src/runtime/streams/stream-session-runtime.ts +12 -29
|
@@ -336,7 +336,8 @@ export class ZLinkHostSpotAddressTransport implements ZLinkSpotAddressTransport
|
|
|
336
336
|
instanceSpot: call.instanceSpot,
|
|
337
337
|
timeoutMs: deadline.requireRemaining(),
|
|
338
338
|
signal: deadline.signal,
|
|
339
|
-
metadata: call.metadata
|
|
339
|
+
metadata: call.metadata,
|
|
340
|
+
onAdmitted: call.onAdmitted
|
|
340
341
|
}),
|
|
341
342
|
deadline.signal
|
|
342
343
|
);
|
|
@@ -416,7 +417,8 @@ export class ZLinkHostSpotAddressTransport implements ZLinkSpotAddressTransport
|
|
|
416
417
|
encoded,
|
|
417
418
|
deadlineUnixMs,
|
|
418
419
|
call.sourceSpot === undefined ? undefined : String(call.sourceSpot.routingId),
|
|
419
|
-
call.metadata
|
|
420
|
+
call.metadata,
|
|
421
|
+
call.onAdmitted
|
|
420
422
|
),
|
|
421
423
|
deadline.signal
|
|
422
424
|
);
|
|
@@ -333,6 +333,8 @@ export class ZLinkSpotRuntimeOptionsFactory {
|
|
|
333
333
|
);
|
|
334
334
|
},
|
|
335
335
|
actorTransferRuntime,
|
|
336
|
+
releaseActorMembers: (meshName, spotId) =>
|
|
337
|
+
this.options.actorManager()?.releaseSpotActorMembers(meshName, spotId) ?? Promise.resolve(),
|
|
336
338
|
boundSessionRuntime: this.options.boundSessionRelay.boundSessions,
|
|
337
339
|
actorHandoffRuntime: this.options.actorHandoff,
|
|
338
340
|
metrics: this.options.metrics,
|
|
@@ -12,6 +12,10 @@ import { ZLinkSpotKind } from '../../contracts/Spots';
|
|
|
12
12
|
import { ZLinkLocationKeyCodec } from './key-codec';
|
|
13
13
|
import { encodeAuthorityKey } from './authority-key-codec';
|
|
14
14
|
import { routingIdsEqual } from '../routing-id';
|
|
15
|
+
import {
|
|
16
|
+
decodeActorAuthorityIdentity,
|
|
17
|
+
rewriteActorAuthorityRoute
|
|
18
|
+
} from '../actors/actor-authority-publication';
|
|
15
19
|
import type { IZLinkLocationLifecycleRuntime, ZLinkOwnershipLostEvent } from './lifecycle-runtime';
|
|
16
20
|
|
|
17
21
|
export enum ZLinkActorClaimStatus {
|
|
@@ -203,41 +207,59 @@ export class ZLinkActorLocationClaims {
|
|
|
203
207
|
return { status: ZLinkActorClaimStatus.Claimed, claimed, generation: result.generation };
|
|
204
208
|
}
|
|
205
209
|
|
|
206
|
-
async
|
|
210
|
+
async commitMembership(
|
|
207
211
|
actorType: string,
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
actorRef: ActorRef,
|
|
213
|
+
authorityOwnerGeneration: bigint,
|
|
210
214
|
spotId: RoutingId,
|
|
211
215
|
spotGeneration: bigint,
|
|
212
|
-
|
|
216
|
+
spotKind: ZLinkSpotKind.Entry | ZLinkSpotKind.User,
|
|
213
217
|
ownerNodeGeneration: bigint
|
|
214
218
|
): Promise<void> {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
219
|
+
const actorId = actorRef.actorId;
|
|
220
|
+
const store = this.authorityStore;
|
|
221
|
+
const owner = this.runtime.currentOwnerToken;
|
|
222
|
+
if (store === undefined || owner === undefined) {
|
|
223
|
+
throw new Error('Actor membership commit requires Location Store authority and owner.');
|
|
224
|
+
}
|
|
225
|
+
const key = encodeAuthorityKey('actor', actorId);
|
|
226
|
+
const current = await store.readAuthority(key);
|
|
227
|
+
if (
|
|
228
|
+
!matchesActorAuthority(current, actorRef, owner) ||
|
|
229
|
+
current.allocation.stableType !== actorType ||
|
|
230
|
+
current.authorityOwnerGeneration !== authorityOwnerGeneration ||
|
|
231
|
+
current.allocation.descriptorLifecycleGeneration !== ownerNodeGeneration
|
|
232
|
+
) {
|
|
233
|
+
throw new Error(`Actor '${actorId}' membership authority fence does not match.`);
|
|
234
|
+
}
|
|
235
|
+
const identity = decodeActorAuthorityIdentity(current.payload, current.objectGeneration);
|
|
236
|
+
if (
|
|
237
|
+
identity === undefined ||
|
|
238
|
+
identity.actorType !== actorType ||
|
|
239
|
+
identity.ownerNodeGeneration !== ownerNodeGeneration ||
|
|
240
|
+
identity.owner.ownerId !== owner.ownerId ||
|
|
241
|
+
identity.owner.leaseGeneration !== owner.leaseGeneration ||
|
|
242
|
+
!routingIdsEqual(identity.actor.nodeRid, actorRef.nodeRid) ||
|
|
243
|
+
identity.meshName !== actorRef.meshName
|
|
244
|
+
) {
|
|
245
|
+
throw new Error(`Actor '${actorId}' membership authority identity does not match.`);
|
|
246
|
+
}
|
|
247
|
+
const result = await store.compareExchangeAuthority(key, current.storeVersion, {
|
|
248
|
+
kind: 'put',
|
|
249
|
+
generationTransition: 'preserve',
|
|
250
|
+
payload: rewriteActorAuthorityRoute(
|
|
251
|
+
current.payload,
|
|
252
|
+
actorRef,
|
|
253
|
+
String(spotId),
|
|
254
|
+
spotGeneration,
|
|
255
|
+
spotKind
|
|
256
|
+
)
|
|
257
|
+
});
|
|
258
|
+
if (result.kind !== 'stored') {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Actor '${actorId}' membership commit was rejected with status ${result.kind}.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
241
263
|
}
|
|
242
264
|
|
|
243
265
|
async release(_actorType: string, actorId: string, actorRef?: ActorRef): Promise<void> {
|
|
@@ -118,40 +118,22 @@ export class ZLinkLocationLifecycle {
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
async
|
|
121
|
+
async commitActorMembership(
|
|
122
122
|
actorType: string,
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
actorRef: ActorRef,
|
|
124
|
+
authorityOwnerGeneration: bigint,
|
|
125
125
|
spotId: RoutingId,
|
|
126
126
|
spotGeneration: bigint,
|
|
127
|
-
|
|
127
|
+
spotKind: ZLinkSpotKind.Entry | ZLinkSpotKind.User,
|
|
128
128
|
ownerNodeGeneration: bigint
|
|
129
129
|
): Promise<void> {
|
|
130
|
-
await this.actorClaims.
|
|
130
|
+
await this.actorClaims.commitMembership(
|
|
131
131
|
actorType,
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
actorRef,
|
|
133
|
+
authorityOwnerGeneration,
|
|
134
134
|
spotId,
|
|
135
135
|
spotGeneration,
|
|
136
|
-
|
|
137
|
-
ownerNodeGeneration
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async notifyActorLeftSpot(
|
|
142
|
-
actorType: string,
|
|
143
|
-
actorId: string,
|
|
144
|
-
entrySpotId: RoutingId,
|
|
145
|
-
entrySpotGeneration: bigint,
|
|
146
|
-
membershipEpoch: bigint,
|
|
147
|
-
ownerNodeGeneration: bigint
|
|
148
|
-
): Promise<void> {
|
|
149
|
-
await this.actorClaims.notifyLeftSpot(
|
|
150
|
-
actorType,
|
|
151
|
-
actorId,
|
|
152
|
-
entrySpotId,
|
|
153
|
-
entrySpotGeneration,
|
|
154
|
-
membershipEpoch,
|
|
136
|
+
spotKind,
|
|
155
137
|
ownerNodeGeneration
|
|
156
138
|
);
|
|
157
139
|
}
|
|
@@ -162,7 +162,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
162
162
|
readonly name: string;
|
|
163
163
|
}[];
|
|
164
164
|
private readonly rewriteAuthorityPayloadForOwner?: ZLinkLocationRuntimeOptions['rewriteAuthorityPayloadForOwner'];
|
|
165
|
-
private
|
|
165
|
+
private leaseOperationStartedAtMs = 0;
|
|
166
166
|
private ownerToken?: ZLinkLocationOwnerToken;
|
|
167
167
|
private lastOwnerToken?: ZLinkLocationOwnerToken;
|
|
168
168
|
private ownerLeaseDeadlineMs?: number;
|
|
@@ -342,12 +342,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
342
342
|
const claim = this.renewOwnerLeaseOnce(signal, shutdownSignal);
|
|
343
343
|
this.heartbeatRenewal = claim;
|
|
344
344
|
try {
|
|
345
|
-
|
|
346
|
-
if (claimed) {
|
|
347
|
-
this.scheduleHeartbeat();
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
this.nextLeaseRenewAtMs = this.monotonicNowMs() + this.options.ownerLeaseRenewIntervalMs;
|
|
345
|
+
await claim;
|
|
351
346
|
this.scheduleHeartbeat();
|
|
352
347
|
} catch (error) {
|
|
353
348
|
this.started = false;
|
|
@@ -362,7 +357,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
362
357
|
this.clearTimer(this.heartbeatTimer);
|
|
363
358
|
this.heartbeatTimer = undefined;
|
|
364
359
|
}
|
|
365
|
-
this.
|
|
360
|
+
this.leaseOperationStartedAtMs = 0;
|
|
366
361
|
this.ownerLeaseDeadlineMs = undefined;
|
|
367
362
|
await this.cleanupOwner(signal);
|
|
368
363
|
}
|
|
@@ -422,6 +417,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
422
417
|
const wasOwnerLeaseUsable = this.ownerLeaseUsable;
|
|
423
418
|
try {
|
|
424
419
|
const renewStartedAtMs = this.monotonicNowMs();
|
|
420
|
+
this.leaseOperationStartedAtMs = renewStartedAtMs;
|
|
425
421
|
const result = await withTimeout(
|
|
426
422
|
(renewSignal) =>
|
|
427
423
|
this.stores.ownerLeaseStore.renewOwnerLease(
|
|
@@ -475,10 +471,15 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
475
471
|
shutdownSignal?: () => AbortSignal
|
|
476
472
|
): Promise<boolean> {
|
|
477
473
|
const claimStartedAtMs = this.monotonicNowMs();
|
|
478
|
-
|
|
474
|
+
this.leaseOperationStartedAtMs = claimStartedAtMs;
|
|
475
|
+
const timeoutStartedAtMs = performance.now();
|
|
479
476
|
let claim: Awaited<ReturnType<ZLinkOwnerLeaseStore['claimOwnerLease']>>;
|
|
480
477
|
try {
|
|
481
|
-
claim = await this.claimOwnerLeaseWithConfirmation(
|
|
478
|
+
claim = await this.claimOwnerLeaseWithConfirmation(
|
|
479
|
+
timeoutStartedAtMs,
|
|
480
|
+
signal,
|
|
481
|
+
shutdownSignal
|
|
482
|
+
);
|
|
482
483
|
} catch (error) {
|
|
483
484
|
if (signal?.aborted === true) throw error;
|
|
484
485
|
if (error instanceof ZLinkOwnerLeaseClaimError) throw error;
|
|
@@ -508,7 +509,6 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
508
509
|
claim.storeNow
|
|
509
510
|
);
|
|
510
511
|
this.lastError = undefined;
|
|
511
|
-
this.nextLeaseRenewAtMs = this.monotonicNowMs() + this.options.ownerLeaseRenewIntervalMs;
|
|
512
512
|
for (const handler of this.ownerLeaseRenewedHandlers) {
|
|
513
513
|
handler({
|
|
514
514
|
kind: 'renewed',
|
|
@@ -525,7 +525,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
private async claimOwnerLeaseWithConfirmation(
|
|
528
|
-
|
|
528
|
+
timeoutStartedAtMs: number,
|
|
529
529
|
signal?: AbortSignal,
|
|
530
530
|
shutdownSignal?: () => AbortSignal
|
|
531
531
|
): Promise<Awaited<ReturnType<ZLinkOwnerLeaseStore['claimOwnerLease']>>> {
|
|
@@ -540,7 +540,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
540
540
|
this.options.ownerLeaseTtlMs,
|
|
541
541
|
claimSignal
|
|
542
542
|
)),
|
|
543
|
-
remainingTimeoutMs(
|
|
543
|
+
remainingTimeoutMs(timeoutStartedAtMs, this.options.ownerLeaseRenewTimeoutMs),
|
|
544
544
|
signal
|
|
545
545
|
);
|
|
546
546
|
} catch (error) {
|
|
@@ -550,7 +550,9 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
550
550
|
try {
|
|
551
551
|
await withTimeout(
|
|
552
552
|
() => request!,
|
|
553
|
-
cleanupSignal === undefined
|
|
553
|
+
cleanupSignal === undefined
|
|
554
|
+
? remainingTimeoutMs(timeoutStartedAtMs, this.options.ownerLeaseRenewTimeoutMs)
|
|
555
|
+
: undefined,
|
|
554
556
|
cleanupSignal
|
|
555
557
|
);
|
|
556
558
|
} catch (completionError) {
|
|
@@ -567,7 +569,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
567
569
|
}
|
|
568
570
|
const confirmed = await this.confirmOwnerLease(
|
|
569
571
|
error,
|
|
570
|
-
|
|
572
|
+
timeoutStartedAtMs,
|
|
571
573
|
signal,
|
|
572
574
|
shutdownSignal
|
|
573
575
|
);
|
|
@@ -577,13 +579,13 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
577
579
|
if (claim.kind !== 'conflict') return claim;
|
|
578
580
|
const conflict = new ZLinkOwnerLeaseClaimError(claim.kind);
|
|
579
581
|
return (
|
|
580
|
-
(await this.confirmOwnerLease(conflict,
|
|
582
|
+
(await this.confirmOwnerLease(conflict, timeoutStartedAtMs, signal, shutdownSignal)) ?? claim
|
|
581
583
|
);
|
|
582
584
|
}
|
|
583
585
|
|
|
584
586
|
private async confirmOwnerLease(
|
|
585
587
|
originalError: unknown,
|
|
586
|
-
|
|
588
|
+
timeoutStartedAtMs: number,
|
|
587
589
|
signal?: AbortSignal,
|
|
588
590
|
shutdownSignal?: () => AbortSignal
|
|
589
591
|
): Promise<
|
|
@@ -593,7 +595,9 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
593
595
|
const cancelled = signal?.aborted === true;
|
|
594
596
|
const cleanupSignal = cancelled ? shutdownSignal?.() : undefined;
|
|
595
597
|
const confirmationTimeoutMs =
|
|
596
|
-
cleanupSignal === undefined
|
|
598
|
+
cleanupSignal === undefined
|
|
599
|
+
? remainingTimeoutMs(timeoutStartedAtMs, this.options.ownerLeaseRenewTimeoutMs)
|
|
600
|
+
: undefined;
|
|
597
601
|
if (confirmationTimeoutMs !== undefined && confirmationTimeoutMs <= 0) throw originalError;
|
|
598
602
|
let confirmed: Awaited<ReturnType<ZLinkOwnerLeaseStore['readOwnerLease']>>;
|
|
599
603
|
try {
|
|
@@ -615,7 +619,9 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
615
619
|
await withTimeout(
|
|
616
620
|
(releaseSignal) =>
|
|
617
621
|
this.stores.ownerLeaseStore.releaseOwnerLease(confirmed.token, releaseSignal),
|
|
618
|
-
cleanupSignal === undefined
|
|
622
|
+
cleanupSignal === undefined
|
|
623
|
+
? remainingTimeoutMs(timeoutStartedAtMs, this.options.ownerLeaseRenewTimeoutMs)
|
|
624
|
+
: undefined,
|
|
619
625
|
cleanupSignal
|
|
620
626
|
);
|
|
621
627
|
} catch (releaseError) {
|
|
@@ -1250,8 +1256,7 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
1250
1256
|
if (!this.started) {
|
|
1251
1257
|
return;
|
|
1252
1258
|
}
|
|
1253
|
-
const scheduledAt =
|
|
1254
|
-
this.nextLeaseRenewAtMs ?? this.monotonicNowMs() + this.options.ownerLeaseRenewIntervalMs;
|
|
1259
|
+
const scheduledAt = this.leaseOperationStartedAtMs + this.options.ownerLeaseRenewIntervalMs;
|
|
1255
1260
|
const delayMs = Math.max(0, scheduledAt - this.monotonicNowMs());
|
|
1256
1261
|
this.heartbeatTimer = this.setTimer(() => {
|
|
1257
1262
|
this.heartbeatTimer = undefined;
|
|
@@ -1259,7 +1264,6 @@ export class ZLinkLocationRuntime implements ZLinkLocationRuntimeQuery {
|
|
|
1259
1264
|
for (const scope of this.leaseScopes) {
|
|
1260
1265
|
this.metrics?.recordOwnerLeaseRenewLateness(lateness, scope.kind, scope.name);
|
|
1261
1266
|
}
|
|
1262
|
-
this.nextLeaseRenewAtMs = this.monotonicNowMs() + this.options.ownerLeaseRenewIntervalMs;
|
|
1263
1267
|
if (
|
|
1264
1268
|
this.ownerLeaseDeadlineMs !== undefined &&
|
|
1265
1269
|
this.monotonicNowMs() >= this.ownerLeaseDeadlineMs
|
|
@@ -1413,8 +1417,8 @@ class OwnerLeaseRenewTimeoutError extends Error {
|
|
|
1413
1417
|
}
|
|
1414
1418
|
}
|
|
1415
1419
|
|
|
1416
|
-
function remainingTimeoutMs(
|
|
1417
|
-
return Math.max(0,
|
|
1420
|
+
function remainingTimeoutMs(timeoutStartedAtMs: number, timeoutMs: number): number {
|
|
1421
|
+
return Math.max(0, timeoutMs - (performance.now() - timeoutStartedAtMs));
|
|
1418
1422
|
}
|
|
1419
1423
|
|
|
1420
1424
|
function errorMessage(error: unknown): string {
|
|
@@ -1,326 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as profile from '@zlink-systems/stream-wire/internal/json';
|
|
2
2
|
|
|
3
|
-
export const FRAMEWORK_JSON_V1_PROFILE =
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface FrameworkJsonParseOptions {
|
|
9
|
-
readonly rejectPropertyName?: (name: string) => boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function stringifyFrameworkJsonV1(value: unknown, schema?: ZLinkJsonSchema): string {
|
|
13
|
-
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
|
|
14
|
-
throw new TypeError('framework-json-v1 payload is not a JSON value.');
|
|
15
|
-
}
|
|
16
|
-
const encoded = JSON.stringify(value, frameworkJsonV1Replacer);
|
|
17
|
-
if (schema !== undefined) {
|
|
18
|
-
validateFrameworkJsonV1Value(JSON.parse(encoded), schema, '$', false);
|
|
19
|
-
}
|
|
20
|
-
return encoded;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Module-level so JSON.stringify does not allocate a fresh closure per
|
|
24
|
-
// encoded message. On the root call `this` is the {'': value} wrapper, so
|
|
25
|
-
// this[key] resolves the raw (pre-toJSON) source for every key including ''.
|
|
26
|
-
function frameworkJsonV1Replacer(
|
|
27
|
-
this: Record<string, unknown>,
|
|
28
|
-
key: string,
|
|
29
|
-
item: unknown
|
|
30
|
-
): unknown {
|
|
31
|
-
const source = this[key];
|
|
32
|
-
if (typeof source === 'number' && !Number.isFinite(source)) {
|
|
33
|
-
throw new TypeError('framework-json-v1 only accepts finite JSON numbers.');
|
|
34
|
-
}
|
|
35
|
-
if (typeof source === 'bigint') {
|
|
36
|
-
if (source < SIGNED_64_MIN || source > UNSIGNED_64_MAX) {
|
|
37
|
-
throw new RangeError('framework-json-v1 64-bit integer is outside the supported range.');
|
|
38
|
-
}
|
|
39
|
-
return source.toString(10);
|
|
40
|
-
}
|
|
41
|
-
if (source instanceof Uint8Array) {
|
|
42
|
-
return Buffer.from(source.buffer, source.byteOffset, source.byteLength).toString('base64');
|
|
43
|
-
}
|
|
44
|
-
if (source instanceof Date) {
|
|
45
|
-
throw new TypeError('framework-json-v1 does not implicitly encode Date values.');
|
|
46
|
-
}
|
|
47
|
-
if (
|
|
48
|
-
typeof source === 'object' &&
|
|
49
|
-
source !== null &&
|
|
50
|
-
typeof (source as { readonly toJSON?: unknown }).toJSON === 'function'
|
|
51
|
-
) {
|
|
52
|
-
throw new TypeError('framework-json-v1 does not implicitly invoke custom toJSON methods.');
|
|
53
|
-
}
|
|
54
|
-
return item;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function parseFrameworkJsonV1(
|
|
58
|
-
text: string,
|
|
59
|
-
options: FrameworkJsonParseOptions = {},
|
|
60
|
-
schema?: ZLinkJsonSchema
|
|
61
|
-
): unknown {
|
|
62
|
-
if (text.charCodeAt(0) === 0xfeff) {
|
|
63
|
-
throw new SyntaxError('framework-json-v1 does not allow a UTF-8 BOM.');
|
|
64
|
-
}
|
|
65
|
-
new JsonPropertyScanner(text, options).scan();
|
|
66
|
-
const parsed = JSON.parse(text);
|
|
67
|
-
return schema === undefined ? parsed : validateFrameworkJsonV1Value(parsed, schema, '$', true);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Sticky pattern shared across scans; matching happens synchronously so the
|
|
71
|
-
// mutable lastIndex never crosses calls.
|
|
72
|
-
const NUMBER_TOKEN_PATTERN = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/uy;
|
|
73
|
-
|
|
74
|
-
const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;
|
|
75
|
-
const INT32_MIN = -2_147_483_648;
|
|
76
|
-
const INT32_MAX = 2_147_483_647;
|
|
77
|
-
const UINT32_MAX = 4_294_967_295;
|
|
78
|
-
|
|
79
|
-
function validateFrameworkJsonV1Value(
|
|
80
|
-
value: unknown,
|
|
81
|
-
schema: ZLinkJsonSchema,
|
|
82
|
-
path: string,
|
|
83
|
-
decode: boolean
|
|
84
|
-
): unknown {
|
|
85
|
-
switch (schema.type) {
|
|
86
|
-
case 'boolean':
|
|
87
|
-
if (typeof value !== 'boolean') schemaFailure(path, 'a boolean');
|
|
88
|
-
return value;
|
|
89
|
-
case 'string':
|
|
90
|
-
if (typeof value !== 'string') schemaFailure(path, 'a string');
|
|
91
|
-
return value;
|
|
92
|
-
case 'number':
|
|
93
|
-
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
94
|
-
schemaFailure(path, 'a finite number');
|
|
95
|
-
return value;
|
|
96
|
-
case 'int32':
|
|
97
|
-
if (
|
|
98
|
-
!Number.isInteger(value) ||
|
|
99
|
-
(value as number) < INT32_MIN ||
|
|
100
|
-
(value as number) > INT32_MAX
|
|
101
|
-
) {
|
|
102
|
-
schemaFailure(path, 'an int32 JSON number');
|
|
103
|
-
}
|
|
104
|
-
return value;
|
|
105
|
-
case 'uint32':
|
|
106
|
-
if (!Number.isInteger(value) || (value as number) < 0 || (value as number) > UINT32_MAX) {
|
|
107
|
-
schemaFailure(path, 'a uint32 JSON number');
|
|
108
|
-
}
|
|
109
|
-
return value;
|
|
110
|
-
case 'int64':
|
|
111
|
-
return validateDecimalInteger(value, path, true, decode);
|
|
112
|
-
case 'uint64':
|
|
113
|
-
return validateDecimalInteger(value, path, false, decode);
|
|
114
|
-
case 'bytes': {
|
|
115
|
-
if (typeof value !== 'string' || !BASE64_PATTERN.test(value)) {
|
|
116
|
-
schemaFailure(path, 'padded RFC 4648 base64');
|
|
117
|
-
}
|
|
118
|
-
const bytes = Buffer.from(value as string, 'base64');
|
|
119
|
-
if (bytes.toString('base64') !== value)
|
|
120
|
-
schemaFailure(path, 'canonical padded RFC 4648 base64');
|
|
121
|
-
return decode ? Uint8Array.from(bytes) : value;
|
|
122
|
-
}
|
|
123
|
-
case 'enum':
|
|
124
|
-
if (typeof value !== 'string' || !schema.names.includes(value)) {
|
|
125
|
-
schemaFailure(path, `one of ${schema.names.join(', ')}`);
|
|
126
|
-
}
|
|
127
|
-
return value;
|
|
128
|
-
case 'nullable':
|
|
129
|
-
return value === null
|
|
130
|
-
? null
|
|
131
|
-
: validateFrameworkJsonV1Value(value, schema.value, path, decode);
|
|
132
|
-
case 'array':
|
|
133
|
-
if (!Array.isArray(value)) schemaFailure(path, 'an array');
|
|
134
|
-
return (value as unknown[]).map((item, index) =>
|
|
135
|
-
validateFrameworkJsonV1Value(item, schema.items, `${path}[${index}]`, decode)
|
|
136
|
-
);
|
|
137
|
-
case 'record': {
|
|
138
|
-
if (!isJsonObject(value)) schemaFailure(path, 'an object');
|
|
139
|
-
const result: Record<string, unknown> = {};
|
|
140
|
-
for (const [name, item] of Object.entries(value as Record<string, unknown>)) {
|
|
141
|
-
result[name] = validateFrameworkJsonV1Value(item, schema.values, `${path}.${name}`, decode);
|
|
142
|
-
}
|
|
143
|
-
return decode ? result : value;
|
|
144
|
-
}
|
|
145
|
-
case 'object': {
|
|
146
|
-
if (!isJsonObject(value)) schemaFailure(path, 'an object');
|
|
147
|
-
const input = value as Record<string, unknown>;
|
|
148
|
-
for (const name of schema.required) {
|
|
149
|
-
if (!Object.prototype.hasOwnProperty.call(input, name)) {
|
|
150
|
-
throw new TypeError(`framework-json-v1 required property '${path}.${name}' is missing.`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const result: Record<string, unknown> = {};
|
|
154
|
-
for (const [name, item] of Object.entries(input)) {
|
|
155
|
-
const propertySchema: unknown = schema.properties[name];
|
|
156
|
-
if (propertySchema === undefined) {
|
|
157
|
-
if (schema.additionalProperties === false) {
|
|
158
|
-
throw new TypeError(`framework-json-v1 property '${path}.${name}' is not allowed.`);
|
|
159
|
-
}
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
result[name] = validateFrameworkJsonV1Value(
|
|
163
|
-
item,
|
|
164
|
-
propertySchema as ZLinkJsonSchema,
|
|
165
|
-
`${path}.${name}`,
|
|
166
|
-
decode
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
return decode ? result : value;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function validateDecimalInteger(
|
|
175
|
-
value: unknown,
|
|
176
|
-
path: string,
|
|
177
|
-
signed: boolean,
|
|
178
|
-
decode: boolean
|
|
179
|
-
): unknown {
|
|
180
|
-
if (typeof value !== 'string' || !/^(?:0|[1-9][0-9]*|-[1-9][0-9]*)$/u.test(value)) {
|
|
181
|
-
schemaFailure(
|
|
182
|
-
path,
|
|
183
|
-
signed ? 'a canonical int64 decimal string' : 'a canonical uint64 decimal string'
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
const parsed = BigInt(value as string);
|
|
187
|
-
const minimum = signed ? SIGNED_64_MIN : 0n;
|
|
188
|
-
const maximum = signed ? (1n << 63n) - 1n : UNSIGNED_64_MAX;
|
|
189
|
-
if (parsed < minimum || parsed > maximum)
|
|
190
|
-
schemaFailure(path, signed ? 'an int64 value' : 'a uint64 value');
|
|
191
|
-
return decode ? parsed : value;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function isJsonObject(value: unknown): value is Record<string, unknown> {
|
|
195
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function schemaFailure(path: string, expected: string): never {
|
|
199
|
-
throw new TypeError(`framework-json-v1 value '${path}' must be ${expected}.`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
class JsonPropertyScanner {
|
|
203
|
-
private index = 0;
|
|
204
|
-
|
|
205
|
-
constructor(
|
|
206
|
-
private readonly text: string,
|
|
207
|
-
private readonly options: FrameworkJsonParseOptions
|
|
208
|
-
) {}
|
|
209
|
-
|
|
210
|
-
scan(): void {
|
|
211
|
-
this.skipWhitespace();
|
|
212
|
-
this.scanValue();
|
|
213
|
-
this.skipWhitespace();
|
|
214
|
-
if (this.index !== this.text.length) this.fail('trailing data');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private scanValue(): void {
|
|
218
|
-
this.skipWhitespace();
|
|
219
|
-
const token = this.text[this.index];
|
|
220
|
-
if (token === '{') return this.scanObject();
|
|
221
|
-
if (token === '[') return this.scanArray();
|
|
222
|
-
if (token === '"') {
|
|
223
|
-
this.scanString();
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (token === 't') return this.scanLiteral('true');
|
|
227
|
-
if (token === 'f') return this.scanLiteral('false');
|
|
228
|
-
if (token === 'n') return this.scanLiteral('null');
|
|
229
|
-
this.scanNumber();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private scanObject(): void {
|
|
233
|
-
this.index++;
|
|
234
|
-
this.skipWhitespace();
|
|
235
|
-
if (this.text[this.index] === '}') {
|
|
236
|
-
this.index++;
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const properties = new Set<string>();
|
|
240
|
-
for (;;) {
|
|
241
|
-
this.skipWhitespace();
|
|
242
|
-
if (this.text[this.index] !== '"') this.fail('object property name');
|
|
243
|
-
const property = this.scanString();
|
|
244
|
-
if (properties.has(property)) {
|
|
245
|
-
throw new SyntaxError(`framework-json-v1 duplicate property '${property}'.`);
|
|
246
|
-
}
|
|
247
|
-
properties.add(property);
|
|
248
|
-
if (this.options.rejectPropertyName?.(property) === true) {
|
|
249
|
-
throw new SyntaxError(`framework-json-v1 property '${property}' is not allowed.`);
|
|
250
|
-
}
|
|
251
|
-
this.skipWhitespace();
|
|
252
|
-
if (this.text[this.index] !== ':') this.fail("':' after object property");
|
|
253
|
-
this.index++;
|
|
254
|
-
this.scanValue();
|
|
255
|
-
this.skipWhitespace();
|
|
256
|
-
const delimiter = this.text[this.index++];
|
|
257
|
-
if (delimiter === '}') return;
|
|
258
|
-
if (delimiter !== ',') this.fail("',' or '}' after object property");
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private scanArray(): void {
|
|
263
|
-
this.index++;
|
|
264
|
-
this.skipWhitespace();
|
|
265
|
-
if (this.text[this.index] === ']') {
|
|
266
|
-
this.index++;
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
for (;;) {
|
|
270
|
-
this.scanValue();
|
|
271
|
-
this.skipWhitespace();
|
|
272
|
-
const delimiter = this.text[this.index++];
|
|
273
|
-
if (delimiter === ']') return;
|
|
274
|
-
if (delimiter !== ',') this.fail("',' or ']' after array item");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private scanString(): string {
|
|
279
|
-
const start = this.index++;
|
|
280
|
-
let sawEscape = false;
|
|
281
|
-
for (;;) {
|
|
282
|
-
if (this.index >= this.text.length) this.fail('unterminated string');
|
|
283
|
-
const character = this.text[this.index++]!;
|
|
284
|
-
if (character === '"') {
|
|
285
|
-
// Without escapes the token content is verbatim; the outer
|
|
286
|
-
// JSON.parse pass still rejects any raw control characters.
|
|
287
|
-
return sawEscape
|
|
288
|
-
? (JSON.parse(this.text.slice(start, this.index)) as string)
|
|
289
|
-
: this.text.slice(start + 1, this.index - 1);
|
|
290
|
-
}
|
|
291
|
-
if (character === '\\') {
|
|
292
|
-
sawEscape = true;
|
|
293
|
-
if (this.index >= this.text.length) this.fail('unterminated escape');
|
|
294
|
-
const escaped = this.text[this.index++]!;
|
|
295
|
-
if (escaped === 'u') this.index += 4;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private scanLiteral(value: 'true' | 'false' | 'null'): void {
|
|
301
|
-
if (!this.text.startsWith(value, this.index)) this.fail(value);
|
|
302
|
-
this.index += value.length;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private scanNumber(): void {
|
|
306
|
-
NUMBER_TOKEN_PATTERN.lastIndex = this.index;
|
|
307
|
-
const match = NUMBER_TOKEN_PATTERN.exec(this.text);
|
|
308
|
-
if (match === null) this.fail('JSON value');
|
|
309
|
-
this.index += match[0].length;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
private skipWhitespace(): void {
|
|
313
|
-
for (;;) {
|
|
314
|
-
const code = this.text.charCodeAt(this.index);
|
|
315
|
-
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) {
|
|
316
|
-
this.index += 1;
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private fail(expected: string): never {
|
|
324
|
-
throw new SyntaxError(`framework-json-v1 expected ${expected} at offset ${this.index}.`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
3
|
+
export const FRAMEWORK_JSON_V1_PROFILE = profile.FRAMEWORK_JSON_V1_PROFILE;
|
|
4
|
+
export const stringifyFrameworkJsonV1 = profile.stringifyFrameworkJsonV1;
|
|
5
|
+
export const parseFrameworkJsonV1 = profile.parseFrameworkJsonV1;
|
|
6
|
+
export type { FrameworkJsonParseOptions } from '@zlink-systems/stream-wire/internal/json';
|