@earendil-works/pi-durable 1.0.4 → 1.1.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 (63) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +2 -0
  3. package/dist/harness/agent.d.ts.map +1 -1
  4. package/dist/harness/agent.js +3 -0
  5. package/dist/harness/agent.js.map +1 -1
  6. package/dist/harness/compaction.js +1 -1
  7. package/dist/harness/compaction.js.map +1 -1
  8. package/dist/harness/context.d.ts +29 -1
  9. package/dist/harness/context.d.ts.map +1 -1
  10. package/dist/harness/context.js +125 -24
  11. package/dist/harness/context.js.map +1 -1
  12. package/dist/harness/generation.d.ts.map +1 -1
  13. package/dist/harness/generation.js +3 -3
  14. package/dist/harness/generation.js.map +1 -1
  15. package/dist/harness/harness.d.ts.map +1 -1
  16. package/dist/harness/harness.js +4 -3
  17. package/dist/harness/harness.js.map +1 -1
  18. package/dist/harness/scheduler.d.ts.map +1 -1
  19. package/dist/harness/scheduler.js +98 -5
  20. package/dist/harness/scheduler.js.map +1 -1
  21. package/dist/harness/tool.d.ts +1 -1
  22. package/dist/harness/tool.d.ts.map +1 -1
  23. package/dist/harness/tool.js +15 -5
  24. package/dist/harness/tool.js.map +1 -1
  25. package/dist/harness/types.d.ts +18 -2
  26. package/dist/harness/types.d.ts.map +1 -1
  27. package/dist/harness/types.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/session/session.d.ts +5 -3
  32. package/dist/session/session.d.ts.map +1 -1
  33. package/dist/session/session.js +5 -4
  34. package/dist/session/session.js.map +1 -1
  35. package/dist/session/transaction.d.ts +2 -0
  36. package/dist/session/transaction.d.ts.map +1 -1
  37. package/dist/session/transaction.js +18 -1
  38. package/dist/session/transaction.js.map +1 -1
  39. package/dist/storage/jsonl/storage.d.ts +4 -0
  40. package/dist/storage/jsonl/storage.d.ts.map +1 -1
  41. package/dist/storage/memory.d.ts +2 -0
  42. package/dist/storage/memory.d.ts.map +1 -1
  43. package/dist/storage/memory.js +72 -18
  44. package/dist/storage/memory.js.map +1 -1
  45. package/dist/storage/scan.d.ts +18 -0
  46. package/dist/storage/scan.d.ts.map +1 -0
  47. package/dist/storage/scan.js +28 -0
  48. package/dist/storage/scan.js.map +1 -0
  49. package/dist/storage/sqlite/cloudflare.d.ts +46 -0
  50. package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
  51. package/dist/storage/sqlite/cloudflare.js +106 -0
  52. package/dist/storage/sqlite/cloudflare.js.map +1 -0
  53. package/dist/storage/sqlite/storage.d.ts +2 -0
  54. package/dist/storage/sqlite/storage.d.ts.map +1 -1
  55. package/dist/storage/sqlite/storage.js +68 -16
  56. package/dist/storage/sqlite/storage.js.map +1 -1
  57. package/dist/testing/storage-conformance.d.ts.map +1 -1
  58. package/dist/testing/storage-conformance.js +58 -0
  59. package/dist/testing/storage-conformance.js.map +1 -1
  60. package/dist/types.d.ts +30 -7
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js.map +1 -1
  63. package/package.json +10 -4
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/harness/scheduler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/harness/scheduler.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { copyJson } from "@earendil-works/chord";
2
2
  import { awaitWithContext, withAbortSignal } from "@earendil-works/chord/context";
3
3
  import { agentHooks } from "./agent.js";
4
- import { readContext } from "./context.js";
4
+ import { readContextFrom } from "./context.js";
5
5
  import { closedError, scanAll, Waiters } from "./util.js";
6
6
  const SCAN_PAGE_SIZE = 256;
7
7
  /** Longest delay `setTimeout` supports; longer sleeps wait in several steps. */
