depa-actor 0.2.0 → 0.2.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/dist/addressing.d.ts +100 -0
- package/dist/addressing.d.ts.map +1 -0
- package/dist/addressing.js +409 -0
- package/dist/addressing.js.map +1 -0
- package/dist/dispatch/ActorDispatchAdapter.d.ts +74 -17
- package/dist/dispatch/ActorDispatchAdapter.d.ts.map +1 -1
- package/dist/dispatch/ActorDispatchAdapter.js +109 -24
- package/dist/dispatch/ActorDispatchAdapter.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/ActorPipeline.d.ts +35 -8
- package/dist/pipeline/ActorPipeline.d.ts.map +1 -1
- package/dist/pipeline/ActorPipeline.js +26 -16
- package/dist/pipeline/ActorPipeline.js.map +1 -1
- package/dist-cjs/addressing.cjs +424 -0
- package/dist-cjs/dispatch/ActorDispatchAdapter.cjs +109 -24
- package/dist-cjs/index.cjs +26 -1
- package/dist-cjs/pipeline/ActorPipeline.cjs +26 -16
- package/package.json +4 -1
- package/src/addressing.ts +756 -0
- package/src/dispatch/ActorDispatchAdapter.ts +218 -47
- package/src/index.ts +55 -2
- package/src/pipeline/ActorPipeline.ts +103 -57
|
@@ -0,0 +1,424 @@
|
|
|
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 ADDRESS_SEGMENT = /^[A-Za-z0-9](?:[A-Za-z0-9._~-]{0,126}[A-Za-z0-9])?$/;
|
|
53
|
+
const OPAQUE_TOKEN = /^[A-Za-z0-9](?:[A-Za-z0-9._~:/-]{0,254}[A-Za-z0-9])?$/;
|
|
54
|
+
const runtimeStates = new WeakMap();
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
function assertExactRecord(value, keys, code, label) {
|
|
59
|
+
if (!isRecord(value)) {
|
|
60
|
+
throw new ActorAddressingError(code, `${label} must be an object`);
|
|
61
|
+
}
|
|
62
|
+
const actualKeys = Object.keys(value).sort();
|
|
63
|
+
const expectedKeys = [...keys].sort();
|
|
64
|
+
if (actualKeys.length !== expectedKeys.length ||
|
|
65
|
+
actualKeys.some((key, index) => key !== expectedKeys[index])) {
|
|
66
|
+
throw new ActorAddressingError(code, `${label} must contain only: ${expectedKeys.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function parseAddressSegment(value, field, code = 'invalid-address') {
|
|
70
|
+
if (typeof value !== 'string' ||
|
|
71
|
+
value !== value.normalize('NFC') ||
|
|
72
|
+
!ADDRESS_SEGMENT.test(value)) {
|
|
73
|
+
throw new ActorAddressingError(code, `${field} must be a canonical actor-address segment`);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function parseOpaqueToken(value, field, code) {
|
|
78
|
+
if (typeof value !== 'string' ||
|
|
79
|
+
value !== value.normalize('NFC') ||
|
|
80
|
+
!OPAQUE_TOKEN.test(value) ||
|
|
81
|
+
value.includes('..') ||
|
|
82
|
+
value.includes('//') ||
|
|
83
|
+
value.includes('%')) {
|
|
84
|
+
throw new ActorAddressingError(code, `${field} must be a canonical non-empty token`);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function parseActorAddress(value) {
|
|
89
|
+
assertExactRecord(value, ADDRESS_KEYS, 'invalid-address', 'ActorAddress');
|
|
90
|
+
if (value.schemaVersion !== exports.ACTOR_ADDRESS_SCHEMA_VERSION) {
|
|
91
|
+
throw new ActorAddressingError('invalid-address', 'Unsupported ActorAddress schemaVersion');
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
schemaVersion: exports.ACTOR_ADDRESS_SCHEMA_VERSION,
|
|
95
|
+
namespace: parseAddressSegment(value.namespace, 'namespace'),
|
|
96
|
+
deploymentId: parseAddressSegment(value.deploymentId, 'deploymentId'),
|
|
97
|
+
actorKind: parseAddressSegment(value.actorKind, 'actorKind'),
|
|
98
|
+
logicalKey: parseAddressSegment(value.logicalKey, 'logicalKey'),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function actorAddressKey(address) {
|
|
102
|
+
const parsed = parseActorAddress(address);
|
|
103
|
+
return [
|
|
104
|
+
parsed.schemaVersion,
|
|
105
|
+
parsed.namespace,
|
|
106
|
+
parsed.deploymentId,
|
|
107
|
+
parsed.actorKind,
|
|
108
|
+
parsed.logicalKey,
|
|
109
|
+
].join(':');
|
|
110
|
+
}
|
|
111
|
+
function parseActorSelector(value) {
|
|
112
|
+
if (!isRecord(value)) {
|
|
113
|
+
throw new ActorAddressingError('invalid-selector', 'ActorSelector must be an object');
|
|
114
|
+
}
|
|
115
|
+
const keys = Object.keys(value);
|
|
116
|
+
if (keys.length !== 1 || (keys[0] !== 'byAddress' && keys[0] !== 'byAlias')) {
|
|
117
|
+
throw new ActorAddressingError('invalid-selector', 'ActorSelector must contain exactly one of byAddress or byAlias');
|
|
118
|
+
}
|
|
119
|
+
if (keys[0] === 'byAddress') {
|
|
120
|
+
try {
|
|
121
|
+
return Object.freeze({ byAddress: parseActorAddress(value.byAddress) });
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error instanceof ActorAddressingError) {
|
|
125
|
+
throw new ActorAddressingError('invalid-selector', `Invalid byAddress: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Object.freeze({
|
|
131
|
+
byAlias: parseOpaqueToken(value.byAlias, 'byAlias', 'invalid-selector'),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function parseActorOwnerSnapshot(value) {
|
|
135
|
+
assertExactRecord(value, OWNER_SNAPSHOT_KEYS, 'invalid-owner-snapshot', 'ActorOwnerSnapshot');
|
|
136
|
+
if (value.schemaVersion !== exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION) {
|
|
137
|
+
throw new ActorAddressingError('invalid-owner-snapshot', 'Unsupported owner snapshot schemaVersion');
|
|
138
|
+
}
|
|
139
|
+
if (!Array.isArray(value.registrations)) {
|
|
140
|
+
throw new ActorAddressingError('invalid-owner-snapshot', 'registrations must be an array');
|
|
141
|
+
}
|
|
142
|
+
const registrations = [];
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
for (const registration of value.registrations) {
|
|
145
|
+
assertExactRecord(registration, OWNER_REGISTRATION_KEYS, 'invalid-owner-snapshot', 'owner snapshot registration');
|
|
146
|
+
let address;
|
|
147
|
+
try {
|
|
148
|
+
address = parseActorAddress(registration.address);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error instanceof ActorAddressingError) {
|
|
152
|
+
throw new ActorAddressingError('invalid-owner-snapshot', error.message);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
const key = actorAddressKey(address);
|
|
157
|
+
if (seen.has(key)) {
|
|
158
|
+
throw new ActorAddressingError('duplicate-snapshot-address', `Owner snapshot contains duplicate address: ${key}`);
|
|
159
|
+
}
|
|
160
|
+
seen.add(key);
|
|
161
|
+
registrations.push(Object.freeze({
|
|
162
|
+
address,
|
|
163
|
+
handlerIdentity: parseOpaqueToken(registration.handlerIdentity, 'handlerIdentity', 'invalid-owner-snapshot'),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
return Object.freeze({
|
|
167
|
+
schemaVersion: exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION,
|
|
168
|
+
ownerRevision: parseOpaqueToken(value.ownerRevision, 'ownerRevision', 'invalid-owner-snapshot'),
|
|
169
|
+
snapshotReceipt: parseOpaqueToken(value.snapshotReceipt, 'snapshotReceipt', 'invalid-owner-snapshot'),
|
|
170
|
+
registrations: Object.freeze(registrations),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function parseActorRegistrationReceipt(value) {
|
|
174
|
+
assertExactRecord(value, REGISTRATION_RECEIPT_KEYS, 'registration-mismatch', 'ActorRegistrationReceipt');
|
|
175
|
+
if (value.schemaVersion !== exports.ACTOR_REGISTRATION_SCHEMA_VERSION) {
|
|
176
|
+
throw new ActorAddressingError('registration-mismatch', 'Unsupported registration schemaVersion');
|
|
177
|
+
}
|
|
178
|
+
return Object.freeze({
|
|
179
|
+
schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
|
|
180
|
+
address: parseActorAddress(value.address),
|
|
181
|
+
ownerRevision: parseOpaqueToken(value.ownerRevision, 'ownerRevision', 'registration-mismatch'),
|
|
182
|
+
snapshotReceipt: parseOpaqueToken(value.snapshotReceipt, 'snapshotReceipt', 'registration-mismatch'),
|
|
183
|
+
handlerIdentity: parseOpaqueToken(value.handlerIdentity, 'handlerIdentity', 'registration-mismatch'),
|
|
184
|
+
registrationId: parseOpaqueToken(value.registrationId, 'registrationId', 'registration-mismatch'),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function assertEmptyConfig(config) {
|
|
188
|
+
if (!isRecord(config) || Object.keys(config).length !== 0) {
|
|
189
|
+
throw new ActorAddressingError('registration-mismatch', 'Processor config must be an empty object');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function runtimeState(runtime) {
|
|
193
|
+
const state = runtimeStates.get(runtime);
|
|
194
|
+
if (!state) {
|
|
195
|
+
throw new ActorAddressingError('registration-mismatch', 'runtime must be created by createLocalActorAddressingRuntime');
|
|
196
|
+
}
|
|
197
|
+
return state;
|
|
198
|
+
}
|
|
199
|
+
function createLocalActorAddressingRuntime(config = {}) {
|
|
200
|
+
if (!isRecord(config)) {
|
|
201
|
+
throw new ActorAddressingError('registration-mismatch', 'runtime config must be an object');
|
|
202
|
+
}
|
|
203
|
+
const allowedKeys = new Set(['runtimeInstanceId', 'resolveAlias', 'registrationIdFactory']);
|
|
204
|
+
if (Object.keys(config).some((key) => !allowedKeys.has(key))) {
|
|
205
|
+
throw new ActorAddressingError('registration-mismatch', 'runtime config contains an unsupported field');
|
|
206
|
+
}
|
|
207
|
+
if (config.resolveAlias !== undefined && typeof config.resolveAlias !== 'function') {
|
|
208
|
+
throw new ActorAddressingError('registration-mismatch', 'resolveAlias must be a function');
|
|
209
|
+
}
|
|
210
|
+
if (config.registrationIdFactory !== undefined &&
|
|
211
|
+
typeof config.registrationIdFactory !== 'function') {
|
|
212
|
+
throw new ActorAddressingError('registration-mismatch', 'registrationIdFactory must be a function');
|
|
213
|
+
}
|
|
214
|
+
const randomPart = Math.random().toString(36).slice(2) || '0';
|
|
215
|
+
const runtimeInstanceId = parseOpaqueToken(config.runtimeInstanceId ?? `local-runtime-${Date.now().toString(36)}-${randomPart}`, 'runtimeInstanceId', 'registration-mismatch');
|
|
216
|
+
const runtime = Object.freeze({ runtimeInstanceId });
|
|
217
|
+
runtimeStates.set(runtime, {
|
|
218
|
+
registrations: new Map(),
|
|
219
|
+
registrationIds: new Set(),
|
|
220
|
+
resolveAlias: config.resolveAlias,
|
|
221
|
+
registrationIdFactory: config.registrationIdFactory ??
|
|
222
|
+
((input) => `${input.runtimeInstanceId}:registration-${input.sequence}`),
|
|
223
|
+
sequence: 0,
|
|
224
|
+
});
|
|
225
|
+
return runtime;
|
|
226
|
+
}
|
|
227
|
+
function parseEndpoint(value) {
|
|
228
|
+
assertExactRecord(value, ENDPOINT_KEYS, 'registration-mismatch', 'LocalActorEndpoint');
|
|
229
|
+
const handlerIdentity = parseOpaqueToken(value.handlerIdentity, 'handlerIdentity', 'registration-mismatch');
|
|
230
|
+
if (typeof value.dispatch !== 'function') {
|
|
231
|
+
throw new ActorAddressingError('registration-mismatch', 'endpoint.dispatch must be a function');
|
|
232
|
+
}
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
handlerIdentity,
|
|
235
|
+
dispatch: value.dispatch,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function parseRegisterInput(input) {
|
|
239
|
+
assertExactRecord(input, REGISTER_INPUT_KEYS, 'registration-mismatch', 'RegisterActorInput');
|
|
240
|
+
return {
|
|
241
|
+
address: parseActorAddress(input.address),
|
|
242
|
+
ownerSnapshot: parseActorOwnerSnapshot(input.ownerSnapshot),
|
|
243
|
+
endpoint: parseEndpoint(input.endpoint),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function resolveRegistration(runtime, selector) {
|
|
247
|
+
const state = runtimeState(runtime);
|
|
248
|
+
const parsedSelector = parseActorSelector(selector);
|
|
249
|
+
let address;
|
|
250
|
+
if ('byAddress' in parsedSelector) {
|
|
251
|
+
address = parsedSelector.byAddress;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const resolved = state.resolveAlias?.(parsedSelector.byAlias);
|
|
255
|
+
if (resolved === undefined) {
|
|
256
|
+
throw new ActorAddressingError('unresolved-selector', `Actor alias is unresolved: ${parsedSelector.byAlias}`);
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
address = parseActorAddress(resolved);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (error instanceof ActorAddressingError) {
|
|
263
|
+
throw new ActorAddressingError('unresolved-selector', `Actor alias resolved to an invalid address: ${parsedSelector.byAlias}`);
|
|
264
|
+
}
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const registration = state.registrations.get(actorAddressKey(address));
|
|
269
|
+
if (!registration) {
|
|
270
|
+
throw new ActorAddressingError('unresolved-selector', `Actor address is not registered: ${actorAddressKey(address)}`);
|
|
271
|
+
}
|
|
272
|
+
return registration;
|
|
273
|
+
}
|
|
274
|
+
function expectedRegistrationId(value, required) {
|
|
275
|
+
if (!isRecord(value)) {
|
|
276
|
+
throw new ActorAddressingError('stale-registration', 'invocation must be an object');
|
|
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)
|
|
283
|
+
return undefined;
|
|
284
|
+
return parseOpaqueToken(value.expectedRegistrationId, 'expectedRegistrationId', 'stale-registration');
|
|
285
|
+
}
|
|
286
|
+
function assertCurrentRegistration(registration, expectedId) {
|
|
287
|
+
if (expectedId !== undefined && expectedId !== registration.registrationId) {
|
|
288
|
+
throw new ActorAddressingError('stale-registration', `Expected registration ${expectedId} is not current`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function registerActor(runtime, input, config) {
|
|
292
|
+
assertEmptyConfig(config);
|
|
293
|
+
const parsed = parseRegisterInput(input);
|
|
294
|
+
const state = runtimeState(runtime);
|
|
295
|
+
const key = actorAddressKey(parsed.address);
|
|
296
|
+
if (state.registrations.has(key)) {
|
|
297
|
+
throw new ActorAddressingError('already-registered', `Actor is already registered: ${key}`);
|
|
298
|
+
}
|
|
299
|
+
const ownerEntry = parsed.ownerSnapshot.registrations.find((entry) => actorAddressKey(entry.address) === key);
|
|
300
|
+
if (!ownerEntry || ownerEntry.handlerIdentity !== parsed.endpoint.handlerIdentity) {
|
|
301
|
+
throw new ActorAddressingError('registration-mismatch', 'Address and handler identity must be authorized by the owner snapshot');
|
|
302
|
+
}
|
|
303
|
+
const sequence = state.sequence + 1;
|
|
304
|
+
const registrationId = parseOpaqueToken(state.registrationIdFactory({
|
|
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}`);
|
|
311
|
+
}
|
|
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
|
+
}
|
|
329
|
+
function resolveActor(runtime, selector, invocation, config) {
|
|
330
|
+
assertEmptyConfig(config);
|
|
331
|
+
const expectedId = expectedRegistrationId(invocation, false);
|
|
332
|
+
const registration = resolveRegistration(runtime, selector);
|
|
333
|
+
assertCurrentRegistration(registration.receipt, expectedId);
|
|
334
|
+
return Object.freeze({
|
|
335
|
+
address: registration.address,
|
|
336
|
+
receipt: registration.receipt,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
async function dispatchActor(runtime, selector, invocation, config) {
|
|
340
|
+
assertEmptyConfig(config);
|
|
341
|
+
if (!isRecord(invocation)) {
|
|
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
|
+
}
|
|
348
|
+
const registration = resolveRegistration(runtime, selector);
|
|
349
|
+
const expectedId = invocation.expectedRegistrationId === undefined
|
|
350
|
+
? undefined
|
|
351
|
+
: parseOpaqueToken(invocation.expectedRegistrationId, 'expectedRegistrationId', 'stale-registration');
|
|
352
|
+
assertCurrentRegistration(registration.receipt, expectedId);
|
|
353
|
+
return await registration.endpoint.dispatch(invocation.input);
|
|
354
|
+
}
|
|
355
|
+
function unregisterActor(runtime, selector, invocation, config) {
|
|
356
|
+
assertEmptyConfig(config);
|
|
357
|
+
const expectedId = expectedRegistrationId(invocation, true);
|
|
358
|
+
const registration = resolveRegistration(runtime, selector);
|
|
359
|
+
assertCurrentRegistration(registration.receipt, expectedId);
|
|
360
|
+
runtimeState(runtime).registrations.delete(actorAddressKey(registration.address));
|
|
361
|
+
runtimeState(runtime).registrationIds.delete(registration.receipt.registrationId);
|
|
362
|
+
return registration.receipt;
|
|
363
|
+
}
|
|
364
|
+
function rebuildActorRegistrations(runtime, input, config) {
|
|
365
|
+
const snapshot = parseActorOwnerSnapshot(input);
|
|
366
|
+
assertExactRecord(config, ['endpointFor'], 'registration-mismatch', 'RebuildActorRegistrationsConfig');
|
|
367
|
+
if (typeof config.endpointFor !== 'function') {
|
|
368
|
+
throw new ActorAddressingError('registration-mismatch', 'endpointFor must be a function');
|
|
369
|
+
}
|
|
370
|
+
const state = runtimeState(runtime);
|
|
371
|
+
if (state.registrations.size !== 0) {
|
|
372
|
+
throw new ActorAddressingError('already-registered', 'Registration recovery requires a fresh empty local runtime');
|
|
373
|
+
}
|
|
374
|
+
const endpoints = snapshot.registrations.map((registration) => {
|
|
375
|
+
const endpoint = parseEndpoint(config.endpointFor(registration));
|
|
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}`);
|
|
392
|
+
}
|
|
393
|
+
plannedIds.add(registrationId);
|
|
394
|
+
const receipt = Object.freeze({
|
|
395
|
+
schemaVersion: exports.ACTOR_REGISTRATION_SCHEMA_VERSION,
|
|
396
|
+
address: registration.address,
|
|
397
|
+
ownerRevision: snapshot.ownerRevision,
|
|
398
|
+
snapshotReceipt: snapshot.snapshotReceipt,
|
|
399
|
+
handlerIdentity: registration.handlerIdentity,
|
|
400
|
+
registrationId,
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
key: actorAddressKey(registration.address),
|
|
404
|
+
registration: {
|
|
405
|
+
address: registration.address,
|
|
406
|
+
receipt,
|
|
407
|
+
endpoint: endpoints[index],
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
for (const item of planned) {
|
|
412
|
+
state.registrations.set(item.key, item.registration);
|
|
413
|
+
state.registrationIds.add(item.registration.receipt.registrationId);
|
|
414
|
+
}
|
|
415
|
+
state.sequence = nextSequence;
|
|
416
|
+
return Object.freeze(planned.map((item) => item.registration.receipt));
|
|
417
|
+
}
|
|
418
|
+
function createActorRefEndpoint(ref, tag, handlerIdentity) {
|
|
419
|
+
const parsedHandlerIdentity = parseOpaqueToken(handlerIdentity, 'handlerIdentity', 'registration-mismatch');
|
|
420
|
+
return Object.freeze({
|
|
421
|
+
handlerIdentity: parsedHandlerIdentity,
|
|
422
|
+
dispatch: (input) => ref.send(tag, input),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
@@ -2,38 +2,123 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* depa-actor — Dispatch Bridge
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Drives the depa-processor `DispatchEngine` (all 7 strategies) from the actor
|
|
6
|
+
* side. The envelope → DispatchRequest adaptation and the actor `tag` overlay
|
|
7
|
+
* live HERE, in depa-actor — the generic `DispatchEngine` / `DispatchStrategyConfig`
|
|
8
|
+
* stay free of any actor (`envelope` / `tag` / `ActorSelf`) knowledge.
|
|
9
|
+
*
|
|
10
|
+
* Two orthogonal axes:
|
|
11
|
+
* 1. `tag` — actor's first-level route (which mailbox). Selecting a handler by
|
|
12
|
+
* tag is the actor system's job (`ActorDef.handlers[tag]`); a dispatch handler
|
|
13
|
+
* built here is slotted UNDER a tag, so tag selection happens upstream.
|
|
14
|
+
* `tag` is NOT a DispatchStrategyType — it is an orthogonal overlay dimension.
|
|
15
|
+
* 2. `strategy` — one of the 7 `DispatchStrategyType` values, used as the
|
|
16
|
+
* second-level resolution within the selected tag.
|
|
8
17
|
*/
|
|
9
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
19
|
exports.createDispatchHandler = createDispatchHandler;
|
|
11
|
-
|
|
20
|
+
const depa_processor_1 = require("depa-processor");
|
|
21
|
+
// ─── Internal helpers ────────────────────────────────────────────────
|
|
22
|
+
function isStrategyConfig(routes) {
|
|
23
|
+
return routes instanceof depa_processor_1.DispatchStrategyConfig;
|
|
24
|
+
}
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
26
|
+
* Build a `DispatchStrategyConfig<void>` from a plain key → handler map for the
|
|
27
|
+
* supported key-based strategies. The map values are pre-bound to `(self, envelope)`
|
|
28
|
+
* by capturing the current dispatch's `self` / `envelope` via a thunk.
|
|
15
29
|
*/
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
function buildKeyBasedConfig(strategy, routes, self, envelope) {
|
|
31
|
+
const handlerMap = new Map();
|
|
32
|
+
for (const [key, handler] of Object.entries(routes)) {
|
|
33
|
+
handlerMap.set(key, () => handler(self, envelope));
|
|
34
|
+
}
|
|
35
|
+
switch (strategy) {
|
|
36
|
+
case depa_processor_1.DispatchStrategyType.ROUTE_KEY:
|
|
37
|
+
return depa_processor_1.DispatchStrategyConfig.forRouteKeyStrategy({
|
|
38
|
+
handlerMap: handlerMap,
|
|
39
|
+
});
|
|
40
|
+
case depa_processor_1.DispatchStrategyType.ENUM:
|
|
41
|
+
return depa_processor_1.DispatchStrategyConfig.forEnumStrategy({
|
|
42
|
+
handlerMap,
|
|
43
|
+
});
|
|
44
|
+
case depa_processor_1.DispatchStrategyType.COMMAND_TABLE:
|
|
45
|
+
return depa_processor_1.DispatchStrategyConfig.forCommandStrategy({
|
|
46
|
+
commandConverter: (command) => handlerMap.has(command) ? command : null,
|
|
47
|
+
handlerExtractor: (commandEnum) => handlerMap.get(commandEnum) ?? null,
|
|
48
|
+
});
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(`createDispatchHandler: strategy ${strategy} requires a prebuilt ` +
|
|
51
|
+
`DispatchStrategyConfig in 'routes' (plain key→handler maps support ` +
|
|
52
|
+
`ROUTE_KEY / ENUM / COMMAND_TABLE only).`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Build the strategy-appropriate DispatchRequest from the envelope. */
|
|
56
|
+
function buildRequest(strategy, envelope, extractors) {
|
|
57
|
+
const input = extractors?.inputOf ? extractors.inputOf(envelope) : envelope.payload;
|
|
58
|
+
const routeKey = extractors?.routeKeyOf
|
|
59
|
+
? extractors.routeKeyOf(envelope)
|
|
60
|
+
: envelope.tag;
|
|
61
|
+
const path = extractors?.pathOf ? extractors.pathOf(envelope) : envelope.tag;
|
|
62
|
+
switch (strategy) {
|
|
63
|
+
case depa_processor_1.DispatchStrategyType.CLASS:
|
|
64
|
+
return (0, depa_processor_1.createClassDispatchRequest)(input);
|
|
65
|
+
case depa_processor_1.DispatchStrategyType.ROUTE_KEY:
|
|
66
|
+
return (0, depa_processor_1.createRouteKeyDispatchRequest)(routeKey, input, false);
|
|
67
|
+
case depa_processor_1.DispatchStrategyType.ENUM:
|
|
68
|
+
return (0, depa_processor_1.createEnumDispatchRequest)(extractors?.enumOf ? extractors.enumOf(envelope) : envelope.tag, input);
|
|
69
|
+
case depa_processor_1.DispatchStrategyType.ROUTE_KEY_TO_ENUM:
|
|
70
|
+
return (0, depa_processor_1.createRouteKeyToEnumDispatchRequest)(routeKey, input);
|
|
71
|
+
case depa_processor_1.DispatchStrategyType.COMMAND_TABLE:
|
|
72
|
+
return (0, depa_processor_1.createCommandDispatchRequest)(routeKey, input);
|
|
73
|
+
case depa_processor_1.DispatchStrategyType.PATH:
|
|
74
|
+
return (0, depa_processor_1.createPathDispatchRequest)({
|
|
75
|
+
runtime: undefined,
|
|
76
|
+
request: input,
|
|
77
|
+
path,
|
|
78
|
+
});
|
|
79
|
+
case depa_processor_1.DispatchStrategyType.ACTION_PATH:
|
|
80
|
+
return (0, depa_processor_1.createActionPathDispatchRequest)({
|
|
81
|
+
runtime: undefined,
|
|
82
|
+
request: input,
|
|
83
|
+
action: extractors?.actionOf ? extractors.actionOf(envelope) : undefined,
|
|
84
|
+
path,
|
|
85
|
+
});
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`createDispatchHandler: unknown strategy ${String(strategy)}`);
|
|
23
88
|
}
|
|
89
|
+
}
|
|
90
|
+
// ─── createDispatchHandler ───────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Creates an ActorHandler that resolves an envelope to a sub-handler via one of
|
|
93
|
+
* the 7 depa-processor dispatch strategies (opt-in, per-handler — DX form C / B2).
|
|
94
|
+
*
|
|
95
|
+
* Object-param signature: `{ strategy, routes, defaultHandler?, extractors? }`.
|
|
96
|
+
*
|
|
97
|
+
* Composability:
|
|
98
|
+
* - slot the returned handler under a `tag` in `ActorDef.handlers` → `tag` overlay
|
|
99
|
+
* (first-level) + `strategy` (second-level), the two axes are orthogonal.
|
|
100
|
+
* - the resolved sub-handler may itself be a `createPipelineHandler(...)` result,
|
|
101
|
+
* so dispatch and pipeline nest freely.
|
|
102
|
+
*
|
|
103
|
+
* No declarative `ActorDef.dispatch` field and no global feature flag / enableXxx
|
|
104
|
+
* API exist — enabling rich dispatch is expressed purely by import + composition.
|
|
105
|
+
*/
|
|
106
|
+
function createDispatchHandler(params) {
|
|
107
|
+
const { strategy, routes, defaultHandler, extractors } = params;
|
|
24
108
|
return async (self, envelope) => {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
109
|
+
const config = isStrategyConfig(routes)
|
|
110
|
+
? routes
|
|
111
|
+
: buildKeyBasedConfig(strategy, routes, self, envelope);
|
|
112
|
+
const engine = new depa_processor_1.DispatchEngine();
|
|
113
|
+
engine.registerStrategy(config);
|
|
114
|
+
const request = buildRequest(strategy, envelope, extractors);
|
|
115
|
+
const result = await engine.dispatch(request);
|
|
116
|
+
if (result.isHandled()) {
|
|
117
|
+
// Await the sub-handler's (possibly async) side effect.
|
|
118
|
+
await result.getResult();
|
|
119
|
+
return;
|
|
35
120
|
}
|
|
36
|
-
|
|
121
|
+
if (defaultHandler) {
|
|
37
122
|
await defaultHandler(self, envelope);
|
|
38
123
|
}
|
|
39
124
|
};
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.DEFAULT_ORCHESTRATOR_OPTIONS = exports.DispatchStrategyConfig = exports.DispatchStrategyType = exports.createDispatchHandler = exports.createPipelineHandler = exports.dispatchInstructions = exports.takeNextCommandFromGroup = exports.pushFrontCommandToGroup = exports.pushFrontCommand = exports.pushBackCommandToGroup = exports.pushBackCommand = exports.popSelectedCommandFromGroup = exports.popNextCommand = exports.popFrontCommand = exports.popBackCommand = exports.drainWhereCommandDequeFromGroup = exports.drainCommandDequeFromGroup = exports.defaultCommandDequeSelector = exports.createCommandDequeGroup = exports.createCommandDeque = exports.createOperandStack = exports.createInstructionStack = exports.createStackMachine = exports.createRuntimeIndexHook = exports.RuntimeIndexHook = exports.createPersistenceEffectPort = exports.createRecoveryHooks = exports.createSnapshotCodec = exports.createCompletionBindingRegistry = exports.createCompletionSignalRegistry = exports.CompletionBindingRegistry = exports.CompletionSignalRegistry = exports.ActorRuntime = exports.createActorRefEndpoint = exports.rebuildActorRegistrations = exports.unregisterActor = exports.dispatchActor = exports.resolveActor = exports.registerActor = exports.createLocalActorAddressingRuntime = exports.parseActorRegistrationReceipt = exports.parseActorOwnerSnapshot = exports.parseActorSelector = exports.parseActorAddress = exports.actorAddressKey = exports.ActorAddressingError = exports.ACTOR_REGISTRATION_SCHEMA_VERSION = exports.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION = exports.ACTOR_ADDRESS_SCHEMA_VERSION = exports.ActorSystem = void 0;
|
|
4
|
+
exports.dispatchEffects = exports.createAiAgentSchedulerHooks = exports.scheduleOne = exports.selectNextFiberId = exports.computeEffectivePriority = exports.applyFailure = exports.reduceOrchestrator = exports.createOrchestratorState = void 0;
|
|
4
5
|
var ActorSystem_js_1 = require("./core/ActorSystem.cjs");
|
|
5
6
|
Object.defineProperty(exports, "ActorSystem", { enumerable: true, get: function () { return ActorSystem_js_1.ActorSystem; } });
|
|
7
|
+
var addressing_js_1 = require("./addressing.cjs");
|
|
8
|
+
Object.defineProperty(exports, "ACTOR_ADDRESS_SCHEMA_VERSION", { enumerable: true, get: function () { return addressing_js_1.ACTOR_ADDRESS_SCHEMA_VERSION; } });
|
|
9
|
+
Object.defineProperty(exports, "ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION", { enumerable: true, get: function () { return addressing_js_1.ACTOR_OWNER_SNAPSHOT_SCHEMA_VERSION; } });
|
|
10
|
+
Object.defineProperty(exports, "ACTOR_REGISTRATION_SCHEMA_VERSION", { enumerable: true, get: function () { return addressing_js_1.ACTOR_REGISTRATION_SCHEMA_VERSION; } });
|
|
11
|
+
Object.defineProperty(exports, "ActorAddressingError", { enumerable: true, get: function () { return addressing_js_1.ActorAddressingError; } });
|
|
12
|
+
Object.defineProperty(exports, "actorAddressKey", { enumerable: true, get: function () { return addressing_js_1.actorAddressKey; } });
|
|
13
|
+
Object.defineProperty(exports, "parseActorAddress", { enumerable: true, get: function () { return addressing_js_1.parseActorAddress; } });
|
|
14
|
+
Object.defineProperty(exports, "parseActorSelector", { enumerable: true, get: function () { return addressing_js_1.parseActorSelector; } });
|
|
15
|
+
Object.defineProperty(exports, "parseActorOwnerSnapshot", { enumerable: true, get: function () { return addressing_js_1.parseActorOwnerSnapshot; } });
|
|
16
|
+
Object.defineProperty(exports, "parseActorRegistrationReceipt", { enumerable: true, get: function () { return addressing_js_1.parseActorRegistrationReceipt; } });
|
|
17
|
+
Object.defineProperty(exports, "createLocalActorAddressingRuntime", { enumerable: true, get: function () { return addressing_js_1.createLocalActorAddressingRuntime; } });
|
|
18
|
+
Object.defineProperty(exports, "registerActor", { enumerable: true, get: function () { return addressing_js_1.registerActor; } });
|
|
19
|
+
Object.defineProperty(exports, "resolveActor", { enumerable: true, get: function () { return addressing_js_1.resolveActor; } });
|
|
20
|
+
Object.defineProperty(exports, "dispatchActor", { enumerable: true, get: function () { return addressing_js_1.dispatchActor; } });
|
|
21
|
+
Object.defineProperty(exports, "unregisterActor", { enumerable: true, get: function () { return addressing_js_1.unregisterActor; } });
|
|
22
|
+
Object.defineProperty(exports, "rebuildActorRegistrations", { enumerable: true, get: function () { return addressing_js_1.rebuildActorRegistrations; } });
|
|
23
|
+
Object.defineProperty(exports, "createActorRefEndpoint", { enumerable: true, get: function () { return addressing_js_1.createActorRefEndpoint; } });
|
|
6
24
|
var ActorRuntime_js_1 = require("./runtime/ActorRuntime.cjs");
|
|
7
25
|
Object.defineProperty(exports, "ActorRuntime", { enumerable: true, get: function () { return ActorRuntime_js_1.ActorRuntime; } });
|
|
8
26
|
var completion_js_1 = require("./runtime/completion.cjs");
|
|
@@ -40,6 +58,13 @@ var ActorPipeline_js_1 = require("./pipeline/ActorPipeline.cjs");
|
|
|
40
58
|
Object.defineProperty(exports, "createPipelineHandler", { enumerable: true, get: function () { return ActorPipeline_js_1.createPipelineHandler; } });
|
|
41
59
|
var ActorDispatchAdapter_js_1 = require("./dispatch/ActorDispatchAdapter.cjs");
|
|
42
60
|
Object.defineProperty(exports, "createDispatchHandler", { enumerable: true, get: function () { return ActorDispatchAdapter_js_1.createDispatchHandler; } });
|
|
61
|
+
// Dispatch strategy enum + the common-tier strategy config factories
|
|
62
|
+
// (ROUTE_KEY / ENUM / COMMAND_TABLE). Advanced strategies (PATH / ACTION_PATH +
|
|
63
|
+
// AntPathMatcher / PathActionMatchRule), manifest-* and router/ stay in
|
|
64
|
+
// depa-processor and are imported directly by users who need them.
|
|
65
|
+
var depa_processor_1 = require("depa-processor");
|
|
66
|
+
Object.defineProperty(exports, "DispatchStrategyType", { enumerable: true, get: function () { return depa_processor_1.DispatchStrategyType; } });
|
|
67
|
+
Object.defineProperty(exports, "DispatchStrategyConfig", { enumerable: true, get: function () { return depa_processor_1.DispatchStrategyConfig; } });
|
|
43
68
|
var index_js_2 = require("./orchestration/index.cjs");
|
|
44
69
|
Object.defineProperty(exports, "DEFAULT_ORCHESTRATOR_OPTIONS", { enumerable: true, get: function () { return index_js_2.DEFAULT_ORCHESTRATOR_OPTIONS; } });
|
|
45
70
|
Object.defineProperty(exports, "createOrchestratorState", { enumerable: true, get: function () { return index_js_2.createOrchestratorState; } });
|