depa-actor 0.1.2 → 0.2.0

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.
Files changed (86) hide show
  1. package/dist/core/ActorSystem.d.ts +1 -1
  2. package/dist/core/ActorSystem.d.ts.map +1 -1
  3. package/dist/dispatch/ActorDispatchAdapter.d.ts +1 -1
  4. package/dist/dispatch/ActorDispatchAdapter.d.ts.map +1 -1
  5. package/dist/execution/commandDeque.d.ts +121 -0
  6. package/dist/execution/commandDeque.d.ts.map +1 -0
  7. package/dist/execution/commandDeque.js +415 -0
  8. package/dist/execution/commandDeque.js.map +1 -0
  9. package/dist/execution/dispatcher.d.ts +72 -0
  10. package/dist/execution/dispatcher.d.ts.map +1 -0
  11. package/dist/execution/dispatcher.js +77 -0
  12. package/dist/execution/dispatcher.js.map +1 -0
  13. package/dist/execution/index.d.ts +7 -0
  14. package/dist/execution/index.d.ts.map +1 -0
  15. package/dist/execution/index.js +4 -0
  16. package/dist/execution/index.js.map +1 -0
  17. package/dist/execution/stack.d.ts +24 -0
  18. package/dist/execution/stack.d.ts.map +1 -0
  19. package/dist/execution/stack.js +84 -0
  20. package/dist/execution/stack.js.map +1 -0
  21. package/dist/index.d.ts +17 -15
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +9 -8
  24. package/dist/index.js.map +1 -1
  25. package/dist/orchestration/index.d.ts +8 -8
  26. package/dist/orchestration/index.d.ts.map +1 -1
  27. package/dist/orchestration/index.js +6 -6
  28. package/dist/orchestration/index.js.map +1 -1
  29. package/dist/orchestration/presets/aiAgent.d.ts +2 -2
  30. package/dist/orchestration/presets/aiAgent.d.ts.map +1 -1
  31. package/dist/orchestration/recovery.d.ts +2 -2
  32. package/dist/orchestration/recovery.d.ts.map +1 -1
  33. package/dist/orchestration/reducer.d.ts +2 -2
  34. package/dist/orchestration/reducer.d.ts.map +1 -1
  35. package/dist/orchestration/reducer.js +2 -2
  36. package/dist/orchestration/reducer.js.map +1 -1
  37. package/dist/orchestration/runtimeAdapter.d.ts +3 -3
  38. package/dist/orchestration/runtimeAdapter.d.ts.map +1 -1
  39. package/dist/orchestration/scheduler.d.ts +2 -2
  40. package/dist/orchestration/scheduler.d.ts.map +1 -1
  41. package/dist/orchestration/types.d.ts +1 -1
  42. package/dist/orchestration/types.d.ts.map +1 -1
  43. package/dist/pipeline/ActorPipeline.d.ts +1 -1
  44. package/dist/pipeline/ActorPipeline.d.ts.map +1 -1
  45. package/dist/runtime/ActorRuntime.d.ts +2 -2
  46. package/dist/runtime/ActorRuntime.d.ts.map +1 -1
  47. package/dist/runtime/ActorRuntime.js +1 -1
  48. package/dist/runtime/ActorRuntime.js.map +1 -1
  49. package/dist-cjs/core/ActorSystem.cjs +172 -0
  50. package/dist-cjs/core/types.cjs +10 -0
  51. package/dist-cjs/dispatch/ActorDispatchAdapter.cjs +40 -0
  52. package/dist-cjs/execution/commandDeque.cjs +430 -0
  53. package/dist-cjs/execution/dispatcher.cjs +79 -0
  54. package/dist-cjs/execution/index.cjs +24 -0
  55. package/dist-cjs/execution/stack.cjs +88 -0
  56. package/dist-cjs/index.cjs +52 -0
  57. package/dist-cjs/orchestration/index.cjs +19 -0
  58. package/dist-cjs/orchestration/presets/aiAgent.cjs +40 -0
  59. package/dist-cjs/orchestration/recovery.cjs +112 -0
  60. package/dist-cjs/orchestration/reducer.cjs +276 -0
  61. package/dist-cjs/orchestration/runtimeAdapter.cjs +14 -0
  62. package/dist-cjs/orchestration/scheduler.cjs +86 -0
  63. package/dist-cjs/orchestration/types.cjs +14 -0
  64. package/dist-cjs/package.json +3 -0
  65. package/dist-cjs/pipeline/ActorPipeline.cjs +34 -0
  66. package/dist-cjs/runtime/ActorRuntime.cjs +77 -0
  67. package/dist-cjs/runtime/completion.cjs +75 -0
  68. package/dist-cjs/runtime/indexing.cjs +34 -0
  69. package/dist-cjs/runtime/snapshot.cjs +14 -0
  70. package/package.json +6 -4
  71. package/src/core/ActorSystem.ts +1 -1
  72. package/src/dispatch/ActorDispatchAdapter.ts +1 -1
  73. package/src/execution/commandDeque.ts +656 -0
  74. package/src/execution/dispatcher.ts +188 -0
  75. package/src/execution/index.ts +58 -0
  76. package/src/execution/stack.ts +130 -0
  77. package/src/index.ts +67 -15
  78. package/src/orchestration/index.ts +8 -8
  79. package/src/orchestration/presets/aiAgent.ts +2 -2
  80. package/src/orchestration/recovery.ts +2 -2
  81. package/src/orchestration/reducer.ts +3 -3
  82. package/src/orchestration/runtimeAdapter.ts +3 -3
  83. package/src/orchestration/scheduler.ts +2 -2
  84. package/src/orchestration/types.ts +1 -1
  85. package/src/pipeline/ActorPipeline.ts +1 -1
  86. package/src/runtime/ActorRuntime.ts +2 -2
