depa-actor 0.2.0 → 0.2.2

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,532 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActorAddressingError = exports.ACTOR_REGISTRATION_SCHEMA_VERSION = exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION = exports.ACTOR_ADDRESS_SCHEMA_VERSION = void 0;
4
+ exports.parseActorAddress = parseActorAddress;
5
+ exports.actorAddressKey = actorAddressKey;
6
+ exports.parseActorSelector = parseActorSelector;
7
+ exports.parseActorOwnerSnapshot = parseActorOwnerSnapshot;
8
+ exports.parseActorRegistrationReceipt = parseActorRegistrationReceipt;
9
+ exports.createLocalActorAddressingRuntime = createLocalActorAddressingRuntime;
10
+ exports.registerActor = registerActor;
11
+ exports.resolveActor = resolveActor;
12
+ exports.dispatchActor = dispatchActor;
13
+ exports.unregisterActor = unregisterActor;
14
+ exports.rebuildActorRegistrations = rebuildActorRegistrations;
15
+ exports.createActorRefEndpoint = createActorRefEndpoint;
16
+ exports.ACTOR_ADDRESS_SCHEMA_VERSION = 'depa-actor-address/v1';
17
+ exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION = 'depa-actor-owner-snapshot/v1';
18
+ exports.ACTOR_REGISTRATION_SCHEMA_VERSION = 'depa-actor-registration/v1';
19
+ class ActorAddressingError extends Error {
20
+ code;
21
+ name = 'ActorAddressingError';
22
+ constructor(code, message) {
23
+ super(message);
24
+ this.code = code;
25
+ }
26
+ }
27
+ exports.ActorAddressingError = ActorAddressingError;
28
+ const ADDRESS_KEYS = [
29
+ 'schemaVersion',
30
+ 'namespace',
31
+ 'deploymentId',
32
+ 'actorKind',
33
+ 'logicalKey',
34
+ ];
35
+ const OWNER_SNAPSHOT_KEYS = [
36
+ 'schemaVersion',
37
+ 'ownerRevision',
38
+ 'snapshotReceipt',
39
+ 'registrations',
40
+ ];
41
+ const OWNER_REGISTRATION_KEYS = ['address', 'handlerIdentity'];
42
+ const REGISTRATION_RECEIPT_KEYS = [
43
+ 'schemaVersion',
44
+ 'address',
45
+ 'ownerRevision',
46
+ 'snapshotReceipt',
47
+ 'handlerIdentity',
48
+ 'registrationId',
49
+ ];
50
+ const REGISTER_INPUT_KEYS = ['address', 'ownerSnapshot', 'endpoint'];
51
+ const ENDPOINT_KEYS = ['handlerIdentity', 'dispatch'];
52
+ const RUNTIME_CONFIG_KEYS = ['runtimeInstanceId', 'resolveAlias', 'registrationIdFactory'];
53
+ const EXPECTED_REGISTRATION_ID_KEYS = ['expectedRegistrationId'];
54
+ const DISPATCH_INVOCATION_REQUIRED_KEYS = ['input'];
55
+ const ADDRESS_SEGMENT = /^[A-Za-z0-9](?:[A-Za-z0-9._~-]{0,126}[A-Za-z0-9])?$/;
56
+ const OPAQUE_TOKEN = /^[A-Za-z0-9](?:[A-Za-z0-9._~:/-]{0,254}[A-Za-z0-9])?$/;
57
+ const runtimeStates = new WeakMap();
58
+ let runtimeNonceSequence = 0;
59
+ function closedRecordError(code, label, detail) {
60
+ return new ActorAddressingError(code, `${label} ${detail}`);
61
+ }
62
+ function readClosedRecord(value, requiredKeys, optionalKeys, code, label) {
63
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
64
+ throw closedRecordError(code, label, 'must be an object');
65
+ }
66
+ const prototype = Reflect.getPrototypeOf(value);
67
+ if (prototype !== Object.prototype && prototype !== null) {
68
+ throw closedRecordError(code, label, 'must use Object.prototype or a null prototype');
69
+ }
70
+ const allowedKeys = new Set([...requiredKeys, ...optionalKeys]);
71
+ const values = new Map();
72
+ for (const key of Reflect.ownKeys(value)) {
73
+ if (typeof key !== 'string' || !allowedKeys.has(key)) {
74
+ throw closedRecordError(code, label, `must contain only: ${[...allowedKeys].sort().join(', ')}`);
75
+ }
76
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
77
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
78
+ throw closedRecordError(code, label, `${key} must be an own enumerable data property`);
79
+ }
80
+ values.set(key, descriptor.value);
81
+ }
82
+ for (const key of requiredKeys) {
83
+ if (!values.has(key)) {
84
+ throw closedRecordError(code, label, `must contain required field: ${key}`);
85
+ }
86
+ }
87
+ return values;
88
+ }
89
+ function readExactRecord(value, keys, code, label) {
90
+ return readClosedRecord(value, keys, [], code, label);
91
+ }
92
+ function readClosedArray(value, code, label) {
93
+ if (!Array.isArray(value) || Reflect.getPrototypeOf(value) !== Array.prototype) {
94
+ throw closedRecordError(code, label, 'must be a plain array');
95
+ }
96
+ const lengthDescriptor = Reflect.getOwnPropertyDescriptor(value, 'length');
97
+ const lengthValue = lengthDescriptor && 'value' in lengthDescriptor ? lengthDescriptor.value : undefined;
98
+ if (!lengthDescriptor ||
99
+ !('value' in lengthDescriptor) ||
100
+ lengthDescriptor.enumerable ||
101
+ typeof lengthValue !== 'number' ||
102
+ !Number.isSafeInteger(lengthValue) ||
103
+ lengthValue < 0) {
104
+ throw closedRecordError(code, label, 'must have the standard own data length property');
105
+ }
106
+ const length = lengthValue;
107
+ const expectedKeys = new Set(['length']);
108
+ for (let index = 0; index < length; index += 1) {
109
+ expectedKeys.add(String(index));
110
+ }
111
+ const ownKeys = Reflect.ownKeys(value);
112
+ if (ownKeys.length !== expectedKeys.size ||
113
+ ownKeys.some((key) => typeof key !== 'string' || !expectedKeys.has(key))) {
114
+ throw closedRecordError(code, label, 'must be dense and contain no extra own keys');
115
+ }
116
+ const elements = [];
117
+ for (let index = 0; index < length; index += 1) {
118
+ const key = String(index);
119
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
120
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
121
+ throw closedRecordError(code, label, `${key} must be an own enumerable data property`);
122
+ }
123
+ elements.push(descriptor.value);
124
+ }
125
+ return elements;
126
+ }
127
+ function parseAddressSegment(value, field, code = 'invalid-address') {
128
+ if (typeof value !== 'string' ||
129
+ value !== value.normalize('NFC') ||
130
+ !ADDRESS_SEGMENT.test(value)) {
131
+ throw new ActorAddressingError(code, `${field} must be a canonical actor-address segment`);
132
+ }
133
+ return value;
134
+ }
135
+ function parseOpaqueToken(value, field, code) {
136
+ if (typeof value !== 'string' ||
137
+ value !== value.normalize('NFC') ||
138
+ !OPAQUE_TOKEN.test(value) ||
139
+ value.includes('..') ||
140
+ value.includes('//') ||
141
+ value.includes('%')) {
142
+ throw new ActorAddressingError(code, `${field} must be a canonical non-empty token`);
143
+ }
144
+ return value;
145
+ }
146
+ function parseActorAddress(value) {
147
+ const record = readExactRecord(value, ADDRESS_KEYS, 'invalid-address', 'ActorAddress');
148
+ if (record.get('schemaVersion') !== exports.ACTOR_ADDRESS_SCHEMA_VERSION) {
149
+ throw new ActorAddressingError('invalid-address', 'Unsupported ActorAddress schemaVersion');
150
+ }
151
+ return Object.freeze({
152
+ schemaVersion: exports.ACTOR_ADDRESS_SCHEMA_VERSION,
153
+ namespace: parseAddressSegment(record.get('namespace'), 'namespace'),
154
+ deploymentId: parseAddressSegment(record.get('deploymentId'), 'deploymentId'),
155
+ actorKind: parseAddressSegment(record.get('actorKind'), 'actorKind'),
156
+ logicalKey: parseAddressSegment(record.get('logicalKey'), 'logicalKey'),
157
+ });
158
+ }
159
+ function actorAddressKey(address) {
160
+ const parsed = parseActorAddress(address);
161
+ return [
162
+ parsed.schemaVersion,
163
+ parsed.namespace,
164
+ parsed.deploymentId,
165
+ parsed.actorKind,
166
+ parsed.logicalKey,
167
+ ].join(':');
168
+ }
169
+ function parseActorSelector(value) {
170
+ const record = readClosedRecord(value, [], ['byAddress', 'byAlias'], 'invalid-selector', 'ActorSelector');
171
+ if (record.size !== 1) {
172
+ throw new ActorAddressingError('invalid-selector', 'ActorSelector must contain exactly one of byAddress or byAlias');
173
+ }
174
+ if (record.has('byAddress')) {
175
+ try {
176
+ return Object.freeze({ byAddress: parseActorAddress(record.get('byAddress')) });
177
+ }
178
+ catch (error) {
179
+ if (error instanceof ActorAddressingError) {
180
+ throw new ActorAddressingError('invalid-selector', `Invalid byAddress: ${error.message}`);
181
+ }
182
+ throw error;
183
+ }
184
+ }
185
+ return Object.freeze({
186
+ byAlias: parseOpaqueToken(record.get('byAlias'), 'byAlias', 'invalid-selector'),
187
+ });
188
+ }
189
+ function parseActorOwnerSnapshot(value) {
190
+ const record = readExactRecord(value, OWNER_SNAPSHOT_KEYS, 'invalid-owner-snapshot', 'ActorOwnerSnapshot');
191
+ if (record.get('schemaVersion') !== exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION) {
192
+ throw new ActorAddressingError('invalid-owner-snapshot', 'Unsupported owner snapshot schemaVersion');
193
+ }
194
+ const sourceRegistrations = readClosedArray(record.get('registrations'), 'invalid-owner-snapshot', 'registrations');
195
+ const registrations = [];
196
+ const seen = new Set();
197
+ for (const registration of sourceRegistrations) {
198
+ const registrationRecord = readExactRecord(registration, OWNER_REGISTRATION_KEYS, 'invalid-owner-snapshot', 'owner snapshot registration');
199
+ let address;
200
+ try {
201
+ address = parseActorAddress(registrationRecord.get('address'));
202
+ }
203
+ catch (error) {
204
+ if (error instanceof ActorAddressingError) {
205
+ throw new ActorAddressingError('invalid-owner-snapshot', error.message);
206
+ }
207
+ throw error;
208
+ }
209
+ const key = actorAddressKey(address);
210
+ if (seen.has(key)) {
211
+ throw new ActorAddressingError('duplicate-snapshot-address', `Owner snapshot contains duplicate address: ${key}`);
212
+ }
213
+ seen.add(key);
214
+ registrations.push(Object.freeze({
215
+ address,
216
+ handlerIdentity: parseOpaqueToken(registrationRecord.get('handlerIdentity'), 'handlerIdentity', 'invalid-owner-snapshot'),
217
+ }));
218
+ }
219
+ return Object.freeze({
220
+ schemaVersion: exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION,
221
+ ownerRevision: parseOpaqueToken(record.get('ownerRevision'), 'ownerRevision', 'invalid-owner-snapshot'),
222
+ snapshotReceipt: parseOpaqueToken(record.get('snapshotReceipt'), 'snapshotReceipt', 'invalid-owner-snapshot'),
223
+ registrations: Object.freeze(registrations),
224
+ });
225
+ }
226
+ function parseActorRegistrationReceipt(value) {
227
+ const record = readExactRecord(value, REGISTRATION_RECEIPT_KEYS, 'registration-mismatch', 'ActorRegistrationReceipt');
228
+ if (record.get('schemaVersion') !== exports.ACTOR_REGISTRATION_SCHEMA_VERSION) {
229
+ throw new ActorAddressingError('registration-mismatch', 'Unsupported registration schemaVersion');
230
+ }
231
+ return Object.freeze({
232
+ schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
233
+ address: parseActorAddress(record.get('address')),
234
+ ownerRevision: parseOpaqueToken(record.get('ownerRevision'), 'ownerRevision', 'registration-mismatch'),
235
+ snapshotReceipt: parseOpaqueToken(record.get('snapshotReceipt'), 'snapshotReceipt', 'registration-mismatch'),
236
+ handlerIdentity: parseOpaqueToken(record.get('handlerIdentity'), 'handlerIdentity', 'registration-mismatch'),
237
+ registrationId: parseOpaqueToken(record.get('registrationId'), 'registrationId', 'registration-mismatch'),
238
+ });
239
+ }
240
+ function assertEmptyConfig(config) {
241
+ readExactRecord(config, [], 'registration-mismatch', 'Processor config');
242
+ }
243
+ function runtimeState(runtime) {
244
+ const state = runtimeStates.get(runtime);
245
+ if (!state) {
246
+ throw new ActorAddressingError('registration-mismatch', 'runtime must be created by createLocalActorAddressingRuntime');
247
+ }
248
+ return state;
249
+ }
250
+ function createRuntimeNonce() {
251
+ runtimeNonceSequence += 1;
252
+ const randomPart = typeof globalThis.crypto?.randomUUID === 'function'
253
+ ? globalThis.crypto.randomUUID().replaceAll('-', '')
254
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2) || '0'}`;
255
+ return `runtime-${randomPart}-${runtimeNonceSequence.toString(36)}`;
256
+ }
257
+ function beginMutation(state, operation) {
258
+ if (state.mutationGuard !== undefined) {
259
+ throw new ActorAddressingError('registration-mismatch', `${operation} cannot mutate the runtime while ${state.mutationGuard} is in progress`);
260
+ }
261
+ state.mutationGuard = operation;
262
+ return state.mutationVersion;
263
+ }
264
+ function endMutation(state, operation) {
265
+ if (state.mutationGuard === operation) {
266
+ state.mutationGuard = undefined;
267
+ }
268
+ }
269
+ function createLocalActorAddressingRuntime(config = {}) {
270
+ const record = readClosedRecord(config, [], RUNTIME_CONFIG_KEYS, 'registration-mismatch', 'runtime config');
271
+ const resolveAlias = record.get('resolveAlias');
272
+ const registrationIdFactory = record.get('registrationIdFactory');
273
+ if (resolveAlias !== undefined && typeof resolveAlias !== 'function') {
274
+ throw new ActorAddressingError('registration-mismatch', 'resolveAlias must be a function');
275
+ }
276
+ if (registrationIdFactory !== undefined && typeof registrationIdFactory !== 'function') {
277
+ throw new ActorAddressingError('registration-mismatch', 'registrationIdFactory must be a function');
278
+ }
279
+ const randomPart = Math.random().toString(36).slice(2) || '0';
280
+ const runtimeInstanceId = parseOpaqueToken(record.get('runtimeInstanceId') ?? `local-runtime-${Date.now().toString(36)}-${randomPart}`, 'runtimeInstanceId', 'registration-mismatch');
281
+ const runtime = Object.freeze({ runtimeInstanceId });
282
+ runtimeStates.set(runtime, {
283
+ registrations: new Map(),
284
+ usedRegistrationIds: new Set(),
285
+ runtimeNonce: createRuntimeNonce(),
286
+ resolveAlias: resolveAlias,
287
+ registrationIdFactory: registrationIdFactory ??
288
+ ((input) => `${input.runtimeInstanceId}:registration-${input.sequence}`),
289
+ sequence: 0,
290
+ mutationVersion: 0,
291
+ mutationGuard: undefined,
292
+ });
293
+ return runtime;
294
+ }
295
+ function parseEndpoint(value) {
296
+ const record = readExactRecord(value, ENDPOINT_KEYS, 'registration-mismatch', 'LocalActorEndpoint');
297
+ const handlerIdentity = parseOpaqueToken(record.get('handlerIdentity'), 'handlerIdentity', 'registration-mismatch');
298
+ const dispatch = record.get('dispatch');
299
+ if (typeof dispatch !== 'function') {
300
+ throw new ActorAddressingError('registration-mismatch', 'endpoint.dispatch must be a function');
301
+ }
302
+ return Object.freeze({
303
+ handlerIdentity,
304
+ dispatch: dispatch,
305
+ });
306
+ }
307
+ function parseRegisterInput(input) {
308
+ const record = readExactRecord(input, REGISTER_INPUT_KEYS, 'registration-mismatch', 'RegisterActorInput');
309
+ return {
310
+ address: parseActorAddress(record.get('address')),
311
+ ownerSnapshot: parseActorOwnerSnapshot(record.get('ownerSnapshot')),
312
+ endpoint: parseEndpoint(record.get('endpoint')),
313
+ };
314
+ }
315
+ function resolveRegistration(runtime, selector) {
316
+ const state = runtimeState(runtime);
317
+ const parsedSelector = parseActorSelector(selector);
318
+ let address;
319
+ if ('byAddress' in parsedSelector) {
320
+ address = parsedSelector.byAddress;
321
+ }
322
+ else {
323
+ const resolved = state.resolveAlias?.(parsedSelector.byAlias);
324
+ if (resolved === undefined) {
325
+ throw new ActorAddressingError('unresolved-selector', `Actor alias is unresolved: ${parsedSelector.byAlias}`);
326
+ }
327
+ try {
328
+ address = parseActorAddress(resolved);
329
+ }
330
+ catch (error) {
331
+ if (error instanceof ActorAddressingError) {
332
+ throw new ActorAddressingError('unresolved-selector', `Actor alias resolved to an invalid address: ${parsedSelector.byAlias}`);
333
+ }
334
+ throw error;
335
+ }
336
+ }
337
+ const registration = state.registrations.get(actorAddressKey(address));
338
+ if (!registration) {
339
+ throw new ActorAddressingError('unresolved-selector', `Actor address is not registered: ${actorAddressKey(address)}`);
340
+ }
341
+ return registration;
342
+ }
343
+ function expectedRegistrationId(value, required) {
344
+ const record = readClosedRecord(value, required ? EXPECTED_REGISTRATION_ID_KEYS : [], required ? [] : EXPECTED_REGISTRATION_ID_KEYS, 'stale-registration', 'invocation');
345
+ const valueToParse = record.get('expectedRegistrationId');
346
+ if (valueToParse === undefined && !required)
347
+ return undefined;
348
+ return parseOpaqueToken(valueToParse, 'expectedRegistrationId', 'stale-registration');
349
+ }
350
+ function assertCurrentRegistration(registration, expectedId) {
351
+ if (expectedId !== undefined && expectedId !== registration.registrationId) {
352
+ throw new ActorAddressingError('stale-registration', `Expected registration ${expectedId} is not current`);
353
+ }
354
+ }
355
+ function hashRegistrationIdComponent(value) {
356
+ let hash = 0x811c9dc5;
357
+ for (let index = 0; index < value.length; index += 1) {
358
+ hash = Math.imul(hash ^ value.charCodeAt(index), 0x01000193);
359
+ }
360
+ return (hash >>> 0).toString(36);
361
+ }
362
+ function mintRegistrationId(runtime, state, sequence, address, additionallyUsed = new Set()) {
363
+ const factoryValue = parseOpaqueToken(state.registrationIdFactory({
364
+ runtimeInstanceId: runtime.runtimeInstanceId,
365
+ sequence,
366
+ address,
367
+ }), 'registrationIdFactory result', 'registration-mismatch');
368
+ const factoryComponent = factoryValue.length <= 128
369
+ ? factoryValue
370
+ : `factory-${hashRegistrationIdComponent(factoryValue)}`;
371
+ const registrationId = parseOpaqueToken(`${factoryComponent}:${state.runtimeNonce}:${sequence.toString(36)}`, 'registrationId', 'registration-mismatch');
372
+ if (state.usedRegistrationIds.has(registrationId) || additionallyUsed.has(registrationId)) {
373
+ throw new ActorAddressingError('registration-mismatch', `registrationIdFactory produced an already-used registration id: ${registrationId}`);
374
+ }
375
+ return registrationId;
376
+ }
377
+ function registerActor(runtime, input, config) {
378
+ assertEmptyConfig(config);
379
+ const state = runtimeState(runtime);
380
+ const operation = 'registerActor';
381
+ const startingVersion = beginMutation(state, operation);
382
+ try {
383
+ const parsed = parseRegisterInput(input);
384
+ const key = actorAddressKey(parsed.address);
385
+ if (state.registrations.has(key)) {
386
+ throw new ActorAddressingError('already-registered', `Actor is already registered: ${key}`);
387
+ }
388
+ const ownerEntry = parsed.ownerSnapshot.registrations.find((entry) => actorAddressKey(entry.address) === key);
389
+ if (!ownerEntry || ownerEntry.handlerIdentity !== parsed.endpoint.handlerIdentity) {
390
+ throw new ActorAddressingError('registration-mismatch', 'Address and handler identity must be authorized by the owner snapshot');
391
+ }
392
+ const sequence = state.sequence + 1;
393
+ const registrationId = mintRegistrationId(runtime, state, sequence, parsed.address);
394
+ if (state.mutationVersion !== startingVersion) {
395
+ throw new ActorAddressingError('registration-mismatch', 'Runtime changed while registration was being constructed');
396
+ }
397
+ const receipt = Object.freeze({
398
+ schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
399
+ address: parsed.address,
400
+ ownerRevision: parsed.ownerSnapshot.ownerRevision,
401
+ snapshotReceipt: parsed.ownerSnapshot.snapshotReceipt,
402
+ handlerIdentity: parsed.endpoint.handlerIdentity,
403
+ registrationId,
404
+ });
405
+ state.sequence = sequence;
406
+ state.registrations.set(key, {
407
+ address: parsed.address,
408
+ receipt,
409
+ endpoint: parsed.endpoint,
410
+ });
411
+ state.usedRegistrationIds.add(registrationId);
412
+ state.mutationVersion = startingVersion + 1;
413
+ return receipt;
414
+ }
415
+ finally {
416
+ endMutation(state, operation);
417
+ }
418
+ }
419
+ function resolveActor(runtime, selector, invocation, config) {
420
+ assertEmptyConfig(config);
421
+ const expectedId = expectedRegistrationId(invocation, false);
422
+ const registration = resolveRegistration(runtime, selector);
423
+ assertCurrentRegistration(registration.receipt, expectedId);
424
+ return Object.freeze({
425
+ address: registration.address,
426
+ receipt: registration.receipt,
427
+ });
428
+ }
429
+ async function dispatchActor(runtime, selector, invocation, config) {
430
+ assertEmptyConfig(config);
431
+ const invocationRecord = readClosedRecord(invocation, DISPATCH_INVOCATION_REQUIRED_KEYS, EXPECTED_REGISTRATION_ID_KEYS, 'stale-registration', 'dispatch invocation');
432
+ const registration = resolveRegistration(runtime, selector);
433
+ const expectedRegistrationIdValue = invocationRecord.get('expectedRegistrationId');
434
+ const expectedId = expectedRegistrationIdValue === undefined
435
+ ? undefined
436
+ : parseOpaqueToken(expectedRegistrationIdValue, 'expectedRegistrationId', 'stale-registration');
437
+ assertCurrentRegistration(registration.receipt, expectedId);
438
+ return await registration.endpoint.dispatch(invocationRecord.get('input'));
439
+ }
440
+ function unregisterActor(runtime, selector, invocation, config) {
441
+ assertEmptyConfig(config);
442
+ const expectedId = expectedRegistrationId(invocation, true);
443
+ const state = runtimeState(runtime);
444
+ const operation = 'unregisterActor';
445
+ const startingVersion = beginMutation(state, operation);
446
+ try {
447
+ const registration = resolveRegistration(runtime, selector);
448
+ assertCurrentRegistration(registration.receipt, expectedId);
449
+ if (state.mutationVersion !== startingVersion) {
450
+ throw new ActorAddressingError('registration-mismatch', 'Runtime changed while unregister was being resolved');
451
+ }
452
+ state.registrations.delete(actorAddressKey(registration.address));
453
+ state.mutationVersion = startingVersion + 1;
454
+ return registration.receipt;
455
+ }
456
+ finally {
457
+ endMutation(state, operation);
458
+ }
459
+ }
460
+ function rebuildActorRegistrations(runtime, input, config) {
461
+ const snapshot = parseActorOwnerSnapshot(input);
462
+ const configRecord = readExactRecord(config, ['endpointFor'], 'registration-mismatch', 'RebuildActorRegistrationsConfig');
463
+ const endpointFor = configRecord.get('endpointFor');
464
+ if (typeof endpointFor !== 'function') {
465
+ throw new ActorAddressingError('registration-mismatch', 'endpointFor must be a function');
466
+ }
467
+ const state = runtimeState(runtime);
468
+ const operation = 'rebuildActorRegistrations';
469
+ const startingVersion = beginMutation(state, operation);
470
+ try {
471
+ if (state.registrations.size !== 0) {
472
+ throw new ActorAddressingError('already-registered', 'Registration recovery requires a fresh empty local runtime');
473
+ }
474
+ const endpoints = snapshot.registrations.map((registration) => {
475
+ const endpoint = parseEndpoint(endpointFor(registration));
476
+ if (endpoint.handlerIdentity !== registration.handlerIdentity) {
477
+ throw new ActorAddressingError('registration-mismatch', `Recovered endpoint identity does not match snapshot for ${actorAddressKey(registration.address)}`);
478
+ }
479
+ return endpoint;
480
+ });
481
+ if (state.mutationVersion !== startingVersion || state.registrations.size !== 0) {
482
+ throw new ActorAddressingError('registration-mismatch', 'Runtime changed while recovery endpoints were being constructed');
483
+ }
484
+ let nextSequence = state.sequence;
485
+ const plannedIds = new Set();
486
+ const planned = snapshot.registrations.map((registration, index) => {
487
+ nextSequence += 1;
488
+ const registrationId = mintRegistrationId(runtime, state, nextSequence, registration.address, plannedIds);
489
+ plannedIds.add(registrationId);
490
+ const receipt = Object.freeze({
491
+ schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
492
+ address: registration.address,
493
+ ownerRevision: snapshot.ownerRevision,
494
+ snapshotReceipt: snapshot.snapshotReceipt,
495
+ handlerIdentity: registration.handlerIdentity,
496
+ registrationId,
497
+ });
498
+ return {
499
+ key: actorAddressKey(registration.address),
500
+ registration: {
501
+ address: registration.address,
502
+ receipt,
503
+ endpoint: endpoints[index],
504
+ },
505
+ };
506
+ });
507
+ if (state.mutationVersion !== startingVersion || state.registrations.size !== 0) {
508
+ throw new ActorAddressingError('registration-mismatch', 'Runtime changed while recovery was being planned');
509
+ }
510
+ const nextRegistrations = new Map(planned.map((item) => [item.key, item.registration]));
511
+ const nextUsedRegistrationIds = new Set(state.usedRegistrationIds);
512
+ for (const registrationId of plannedIds) {
513
+ nextUsedRegistrationIds.add(registrationId);
514
+ }
515
+ const receipts = Object.freeze(planned.map((item) => item.registration.receipt));
516
+ state.registrations = nextRegistrations;
517
+ state.usedRegistrationIds = nextUsedRegistrationIds;
518
+ state.sequence = nextSequence;
519
+ state.mutationVersion = startingVersion + 1;
520
+ return receipts;
521
+ }
522
+ finally {
523
+ endMutation(state, operation);
524
+ }
525
+ }
526
+ function createActorRefEndpoint(ref, tag, handlerIdentity) {
527
+ const parsedHandlerIdentity = parseOpaqueToken(handlerIdentity, 'handlerIdentity', 'registration-mismatch');
528
+ return Object.freeze({
529
+ handlerIdentity: parsedHandlerIdentity,
530
+ dispatch: (input) => ref.send(tag, input),
531
+ });
532
+ }