@@ -37,6 +37,14 @@ export class TaskScheduler {
37
37
  #conversation;
38
38
  #context;
39
39
  #live = new Map();
40
+ /**
41
+ * Context range last read through a task runtime, per conversation: a later read, by any of its tasks, scans only
42
+ * newer entries. `idleSince` is the Harness time it was first seen idle. Derived and never persisted; dropped at the
43
+ * first idle check after `settings.contextRetentionMs` of idleness, and at close.
44
+ */
45
+ #contexts = new Map();
46
+ /** Timer for the earliest expiry of an idle context, only where timers can be unreferenced (see `#scheduleExpiry`). */
47
+ #expiry;
40
48
  #invocations = new Map();
41
49
  #taskWaiters = new Waiters();
42
50
  /** Idle waiters by conversation; `undefined` waits for the whole Harness. */
@@ -279,14 +287,77 @@ export class TaskScheduler {
279
287
  this.#scheduleReconcile();
280
288
  if (!changed)
281
289
  return;
282
- this.#resolveIdleWaiters();
290
+ this.#settleIdle();
283
291
  this.#kick();
284
292
  }
285
- #resolveIdleWaiters() {
293
+ /**
294
+ * `settings.contextRetentionMs`, or 0 when the host's settings throw. Kept contexts are only a cache, so dropping them
295
+ * is safe, while a throw here would escape commit listeners, reconciliation, and the expiry timer.
296
+ */
297
+ #contextRetentionMs() {
298
+ try {
299
+ return this.#settings().contextRetentionMs;
300
+ }
301
+ catch (error) {
302
+ this.#report(error);
303
+ return 0;
304
+ }
305
+ }
306
+ /** Resolve idle waiters, and drop each kept context whose conversation has been idle for the retention period. */
307
+ #settleIdle() {
286
308
  for (const conversationId of this.#idleWaiters.keys()) {
287
309
  if (this.#idle(conversationId))
288
310
  this.#idleWaiters.resolve(conversationId);
289
311
  }
312
+ const now = this.#now();
313
+ const retention = this.#contextRetentionMs();
314
+ for (const [conversationId, kept] of this.#contexts) {
315
+ if (kept.idleSince !== undefined && now - kept.idleSince >= retention) {
316
+ this.#contexts.delete(conversationId);
317
+ }
318
+ else if (!this.#idle(conversationId)) {
319
+ kept.idleSince = undefined;
320
+ }
321
+ else if (retention > 0) {
322
+ kept.idleSince ??= now;
323
+ }
324
+ else {
325
+ this.#contexts.delete(conversationId);
326
+ }
327
+ }
328
+ this.#scheduleExpiry();
329
+ }
330
+ /**
331
+ * Run `#settleIdle()` when the earliest idle context expires. The timer is unreferenced, so it never keeps the process
332
+ * alive. Where timers cannot be unreferenced, as in Cloudflare Workers, none is kept: a pending timer could keep a
333
+ * Durable Object from being evicted, and eviction frees the contexts. There, task changes alone check expiry.
334
+ */
335
+ #scheduleExpiry() {
336
+ const retention = this.#contextRetentionMs();
337
+ let at;
338
+ for (const kept of this.#contexts.values()) {
339
+ if (kept.idleSince !== undefined && (at === undefined || kept.idleSince + retention < at)) {
340
+ at = kept.idleSince + retention;
341
+ }
342
+ }
343
+ if (this.#expiry !== undefined && this.#expiry.at === at)
344
+ return;
345
+ if (this.#expiry !== undefined)
346
+ clearTimeout(this.#expiry.timer);
347
+ this.#expiry = undefined;
348
+ if (at === undefined || this.#closing)
349
+ return;
350
+ const timer = setTimeout(() => {
351
+ this.#expiry = undefined;
352
+ this.#settleIdle();
353
+ }, Math.min(Math.max(0, at - this.#now()), MAX_TIMER_DELAY));
354
+ const unref = timer.unref;
355
+ if (typeof unref !== "function") {
356
+ clearTimeout(timer);
357
+ return;
358
+ }
359
+ unref.call(timer);
360
+ this.#expiry = { at, timer };
290
361
  }
291
362
  // ─── Ownership ───────────────────────────────────────────────────────────
292
363
  #scheduleReconcile() {
@@ -349,7 +420,7 @@ export class TaskScheduler {
349
420
  if (!this.#closing)
350
421
  this.#report(error);
351
422
  }
352
- this.#resolveIdleWaiters();
423
+ this.#settleIdle();
353
424
  }
354
425
  /** Whether any of `ids` holds or ended with an outcome other than `completed`. */
355
426
  async #anyFailed(ids) {
@@ -552,6 +623,10 @@ export class TaskScheduler {
552
623
  const error = closedError();
553
624
  this.#taskWaiters.rejectAll(error);
554
625
  this.#idleWaiters.rejectAll(error);
626
+ this.#contexts.clear();
627
+ if (this.#expiry !== undefined)
628
+ clearTimeout(this.#expiry.timer);
629
+ this.#expiry = undefined;
555
630
  for (const invocation of this.#invocations.values())
556
631
  invocation.controller.abort();
557
632
  }
@@ -1022,7 +1097,25 @@ export class TaskScheduler {
1022
1097
  return token === undefined || entry?.kind === token.kind ? entry : undefined;
1023
1098
  });
1024
1099
  }),
1025
- context: (conversationId, context, at) => this.#read(invocation, () => readContext(this.#session, this.#storage, conversationId, context, at)),
1100
+ context: (conversationId, context, options) => this.#read(invocation, async () => {
1101
+ const { view, range } = await readContextFrom(this.#session, this.#storage, conversationId, context, options?.at, this.#contexts.get(conversationId)?.range);
1102
+ // Keep it unless the invocation ended or a concurrent read already kept a newer range.
1103
+ const kept = this.#contexts.get(conversationId);
1104
+ if (range !== undefined &&
1105
+ !this.#closing &&
1106
+ !invocation.ended &&
1107
+ (kept === undefined || kept.range.bounds.tail <= range.bounds.tail)) {
1108
+ // A read of another, idle conversation starts or continues its retention period.
1109
+ if (!this.#idle(conversationId)) {
1110
+ this.#contexts.set(conversationId, { range, idleSince: undefined });
1111
+ }
1112
+ else if (this.#contextRetentionMs() > 0) {
1113
+ this.#contexts.set(conversationId, { range, idleSince: kept?.idleSince ?? this.#now() });
1114
+ this.#scheduleExpiry();
1115
+ }
1116
+ }
1117
+ return view;
1118
+ }),
1026
1119
  now: () => {
1027
1120
  if (invocation.ended)
1028
1121
  throw endedError(invocation);