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.
@@ -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
- * Optional bridge to depa-processor DispatchEngine.
6
- * Only this file imports from depa-processor concepts.
7
- * No hard dependencyuses structural typing.
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
- // ─── createDispatchHandler ───────────────────────────────────────────
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
- * Creates an ActorHandler that routes envelopes through dispatch routes.
14
- * Falls back to `defaultHandler` for tags not covered by any route.
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 createDispatchHandler(routes, defaultHandler) {
17
- // Build tag route index for O(1) lookup
18
- const tagIndex = new Map();
19
- for (const route of routes) {
20
- for (const tag of route.tags) {
21
- tagIndex.set(tag, route);
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 route = tagIndex.get(envelope.tag);
26
- if (route) {
27
- const key = route.resolveKey(envelope);
28
- const handler = route.routes[key];
29
- if (handler) {
30
- await handler(self, envelope);
31
- }
32
- else if (route.fallback) {
33
- await route.fallback(self, envelope, key);
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
- else if (defaultHandler) {
121
+ if (defaultHandler) {
37
122
  await defaultHandler(self, envelope);
38
123
  }
39
124
  };
@@ -1,8 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dispatchEffects = exports.createAiAgentSchedulerHooks = exports.scheduleOne = exports.selectNextFiberId = exports.computeEffectivePriority = exports.applyFailure = exports.reduceOrchestrator = exports.createOrchestratorState = exports.DEFAULT_ORCHESTRATOR_OPTIONS = 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.ActorSystem = void 0;
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; } });