@@ -0,0 +1,656 @@
1
+ export type CommandDequeId = string;
2
+
3
+ export interface CommandDequeItem<TCommand> {
4
+ id: string;
5
+ command: TCommand;
6
+ insertedAt: number;
7
+ order: number;
8
+ }
9
+
10
+ export interface CommandDequeState<TCommand> {
11
+ id: CommandDequeId;
12
+ lane?: string;
13
+ priority: number;
14
+ items: CommandDequeItem<TCommand>[];
15
+ }
16
+
17
+ export interface CommandDequeGroupState<TCommand> {
18
+ deques: Record<CommandDequeId, CommandDequeState<TCommand>>;
19
+ sequence: number;
20
+ }
21
+
22
+ export interface CommandDequeOptions {
23
+ id?: CommandDequeId;
24
+ lane?: string;
25
+ priority?: number;
26
+ now?: () => number;
27
+ idFactory?: (args: { sequence: number; dequeId: CommandDequeId }) => string;
28
+ }
29
+
30
+ export interface PushCommandOptions {
31
+ id?: string;
32
+ insertedAt?: number;
33
+ }
34
+
35
+ export interface CommandDeque<TCommand> {
36
+ readonly id: CommandDequeId;
37
+ pushBack(command: TCommand, options?: PushCommandOptions): CommandDequeItem<TCommand>;
38
+ pushFront(command: TCommand, options?: PushCommandOptions): CommandDequeItem<TCommand>;
39
+ peekNext(): CommandDequeItem<TCommand> | undefined;
40
+ popNext(): CommandDequeItem<TCommand> | undefined;
41
+ peekFront(): CommandDequeItem<TCommand> | undefined;
42
+ peekBack(): CommandDequeItem<TCommand> | undefined;
43
+ popFront(): CommandDequeItem<TCommand> | undefined;
44
+ popBack(): CommandDequeItem<TCommand> | undefined;
45
+ listItems(): CommandDequeItem<TCommand>[];
46
+ getItem(id: string): CommandDequeItem<TCommand> | undefined;
47
+ nextAfter(itemId: string): CommandDequeItem<TCommand> | undefined;
48
+ prevBefore(itemId: string): CommandDequeItem<TCommand> | undefined;
49
+ drain(): CommandDequeItem<TCommand>[];
50
+ drainWhere(predicate: (item: CommandDequeItem<TCommand>) => boolean): CommandDequeItem<TCommand>[];
51
+ hasPending(): boolean;
52
+ snapshot(): CommandDequeState<TCommand>;
53
+ }
54
+
55
+ export interface CommandDequeSelection {
56
+ dequeId: CommandDequeId;
57
+ itemId: string;
58
+ }
59
+
60
+ export type CommandDequeSelector<TCommand, TRuntimeState> = (args: {
61
+ group: CommandDequeGroupState<TCommand>;
62
+ runtimeState: TRuntimeState;
63
+ }) => CommandDequeSelection | undefined;
64
+
65
+ export interface SelectedCommandDequeItem<TCommand> extends CommandDequeSelection {
66
+ deque: CommandDequeState<TCommand>;
67
+ item: CommandDequeItem<TCommand>;
68
+ }
69
+
70
+ export interface CommandDequeDefinition {
71
+ id: CommandDequeId;
72
+ lane?: string;
73
+ priority: number;
74
+ }
75
+
76
+ export interface CommandDequeGroupOptions<TCommand, TRuntimeState> {
77
+ deques?: CommandDequeDefinition[];
78
+ now?: () => number;
79
+ idFactory?: (args: { sequence: number; dequeId: CommandDequeId }) => string;
80
+ selector?: CommandDequeSelector<TCommand, TRuntimeState>;
81
+ }
82
+
83
+ export interface PushCommandToDequeOptions extends PushCommandOptions {
84
+ dequeId: CommandDequeId;
85
+ }
86
+
87
+ export interface CommandDequeGroup<TCommand, TRuntimeState = unknown> {
88
+ getDeque(dequeId: CommandDequeId): CommandDeque<TCommand>;
89
+ ensureDeque(definition: CommandDequeDefinition): CommandDeque<TCommand>;
90
+ pushBack(command: TCommand, options: PushCommandToDequeOptions): CommandDequeItem<TCommand>;
91
+ pushFront(command: TCommand, options: PushCommandToDequeOptions): CommandDequeItem<TCommand>;
92
+ selectNext(runtimeState: TRuntimeState, selector?: CommandDequeSelector<TCommand, TRuntimeState>): SelectedCommandDequeItem<TCommand> | undefined;
93
+ popSelected(selection: CommandDequeSelection): SelectedCommandDequeItem<TCommand> | undefined;
94
+ takeNext(runtimeState: TRuntimeState, selector?: CommandDequeSelector<TCommand, TRuntimeState>): SelectedCommandDequeItem<TCommand> | undefined;
95
+ drainDeque(dequeId: CommandDequeId): CommandDequeItem<TCommand>[];
96
+ drainWhereDeque(
97
+ dequeId: CommandDequeId,
98
+ predicate: (item: CommandDequeItem<TCommand>) => boolean,
99
+ ): CommandDequeItem<TCommand>[];
100
+ listDequeIds(): CommandDequeId[];
101
+ snapshot(): CommandDequeGroupState<TCommand>;
102
+ }
103
+
104
+ export interface CommandDequeGroupMutationResult<TCommand> {
105
+ state: CommandDequeGroupState<TCommand>;
106
+ item?: CommandDequeItem<TCommand>;
107
+ items?: CommandDequeItem<TCommand>[];
108
+ selection?: SelectedCommandDequeItem<TCommand>;
109
+ }
110
+
111
+ const DEFAULT_DEQUE_ID = 'default';
112
+ const DEFAULT_PRIORITY = 100;
113
+
114
+ class SingleCommandDeque<TCommand> implements CommandDeque<TCommand> {
115
+ private readonly state: CommandDequeState<TCommand>;
116
+ private sequence: number;
117
+ private readonly now?: () => number;
118
+ private readonly idFactory?: (args: { sequence: number; dequeId: CommandDequeId }) => string;
119
+
120
+ constructor(options?: CommandDequeOptions, state?: CommandDequeState<TCommand>, sequence = 0) {
121
+ this.state = state
122
+ ? cloneDequeState(state)
123
+ : {
124
+ id: options?.id ?? DEFAULT_DEQUE_ID,
125
+ lane: options?.lane,
126
+ priority: options?.priority ?? DEFAULT_PRIORITY,
127
+ items: [],
128
+ };
129
+ this.sequence = sequence;
130
+ this.now = options?.now;
131
+ this.idFactory = options?.idFactory;
132
+ }
133
+
134
+ get id(): CommandDequeId {
135
+ return this.state.id;
136
+ }
137
+
138
+ pushBack(command: TCommand, options?: PushCommandOptions): CommandDequeItem<TCommand> {
139
+ const item = this.createItem(command, options);
140
+ this.state.items.push(item);
141
+ return item;
142
+ }
143
+
144
+ pushFront(command: TCommand, options?: PushCommandOptions): CommandDequeItem<TCommand> {
145
+ const item = this.createItem(command, options);
146
+ this.state.items.unshift(item);
147
+ return item;
148
+ }
149
+
150
+ peekNext(): CommandDequeItem<TCommand> | undefined {
151
+ return this.peekFront();
152
+ }
153
+
154
+ popNext(): CommandDequeItem<TCommand> | undefined {
155
+ return this.popFront();
156
+ }
157
+
158
+ peekFront(): CommandDequeItem<TCommand> | undefined {
159
+ return this.state.items[0];
160
+ }
161
+
162
+ peekBack(): CommandDequeItem<TCommand> | undefined {
163
+ return this.state.items.at(-1);
164
+ }
165
+
166
+ popFront(): CommandDequeItem<TCommand> | undefined {
167
+ return this.state.items.shift();
168
+ }
169
+
170
+ popBack(): CommandDequeItem<TCommand> | undefined {
171
+ return this.state.items.pop();
172
+ }
173
+
174
+ listItems(): CommandDequeItem<TCommand>[] {
175
+ return this.state.items.map((item) => ({ ...item }));
176
+ }
177
+
178
+ getItem(id: string): CommandDequeItem<TCommand> | undefined {
179
+ const item = this.state.items.find((candidate) => candidate.id === id);
180
+ return item ? { ...item } : undefined;
181
+ }
182
+
183
+ nextAfter(itemId: string): CommandDequeItem<TCommand> | undefined {
184
+ const index = this.state.items.findIndex((item) => item.id === itemId);
185
+ const item = index >= 0 ? this.state.items[index + 1] : undefined;
186
+ return item ? { ...item } : undefined;
187
+ }
188
+
189
+ prevBefore(itemId: string): CommandDequeItem<TCommand> | undefined {
190
+ const index = this.state.items.findIndex((item) => item.id === itemId);
191
+ const item = index >= 0 ? this.state.items[index - 1] : undefined;
192
+ return item ? { ...item } : undefined;
193
+ }
194
+
195
+ drain(): CommandDequeItem<TCommand>[] {
196
+ const drained = this.listItems();
197
+ this.state.items.length = 0;
198
+ return drained;
199
+ }
200
+
201
+ drainWhere(predicate: (item: CommandDequeItem<TCommand>) => boolean): CommandDequeItem<TCommand>[] {
202
+ const drained: CommandDequeItem<TCommand>[] = [];
203
+ const kept: CommandDequeItem<TCommand>[] = [];
204
+ for (const item of this.state.items) {
205
+ if (predicate(item)) {
206
+ drained.push({ ...item });
207
+ } else {
208
+ kept.push(item);
209
+ }
210
+ }
211
+ this.state.items.length = 0;
212
+ this.state.items.push(...kept);
213
+ return drained;
214
+ }
215
+
216
+ hasPending(): boolean {
217
+ return this.state.items.length > 0;
218
+ }
219
+
220
+ snapshot(): CommandDequeState<TCommand> {
221
+ return cloneDequeState(this.state);
222
+ }
223
+
224
+ private createItem(command: TCommand, options?: PushCommandOptions): CommandDequeItem<TCommand> {
225
+ const nextSequence = this.sequence + 1;
226
+ this.sequence = nextSequence;
227
+ return {
228
+ id: options?.id ?? this.idFactory?.({ sequence: nextSequence, dequeId: this.state.id }) ?? `cmd-${nextSequence}`,
229
+ command,
230
+ insertedAt: options?.insertedAt ?? this.now?.() ?? Date.now(),
231
+ order: nextSequence,
232
+ };
233
+ }
234
+ }
235
+
236
+ class MultiCommandDequeGroup<TCommand, TRuntimeState> implements CommandDequeGroup<TCommand, TRuntimeState> {
237
+ private readonly state: CommandDequeGroupState<TCommand>;
238
+ private readonly selector: CommandDequeSelector<TCommand, TRuntimeState>;
239
+ private readonly now?: () => number;
240
+ private readonly idFactory?: (args: { sequence: number; dequeId: CommandDequeId }) => string;
241
+
242
+ constructor(
243
+ options?: CommandDequeGroupOptions<TCommand, TRuntimeState>,
244
+ state?: CommandDequeGroupState<TCommand>,
245
+ ) {
246
+ this.state = state
247
+ ? cloneGroupState(state)
248
+ : {
249
+ deques: {},
250
+ sequence: 0,
251
+ };
252
+ this.now = options?.now;
253
+ this.idFactory = options?.idFactory;
254
+ this.selector = options?.selector ?? defaultCommandDequeSelector;
255
+ for (const definition of options?.deques ?? []) {
256
+ this.ensureDequeState(definition);
257
+ }
258
+ }
259
+
260
+ getDeque(dequeId: CommandDequeId): CommandDeque<TCommand> {
261
+ if (!this.state.deques[dequeId]) {
262
+ this.state.deques[dequeId] = {
263
+ id: dequeId,
264
+ priority: DEFAULT_PRIORITY,
265
+ items: [],
266
+ };
267
+ }
268
+ return this.createDequeView(dequeId);
269
+ }
270
+
271
+ ensureDeque(definition: CommandDequeDefinition): CommandDeque<TCommand> {
272
+ const state = this.ensureDequeState(definition);
273
+ return this.createDequeView(state.id);
274
+ }
275
+
276
+ pushBack(command: TCommand, options: PushCommandToDequeOptions): CommandDequeItem<TCommand> {
277
+ return this.push(command, options, 'back');
278
+ }
279
+
280
+ pushFront(command: TCommand, options: PushCommandToDequeOptions): CommandDequeItem<TCommand> {
281
+ return this.push(command, options, 'front');
282
+ }
283
+
284
+ selectNext(
285
+ runtimeState: TRuntimeState,
286
+ selector: CommandDequeSelector<TCommand, TRuntimeState> = this.selector,
287
+ ): SelectedCommandDequeItem<TCommand> | undefined {
288
+ const selection = selector({ group: this.snapshot(), runtimeState });
289
+ if (!selection) {
290
+ return undefined;
291
+ }
292
+ return this.resolveSelection(selection);
293
+ }
294
+
295
+ popSelected(selection: CommandDequeSelection): SelectedCommandDequeItem<TCommand> | undefined {
296
+ const deque = this.state.deques[selection.dequeId];
297
+ if (!deque) {
298
+ return undefined;
299
+ }
300
+ const index = deque.items.findIndex((item) => item.id === selection.itemId);
301
+ if (index < 0) {
302
+ return undefined;
303
+ }
304
+ const [item] = deque.items.splice(index, 1);
305
+ return {
306
+ dequeId: deque.id,
307
+ itemId: item.id,
308
+ deque: cloneDequeState(deque),
309
+ item: { ...item },
310
+ };
311
+ }
312
+
313
+ takeNext(
314
+ runtimeState: TRuntimeState,
315
+ selector: CommandDequeSelector<TCommand, TRuntimeState> = this.selector,
316
+ ): SelectedCommandDequeItem<TCommand> | undefined {
317
+ const selected = this.selectNext(runtimeState, selector);
318
+ if (!selected) {
319
+ return undefined;
320
+ }
321
+ return this.popSelected(selected);
322
+ }
323
+
324
+ drainDeque(dequeId: CommandDequeId): CommandDequeItem<TCommand>[] {
325
+ const deque = this.state.deques[dequeId];
326
+ if (!deque) {
327
+ return [];
328
+ }
329
+ const drained = deque.items.map((item) => ({ ...item }));
330
+ deque.items.length = 0;
331
+ return drained;
332
+ }
333
+
334
+ drainWhereDeque(
335
+ dequeId: CommandDequeId,
336
+ predicate: (item: CommandDequeItem<TCommand>) => boolean,
337
+ ): CommandDequeItem<TCommand>[] {
338
+ const deque = this.state.deques[dequeId];
339
+ if (!deque) {
340
+ return [];
341
+ }
342
+ const drained: CommandDequeItem<TCommand>[] = [];
343
+ const kept: CommandDequeItem<TCommand>[] = [];
344
+ for (const item of deque.items) {
345
+ if (predicate(item)) {
346
+ drained.push({ ...item });
347
+ } else {
348
+ kept.push(item);
349
+ }
350
+ }
351
+ deque.items.length = 0;
352
+ deque.items.push(...kept);
353
+ return drained;
354
+ }
355
+
356
+ listDequeIds(): CommandDequeId[] {
357
+ return Object.keys(this.state.deques);
358
+ }
359
+
360
+ snapshot(): CommandDequeGroupState<TCommand> {
361
+ return cloneGroupState(this.state);
362
+ }
363
+
364
+ private push(
365
+ command: TCommand,
366
+ options: PushCommandToDequeOptions,
367
+ side: 'front' | 'back',
368
+ ): CommandDequeItem<TCommand> {
369
+ const deque = this.ensureDequeState({
370
+ id: options.dequeId,
371
+ priority: this.state.deques[options.dequeId]?.priority ?? DEFAULT_PRIORITY,
372
+ });
373
+ const nextSequence = this.state.sequence + 1;
374
+ this.state.sequence = nextSequence;
375
+ const item = {
376
+ id: options.id ?? this.idFactory?.({ sequence: nextSequence, dequeId: deque.id }) ?? `cmd-${nextSequence}`,
377
+ command,
378
+ insertedAt: options.insertedAt ?? this.now?.() ?? Date.now(),
379
+ order: nextSequence,
380
+ };
381
+ if (side === 'front') {
382
+ deque.items.unshift(item);
383
+ } else {
384
+ deque.items.push(item);
385
+ }
386
+ return { ...item };
387
+ }
388
+
389
+ private createDequeView(dequeId: CommandDequeId): CommandDeque<TCommand> {
390
+ return {
391
+ id: dequeId,
392
+ pushBack: (command, options) => this.pushBack(command, { ...options, dequeId }),
393
+ pushFront: (command, options) => this.pushFront(command, { ...options, dequeId }),
394
+ peekNext: () => this.peekDequeFront(dequeId),
395
+ popNext: () => this.popDequeFront(dequeId),
396
+ peekFront: () => this.peekDequeFront(dequeId),
397
+ peekBack: () => this.peekDequeBack(dequeId),
398
+ popFront: () => this.popDequeFront(dequeId),
399
+ popBack: () => this.popDequeBack(dequeId),
400
+ listItems: () => this.state.deques[dequeId]?.items.map((item) => ({ ...item })) ?? [],
401
+ getItem: (id) => {
402
+ const item = this.state.deques[dequeId]?.items.find((candidate) => candidate.id === id);
403
+ return item ? { ...item } : undefined;
404
+ },
405
+ nextAfter: (itemId) => this.neighbor(dequeId, itemId, 1),
406
+ prevBefore: (itemId) => this.neighbor(dequeId, itemId, -1),
407
+ drain: () => this.drainDeque(dequeId),
408
+ drainWhere: (predicate) => this.drainWhereDeque(dequeId, predicate),
409
+ hasPending: () => (this.state.deques[dequeId]?.items.length ?? 0) > 0,
410
+ snapshot: () => cloneDequeState(this.state.deques[dequeId] ?? this.ensureDequeState({ id: dequeId, priority: DEFAULT_PRIORITY })),
411
+ };
412
+ }
413
+
414
+ private peekDequeFront(dequeId: CommandDequeId): CommandDequeItem<TCommand> | undefined {
415
+ const item = this.state.deques[dequeId]?.items[0];
416
+ return item ? { ...item } : undefined;
417
+ }
418
+
419
+ private peekDequeBack(dequeId: CommandDequeId): CommandDequeItem<TCommand> | undefined {
420
+ const item = this.state.deques[dequeId]?.items.at(-1);
421
+ return item ? { ...item } : undefined;
422
+ }
423
+
424
+ private popDequeFront(dequeId: CommandDequeId): CommandDequeItem<TCommand> | undefined {
425
+ const item = this.state.deques[dequeId]?.items.shift();
426
+ return item ? { ...item } : undefined;
427
+ }
428
+
429
+ private popDequeBack(dequeId: CommandDequeId): CommandDequeItem<TCommand> | undefined {
430
+ const item = this.state.deques[dequeId]?.items.pop();
431
+ return item ? { ...item } : undefined;
432
+ }
433
+
434
+ private neighbor(dequeId: CommandDequeId, itemId: string, offset: 1 | -1): CommandDequeItem<TCommand> | undefined {
435
+ const items = this.state.deques[dequeId]?.items;
436
+ if (!items) {
437
+ return undefined;
438
+ }
439
+ const index = items.findIndex((item) => item.id === itemId);
440
+ const item = index >= 0 ? items[index + offset] : undefined;
441
+ return item ? { ...item } : undefined;
442
+ }
443
+
444
+ private resolveSelection(selection: CommandDequeSelection): SelectedCommandDequeItem<TCommand> | undefined {
445
+ const deque = this.state.deques[selection.dequeId];
446
+ const item = deque?.items.find((candidate) => candidate.id === selection.itemId);
447
+ if (!deque || !item) {
448
+ return undefined;
449
+ }
450
+ return {
451
+ dequeId: deque.id,
452
+ itemId: item.id,
453
+ deque: cloneDequeState(deque),
454
+ item: { ...item },
455
+ };
456
+ }
457
+
458
+ private ensureDequeState(definition: CommandDequeDefinition): CommandDequeState<TCommand> {
459
+ const existing = this.state.deques[definition.id];
460
+ if (existing) {
461
+ if (definition.lane !== undefined) {
462
+ existing.lane = definition.lane;
463
+ }
464
+ existing.priority = definition.priority;
465
+ return existing;
466
+ }
467
+ const created = {
468
+ id: definition.id,
469
+ lane: definition.lane,
470
+ priority: definition.priority,
471
+ items: [],
472
+ };
473
+ this.state.deques[definition.id] = created;
474
+ return created;
475
+ }
476
+ }
477
+
478
+ export function defaultCommandDequeSelector<TCommand, TRuntimeState>({
479
+ group,
480
+ }: {
481
+ group: CommandDequeGroupState<TCommand>;
482
+ runtimeState: TRuntimeState;
483
+ }): CommandDequeSelection | undefined {
484
+ const candidates = Object.values(group.deques)
485
+ .filter((deque) => deque.items.length > 0)
486
+ .map((deque) => ({
487
+ dequeId: deque.id,
488
+ priority: deque.priority,
489
+ item: deque.items[0],
490
+ }));
491
+
492
+ candidates.sort((a, b) => {
493
+ if (a.priority !== b.priority) {
494
+ return a.priority - b.priority;
495
+ }
496
+ return a.item.order - b.item.order;
497
+ });
498
+
499
+ const selected = candidates[0];
500
+ return selected ? { dequeId: selected.dequeId, itemId: selected.item.id } : undefined;
501
+ }
502
+
503
+ export function createCommandDeque<TCommand>(
504
+ options?: CommandDequeOptions,
505
+ state?: CommandDequeState<TCommand>,
506
+ ): CommandDeque<TCommand> {
507
+ return new SingleCommandDeque(options, state, maxOrder(state?.items ?? []));
508
+ }
509
+
510
+ export function createCommandDequeGroup<TCommand, TRuntimeState = unknown>(
511
+ options?: CommandDequeGroupOptions<TCommand, TRuntimeState>,
512
+ state?: CommandDequeGroupState<TCommand>,
513
+ ): CommandDequeGroup<TCommand, TRuntimeState> {
514
+ return new MultiCommandDequeGroup(options, state);
515
+ }
516
+
517
+ export interface CommandDequeMutationResult<TCommand> {
518
+ state: CommandDequeState<TCommand>;
519
+ item?: CommandDequeItem<TCommand>;
520
+ items?: CommandDequeItem<TCommand>[];
521
+ }
522
+
523
+ export function pushBackCommand<TCommand>(
524
+ state: CommandDequeState<TCommand>,
525
+ command: TCommand,
526
+ options?: CommandDequeOptions & PushCommandOptions,
527
+ ): CommandDequeMutationResult<TCommand> {
528
+ const deque = createCommandDeque(options, state);
529
+ const item = deque.pushBack(command, options);
530
+ return { state: deque.snapshot(), item };
531
+ }
532
+
533
+ export function pushFrontCommand<TCommand>(
534
+ state: CommandDequeState<TCommand>,
535
+ command: TCommand,
536
+ options?: CommandDequeOptions & PushCommandOptions,
537
+ ): CommandDequeMutationResult<TCommand> {
538
+ const deque = createCommandDeque(options, state);
539
+ const item = deque.pushFront(command, options);
540
+ return { state: deque.snapshot(), item };
541
+ }
542
+
543
+ export function popNextCommand<TCommand>(
544
+ state: CommandDequeState<TCommand>,
545
+ ): CommandDequeMutationResult<TCommand> {
546
+ const deque = createCommandDeque(undefined, state);
547
+ const item = deque.popNext();
548
+ return { state: deque.snapshot(), item };
549
+ }
550
+
551
+ export function popFrontCommand<TCommand>(
552
+ state: CommandDequeState<TCommand>,
553
+ ): CommandDequeMutationResult<TCommand> {
554
+ const deque = createCommandDeque(undefined, state);
555
+ const item = deque.popFront();
556
+ return { state: deque.snapshot(), item };
557
+ }
558
+
559
+ export function popBackCommand<TCommand>(
560
+ state: CommandDequeState<TCommand>,
561
+ ): CommandDequeMutationResult<TCommand> {
562
+ const deque = createCommandDeque(undefined, state);
563
+ const item = deque.popBack();
564
+ return { state: deque.snapshot(), item };
565
+ }
566
+
567
+ export function pushBackCommandToGroup<TCommand, TRuntimeState = unknown>(
568
+ state: CommandDequeGroupState<TCommand>,
569
+ command: TCommand,
570
+ options: CommandDequeGroupOptions<TCommand, TRuntimeState> & PushCommandToDequeOptions,
571
+ ): CommandDequeGroupMutationResult<TCommand> {
572
+ const group = createCommandDequeGroup(options, state);
573
+ const item = group.pushBack(command, options);
574
+ return { state: group.snapshot(), item };
575
+ }
576
+
577
+ export function pushFrontCommandToGroup<TCommand, TRuntimeState = unknown>(
578
+ state: CommandDequeGroupState<TCommand>,
579
+ command: TCommand,
580
+ options: CommandDequeGroupOptions<TCommand, TRuntimeState> & PushCommandToDequeOptions,
581
+ ): CommandDequeGroupMutationResult<TCommand> {
582
+ const group = createCommandDequeGroup(options, state);
583
+ const item = group.pushFront(command, options);
584
+ return { state: group.snapshot(), item };
585
+ }
586
+
587
+ export function popSelectedCommandFromGroup<TCommand, TRuntimeState = unknown>(
588
+ state: CommandDequeGroupState<TCommand>,
589
+ selection: CommandDequeSelection,
590
+ options?: CommandDequeGroupOptions<TCommand, TRuntimeState>,
591
+ ): CommandDequeGroupMutationResult<TCommand> {
592
+ const group = createCommandDequeGroup(options, state);
593
+ const selected = group.popSelected(selection);
594
+ return {
595
+ state: group.snapshot(),
596
+ item: selected?.item,
597
+ selection: selected,
598
+ };
599
+ }
600
+
601
+ export function takeNextCommandFromGroup<TCommand, TRuntimeState = unknown>(
602
+ state: CommandDequeGroupState<TCommand>,
603
+ runtimeState: TRuntimeState,
604
+ options?: CommandDequeGroupOptions<TCommand, TRuntimeState>,
605
+ ): CommandDequeGroupMutationResult<TCommand> {
606
+ const group = createCommandDequeGroup(options, state);
607
+ const selected = group.takeNext(runtimeState);
608
+ return {
609
+ state: group.snapshot(),
610
+ item: selected?.item,
611
+ selection: selected,
612
+ };
613
+ }
614
+
615
+ export function drainCommandDequeFromGroup<TCommand, TRuntimeState = unknown>(
616
+ state: CommandDequeGroupState<TCommand>,
617
+ dequeId: CommandDequeId,
618
+ options?: CommandDequeGroupOptions<TCommand, TRuntimeState>,
619
+ ): CommandDequeGroupMutationResult<TCommand> {
620
+ const group = createCommandDequeGroup(options, state);
621
+ const items = group.drainDeque(dequeId);
622
+ return { state: group.snapshot(), items };
623
+ }
624
+
625
+ export function drainWhereCommandDequeFromGroup<TCommand, TRuntimeState = unknown>(
626
+ state: CommandDequeGroupState<TCommand>,
627
+ dequeId: CommandDequeId,
628
+ predicate: (item: CommandDequeItem<TCommand>) => boolean,
629
+ options?: CommandDequeGroupOptions<TCommand, TRuntimeState>,
630
+ ): CommandDequeGroupMutationResult<TCommand> {
631
+ const group = createCommandDequeGroup(options, state);
632
+ const items = group.drainWhereDeque(dequeId, predicate);
633
+ return { state: group.snapshot(), items };
634
+ }
635
+
636
+ function cloneDequeState<TCommand>(state: CommandDequeState<TCommand>): CommandDequeState<TCommand> {
637
+ return {
638
+ id: state.id,
639
+ lane: state.lane,
640
+ priority: state.priority,
641
+ items: state.items.map((item) => ({ ...item })),
642
+ };
643
+ }
644
+
645
+ function cloneGroupState<TCommand>(state: CommandDequeGroupState<TCommand>): CommandDequeGroupState<TCommand> {
646
+ return {
647
+ deques: Object.fromEntries(
648
+ Object.entries(state.deques).map(([id, deque]) => [id, cloneDequeState(deque)]),
649
+ ),
650
+ sequence: state.sequence,
651
+ };
652
+ }
653
+
654
+ function maxOrder<TCommand>(items: CommandDequeItem<TCommand>[]): number {
655
+ return items.reduce((max, item) => Math.max(max, item.order), 0);
656
+ }