@deepseek-ai/dsh-schedule 0.1.1-rc.2 → 0.1.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
2
3
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
4
  //#region lib/types/domain.js
@@ -395,6 +396,44 @@ function dispatchedRecord(record, change) {
395
396
  });
396
397
  }
397
398
  /**
399
+ * Apply already-decoded Schedule changes to one complete fold value.
400
+ *
401
+ * This is the single transition authority shared by full-log replay and the
402
+ * incremental Session projection. One mutable Map/Set pair spans the whole
403
+ * batch; the returned arrays are materialized and frozen once.
404
+ * @param folded - complete active records and used-id history before the changes.
405
+ * @param changes - strictly decoded durable mutations in log order.
406
+ * @returns the complete fold value after every mutation.
407
+ */
408
+ function applyScheduleChanges(folded, changes) {
409
+ const active = new Map(folded.active.map((record) => [record.id, record]));
410
+ const seen = new Set(folded.seenIds);
411
+ for (const change of changes) switch (change.operation) {
412
+ case "create":
413
+ if (seen.has(change.schedule.id)) throw new ScheduleLogError(`schedule id ${JSON.stringify(change.schedule.id)} was reused`);
414
+ seen.add(change.schedule.id);
415
+ active.set(change.schedule.id, change.schedule);
416
+ break;
417
+ case "delete":
418
+ if (!active.delete(change.id)) throw new ScheduleLogError(`schedule delete targets inactive id ${JSON.stringify(change.id)}`);
419
+ break;
420
+ case "dispatch": {
421
+ const record = active.get(change.id);
422
+ if (record === void 0) throw new ScheduleLogError(`schedule dispatch targets inactive id ${JSON.stringify(change.id)}`);
423
+ const next = dispatchedRecord(record, change);
424
+ if (next === void 0) active.delete(change.id);
425
+ else active.set(change.id, next);
426
+ break;
427
+ }
428
+ /* v8 ignore next 3 -- decodeScheduleChange returns a closed operation union. */
429
+ default: throw new ScheduleLogError(`unknown decoded schedule change ${String(change)}`);
430
+ }
431
+ return Object.freeze({
432
+ active: Object.freeze([...active.values()]),
433
+ seenIds: Object.freeze([...seen])
434
+ });
435
+ }
436
+ /**
398
437
  * Fold the package-owned stream after the durable fork seed boundary.
399
438
  * @param events - Complete ordered session log or candidate-extended log.
400
439
  * @param seedLength - Inherited prefix length excluded from child ownership.
@@ -402,36 +441,14 @@ function dispatchedRecord(record, change) {
402
441
  */
