depa-actor 0.2.1 → 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.
- package/dist/addressing.d.ts.map +1 -1
- package/dist/addressing.js +274 -166
- package/dist/addressing.js.map +1 -1
- package/dist-cjs/addressing.cjs +274 -166
- package/package.json +1 -1
- package/src/addressing.ts +415 -195
package/dist-cjs/addressing.cjs
CHANGED
|
@@ -49,22 +49,80 @@ const REGISTRATION_RECEIPT_KEYS = [
|
|
|
49
49
|
];
|
|
50
50
|
const REGISTER_INPUT_KEYS = ['address', 'ownerSnapshot', 'endpoint'];
|
|
51
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'];
|
|
52
55
|
const ADDRESS_SEGMENT = /^[A-Za-z0-9](?:[A-Za-z0-9._~-]{0,126}[A-Za-z0-9])?$/;
|
|
53
56
|
const OPAQUE_TOKEN = /^[A-Za-z0-9](?:[A-Za-z0-9._~:/-]{0,254}[A-Za-z0-9])?$/;
|
|
54
57
|
const runtimeStates = new WeakMap();
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
let runtimeNonceSequence = 0;
|
|
59
|
+
function closedRecordError(code, label, detail) {
|
|
60
|
+
return new ActorAddressingError(code, `${label} ${detail}`);
|
|
57
61
|
}
|
|
58
|
-
function
|
|
59
|
-
if (
|
|
60
|
-
throw
|
|
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
|
+
}
|
|
61
86
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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);
|
|
67
124
|
}
|
|
125
|
+
return elements;
|
|
68
126
|
}
|
|
69
127
|
function parseAddressSegment(value, field, code = 'invalid-address') {
|
|
70
128
|
if (typeof value !== 'string' ||
|
|
@@ -86,16 +144,16 @@ function parseOpaqueToken(value, field, code) {
|
|
|
86
144
|
return value;
|
|
87
145
|
}
|
|
88
146
|
function parseActorAddress(value) {
|
|
89
|
-
|
|
90
|
-
if (
|
|
147
|
+
const record = readExactRecord(value, ADDRESS_KEYS, 'invalid-address', 'ActorAddress');
|
|
148
|
+
if (record.get('schemaVersion') !== exports.ACTOR_ADDRESS_SCHEMA_VERSION) {
|
|
91
149
|
throw new ActorAddressingError('invalid-address', 'Unsupported ActorAddress schemaVersion');
|
|
92
150
|
}
|
|
93
151
|
return Object.freeze({
|
|
94
152
|
schemaVersion: exports.ACTOR_ADDRESS_SCHEMA_VERSION,
|
|
95
|
-
namespace: parseAddressSegment(
|
|
96
|
-
deploymentId: parseAddressSegment(
|
|
97
|
-
actorKind: parseAddressSegment(
|
|
98
|
-
logicalKey: parseAddressSegment(
|
|
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'),
|
|
99
157
|
});
|
|
100
158
|
}
|
|
101
159
|
function actorAddressKey(address) {
|
|
@@ -109,16 +167,13 @@ function actorAddressKey(address) {
|
|
|
109
167
|
].join(':');
|
|
110
168
|
}
|
|
111
169
|
function parseActorSelector(value) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
const keys = Object.keys(value);
|
|
116
|
-
if (keys.length !== 1 || (keys[0] !== 'byAddress' && keys[0] !== 'byAlias')) {
|
|
170
|
+
const record = readClosedRecord(value, [], ['byAddress', 'byAlias'], 'invalid-selector', 'ActorSelector');
|
|
171
|
+
if (record.size !== 1) {
|
|
117
172
|
throw new ActorAddressingError('invalid-selector', 'ActorSelector must contain exactly one of byAddress or byAlias');
|
|
118
173
|
}
|
|
119
|
-
if (
|
|
174
|
+
if (record.has('byAddress')) {
|
|
120
175
|
try {
|
|
121
|
-
return Object.freeze({ byAddress: parseActorAddress(
|
|
176
|
+
return Object.freeze({ byAddress: parseActorAddress(record.get('byAddress')) });
|
|
122
177
|
}
|
|
123
178
|
catch (error) {
|
|
124
179
|
if (error instanceof ActorAddressingError) {
|
|
@@ -128,24 +183,22 @@ function parseActorSelector(value) {
|
|
|
128
183
|
}
|
|
129
184
|
}
|
|
130
185
|
return Object.freeze({
|
|
131
|
-
byAlias: parseOpaqueToken(
|
|
186
|
+
byAlias: parseOpaqueToken(record.get('byAlias'), 'byAlias', 'invalid-selector'),
|
|
132
187
|
});
|
|
133
188
|
}
|
|
134
189
|
function parseActorOwnerSnapshot(value) {
|
|
135
|
-
|
|
136
|
-
if (
|
|
190
|
+
const record = readExactRecord(value, OWNER_SNAPSHOT_KEYS, 'invalid-owner-snapshot', 'ActorOwnerSnapshot');
|
|
191
|
+
if (record.get('schemaVersion') !== exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION) {
|
|
137
192
|
throw new ActorAddressingError('invalid-owner-snapshot', 'Unsupported owner snapshot schemaVersion');
|
|
138
193
|
}
|
|
139
|
-
|
|
140
|
-
throw new ActorAddressingError('invalid-owner-snapshot', 'registrations must be an array');
|
|
141
|
-
}
|
|
194
|
+
const sourceRegistrations = readClosedArray(record.get('registrations'), 'invalid-owner-snapshot', 'registrations');
|
|
142
195
|
const registrations = [];
|
|
143
196
|
const seen = new Set();
|
|
144
|
-
for (const registration of
|
|
145
|
-
|
|
197
|
+
for (const registration of sourceRegistrations) {
|
|
198
|
+
const registrationRecord = readExactRecord(registration, OWNER_REGISTRATION_KEYS, 'invalid-owner-snapshot', 'owner snapshot registration');
|
|
146
199
|
let address;
|
|
147
200
|
try {
|
|
148
|
-
address = parseActorAddress(
|
|
201
|
+
address = parseActorAddress(registrationRecord.get('address'));
|
|
149
202
|
}
|
|
150
203
|
catch (error) {
|
|
151
204
|
if (error instanceof ActorAddressingError) {
|
|
@@ -160,34 +213,32 @@ function parseActorOwnerSnapshot(value) {
|
|
|
160
213
|
seen.add(key);
|
|
161
214
|
registrations.push(Object.freeze({
|
|
162
215
|
address,
|
|
163
|
-
handlerIdentity: parseOpaqueToken(
|
|
216
|
+
handlerIdentity: parseOpaqueToken(registrationRecord.get('handlerIdentity'), 'handlerIdentity', 'invalid-owner-snapshot'),
|
|
164
217
|
}));
|
|
165
218
|
}
|
|
166
219
|
return Object.freeze({
|
|
167
220
|
schemaVersion: exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION,
|
|
168
|
-
ownerRevision: parseOpaqueToken(
|
|
169
|
-
snapshotReceipt: parseOpaqueToken(
|
|
221
|
+
ownerRevision: parseOpaqueToken(record.get('ownerRevision'), 'ownerRevision', 'invalid-owner-snapshot'),
|
|
222
|
+
snapshotReceipt: parseOpaqueToken(record.get('snapshotReceipt'), 'snapshotReceipt', 'invalid-owner-snapshot'),
|
|
170
223
|
registrations: Object.freeze(registrations),
|
|
171
224
|
});
|
|
172
225
|
}
|
|
173
226
|
function parseActorRegistrationReceipt(value) {
|
|
174
|
-
|
|
175
|
-
if (
|
|
227
|
+
const record = readExactRecord(value, REGISTRATION_RECEIPT_KEYS, 'registration-mismatch', 'ActorRegistrationReceipt');
|
|
228
|
+
if (record.get('schemaVersion') !== exports.ACTOR_REGISTRATION_SCHEMA_VERSION) {
|
|
176
229
|
throw new ActorAddressingError('registration-mismatch', 'Unsupported registration schemaVersion');
|
|
177
230
|
}
|
|
178
231
|
return Object.freeze({
|
|
179
232
|
schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
|
|
180
|
-
address: parseActorAddress(
|
|
181
|
-
ownerRevision: parseOpaqueToken(
|
|
182
|
-
snapshotReceipt: parseOpaqueToken(
|
|
183
|
-
handlerIdentity: parseOpaqueToken(
|
|
184
|
-
registrationId: parseOpaqueToken(
|
|
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'),
|
|
185
238
|
});
|
|
186
239
|
}
|
|
187
240
|
function assertEmptyConfig(config) {
|
|
188
|
-
|
|
189
|
-
throw new ActorAddressingError('registration-mismatch', 'Processor config must be an empty object');
|
|
190
|
-
}
|
|
241
|
+
readExactRecord(config, [], 'registration-mismatch', 'Processor config');
|
|
191
242
|
}
|
|
192
243
|
function runtimeState(runtime) {
|
|
193
244
|
const state = runtimeStates.get(runtime);
|
|
@@ -196,51 +247,69 @@ function runtimeState(runtime) {
|
|
|
196
247
|
}
|
|
197
248
|
return state;
|
|
198
249
|
}
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
|
|
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`);
|
|
202
260
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
261
|
+
state.mutationGuard = operation;
|
|
262
|
+
return state.mutationVersion;
|
|
263
|
+
}
|
|
264
|
+
function endMutation(state, operation) {
|
|
265
|
+
if (state.mutationGuard === operation) {
|
|
266
|
+
state.mutationGuard = undefined;
|
|
206
267
|
}
|
|
207
|
-
|
|
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') {
|
|
208
274
|
throw new ActorAddressingError('registration-mismatch', 'resolveAlias must be a function');
|
|
209
275
|
}
|
|
210
|
-
if (
|
|
211
|
-
typeof config.registrationIdFactory !== 'function') {
|
|
276
|
+
if (registrationIdFactory !== undefined && typeof registrationIdFactory !== 'function') {
|
|
212
277
|
throw new ActorAddressingError('registration-mismatch', 'registrationIdFactory must be a function');
|
|
213
278
|
}
|
|
214
279
|
const randomPart = Math.random().toString(36).slice(2) || '0';
|
|
215
|
-
const runtimeInstanceId = parseOpaqueToken(
|
|
280
|
+
const runtimeInstanceId = parseOpaqueToken(record.get('runtimeInstanceId') ?? `local-runtime-${Date.now().toString(36)}-${randomPart}`, 'runtimeInstanceId', 'registration-mismatch');
|
|
216
281
|
const runtime = Object.freeze({ runtimeInstanceId });
|
|
217
282
|
runtimeStates.set(runtime, {
|
|
218
283
|
registrations: new Map(),
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
284
|
+
usedRegistrationIds: new Set(),
|
|
285
|
+
runtimeNonce: createRuntimeNonce(),
|
|
286
|
+
resolveAlias: resolveAlias,
|
|
287
|
+
registrationIdFactory: registrationIdFactory ??
|
|
222
288
|
((input) => `${input.runtimeInstanceId}:registration-${input.sequence}`),
|
|
223
289
|
sequence: 0,
|
|
290
|
+
mutationVersion: 0,
|
|
291
|
+
mutationGuard: undefined,
|
|
224
292
|
});
|
|
225
293
|
return runtime;
|
|
226
294
|
}
|
|
227
295
|
function parseEndpoint(value) {
|
|
228
|
-
|
|
229
|
-
const handlerIdentity = parseOpaqueToken(
|
|
230
|
-
|
|
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') {
|
|
231
300
|
throw new ActorAddressingError('registration-mismatch', 'endpoint.dispatch must be a function');
|
|
232
301
|
}
|
|
233
302
|
return Object.freeze({
|
|
234
303
|
handlerIdentity,
|
|
235
|
-
dispatch:
|
|
304
|
+
dispatch: dispatch,
|
|
236
305
|
});
|
|
237
306
|
}
|
|
238
307
|
function parseRegisterInput(input) {
|
|
239
|
-
|
|
308
|
+
const record = readExactRecord(input, REGISTER_INPUT_KEYS, 'registration-mismatch', 'RegisterActorInput');
|
|
240
309
|
return {
|
|
241
|
-
address: parseActorAddress(
|
|
242
|
-
ownerSnapshot: parseActorOwnerSnapshot(
|
|
243
|
-
endpoint: parseEndpoint(
|
|
310
|
+
address: parseActorAddress(record.get('address')),
|
|
311
|
+
ownerSnapshot: parseActorOwnerSnapshot(record.get('ownerSnapshot')),
|
|
312
|
+
endpoint: parseEndpoint(record.get('endpoint')),
|
|
244
313
|
};
|
|
245
314
|
}
|
|
246
315
|
function resolveRegistration(runtime, selector) {
|
|
@@ -272,59 +341,80 @@ function resolveRegistration(runtime, selector) {
|
|
|
272
341
|
return registration;
|
|
273
342
|
}
|
|
274
343
|
function expectedRegistrationId(value, required) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const allowedKeys = required ? ['expectedRegistrationId'] : ['expectedRegistrationId'];
|
|
279
|
-
if (Object.keys(value).some((key) => !allowedKeys.includes(key))) {
|
|
280
|
-
throw new ActorAddressingError('stale-registration', 'invocation contains an unsupported field');
|
|
281
|
-
}
|
|
282
|
-
if (value.expectedRegistrationId === undefined && !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)
|
|
283
347
|
return undefined;
|
|
284
|
-
return parseOpaqueToken(
|
|
348
|
+
return parseOpaqueToken(valueToParse, 'expectedRegistrationId', 'stale-registration');
|
|
285
349
|
}
|
|
286
350
|
function assertCurrentRegistration(registration, expectedId) {
|
|
287
351
|
if (expectedId !== undefined && expectedId !== registration.registrationId) {
|
|
288
352
|
throw new ActorAddressingError('stale-registration', `Expected registration ${expectedId} is not current`);
|
|
289
353
|
}
|
|
290
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
|
+
}
|
|
291
377
|
function registerActor(runtime, input, config) {
|
|
292
378
|
assertEmptyConfig(config);
|
|
293
|
-
const parsed = parseRegisterInput(input);
|
|
294
379
|
const state = runtimeState(runtime);
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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;
|
|
302
414
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
runtimeInstanceId: runtime.runtimeInstanceId,
|
|
306
|
-
sequence,
|
|
307
|
-
address: parsed.address,
|
|
308
|
-
}), 'registrationId', 'registration-mismatch');
|
|
309
|
-
if (state.registrationIds.has(registrationId)) {
|
|
310
|
-
throw new ActorAddressingError('registration-mismatch', `registrationIdFactory returned a duplicate id: ${registrationId}`);
|
|
415
|
+
finally {
|
|
416
|
+
endMutation(state, operation);
|
|
311
417
|
}
|
|
312
|
-
state.sequence = sequence;
|
|
313
|
-
const receipt = Object.freeze({
|
|
314
|
-
schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
|
|
315
|
-
address: parsed.address,
|
|
316
|
-
ownerRevision: parsed.ownerSnapshot.ownerRevision,
|
|
317
|
-
snapshotReceipt: parsed.ownerSnapshot.snapshotReceipt,
|
|
318
|
-
handlerIdentity: parsed.endpoint.handlerIdentity,
|
|
319
|
-
registrationId,
|
|
320
|
-
});
|
|
321
|
-
state.registrations.set(key, {
|
|
322
|
-
address: parsed.address,
|
|
323
|
-
receipt,
|
|
324
|
-
endpoint: parsed.endpoint,
|
|
325
|
-
});
|
|
326
|
-
state.registrationIds.add(registrationId);
|
|
327
|
-
return receipt;
|
|
328
418
|
}
|
|
329
419
|
function resolveActor(runtime, selector, invocation, config) {
|
|
330
420
|
assertEmptyConfig(config);
|
|
@@ -338,82 +428,100 @@ function resolveActor(runtime, selector, invocation, config) {
|
|
|
338
428
|
}
|
|
339
429
|
async function dispatchActor(runtime, selector, invocation, config) {
|
|
340
430
|
assertEmptyConfig(config);
|
|
341
|
-
|
|
342
|
-
throw new ActorAddressingError('stale-registration', 'dispatch invocation must be an object');
|
|
343
|
-
}
|
|
344
|
-
const keys = Object.keys(invocation);
|
|
345
|
-
if (!keys.includes('input') || keys.some((key) => key !== 'input' && key !== 'expectedRegistrationId')) {
|
|
346
|
-
throw new ActorAddressingError('stale-registration', 'dispatch invocation must contain input and optional expectedRegistrationId');
|
|
347
|
-
}
|
|
431
|
+
const invocationRecord = readClosedRecord(invocation, DISPATCH_INVOCATION_REQUIRED_KEYS, EXPECTED_REGISTRATION_ID_KEYS, 'stale-registration', 'dispatch invocation');
|
|
348
432
|
const registration = resolveRegistration(runtime, selector);
|
|
349
|
-
const
|
|
433
|
+
const expectedRegistrationIdValue = invocationRecord.get('expectedRegistrationId');
|
|
434
|
+
const expectedId = expectedRegistrationIdValue === undefined
|
|
350
435
|
? undefined
|
|
351
|
-
: parseOpaqueToken(
|
|
436
|
+
: parseOpaqueToken(expectedRegistrationIdValue, 'expectedRegistrationId', 'stale-registration');
|
|
352
437
|
assertCurrentRegistration(registration.receipt, expectedId);
|
|
353
|
-
return await registration.endpoint.dispatch(
|
|
438
|
+
return await registration.endpoint.dispatch(invocationRecord.get('input'));
|
|
354
439
|
}
|
|
355
440
|
function unregisterActor(runtime, selector, invocation, config) {
|
|
356
441
|
assertEmptyConfig(config);
|
|
357
442
|
const expectedId = expectedRegistrationId(invocation, true);
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
+
}
|
|
363
459
|
}
|
|
364
460
|
function rebuildActorRegistrations(runtime, input, config) {
|
|
365
461
|
const snapshot = parseActorOwnerSnapshot(input);
|
|
366
|
-
|
|
367
|
-
|
|
462
|
+
const configRecord = readExactRecord(config, ['endpointFor'], 'registration-mismatch', 'RebuildActorRegistrationsConfig');
|
|
463
|
+
const endpointFor = configRecord.get('endpointFor');
|
|
464
|
+
if (typeof endpointFor !== 'function') {
|
|
368
465
|
throw new ActorAddressingError('registration-mismatch', 'endpointFor must be a function');
|
|
369
466
|
}
|
|
370
467
|
const state = runtimeState(runtime);
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (endpoint.handlerIdentity !== registration.handlerIdentity) {
|
|
377
|
-
throw new ActorAddressingError('registration-mismatch', `Recovered endpoint identity does not match snapshot for ${actorAddressKey(registration.address)}`);
|
|
378
|
-
}
|
|
379
|
-
return endpoint;
|
|
380
|
-
});
|
|
381
|
-
let nextSequence = state.sequence;
|
|
382
|
-
const plannedIds = new Set();
|
|
383
|
-
const planned = snapshot.registrations.map((registration, index) => {
|
|
384
|
-
nextSequence += 1;
|
|
385
|
-
const registrationId = parseOpaqueToken(state.registrationIdFactory({
|
|
386
|
-
runtimeInstanceId: runtime.runtimeInstanceId,
|
|
387
|
-
sequence: nextSequence,
|
|
388
|
-
address: registration.address,
|
|
389
|
-
}), 'registrationId', 'registration-mismatch');
|
|
390
|
-
if (state.registrationIds.has(registrationId) || plannedIds.has(registrationId)) {
|
|
391
|
-
throw new ActorAddressingError('registration-mismatch', `registrationIdFactory returned a duplicate id: ${registrationId}`);
|
|
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');
|
|
392
473
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
handlerIdentity: registration.handlerIdentity,
|
|
400
|
-
registrationId,
|
|
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;
|
|
401
480
|
});
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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,
|
|
405
492
|
address: registration.address,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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);
|
|
414
524
|
}
|
|
415
|
-
state.sequence = nextSequence;
|
|
416
|
-
return Object.freeze(planned.map((item) => item.registration.receipt));
|
|
417
525
|
}
|
|
418
526
|
function createActorRefEndpoint(ref, tag, handlerIdentity) {
|
|
419
527
|
const parsedHandlerIdentity = parseOpaqueToken(handlerIdentity, 'handlerIdentity', 'registration-mismatch');
|