403
442
  function foldScheduleEvents(events, seedLength = 0) {
404
443
  if (!Number.isSafeInteger(seedLength) || seedLength < 0 || seedLength > events.length) throw new ScheduleLogError("schedule seedLength must be within the supplied event log");
405
- const active = /* @__PURE__ */ new Map();
406
- const seen = /* @__PURE__ */ new Set();
407
- for (const event of events.slice(seedLength)) {
408
- if (event.type !== "schedule/change") continue;
409
- const change = decodeScheduleChange(event.data);
410
- switch (change.operation) {
411
- case "create":
412
- if (seen.has(change.schedule.id)) throw new ScheduleLogError(`schedule id ${JSON.stringify(change.schedule.id)} was reused`);
413
- seen.add(change.schedule.id);
414
- active.set(change.schedule.id, change.schedule);
415
- break;
416
- case "delete":
417
- if (!active.delete(change.id)) throw new ScheduleLogError(`schedule delete targets inactive id ${JSON.stringify(change.id)}`);
418
- break;
419
- case "dispatch": {
420
- const record = active.get(change.id);
421
- if (record === void 0) throw new ScheduleLogError(`schedule dispatch targets inactive id ${JSON.stringify(change.id)}`);
422
- const next = dispatchedRecord(record, change);
423
- if (next === void 0) active.delete(change.id);
424
- else active.set(change.id, next);
425
- break;
426
- }
427
- /* v8 ignore next 3 -- decodeScheduleChange returns a closed operation union. */
428
- default: throw new ScheduleLogError(`unknown decoded schedule change ${String(change)}`);
429
- }
430
- }
431
- return Object.freeze({
432
- active: Object.freeze([...active.values()]),
433
- seenIds: Object.freeze([...seen])
444
+ const initial = Object.freeze({
445
+ active: Object.freeze([]),
446
+ seenIds: Object.freeze([])
434
447
  });
448
+ const changes = function* () {
449
+ for (const event of events.slice(seedLength)) if (event.type === "schedule/change") yield decodeScheduleChange(event.data);
450
+ };
451
+ return applyScheduleChanges(initial, changes());
435
452
  }
436
453
  /**
437
454
  * Allocate the next readable id without reusing any prior session-local id.
@@ -571,6 +588,87 @@ function renderEveryReminderBatchFraming(reminders) {
571
588
  ].join("\n");
572
589
  }
573
590
  //#endregion
591
+ //#region lib/types/projection.js
592
+ /**
593
+ * Strict Session projection of the Schedule domain's active reminder set.
594
+ * @module @deepseek-ai/dsh-schedule/projection
595
+ */
596
+ const scheduleId = z.unknown().transform((value, context) => {
597
+ try {
598
+ return decodeScheduleChange({
599
+ version: 1,
600
+ operation: "delete",
601
+ id: value
602
+ }).id;
603
+ } catch {
604
+ context.addIssue({
605
+ code: "custom",
606
+ message: "invalid Schedule id"
607
+ });
608
+ return z.NEVER;
609
+ }
610
+ });
611
+ const scheduleRecord = z.unknown().transform((value, context) => {
612
+ try {
613
+ return decodeScheduleChange({
614
+ version: 1,
615
+ operation: "create",
616
+ schedule: value
617
+ }).schedule;
618
+ } catch {
619
+ context.addIssue({
620
+ code: "custom",
621
+ message: "invalid Schedule record"
622
+ });
623
+ return z.NEVER;
624
+ }
625
+ });
626
+ const scheduleRecords = z.array(scheduleRecord);
627
+ /** Projection definition sharing the Schedule domain's strict transition authority. */
628
+ const scheduleProjectionDefinition = {
629
+ key: "schedule",
630
+ stateSchema: z.object({
631
+ seedLength: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
632
+ active: scheduleRecords,
633
+ seenIds: z.array(scheduleId)
634
+ }).strict().superRefine((state, context) => {
635
+ const seen = new Set(state.seenIds);
636
+ if (seen.size !== state.seenIds.length) context.addIssue({
637
+ code: "custom",
638
+ message: "seen Schedule ids must be unique"
639
+ });
640
+ const active = /* @__PURE__ */ new Set();
641
+ for (const record of state.active) {
642
+ if (!seen.has(record.id)) context.addIssue({
643
+ code: "custom",
644
+ message: "every active Schedule id must have been seen"
645
+ });
646
+ if (active.has(record.id)) context.addIssue({
647
+ code: "custom",
648
+ message: "active Schedule ids must be unique"
649
+ });
650
+ active.add(record.id);
651
+ }
652
+ }),
653
+ init: (header) => ({
654
+ seedLength: header.seedLength ?? 0,
655
+ active: [],
656
+ seenIds: []
657
+ }),
658
+ apply: (state, event) => {
659
+ if (event.seq < state.seedLength || event.type !== "schedule/change") return state;
660
+ return {
661
+ seedLength: state.seedLength,
662
+ ...applyScheduleChanges(state, [decodeScheduleChange(event.data)])
663
+ };
664
+ },
665
+ wire: {
666
+ viewSchema: scheduleRecords,
667
+ view: (state) => state.active
668
+ },
669
+ stateVersion: 1
670
+ };
671
+ //#endregion
574
672
  //#region lib/types/persistence.js
575
673
  /** Schedule-owned use of the shared session durability barrier. */
576
674
  /** Failure to prove that the current live prefix reached a persistence listener. */
@@ -1350,6 +1448,9 @@ const inject = [
1350
1448
  ];
1351
1449
  /** Install Schedule only for root agents published after this plugin loads. */
1352
1450
  function apply(ctx) {
1451
+ ctx.inject(["sessionProjections"], (projectionCtx) => {
1452
+ projectionCtx.sessionProjections.register(scheduleProjectionDefinition);
1453
+ });
1353
1454
  const runtimes = /* @__PURE__ */ new Map();
1354
1455
  let stopping = false;
1355
1456
  ctx.effect(() => {
package/lib/invariant.js CHANGED
@@ -209,6 +209,44 @@ function dispatchedRecord(record, change) {
209
209
  });
210
210
  }
211
211
  /**
212
+ * Apply already-decoded Schedule changes to one complete fold value.
213
+ *
214
+ * This is the single transition authority shared by full-log replay and the
215
+ * incremental Session projection. One mutable Map/Set pair spans the whole
216
+ * batch; the returned arrays are materialized and frozen once.
217
+ * @param folded - complete active records and used-id history before the changes.
218
+ * @param changes - strictly decoded durable mutations in log order.
219
+ * @returns the complete fold value after every mutation.
220
+ */
221
+ function applyScheduleChanges(folded, changes) {
222
+ const active = new Map(folded.active.map((record) => [record.id, record]));
223
+ const seen = new Set(folded.seenIds);
224
+ for (const change of changes) switch (change.operation) {
225
+ case "create":
226
+ if (seen.has(change.schedule.id)) throw new ScheduleLogError(`schedule id ${JSON.stringify(change.schedule.id)} was reused`);
227
+ seen.add(change.schedule.id);
228
+ active.set(change.schedule.id, change.schedule);
229
+ break;
230
+ case "delete":
231
+ if (!active.delete(change.id)) throw new ScheduleLogError(`schedule delete targets inactive id ${JSON.stringify(change.id)}`);
232
+ break;
233
+ case "dispatch": {
234
+ const record = active.get(change.id);
235
+ if (record === void 0) throw new ScheduleLogError(`schedule dispatch targets inactive id ${JSON.stringify(change.id)}`);
236
+ const next = dispatchedRecord(record, change);
237
+ if (next === void 0) active.delete(change.id);
238
+ else active.set(change.id, next);
239
+ break;
240
+ }
241
+ /* v8 ignore next 3 -- decodeScheduleChange returns a closed operation union. */
242
+ default: throw new ScheduleLogError(`unknown decoded schedule change ${String(change)}`);
243
+ }
244
+ return Object.freeze({
245
+ active: Object.freeze([...active.values()]),
246
+ seenIds: Object.freeze([...seen])
247
+ });
248
+ }
249
+ /**
212
250
  * Fold the package-owned stream after the durable fork seed boundary.
213
251
  * @param events - Complete ordered session log or candidate-extended log.
214
252
  * @param seedLength - Inherited prefix length excluded from child ownership.
@@ -216,36 +254,14 @@ function dispatchedRecord(record, change) {
216
254
  */
217
255
  function foldScheduleEvents(events, seedLength = 0) {
218
256
  if (!Number.isSafeInteger(seedLength) || seedLength < 0 || seedLength > events.length) throw new ScheduleLogError("schedule seedLength must be within the supplied event log");
219
- const active = /* @__PURE__ */ new Map();
220
- const seen = /* @__PURE__ */ new Set();
221
- for (const event of events.slice(seedLength)) {
222
- if (event.type !== "schedule/change") continue;
223
- const change = decodeScheduleChange(event.data);
224
- switch (change.operation) {
225
- case "create":
226
- if (seen.has(change.schedule.id)) throw new ScheduleLogError(`schedule id ${JSON.stringify(change.schedule.id)} was reused`);
227
- seen.add(change.schedule.id);
228
- active.set(change.schedule.id, change.schedule);
229
- break;
230
- case "delete":
231
- if (!active.delete(change.id)) throw new ScheduleLogError(`schedule delete targets inactive id ${JSON.stringify(change.id)}`);
232
- break;
233
- case "dispatch": {
234
- const record = active.get(change.id);
235
- if (record === void 0) throw new ScheduleLogError(`schedule dispatch targets inactive id ${JSON.stringify(change.id)}`);
236
- const next = dispatchedRecord(record, change);
237
- if (next === void 0) active.delete(change.id);
238
- else active.set(change.id, next);
239
- break;
240
- }
241
- /* v8 ignore next 3 -- decodeScheduleChange returns a closed operation union. */
242
- default: throw new ScheduleLogError(`unknown decoded schedule change ${String(change)}`);
243
- }
244
- }
245
- return Object.freeze({
246
- active: Object.freeze([...active.values()]),
247
- seenIds: Object.freeze([...seen])
257
+ const initial = Object.freeze({
258
+ active: Object.freeze([]),
259
+ seenIds: Object.freeze([])
248
260
  });
261
+ const changes = function* () {
262
+ for (const event of events.slice(seedLength)) if (event.type === "schedule/change") yield decodeScheduleChange(event.data);
263
+ };
264
+ return applyScheduleChanges(initial, changes());
249
265
  }
250
266
  //#endregion
251
267
  //#region lib/types/invariant.js
@@ -0,0 +1,3 @@
1
+ /** Browser-safe Schedule vocabulary. @module @deepseek-ai/dsh-schedule/client */
2
+ export type * from './types.ts';
3
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.js.map
@@ -69,6 +69,17 @@ export declare function decodeScheduleChange(value: unknown): ScheduleChange;
69
69
  * @returns The latest due occurrence and first strictly future target, if representable.
70
70
  */
71
71
  export declare function resolveEveryOccurrence(record: EveryScheduleRecord, acceptedAt: number): EveryOccurrence;
72
+ /**
73
+ * Apply already-decoded Schedule changes to one complete fold value.
74
+ *
75
+ * This is the single transition authority shared by full-log replay and the
76
+ * incremental Session projection. One mutable Map/Set pair spans the whole
77
+ * batch; the returned arrays are materialized and frozen once.
78
+ * @param folded - complete active records and used-id history before the changes.
79
+ * @param changes - strictly decoded durable mutations in log order.
80
+ * @returns the complete fold value after every mutation.
81
+ */
82
+ export declare function applyScheduleChanges(folded: FoldedSchedules, changes: Iterable<ScheduleChange>): FoldedSchedules;
72
83
  /**
73
84
  * Fold the package-owned stream after the durable fork seed boundary.
74
85
  * @param events - Complete ordered session log or candidate-extended log.