@demlik/tea 0.4.0 → 0.5.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.
Files changed (127) hide show
  1. package/LICENSE +1 -1
  2. package/dist/agent/index.d.ts +5 -5
  3. package/dist/agent/index.js +10 -10
  4. package/dist/authed-call/index.d.ts +3 -3
  5. package/dist/authed-call/index.js +7 -7
  6. package/dist/await-terminal/index.d.ts +2 -2
  7. package/dist/await-terminal/index.js +3 -2
  8. package/dist/await-terminal/index.js.map +1 -1
  9. package/dist/batch-window/index.d.ts +4 -4
  10. package/dist/batch-window/index.js +3 -3
  11. package/dist/cache/index.d.ts +2 -2
  12. package/dist/cache/index.js +4 -4
  13. package/dist/{chunk-TWDHNIY6.js → chunk-2KBEIGYP.js} +3 -3
  14. package/dist/{chunk-TWDHNIY6.js.map → chunk-2KBEIGYP.js.map} +1 -1
  15. package/dist/{chunk-LYLDCIPH.js → chunk-2UGTDYOK.js} +32 -25
  16. package/dist/chunk-2UGTDYOK.js.map +1 -0
  17. package/dist/{chunk-LJJV6ZD5.js → chunk-66S2KEQQ.js} +4 -4
  18. package/dist/{chunk-LJJV6ZD5.js.map → chunk-66S2KEQQ.js.map} +1 -1
  19. package/dist/{chunk-2AHAR7CX.js → chunk-A7USKARI.js} +3 -3
  20. package/dist/{chunk-2AHAR7CX.js.map → chunk-A7USKARI.js.map} +1 -1
  21. package/dist/{chunk-RMX4EOQK.js → chunk-ASX7PL2K.js} +3 -3
  22. package/dist/{chunk-RMX4EOQK.js.map → chunk-ASX7PL2K.js.map} +1 -1
  23. package/dist/{chunk-JFFVBEDI.js → chunk-AZ3NHUPD.js} +3 -3
  24. package/dist/{chunk-JFFVBEDI.js.map → chunk-AZ3NHUPD.js.map} +1 -1
  25. package/dist/{chunk-7FYQGDMF.js → chunk-AZZLE7XR.js} +3 -3
  26. package/dist/{chunk-7FYQGDMF.js.map → chunk-AZZLE7XR.js.map} +1 -1
  27. package/dist/{chunk-B5RAZVXW.js → chunk-EC6FO42Q.js} +6 -6
  28. package/dist/{chunk-B5RAZVXW.js.map → chunk-EC6FO42Q.js.map} +1 -1
  29. package/dist/{chunk-FCKP6N7Y.js → chunk-ERYY5P6W.js} +3 -3
  30. package/dist/{chunk-FCKP6N7Y.js.map → chunk-ERYY5P6W.js.map} +1 -1
  31. package/dist/{chunk-ET6QQNSQ.js → chunk-IVHQCVFX.js} +3 -3
  32. package/dist/{chunk-ET6QQNSQ.js.map → chunk-IVHQCVFX.js.map} +1 -1
  33. package/dist/{chunk-PBDYW2TU.js → chunk-O7ED34JH.js} +16 -3
  34. package/dist/chunk-O7ED34JH.js.map +1 -0
  35. package/dist/{chunk-SSHDBOXF.js → chunk-S6Y3RNX4.js} +4 -4
  36. package/dist/{chunk-SSHDBOXF.js.map → chunk-S6Y3RNX4.js.map} +1 -1
  37. package/dist/{chunk-5W7CNAHB.js → chunk-UV7VCTNC.js} +7 -7
  38. package/dist/{chunk-5W7CNAHB.js.map → chunk-UV7VCTNC.js.map} +1 -1
  39. package/dist/{chunk-2KNLKQVN.js → chunk-WF46FABO.js} +3 -3
  40. package/dist/{chunk-2KNLKQVN.js.map → chunk-WF46FABO.js.map} +1 -1
  41. package/dist/{chunk-SPWVPIFQ.js → chunk-XMHZ44QC.js} +4 -4
  42. package/dist/{chunk-SPWVPIFQ.js.map → chunk-XMHZ44QC.js.map} +1 -1
  43. package/dist/{chunk-JXYYRKD6.js → chunk-XOAP3LOT.js} +3 -3
  44. package/dist/{chunk-JXYYRKD6.js.map → chunk-XOAP3LOT.js.map} +1 -1
  45. package/dist/{chunk-D4NJBQAH.js → chunk-ZH454CR5.js} +97 -24
  46. package/dist/chunk-ZH454CR5.js.map +1 -0
  47. package/dist/{core-42kapW0n.d.ts → core-JPHsMZVB.d.ts} +9 -6
  48. package/dist/deadline/index.d.ts +2 -2
  49. package/dist/deadline/index.js +3 -3
  50. package/dist/do/index.d.ts +9 -9
  51. package/dist/do/index.js +10 -10
  52. package/dist/do/index.js.map +1 -1
  53. package/dist/{durable-effects-CFRngTNA.d.ts → durable-effects-MRnCBCxV.d.ts} +2 -2
  54. package/dist/extension/index.d.ts +2 -2
  55. package/dist/extension/index.js +3 -3
  56. package/dist/extension/react.js +3 -3
  57. package/dist/extension/subs/index.d.ts +2 -2
  58. package/dist/extension/test-utils.d.ts +1 -1
  59. package/dist/extension/test-utils.js.map +1 -1
  60. package/dist/fan-out/index.d.ts +2 -2
  61. package/dist/idempotent-intake/index.d.ts +2 -2
  62. package/dist/index.d.ts +18 -4
  63. package/dist/index.js +2 -2
  64. package/dist/llm-call/index.d.ts +3 -3
  65. package/dist/llm-call/index.js +7 -7
  66. package/dist/{machine-B_BF4jij.d.ts → machine-nGXCMyHn.d.ts} +3 -3
  67. package/dist/machine-viz/index.d.ts +1 -1
  68. package/dist/machine-viz/index.js +2 -2
  69. package/dist/mem/index.d.ts +2 -2
  70. package/dist/monitored-run/index.d.ts +4 -4
  71. package/dist/monitored-run/index.js +5 -5
  72. package/dist/node/index.d.ts +2 -2
  73. package/dist/paginated-walk/index.d.ts +3 -3
  74. package/dist/paginated-walk/index.js +7 -7
  75. package/dist/parity/index.d.ts +2 -2
  76. package/dist/parity/index.js +3 -3
  77. package/dist/pbt/arbitraries/index.d.ts +3 -3
  78. package/dist/pbt/arbitraries/index.js +1 -1
  79. package/dist/pbt/index.d.ts +1 -1
  80. package/dist/pbt/index.js +4 -4
  81. package/dist/pbt/runners/index.d.ts +1 -1
  82. package/dist/pbt/runners/index.js +3 -3
  83. package/dist/poller/index.d.ts +3 -3
  84. package/dist/poller/index.js +3 -3
  85. package/dist/prediction/index.d.ts +1 -1
  86. package/dist/prediction/index.js +2 -2
  87. package/dist/pure/index.d.ts +1 -1
  88. package/dist/pure/index.js +2 -2
  89. package/dist/react/index.d.ts +2 -2
  90. package/dist/react/index.js +2 -2
  91. package/dist/reconciler/index.d.ts +3 -3
  92. package/dist/reconciler/index.js +7 -7
  93. package/dist/recorder/index.d.ts +2 -2
  94. package/dist/resilient-call/index.d.ts +2 -2
  95. package/dist/resilient-call/index.js +6 -6
  96. package/dist/retry-to-success/index.js +2 -0
  97. package/dist/retry-to-success/index.js.map +1 -1
  98. package/dist/{runtime-types-CukV8kMX.d.ts → runtime-types-BbBq4LWo.d.ts} +50 -5
  99. package/dist/saga/index.d.ts +2 -2
  100. package/dist/snapshot/index.d.ts +1 -1
  101. package/dist/snapshot/index.js +3 -3
  102. package/dist/subs/index.d.ts +16 -9
  103. package/dist/subs/index.js +3 -3
  104. package/dist/testing/index.d.ts +2 -2
  105. package/dist/testing/index.js +2 -2
  106. package/dist/throttled-input/index.d.ts +2 -2
  107. package/dist/throttled-input/index.js +6 -6
  108. package/dist/token-refresh/index.d.ts +1 -1
  109. package/dist/token-refresh/index.js +3 -3
  110. package/dist/trace-replay/index.d.ts +2 -2
  111. package/dist/trace-replay/index.js +3 -3
  112. package/dist/{types-KbL3W1mj.d.ts → types-BMZ7IMrd.d.ts} +1 -1
  113. package/dist/with-deadline/index.d.ts +1 -1
  114. package/dist/with-deadline/index.js +2 -2
  115. package/dist/with-resilience/index.d.ts +2 -2
  116. package/dist/with-resilience/index.js +6 -6
  117. package/dist/with-telemetry/index.d.ts +1 -1
  118. package/dist/with-telemetry/index.js +2 -2
  119. package/dist/work-queue/adapter.d.ts +2 -2
  120. package/dist/work-queue/index.d.ts +3 -3
  121. package/dist/work-queue/index.js.map +1 -1
  122. package/dist/work-queue/ops.d.ts +2 -2
  123. package/dist/workflow/index.d.ts +3 -3
  124. package/package.json +12 -4
  125. package/dist/chunk-D4NJBQAH.js.map +0 -1
  126. package/dist/chunk-LYLDCIPH.js.map +0 -1
  127. package/dist/chunk-PBDYW2TU.js.map +0 -1
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Umut Sirin
3
+ Copyright (c) 2024 Can Sirin
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,19 +1,19 @@
1
- import { C as Cmd } from '../core-42kapW0n.js';
1
+ import { C as Cmd } from '../core-JPHsMZVB.js';
2
2
  export { LlmCall, LlmErr, LlmFailMsg, LlmOk, LlmRunCmd, LlmSucceedMsg, MessageLoader, ModelFactory, Schema } from '../llm-call/index.js';
3
- import { A as AgentTurn, a as AgentConfigCore, C as CompactionPolicy, b as AgentKnob } from '../machine-B_BF4jij.js';
4
- export { v as AgentBootMsg, o as AgentCmd, l as AgentCompactErrMsg, k as AgentCompactOkMsg, j as AgentCompactRunCmd, h as AgentCompactionConfig, L as AgentConfig, u as AgentDetachedHandlers, y as AgentEvent, M as AgentFailure, r as AgentLlmErrMsg, q as AgentLlmOkMsg, n as AgentLlmRunCmd, x as AgentMachineMsg, t as AgentPorts, K as AgentSnapshotConfig, N as AgentState, P as AgentStatus, O as AgentTerminalFailure, s as AgentTimerMsg, p as AgentToMachine, I as Awaiting, d as COMPACTION_PURPOSE, m as CompactInterpret, e as CompactionOutputs, c as CompactionPurpose, g as CompactionSummary, J as Conversation, S as SnapshotInterpret, T as ToolCall, G as ToolOutcome, H as ToolRecord, w as agentBootMsg, z as agentEvents, F as agentTurnSchema, f as compactionSummarySchema, E as isAgentTurn, i as isCompactionSummary, D as liftAgent, B as mergeInterpret, Q as status } from '../machine-B_BF4jij.js';
3
+ import { A as AgentTurn, a as AgentConfigCore, C as CompactionPolicy, b as AgentKnob } from '../machine-nGXCMyHn.js';
4
+ export { c as AgentBootMsg, d as AgentCmd, e as AgentCompactErrMsg, f as AgentCompactOkMsg, g as AgentCompactRunCmd, h as AgentCompactionConfig, i as AgentConfig, j as AgentDetachedHandlers, k as AgentEvent, l as AgentFailure, m as AgentLlmErrMsg, n as AgentLlmOkMsg, o as AgentLlmRunCmd, p as AgentMachineMsg, q as AgentPorts, r as AgentSnapshotConfig, s as AgentState, t as AgentStatus, u as AgentTerminalFailure, v as AgentTimerMsg, w as AgentToMachine, x as Awaiting, y as COMPACTION_PURPOSE, z as CompactInterpret, B as CompactionOutputs, D as CompactionPurpose, E as CompactionSummary, F as Conversation, S as SnapshotInterpret, T as ToolCall, G as ToolOutcome, H as ToolRecord, I as agentBootMsg, J as agentEvents, K as agentTurnSchema, L as compactionSummarySchema, M as isAgentTurn, N as isCompactionSummary, O as liftAgent, P as mergeInterpret, Q as status } from '../machine-nGXCMyHn.js';
5
5
  export { MonitoredRunCmd, RunFailure } from '../monitored-run/index.js';
6
6
  export { DeadlineSub, deadlineSub, subscribeDeadline } from '../deadline/index.js';
7
7
  import '../resilient-call/index.js';
8
8
  import '../cache/index.js';
9
- import '../types-KbL3W1mj.js';
9
+ import '../types-BMZ7IMrd.js';
10
10
  import '../circuit-breaker/index.js';
11
11
  import '../protocol-B5YmJr0d.js';
12
12
  import '../rate-limit/index.js';
13
13
  import '../retry-backoff/index.js';
14
14
  import '../fan-out/index.js';
15
15
  import '../work-queue/index.js';
16
- import '../runtime-types-CukV8kMX.js';
16
+ import '../runtime-types-BbBq4LWo.js';
17
17
  import '../snapshot/index.js';
18
18
 
19
19
  /**
@@ -1,22 +1,22 @@
1
- export { COMPACTION_PURPOSE, agentBootMsg, agentEvents, agentTurnSchema, compactionSummarySchema, createAgent, isAgentTurn, isCompactionSummary, liftAgent, mergeInterpret, status } from '../chunk-5W7CNAHB.js';
2
- import '../chunk-LJJV6ZD5.js';
3
- import '../chunk-FCKP6N7Y.js';
4
- import '../chunk-SPWVPIFQ.js';
5
- import '../chunk-B5RAZVXW.js';
1
+ export { COMPACTION_PURPOSE, agentBootMsg, agentEvents, agentTurnSchema, compactionSummarySchema, createAgent, isAgentTurn, isCompactionSummary, liftAgent, mergeInterpret, status } from '../chunk-UV7VCTNC.js';
2
+ import '../chunk-66S2KEQQ.js';
3
+ import '../chunk-ERYY5P6W.js';
4
+ import '../chunk-XMHZ44QC.js';
5
+ import '../chunk-EC6FO42Q.js';
6
6
  import '../chunk-JELSZN25.js';
7
7
  import '../chunk-D736QUGF.js';
8
- export { deadlineSub, subscribeDeadline } from '../chunk-RMX4EOQK.js';
8
+ export { deadlineSub, subscribeDeadline } from '../chunk-ASX7PL2K.js';
9
9
  import '../chunk-Q2UM7BXU.js';
10
10
  import '../chunk-S2EGRPCM.js';
11
11
  import '../chunk-UF4MZSLT.js';
12
- import '../chunk-2KNLKQVN.js';
12
+ import '../chunk-WF46FABO.js';
13
13
  import '../chunk-MU6MCIAC.js';
14
14
  import '../chunk-FV3Z5MBE.js';
15
- import '../chunk-LYLDCIPH.js';
15
+ import '../chunk-2UGTDYOK.js';
16
16
  import '../chunk-2YFLOCWX.js';
17
17
  import '../chunk-ZJ5AOYBH.js';
18
18
  import '../chunk-G5E74IJB.js';
19
- import '../chunk-D4NJBQAH.js';
20
- import '../chunk-PBDYW2TU.js';
19
+ import '../chunk-ZH454CR5.js';
20
+ import '../chunk-O7ED34JH.js';
21
21
  //# sourceMappingURL=index.js.map
22
22
  //# sourceMappingURL=index.js.map
@@ -1,11 +1,11 @@
1
- import { b as PortEmitter, C as Cmd } from '../core-42kapW0n.js';
1
+ import { P as PortEmitter, C as Cmd } from '../core-JPHsMZVB.js';
2
2
  import { M as MsgType } from '../protocol-B5YmJr0d.js';
3
- import { ResilientConfig, ResilientState, RunCmd, ResilientPorts, SucceedMsg, FailMsg, ResilientTimerMsg } from '../resilient-call/index.js';
3
+ import { ResilientConfig, ResilientState, RunCmd, SucceedMsg, FailMsg, ResilientTimerMsg, ResilientPorts } from '../resilient-call/index.js';
4
4
  import { TokenRefreshConfig, Token, TokenState, RefreshTokenCmd, TokenRefreshMsg } from '../token-refresh/index.js';
5
5
  import { DeadlineSub } from '../deadline/index.js';
6
6
  export { DeadlineExceeded, deadlineSub, subscribeDeadline } from '../deadline/index.js';
7
7
  import '../cache/index.js';
8
- import '../types-KbL3W1mj.js';
8
+ import '../types-BMZ7IMrd.js';
9
9
  import '../circuit-breaker/index.js';
10
10
  import '../rate-limit/index.js';
11
11
  import '../retry-backoff/index.js';
@@ -1,16 +1,16 @@
1
- import { createTokenRefresh, refreshTokenCmd } from '../chunk-TWDHNIY6.js';
2
- import { createResilientCall } from '../chunk-B5RAZVXW.js';
1
+ import { createTokenRefresh, refreshTokenCmd } from '../chunk-2KBEIGYP.js';
2
+ import { createResilientCall } from '../chunk-EC6FO42Q.js';
3
3
  import '../chunk-JELSZN25.js';
4
- export { deadlineSub, subscribeDeadline } from '../chunk-RMX4EOQK.js';
4
+ export { deadlineSub, subscribeDeadline } from '../chunk-ASX7PL2K.js';
5
5
  import '../chunk-Q2UM7BXU.js';
6
6
  import '../chunk-S2EGRPCM.js';
7
7
  import '../chunk-UF4MZSLT.js';
8
- import '../chunk-2KNLKQVN.js';
9
- import '../chunk-LYLDCIPH.js';
8
+ import '../chunk-WF46FABO.js';
9
+ import '../chunk-2UGTDYOK.js';
10
10
  import '../chunk-2YFLOCWX.js';
11
11
  import '../chunk-ZJ5AOYBH.js';
12
- import '../chunk-D4NJBQAH.js';
13
- import { without } from '../chunk-PBDYW2TU.js';
12
+ import '../chunk-ZH454CR5.js';
13
+ import { without } from '../chunk-O7ED34JH.js';
14
14
 
15
15
  // src/authed-call/index.ts
16
16
  function createAuthedCall(config, rng = Math.random) {
@@ -1,5 +1,5 @@
1
- import { R as Runtime, C as CtxArg } from '../runtime-types-CukV8kMX.js';
2
- import { C as Cmd, S as Sub, M as Machine } from '../core-42kapW0n.js';
1
+ import { R as Runtime, C as CtxArg } from '../runtime-types-BbBq4LWo.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../core-JPHsMZVB.js';
3
3
 
4
4
  /**
5
5
  * @demlik/tea/await-terminal — run a ONE-SHOT / decision-procedure machine to a
@@ -1,5 +1,5 @@
1
- import { run } from '../chunk-D4NJBQAH.js';
2
- import '../chunk-PBDYW2TU.js';
1
+ import { run } from '../chunk-ZH454CR5.js';
2
+ import '../chunk-O7ED34JH.js';
3
3
 
4
4
  // src/await-terminal/index.ts
5
5
  var TerminalTimeoutError = class extends Error {
@@ -9,6 +9,7 @@ var TerminalTimeoutError = class extends Error {
9
9
  );
10
10
  this.timeoutMs = timeoutMs;
11
11
  }
12
+ timeoutMs;
12
13
  name = "TerminalTimeoutError";
13
14
  _tag = "TerminalTimeoutError";
14
15
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/await-terminal/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/await-terminal — run a ONE-SHOT / decision-procedure machine to a\n * terminal state and hand the caller a `Promise<State>` that resolves the moment\n * the machine FIRST enters a terminal state.\n *\n * This is the ~10 lines both audit suitability probes hand-wired at their\n * integration seam: a one-shot decision machine (a credibility check, an auth\n * mint, a validation pass — run to a terminal state, then throw-or-proceed at the\n * caller boundary) has no built-in \"await the terminal\", so each consumer re-wires\n * a promise off `observe` that resolves when the state is terminal. This helper\n * lifts that boilerplate into `@demlik/tea` once.\n *\n * It rides the EXISTING observation surface (`packages/tea/src/index.ts`) and adds\n * NO new runtime capability:\n * - `runtime.getState()` (total on a booted `Runtime`) covers the\n * already-terminal-at-attach case — resolve synchronously rather than wait for\n * a transition that may never come.\n * - `runtime.observe((msg, state) => void): () => void` fires on EVERY transition\n * and returns an unsubscribe — the \"first terminal transition\" signal, detached\n * the instant it resolves (no leak, no busy-wait / polling loop).\n * - `runtime.stop()` is the teardown the fire-and-await `runToTerminal` owns.\n *\n * `isTerminal` is caller-supplied — tea has no built-in terminal-set concept and\n * this helper does NOT invent one; the caller names their own terminal states.\n *\n * The optional `timeoutMs` is a plain caller-boundary timer (not an in-loop\n * progress deadline — see `../deadline` / `../with-deadline` for that shape): it\n * clears on resolve and REJECTS with the `instanceof`-checkable\n * {@link TerminalTimeoutError} if the deadline elapses first, never silently\n * resolving.\n */\n\nimport {\n type Cmd,\n type CtxArg,\n type Machine,\n type Runtime,\n run,\n type Sub,\n} from \"../index\";\n\n/**\n * Raised when `awaitTerminal` / `runToTerminal` is wired with a `timeoutMs` and\n * the deadline elapses before any terminal state is reached. Follows the tea\n * error dialect (`override readonly name`) and carries a `_tag` discriminant, so\n * a caller can branch on it either by `instanceof` or on the tag — the timeout is\n * a distinct, checkable rejection, never a silent resolve.\n */\nexport class TerminalTimeoutError extends Error {\n override readonly name = \"TerminalTimeoutError\";\n readonly _tag = \"TerminalTimeoutError\" as const;\n constructor(public readonly timeoutMs: number) {\n super(\n `@demlik/tea: awaitTerminal did not reach a terminal state within ` +\n `${timeoutMs}ms — no transition satisfied isTerminal before the deadline.`,\n );\n }\n}\n\n/** Options shared by `awaitTerminal` and `runToTerminal`. */\nexport interface AwaitTerminalOptions {\n /**\n * Optional bounded deadline in milliseconds. When set, the returned promise\n * REJECTS with {@link TerminalTimeoutError} if no terminal state is reached\n * within the window; the timer is cleared on the resolve path so a resolved\n * await never later rejects or leaves a dangling timer. Omit for an unbounded\n * await (never rejects for time).\n */\n readonly timeoutMs?: number;\n}\n\n/**\n * Attach to an ALREADY-RUNNING `Runtime<S, M, E>` and resolve with the terminal\n * `S` on the first transition for which `isTerminal(state)` is true.\n *\n * If the runtime is ALREADY terminal at attach time (`isTerminal(getState())`),\n * resolves immediately — it never hangs waiting for a transition that will not\n * come. Otherwise it subscribes via `observe` and detaches that subscription the\n * instant it resolves (or times out), so there is no observer leak and no\n * further callbacks fire.\n *\n * @param runtime a booted `Runtime` (from `await run(machine, opts).ready`).\n * @param isTerminal caller-supplied terminal predicate over the machine's state.\n * @param opts optional bounded `timeoutMs` deadline (see {@link AwaitTerminalOptions}).\n */\nexport function awaitTerminal<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n>(\n runtime: Runtime<S, M, E>,\n isTerminal: (state: S) => boolean,\n opts: AwaitTerminalOptions = {},\n): Promise<S> {\n return new Promise<S>((resolve, reject) => {\n // Already-terminal-at-attach: resolve now. Returning here attaches neither an\n // observer nor a timer, so the immediate case cannot leak either.\n const current = runtime.getState();\n if (isTerminal(current)) {\n resolve(current);\n return;\n }\n\n let settled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let detach: (() => void) | undefined;\n\n const cleanup = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n if (detach !== undefined) {\n detach();\n detach = undefined;\n }\n };\n\n detach = runtime.observe((_msg, state) => {\n if (settled) return;\n if (isTerminal(state)) {\n settled = true;\n cleanup();\n resolve(state);\n }\n });\n\n // Narrow into a const so the closure keeps the `number` narrowing (the field\n // itself is `number | undefined`).\n const timeoutMs = opts.timeoutMs;\n if (timeoutMs !== undefined) {\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(new TerminalTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n });\n}\n\n/**\n * Fire-and-await convenience: `run()` the machine, dispatch the seed `msgs`, and\n * resolve with the terminal state — then tear the runtime down (`runtime.stop()`)\n * on BOTH the resolve and reject paths. Wraps {@link awaitTerminal}.\n *\n * The seed carries the machine's `ctx` (conditionally optional, exactly as\n * `run`'s `ctx` — a pure machine omits it) and the `msgs` to dispatch once\n * booted. A `timeoutMs` in `opts` bounds the await identically to `awaitTerminal`.\n *\n * @param machine the machine to boot.\n * @param seed `{ ctx?, msgs }` — the boot context and the seed messages.\n * @param isTerminal caller-supplied terminal predicate over the machine's state.\n * @param opts optional bounded `timeoutMs` deadline (see {@link AwaitTerminalOptions}).\n */\nexport async function runToTerminal<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n seed: CtxArg<Ctx> & { readonly msgs: readonly M[] },\n isTerminal: (state: S) => boolean,\n opts: AwaitTerminalOptions = {},\n): Promise<S> {\n // Split the seed messages out from the boot context: `run`'s opts don't carry\n // `msgs`, so what remains is exactly the conditionally-optional `ctx` arg. The\n // cast is because `CtxArg<Ctx>` is a deferred generic conditional TS won't\n // verify against `run`'s all-optional opts (its weak-type check) — the value\n // is structurally valid; `run` defaults an absent `ctx` to `{}`.\n const { msgs, ...ctxArg } = seed;\n const runtime = await run<S, M, C, U, Ctx>(\n machine,\n ctxArg as Parameters<typeof run<S, M, C, U, Ctx>>[1],\n ).ready;\n try {\n // Attach the terminal await BEFORE dispatching so a terminal transition that\n // lands during a seed dispatch is caught (and the immediate-terminal check\n // inside awaitTerminal covers a machine that boots already terminal).\n const terminal = awaitTerminal(runtime, isTerminal, opts);\n // Dispatch runs serially on the runtime's tail; fire all seeds and let a\n // dispatch failure (a reducer/interpret throw) surface by racing it against\n // the terminal await rather than floating as an unhandled rejection.\n const seeding = Promise.all(msgs.map((msg) => runtime.dispatch(msg)));\n const state = await Promise.race([terminal, seeding.then(() => terminal)]);\n await runtime.stop();\n return state;\n } catch (err) {\n // Tear down on the reject path too (timeout, dispatch failure) — a rejected\n // runToTerminal never leaves the runtime running.\n await runtime.stop().catch(() => {});\n throw err;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/await-terminal/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/await-terminal — run a ONE-SHOT / decision-procedure machine to a\n * terminal state and hand the caller a `Promise<State>` that resolves the moment\n * the machine FIRST enters a terminal state.\n *\n * This is the ~10 lines both audit suitability probes hand-wired at their\n * integration seam: a one-shot decision machine (a credibility check, an auth\n * mint, a validation pass — run to a terminal state, then throw-or-proceed at the\n * caller boundary) has no built-in \"await the terminal\", so each consumer re-wires\n * a promise off `observe` that resolves when the state is terminal. This helper\n * lifts that boilerplate into `@demlik/tea` once.\n *\n * It rides the EXISTING observation surface (`packages/tea/src/index.ts`) and adds\n * NO new runtime capability:\n * - `runtime.getState()` (total on a booted `Runtime`) covers the\n * already-terminal-at-attach case — resolve synchronously rather than wait for\n * a transition that may never come.\n * - `runtime.observe((msg, state) => void): () => void` fires on EVERY transition\n * and returns an unsubscribe — the \"first terminal transition\" signal, detached\n * the instant it resolves (no leak, no busy-wait / polling loop).\n * - `runtime.stop()` is the teardown the fire-and-await `runToTerminal` owns.\n *\n * `isTerminal` is caller-supplied — tea has no built-in terminal-set concept and\n * this helper does NOT invent one; the caller names their own terminal states.\n *\n * The optional `timeoutMs` is a plain caller-boundary timer (not an in-loop\n * progress deadline — see `../deadline` / `../with-deadline` for that shape): it\n * clears on resolve and REJECTS with the `instanceof`-checkable\n * {@link TerminalTimeoutError} if the deadline elapses first, never silently\n * resolving.\n */\n\nimport {\n type Cmd,\n type CtxArg,\n type Machine,\n type Runtime,\n run,\n type Sub,\n} from \"../index\";\n\n/**\n * Raised when `awaitTerminal` / `runToTerminal` is wired with a `timeoutMs` and\n * the deadline elapses before any terminal state is reached. Follows the tea\n * error dialect (`override readonly name`) and carries a `_tag` discriminant, so\n * a caller can branch on it either by `instanceof` or on the tag — the timeout is\n * a distinct, checkable rejection, never a silent resolve.\n */\nexport class TerminalTimeoutError extends Error {\n override readonly name = \"TerminalTimeoutError\";\n readonly _tag = \"TerminalTimeoutError\" as const;\n constructor(public readonly timeoutMs: number) {\n super(\n `@demlik/tea: awaitTerminal did not reach a terminal state within ` +\n `${timeoutMs}ms — no transition satisfied isTerminal before the deadline.`,\n );\n }\n}\n\n/** Options shared by `awaitTerminal` and `runToTerminal`. */\nexport interface AwaitTerminalOptions {\n /**\n * Optional bounded deadline in milliseconds. When set, the returned promise\n * REJECTS with {@link TerminalTimeoutError} if no terminal state is reached\n * within the window; the timer is cleared on the resolve path so a resolved\n * await never later rejects or leaves a dangling timer. Omit for an unbounded\n * await (never rejects for time).\n */\n readonly timeoutMs?: number;\n}\n\n/**\n * Attach to an ALREADY-RUNNING `Runtime<S, M, E>` and resolve with the terminal\n * `S` on the first transition for which `isTerminal(state)` is true.\n *\n * If the runtime is ALREADY terminal at attach time (`isTerminal(getState())`),\n * resolves immediately — it never hangs waiting for a transition that will not\n * come. Otherwise it subscribes via `observe` and detaches that subscription the\n * instant it resolves (or times out), so there is no observer leak and no\n * further callbacks fire.\n *\n * @param runtime a booted `Runtime` (from `await run(machine, opts).ready`).\n * @param isTerminal caller-supplied terminal predicate over the machine's state.\n * @param opts optional bounded `timeoutMs` deadline (see {@link AwaitTerminalOptions}).\n */\nexport function awaitTerminal<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n>(\n runtime: Runtime<S, M, E>,\n isTerminal: (state: S) => boolean,\n opts: AwaitTerminalOptions = {},\n): Promise<S> {\n return new Promise<S>((resolve, reject) => {\n // Already-terminal-at-attach: resolve now. Returning here attaches neither an\n // observer nor a timer, so the immediate case cannot leak either.\n const current = runtime.getState();\n if (isTerminal(current)) {\n resolve(current);\n return;\n }\n\n let settled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let detach: (() => void) | undefined;\n\n const cleanup = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n if (detach !== undefined) {\n detach();\n detach = undefined;\n }\n };\n\n detach = runtime.observe((_msg, state) => {\n if (settled) return;\n if (isTerminal(state)) {\n settled = true;\n cleanup();\n resolve(state);\n }\n });\n\n // Narrow into a const so the closure keeps the `number` narrowing (the field\n // itself is `number | undefined`).\n const timeoutMs = opts.timeoutMs;\n if (timeoutMs !== undefined) {\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(new TerminalTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n });\n}\n\n/**\n * Fire-and-await convenience: `run()` the machine, dispatch the seed `msgs`, and\n * resolve with the terminal state — then tear the runtime down (`runtime.stop()`)\n * on BOTH the resolve and reject paths. Wraps {@link awaitTerminal}.\n *\n * The seed carries the machine's `ctx` (conditionally optional, exactly as\n * `run`'s `ctx` — a pure machine omits it) and the `msgs` to dispatch once\n * booted. A `timeoutMs` in `opts` bounds the await identically to `awaitTerminal`.\n *\n * @param machine the machine to boot.\n * @param seed `{ ctx?, msgs }` — the boot context and the seed messages.\n * @param isTerminal caller-supplied terminal predicate over the machine's state.\n * @param opts optional bounded `timeoutMs` deadline (see {@link AwaitTerminalOptions}).\n */\nexport async function runToTerminal<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n seed: CtxArg<Ctx> & { readonly msgs: readonly M[] },\n isTerminal: (state: S) => boolean,\n opts: AwaitTerminalOptions = {},\n): Promise<S> {\n // Split the seed messages out from the boot context: `run`'s opts don't carry\n // `msgs`, so what remains is exactly the conditionally-optional `ctx` arg. The\n // cast is because `CtxArg<Ctx>` is a deferred generic conditional TS won't\n // verify against `run`'s all-optional opts (its weak-type check) — the value\n // is structurally valid; `run` defaults an absent `ctx` to `{}`.\n const { msgs, ...ctxArg } = seed;\n const runtime = await run<S, M, C, U, Ctx>(\n machine,\n ctxArg as Parameters<typeof run<S, M, C, U, Ctx>>[1],\n ).ready;\n try {\n // Attach the terminal await BEFORE dispatching so a terminal transition that\n // lands during a seed dispatch is caught (and the immediate-terminal check\n // inside awaitTerminal covers a machine that boots already terminal).\n const terminal = awaitTerminal(runtime, isTerminal, opts);\n // Dispatch runs serially on the runtime's tail; fire all seeds and let a\n // dispatch failure (a reducer/interpret throw) surface by racing it against\n // the terminal await rather than floating as an unhandled rejection.\n const seeding = Promise.all(msgs.map((msg) => runtime.dispatch(msg)));\n const state = await Promise.race([terminal, seeding.then(() => terminal)]);\n await runtime.stop();\n return state;\n } catch (err) {\n // Tear down on the reject path too (timeout, dispatch failure) — a rejected\n // runToTerminal never leaves the runtime running.\n await runtime.stop().catch(() => {});\n throw err;\n }\n}\n"]}
@@ -1,9 +1,9 @@
1
- import { C as Cmd } from '../core-42kapW0n.js';
1
+ import { C as Cmd } from '../core-JPHsMZVB.js';
2
2
  export { Debounced, debounce } from '../debounce/index.js';
3
- import { DeadlineSub, DeadlineExceeded } from '../deadline/index.js';
4
- import { S as SubscribeHandler } from '../types-KbL3W1mj.js';
3
+ import { DeadlineExceeded, DeadlineSub } from '../deadline/index.js';
4
+ import { S as SubscribeHandler } from '../types-BMZ7IMrd.js';
5
5
  export { EnqueueInput } from '../work-queue/index.js';
6
- import '../runtime-types-CukV8kMX.js';
6
+ import '../runtime-types-BbBq4LWo.js';
7
7
 
8
8
  /**
9
9
  * @demlik/tea/batch-window — coalesce a stream of items into size- or
@@ -1,8 +1,8 @@
1
1
  export { debounce } from '../chunk-J6527VRQ.js';
2
- import { subscribeDeadline, deadlineExceeded, deadlineSub } from '../chunk-RMX4EOQK.js';
2
+ import { subscribeDeadline, deadlineExceeded, deadlineSub } from '../chunk-ASX7PL2K.js';
3
3
  import '../chunk-2YFLOCWX.js';
4
- import '../chunk-D4NJBQAH.js';
5
- import '../chunk-PBDYW2TU.js';
4
+ import '../chunk-ZH454CR5.js';
5
+ import '../chunk-O7ED34JH.js';
6
6
 
7
7
  // src/batch-window/index.ts
8
8
  function initBatchWindow() {
@@ -1,5 +1,5 @@
1
- import { S as Sub } from '../core-42kapW0n.js';
2
- import { S as SubscribeHandler } from '../types-KbL3W1mj.js';
1
+ import { S as Sub } from '../core-JPHsMZVB.js';
2
+ import { S as SubscribeHandler } from '../types-BMZ7IMrd.js';
3
3
 
4
4
  /**
5
5
  * @demlik/tea/cache — a TTL cache as pure state, PLUS a periodic eviction Sub.
@@ -1,8 +1,8 @@
1
- export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, evictExpired, get, has, initCache, remove, set } from '../chunk-2KNLKQVN.js';
2
- import '../chunk-LYLDCIPH.js';
1
+ export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, evictExpired, get, has, initCache, remove, set } from '../chunk-WF46FABO.js';
2
+ import '../chunk-2UGTDYOK.js';
3
3
  import '../chunk-2YFLOCWX.js';
4
4
  import '../chunk-ZJ5AOYBH.js';
5
- import '../chunk-D4NJBQAH.js';
6
- import '../chunk-PBDYW2TU.js';
5
+ import '../chunk-ZH454CR5.js';
6
+ import '../chunk-O7ED34JH.js';
7
7
  //# sourceMappingURL=index.js.map
8
8
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  import { MsgType } from './chunk-JELSZN25.js';
2
- import { tryInterpret } from './chunk-D4NJBQAH.js';
2
+ import { tryInterpret } from './chunk-ZH454CR5.js';
3
3
 
4
4
  // src/token-refresh/index.ts
5
5
  function initTokenRefresh() {
@@ -57,5 +57,5 @@ function createTokenRefresh(config = {}) {
57
57
  }
58
58
 
59
59
  export { createTokenRefresh, initTokenRefresh, refreshTokenCmd, tokenRefreshFailedMsg, tokenRefreshedMsg };
60
- //# sourceMappingURL=chunk-TWDHNIY6.js.map
61
- //# sourceMappingURL=chunk-TWDHNIY6.js.map
60
+ //# sourceMappingURL=chunk-2KBEIGYP.js.map
61
+ //# sourceMappingURL=chunk-2KBEIGYP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/token-refresh/index.ts"],"names":[],"mappings":";;;;AAkJO,SAAS,gBAAA,GAA+B;AAC7C,EAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAM;AACrC;AAYO,SAAS,eAAA,GAAmC;AACjD,EAAA,OAAO,EAAE,MAAM,eAAA,EAAgB;AACjC;AAaO,SAAS,kBAAkB,KAAA,EAAiC;AACjE,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,cAAA,EAAgB,KAAA,EAAM;AAC/C;AAgBO,SAAS,sBAAsB,KAAA,EAAuC;AAC3E,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,kBAAA,EAAoB,KAAA,EAAM;AACnD;AA0BO,SAAS,kBAAA,CAAmB,MAAA,GAA6B,EAAC,EAAG;AAIlE,EAAA,MAAM,MAAA,GACJ,OAAO,MAAA,KAAW,MAAA,IAAa,OAAO,MAAA,GAAS,CAAA,GAAI,OAAO,MAAA,GAAS,CAAA;AAsBrE,EAAA,SAAS,YAAA,CAAa,OAAmB,EAAA,EAAqB;AAC5D,IAAA,IAAI,KAAA,CAAM,KAAA,KAAU,IAAA,EAAM,OAAO,IAAA;AACjC,IAAA,IAAI,KAAA,CAAM,OAAO,OAAO,IAAA;AAKxB,IAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,MAAM,KAAA,CAAM,SAAS,GAAG,OAAO,IAAA;AACpD,IAAA,OAAO,EAAA,IAAM,KAAA,CAAM,KAAA,CAAM,SAAA,GAAY,MAAA;AAAA,EACvC;AAcA,EAAA,SAAS,MAAM,KAAA,EAA+B;AAC5C,IAAA,IAAI,KAAA,CAAM,OAAO,OAAO,KAAA;AACxB,IAAA,OAAO,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,IAAA,EAAK;AAAA,EACjC;AAmBA,EAAA,SAAS,SAAA,CAAU,OAAmB,KAAA,EAA0B;AAC9D,IAAA,MAAM,UAAA,GAAoB,MAAA,CAAO,EAAA,CAAG,KAAA,CAAM,SAAA,EAAW,EAAE,CAAA,GACnD,EAAE,GAAG,KAAA,EAAO,SAAA,EAAW,CAAA,EAAE,GACzB,KAAA;AACJ,IAAA,OAAO,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,UAAA,EAAY,OAAO,KAAA,EAAM;AAAA,EACrD;AAgBA,EAAA,SAAS,WAAA,CACP,OACA,EAAA,EACuC;AACvC,IAAA,IAAI,YAAA,CAAa,KAAA,EAAO,EAAE,CAAA,EAAG;AAC3B,MAAA,OAAO,CAAC,KAAA,EAAO,CAAC,eAAA,EAAiB,CAAC,CAAA;AAAA,IACpC;AACA,IAAA,OAAO,CAAC,KAAA,EAAO,EAAE,CAAA;AAAA,EACnB;AAgBA,EAAA,SAAS,SACP,KAAA,EACsD;AACtD,IAAA,OAAO;AAAA,MACL,aAAA,EAAe,YAAA;AAAA,QAMb,MAAM,MAAM,OAAA,EAAQ;AAAA,QACpB,CAAC,KAAA,KAAU,iBAAA,CAAkB,KAAK,CAAA;AAAA,QAClC,CAAC,KAAA,KAAU,qBAAA,CAAsB,KAAK;AAAA;AACxC,KACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA;AAAA,IAEL,IAAA,EAAM,gBAAA;AAAA,IACN,YAAA;AAAA,IACA,KAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF","file":"chunk-TWDHNIY6.js","sourcesContent":["/**\n * @demlik/tea/token-refresh — a credential's lifecycle as pure state + verbs,\n * with the actual token fetch deferred to a single injected port.\n *\n * This is the L1 brick `authed-call` (#10 in the composition specs) stacks on:\n * a token slice `{ token, stale }` plus pure transitions that decide *when* a\n * credential must be re-minted, and one `handlers(ports)` splice that performs\n * the re-mint. It follows the uniform knob contract every composition wears —\n * `createTokenRefresh(config) → { init, <verbs>, handlers }` — so a consumer\n * spreads ~3 hooks into their machine instead of hand-wiring the refresh dance.\n *\n * It is a LEAF module in the same mold as `@demlik/tea/retry-backoff`: it\n * depends only on the core (`../index`), never on a sibling brick. The two\n * non-negotiables of every knob hold here by construction:\n *\n * - **Durable** — the token slice is plain JSON-serializable data, so it\n * survives a Durable Object eviction / page reload. A rehydrated machine\n * resumes knowing exactly which token it holds and whether it is stale.\n * - **Replayable** — every transition is a pure verb returning\n * `readonly [State, Cmd[]]`. No verb reads the wall clock or performs I/O,\n * so `replay` reconstructs the slice exactly. Time enters ONLY as an `at`\n * parameter the caller stamps; the network fetch enters ONLY at the\n * `handlers` boundary. This keeps the substrate's purity invariants intact\n * (invariant 2: pure transitions; invariant 4: I/O is an effect, not a\n * reducer body).\n *\n * ## The staleness model — two independent reasons to refresh\n *\n * A token becomes unusable for two unrelated reasons, and the slice tracks both\n * without conflating them:\n *\n * 1. **Expiry** — the token has a known `expiresAt` (epoch ms). It is \"near\n * expiry\" once `at >= expiresAt - skewMs`. The `skewMs` skew refreshes\n * *ahead* of the hard deadline so an in-flight call never races a token\n * that expires mid-request. This is the proactive, time-driven reason and\n * is computed purely from the injected `at` — no clock in the verb.\n *\n * 2. **Server rejection (401)** — the server rejected a request the client\n * believed was still valid (clock skew, a server-side revocation, an early\n * rotation). `on401` flips a `stale` flag regardless of `expiresAt`. This\n * is the reactive reason: the client's belief about validity was wrong, so\n * the next `needsRefresh` returns true even though the clock says the\n * token is fine.\n *\n * `needsRefresh` is the union of the two (plus \"no token at all\"). One source\n * of truth for \"should I fetch a fresh credential before the next call?\", read\n * by the consumer's reducer at the call boundary.\n *\n * Typical wiring (inside a consumer machine):\n *\n * const tr = createTokenRefresh({ skewMs: 30_000 });\n *\n * // in init:\n * init: (loaded) => [loaded ?? { auth: tr.init() }, Cmd.none],\n *\n * // in a reducer cell that is about to make an authed call:\n * before_call: (s, msg) => {\n * const [auth, cmds] = tr.ensureFresh(s.auth, msg.at);\n * return [{ ...s, auth }, cmds]; // emits a refresh Cmd iff a refresh is due\n * },\n *\n * // on a 401 from a guarded call:\n * got_401: (s) => [{ ...s, auth: tr.on401(s.auth) }, []],\n *\n * // when the refresh port resolves:\n * token_refreshed: (s, msg) => [{ ...s, auth: tr.refreshed(s.auth, msg.token) }, []],\n *\n * // splice the I/O:\n * interpret: { ...tr.handlers({ refresh: () => mySdk.mintToken() }) },\n *\n * NOT a substrate primitive: from `../index` it reuses only the `Cmd` /\n * `Interpret` types and the `tryInterpret` Railway helper (a runtime function,\n * not a type) that every effectful brick uses — and no sibling brick. It is a\n * leaf utility consumers reach via the `@demlik/tea/token-refresh` subpath.\n */\n\nimport { type Cmd, type Interpret, tryInterpret } from \"../index\";\nimport { MsgType } from \"../protocol\";\n\n/**\n * A minted credential: the opaque `value` to send on the wire, and the absolute\n * `expiresAt` (epoch milliseconds — the `Date.now()` scale) the issuer stamped\n * it with. Both fields are plain data so a `Token` is trivially serializable\n * into a consumer's `Store<S>`.\n *\n * `value` is `string` because that is the universal shape of a bearer token /\n * API key / session cookie on the wire. A consumer whose refresh port yields a\n * richer object can JSON-encode it into `value`, or keep this module's slice as\n * the \"is it fresh?\" gate and hold the richer object elsewhere in their Model.\n */\nexport interface Token {\n /** The opaque credential to present on each request (bearer token, API key). */\n readonly value: string;\n /** Absolute expiry instant, epoch milliseconds. The `Date.now()` scale. */\n readonly expiresAt: number;\n}\n\n/**\n * Pure configuration — the knob's only field. Carries no mutable state;\n * `TokenState` does. One config is shared across all the refreshes of one\n * logical credential (often a module-scope const).\n */\nexport interface TokenRefreshConfig {\n /**\n * Refresh-ahead skew, in milliseconds. A token is treated as needing a\n * refresh once `at >= expiresAt - skewMs`, i.e. `skewMs` BEFORE its hard\n * expiry. This buys headroom so a call that starts just under the wire never\n * races a token that lapses mid-flight, and absorbs small client/server clock\n * disagreement. `0` means \"refresh exactly at expiry\"; a negative value is\n * clamped to `0` (a skew that pushes the trigger past expiry is meaningless).\n *\n * @default 0\n */\n readonly skewMs?: number;\n}\n\n/**\n * The token slice this knob owns — the Model field a consumer spreads in via\n * `init()`. A two-field record rather than a phase union because the two\n * dimensions are orthogonal: a token can be present-and-fresh, present-but-stale\n * (a 401 came back), absent, or absent-and-stale (cleared after a failed\n * refresh). Every combination is meaningful, so neither field implies the\n * other's value and a discriminated union would only manufacture impossible\n * arms.\n *\n * - `token` — the currently held credential, or `null` when none has been\n * minted yet (fresh boot) or the last refresh failed.\n * - `stale` — set by `on401` when the server rejected a request the client\n * believed valid. Forces the next `needsRefresh` to true regardless of\n * `expiresAt`; cleared by `refreshed` once a new credential lands.\n *\n * Both fields `readonly` so the whole slice threads safely through a pure\n * reducer and serializes into a `Store<S>`.\n */\nexport interface TokenState {\n /** The held credential, or `null` if none is available (boot / failed refresh). */\n readonly token: Token | null;\n /** Whether a 401 has marked the held token unusable ahead of its `expiresAt`. */\n readonly stale: boolean;\n}\n\n/**\n * The starting slice: no token held, not stale. The first `needsRefresh` after\n * this returns `true` (no token), so a consumer's first guarded call mints a\n * credential before going out.\n */\nexport function initTokenRefresh(): TokenState {\n return { token: null, stale: false };\n}\n\n/**\n * The Cmd this knob emits to ask the runtime to mint a fresh token. Plain data\n * (no closure — invariant 1) carrying nothing: the refresh port takes no input,\n * and the slice the result installs is read off the follow-up Msg, not this\n * Cmd. The consumer unions this into their machine's Cmd type and lets\n * `handlers` interpret it.\n */\nexport interface RefreshTokenCmd extends Cmd<\"refresh_token\"> {}\n\n/** Construct a `refresh_token` Cmd. */\nexport function refreshTokenCmd(): RefreshTokenCmd {\n return { type: \"refresh_token\" };\n}\n\n/**\n * The Msg the refresh port dispatches on success — carries the freshly minted\n * `Token`. The consumer unions this into their Msg type and handles it by\n * folding `refreshed(state, msg.token)` into the slice.\n */\nexport interface TokenRefreshedMsg {\n readonly type: typeof MsgType.TokenRefreshed;\n readonly token: Token;\n}\n\n/** Construct a `token_refreshed` Msg carrying the new token. */\nexport function tokenRefreshedMsg(token: Token): TokenRefreshedMsg {\n return { type: MsgType.TokenRefreshed, token };\n}\n\n/**\n * The Msg the refresh port dispatches on failure — carries the rejection so the\n * consumer's reducer can decide whether to back off, surface an error, or give\n * up (errors are data: it is dispatched, never swallowed). The slice itself is\n * left untouched by a failed refresh — the held token (if any) stays held and\n * stale stays set, so the consumer can retry; clearing it is the consumer's\n * policy, expressed in their reducer.\n */\nexport interface TokenRefreshFailedMsg {\n readonly type: typeof MsgType.TokenRefreshFailed;\n readonly error: unknown;\n}\n\n/** Construct a `token_refresh_failed` Msg carrying the rejection. */\nexport function tokenRefreshFailedMsg(error: unknown): TokenRefreshFailedMsg {\n return { type: MsgType.TokenRefreshFailed, error };\n}\n\n/** The two Msgs the refresh port can produce. Union for a consumer's Msg type. */\nexport type TokenRefreshMsg = TokenRefreshedMsg | TokenRefreshFailedMsg;\n\n/** The ports this knob's `handlers` splice needs — the single refresh DI seam. */\nexport interface TokenRefreshPorts {\n /**\n * Mint a fresh credential. Called ONLY from the `refresh_token` interpret\n * handler — never from a verb — so the network I/O and any clock the issuer\n * reads stay at the effect boundary. May reject; the rejection is routed to\n * `token_refresh_failed` (errors are data), never thrown into the runtime.\n */\n readonly refresh: () => Promise<Token>;\n}\n\n/**\n * The knob: hand it a config, get back the slice initializer, the pure verbs,\n * and the `handlers` splice. Mirrors every L2 composition's uniform contract so\n * the call sites read identically across bricks.\n *\n * @example\n * const tr = createTokenRefresh({ skewMs: 30_000 });\n * const s0 = tr.init();\n * const [s1, cmds] = tr.ensureFresh(s0, Date.now()); // cmds = [refreshTokenCmd()]\n */\nexport function createTokenRefresh(config: TokenRefreshConfig = {}) {\n // Clamp once at construction: a negative skew would push the refresh trigger\n // PAST expiry, which inverts the knob's intent (\"refresh ahead\"). NaN (a\n // miswired config) collapses to 0 so `needsRefresh`'s comparison stays total.\n const skewMs =\n config.skewMs !== undefined && config.skewMs > 0 ? config.skewMs : 0;\n\n /**\n * Whether the held token must be refreshed before the next guarded call, at\n * the injected instant `at` (epoch ms). PURE — reads only, no clock, no\n * mutation. True iff ANY of:\n *\n * - no token is held (`token === null`) — nothing to present;\n * - the token was marked `stale` by a 401 — the server disagrees with the\n * client's belief that it is valid;\n * - the token's `expiresAt` is not a finite number (`NaN` / `±Infinity`) —\n * a miswired issuer or a corrupted slice; we cannot reason about when it\n * lapses, so we fail SAFE toward minting a fresh one rather than letting\n * `at >= NaN` (always `false`) or `at >= Infinity` (always `false`) mask\n * a broken expiry as \"fresh forever\";\n * - the token is at or past its skewed expiry (`at >= expiresAt - skewMs`)\n * — the proactive refresh-ahead window has opened.\n *\n * The expiry cutoff is half-open (`>=`), matching the window edges in\n * `rate-limit` / `cache` / `circuit-breaker`: a token minted to expire at `t`\n * with zero skew is considered due at exactly `t`.\n */\n function needsRefresh(state: TokenState, at: number): boolean {\n if (state.token === null) return true;\n if (state.stale) return true;\n // Non-finite expiry (NaN/±Infinity) can never be compared into a \"due\"\n // verdict — `at >= NaN` and `at >= Infinity` are both `false`, which would\n // pin a broken token as fresh forever. Fail safe: treat it as needing a\n // refresh so the next call mints a credential with a real deadline.\n if (!Number.isFinite(state.token.expiresAt)) return true;\n return at >= state.token.expiresAt - skewMs;\n }\n\n /**\n * Mark the held token unusable because a request the client believed valid\n * came back 401. PURE — returns a new slice; the input is never mutated.\n * Sets `stale` regardless of `expiresAt`, so the next `needsRefresh` returns\n * true even when the clock still says the token is fine. The token VALUE is\n * kept (not nulled) — it is the consumer's last-known credential until a\n * refresh replaces it, and keeping it lets observability show what was\n * rejected.\n *\n * Idempotent: a second 401 on an already-stale slice returns the same shape.\n * A 401 with no token held is a no-op (already needs a refresh).\n */\n function on401(state: TokenState): TokenState {\n if (state.stale) return state;\n return { ...state, stale: true };\n }\n\n /**\n * Install a freshly minted `token`, clearing `stale`. PURE — returns a new\n * slice; the input is never mutated. This is the fold for the\n * `token_refreshed` Msg: after it, `needsRefresh(state, at)` is false for any\n * `at` below the new skewed expiry, and the stale flag is gone (the new\n * credential supersedes whatever the 401 rejected).\n *\n * This is the slice's WRITE boundary for a credential, so it is where the\n * durability invariant is paid for: it normalizes a negative-zero `expiresAt`\n * to `+0` before the value enters the slice. `-0` is the one finite double\n * JSON cannot round-trip — `JSON.stringify(-0)` is `\"0\"`, which parses back as\n * `+0`, so a slice carrying `-0` would diverge from its persisted form across\n * a Durable Object eviction / page reload (invariant: the boundary parses, the\n * core trusts). `-0` and `+0` denote the same epoch-ms instant, so collapsing\n * them loses no information and makes the un-round-trippable state\n * unrepresentable in the slice by construction.\n */\n function refreshed(state: TokenState, token: Token): TokenState {\n const normalized: Token = Object.is(token.expiresAt, -0)\n ? { ...token, expiresAt: 0 }\n : token;\n return { ...state, token: normalized, stale: false };\n }\n\n /**\n * The verb a consumer calls at the call boundary: \"make sure I have a fresh\n * credential before the next request.\" PURE — returns `[next, cmds]`. When a\n * refresh is due (`needsRefresh(state, at)`), emits a single `refresh_token`\n * Cmd; otherwise emits none. The slice is returned UNCHANGED in both branches\n * — the refresh itself is async and lands later via `refreshed`, so there is\n * no in-flight phase to record here. (A consumer that wants single-flight —\n * \"don't fire a second refresh while one is pending\" — layers that in their\n * own reducer, e.g. by tracking a pending flag; this brick stays minimal and\n * lets the consumer own that policy.)\n *\n * The `at` is the caller's stamped clock reading (epoch ms), never read here —\n * keeping the verb pure and `replay`-able.\n */\n function ensureFresh(\n state: TokenState,\n at: number,\n ): readonly [TokenState, readonly Cmd[]] {\n if (needsRefresh(state, at)) {\n return [state, [refreshTokenCmd()]];\n }\n return [state, []];\n }\n\n /**\n * The interpret splice: wraps the injected `refresh` port and routes its\n * Ok/Err to `token_refreshed` / `token_refresh_failed` Msgs via the core's\n * `tryInterpret` Railway helper. Spread the result into a consumer's\n * `interpret` map:\n *\n * interpret: { ...other, ...tr.handlers({ refresh: () => sdk.mintToken() }) }\n *\n * The port is the ONLY place the network (and any clock the issuer reads) is\n * touched — every verb above stays pure. `tryInterpret` guarantees the\n * handler never rejects: a port rejection becomes a `token_refresh_failed`\n * Msg (errors are data), so the runtime's dispatch loop is never poisoned by\n * an unhandled rejection.\n */\n function handlers(\n ports: TokenRefreshPorts,\n ): Interpret<TokenRefreshMsg, RefreshTokenCmd, unknown> {\n return {\n refresh_token: tryInterpret<\n RefreshTokenCmd,\n Token,\n TokenRefreshMsg,\n unknown\n >(\n () => ports.refresh(),\n (token) => tokenRefreshedMsg(token),\n (error) => tokenRefreshFailedMsg(error),\n ),\n };\n }\n\n return {\n /** The slice initializer — spread into the consumer's `init`. */\n init: initTokenRefresh,\n needsRefresh,\n on401,\n refreshed,\n ensureFresh,\n handlers,\n };\n}\n\n/** The shape `createTokenRefresh` returns — useful for typing a held knob. */\nexport type TokenRefresh = ReturnType<typeof createTokenRefresh>;\n"]}
1
+ {"version":3,"sources":["../src/token-refresh/index.ts"],"names":[],"mappings":";;;;AAkJO,SAAS,gBAAA,GAA+B;AAC7C,EAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAM;AACrC;AAYO,SAAS,eAAA,GAAmC;AACjD,EAAA,OAAO,EAAE,MAAM,eAAA,EAAgB;AACjC;AAaO,SAAS,kBAAkB,KAAA,EAAiC;AACjE,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,cAAA,EAAgB,KAAA,EAAM;AAC/C;AAgBO,SAAS,sBAAsB,KAAA,EAAuC;AAC3E,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,kBAAA,EAAoB,KAAA,EAAM;AACnD;AA0BO,SAAS,kBAAA,CAAmB,MAAA,GAA6B,EAAC,EAAG;AAIlE,EAAA,MAAM,MAAA,GACJ,OAAO,MAAA,KAAW,MAAA,IAAa,OAAO,MAAA,GAAS,CAAA,GAAI,OAAO,MAAA,GAAS,CAAA;AAsBrE,EAAA,SAAS,YAAA,CAAa,OAAmB,EAAA,EAAqB;AAC5D,IAAA,IAAI,KAAA,CAAM,KAAA,KAAU,IAAA,EAAM,OAAO,IAAA;AACjC,IAAA,IAAI,KAAA,CAAM,OAAO,OAAO,IAAA;AAKxB,IAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,MAAM,KAAA,CAAM,SAAS,GAAG,OAAO,IAAA;AACpD,IAAA,OAAO,EAAA,IAAM,KAAA,CAAM,KAAA,CAAM,SAAA,GAAY,MAAA;AAAA,EACvC;AAcA,EAAA,SAAS,MAAM,KAAA,EAA+B;AAC5C,IAAA,IAAI,KAAA,CAAM,OAAO,OAAO,KAAA;AACxB,IAAA,OAAO,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,IAAA,EAAK;AAAA,EACjC;AAmBA,EAAA,SAAS,SAAA,CAAU,OAAmB,KAAA,EAA0B;AAC9D,IAAA,MAAM,UAAA,GAAoB,MAAA,CAAO,EAAA,CAAG,KAAA,CAAM,SAAA,EAAW,EAAE,CAAA,GACnD,EAAE,GAAG,KAAA,EAAO,SAAA,EAAW,CAAA,EAAE,GACzB,KAAA;AACJ,IAAA,OAAO,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,UAAA,EAAY,OAAO,KAAA,EAAM;AAAA,EACrD;AAgBA,EAAA,SAAS,WAAA,CACP,OACA,EAAA,EACuC;AACvC,IAAA,IAAI,YAAA,CAAa,KAAA,EAAO,EAAE,CAAA,EAAG;AAC3B,MAAA,OAAO,CAAC,KAAA,EAAO,CAAC,eAAA,EAAiB,CAAC,CAAA;AAAA,IACpC;AACA,IAAA,OAAO,CAAC,KAAA,EAAO,EAAE,CAAA;AAAA,EACnB;AAgBA,EAAA,SAAS,SACP,KAAA,EACsD;AACtD,IAAA,OAAO;AAAA,MACL,aAAA,EAAe,YAAA;AAAA,QAMb,MAAM,MAAM,OAAA,EAAQ;AAAA,QACpB,CAAC,KAAA,KAAU,iBAAA,CAAkB,KAAK,CAAA;AAAA,QAClC,CAAC,KAAA,KAAU,qBAAA,CAAsB,KAAK;AAAA;AACxC,KACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA;AAAA,IAEL,IAAA,EAAM,gBAAA;AAAA,IACN,YAAA;AAAA,IACA,KAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF","file":"chunk-2KBEIGYP.js","sourcesContent":["/**\n * @demlik/tea/token-refresh — a credential's lifecycle as pure state + verbs,\n * with the actual token fetch deferred to a single injected port.\n *\n * This is the L1 brick `authed-call` (#10 in the composition specs) stacks on:\n * a token slice `{ token, stale }` plus pure transitions that decide *when* a\n * credential must be re-minted, and one `handlers(ports)` splice that performs\n * the re-mint. It follows the uniform knob contract every composition wears —\n * `createTokenRefresh(config) → { init, <verbs>, handlers }` — so a consumer\n * spreads ~3 hooks into their machine instead of hand-wiring the refresh dance.\n *\n * It is a LEAF module in the same mold as `@demlik/tea/retry-backoff`: it\n * depends only on the core (`../index`), never on a sibling brick. The two\n * non-negotiables of every knob hold here by construction:\n *\n * - **Durable** — the token slice is plain JSON-serializable data, so it\n * survives a Durable Object eviction / page reload. A rehydrated machine\n * resumes knowing exactly which token it holds and whether it is stale.\n * - **Replayable** — every transition is a pure verb returning\n * `readonly [State, Cmd[]]`. No verb reads the wall clock or performs I/O,\n * so `replay` reconstructs the slice exactly. Time enters ONLY as an `at`\n * parameter the caller stamps; the network fetch enters ONLY at the\n * `handlers` boundary. This keeps the substrate's purity invariants intact\n * (invariant 2: pure transitions; invariant 4: I/O is an effect, not a\n * reducer body).\n *\n * ## The staleness model — two independent reasons to refresh\n *\n * A token becomes unusable for two unrelated reasons, and the slice tracks both\n * without conflating them:\n *\n * 1. **Expiry** — the token has a known `expiresAt` (epoch ms). It is \"near\n * expiry\" once `at >= expiresAt - skewMs`. The `skewMs` skew refreshes\n * *ahead* of the hard deadline so an in-flight call never races a token\n * that expires mid-request. This is the proactive, time-driven reason and\n * is computed purely from the injected `at` — no clock in the verb.\n *\n * 2. **Server rejection (401)** — the server rejected a request the client\n * believed was still valid (clock skew, a server-side revocation, an early\n * rotation). `on401` flips a `stale` flag regardless of `expiresAt`. This\n * is the reactive reason: the client's belief about validity was wrong, so\n * the next `needsRefresh` returns true even though the clock says the\n * token is fine.\n *\n * `needsRefresh` is the union of the two (plus \"no token at all\"). One source\n * of truth for \"should I fetch a fresh credential before the next call?\", read\n * by the consumer's reducer at the call boundary.\n *\n * Typical wiring (inside a consumer machine):\n *\n * const tr = createTokenRefresh({ skewMs: 30_000 });\n *\n * // in init:\n * init: (loaded) => [loaded ?? { auth: tr.init() }, Cmd.none],\n *\n * // in a reducer cell that is about to make an authed call:\n * before_call: (s, msg) => {\n * const [auth, cmds] = tr.ensureFresh(s.auth, msg.at);\n * return [{ ...s, auth }, cmds]; // emits a refresh Cmd iff a refresh is due\n * },\n *\n * // on a 401 from a guarded call:\n * got_401: (s) => [{ ...s, auth: tr.on401(s.auth) }, []],\n *\n * // when the refresh port resolves:\n * token_refreshed: (s, msg) => [{ ...s, auth: tr.refreshed(s.auth, msg.token) }, []],\n *\n * // splice the I/O:\n * interpret: { ...tr.handlers({ refresh: () => mySdk.mintToken() }) },\n *\n * NOT a substrate primitive: from `../index` it reuses only the `Cmd` /\n * `Interpret` types and the `tryInterpret` Railway helper (a runtime function,\n * not a type) that every effectful brick uses — and no sibling brick. It is a\n * leaf utility consumers reach via the `@demlik/tea/token-refresh` subpath.\n */\n\nimport { type Cmd, type Interpret, tryInterpret } from \"../index\";\nimport { MsgType } from \"../protocol\";\n\n/**\n * A minted credential: the opaque `value` to send on the wire, and the absolute\n * `expiresAt` (epoch milliseconds — the `Date.now()` scale) the issuer stamped\n * it with. Both fields are plain data so a `Token` is trivially serializable\n * into a consumer's `Store<S>`.\n *\n * `value` is `string` because that is the universal shape of a bearer token /\n * API key / session cookie on the wire. A consumer whose refresh port yields a\n * richer object can JSON-encode it into `value`, or keep this module's slice as\n * the \"is it fresh?\" gate and hold the richer object elsewhere in their Model.\n */\nexport interface Token {\n /** The opaque credential to present on each request (bearer token, API key). */\n readonly value: string;\n /** Absolute expiry instant, epoch milliseconds. The `Date.now()` scale. */\n readonly expiresAt: number;\n}\n\n/**\n * Pure configuration — the knob's only field. Carries no mutable state;\n * `TokenState` does. One config is shared across all the refreshes of one\n * logical credential (often a module-scope const).\n */\nexport interface TokenRefreshConfig {\n /**\n * Refresh-ahead skew, in milliseconds. A token is treated as needing a\n * refresh once `at >= expiresAt - skewMs`, i.e. `skewMs` BEFORE its hard\n * expiry. This buys headroom so a call that starts just under the wire never\n * races a token that lapses mid-flight, and absorbs small client/server clock\n * disagreement. `0` means \"refresh exactly at expiry\"; a negative value is\n * clamped to `0` (a skew that pushes the trigger past expiry is meaningless).\n *\n * @default 0\n */\n readonly skewMs?: number;\n}\n\n/**\n * The token slice this knob owns — the Model field a consumer spreads in via\n * `init()`. A two-field record rather than a phase union because the two\n * dimensions are orthogonal: a token can be present-and-fresh, present-but-stale\n * (a 401 came back), absent, or absent-and-stale (cleared after a failed\n * refresh). Every combination is meaningful, so neither field implies the\n * other's value and a discriminated union would only manufacture impossible\n * arms.\n *\n * - `token` — the currently held credential, or `null` when none has been\n * minted yet (fresh boot) or the last refresh failed.\n * - `stale` — set by `on401` when the server rejected a request the client\n * believed valid. Forces the next `needsRefresh` to true regardless of\n * `expiresAt`; cleared by `refreshed` once a new credential lands.\n *\n * Both fields `readonly` so the whole slice threads safely through a pure\n * reducer and serializes into a `Store<S>`.\n */\nexport interface TokenState {\n /** The held credential, or `null` if none is available (boot / failed refresh). */\n readonly token: Token | null;\n /** Whether a 401 has marked the held token unusable ahead of its `expiresAt`. */\n readonly stale: boolean;\n}\n\n/**\n * The starting slice: no token held, not stale. The first `needsRefresh` after\n * this returns `true` (no token), so a consumer's first guarded call mints a\n * credential before going out.\n */\nexport function initTokenRefresh(): TokenState {\n return { token: null, stale: false };\n}\n\n/**\n * The Cmd this knob emits to ask the runtime to mint a fresh token. Plain data\n * (no closure — invariant 1) carrying nothing: the refresh port takes no input,\n * and the slice the result installs is read off the follow-up Msg, not this\n * Cmd. The consumer unions this into their machine's Cmd type and lets\n * `handlers` interpret it.\n */\nexport interface RefreshTokenCmd extends Cmd<\"refresh_token\"> {}\n\n/** Construct a `refresh_token` Cmd. */\nexport function refreshTokenCmd(): RefreshTokenCmd {\n return { type: \"refresh_token\" };\n}\n\n/**\n * The Msg the refresh port dispatches on success — carries the freshly minted\n * `Token`. The consumer unions this into their Msg type and handles it by\n * folding `refreshed(state, msg.token)` into the slice.\n */\nexport interface TokenRefreshedMsg {\n readonly type: typeof MsgType.TokenRefreshed;\n readonly token: Token;\n}\n\n/** Construct a `token_refreshed` Msg carrying the new token. */\nexport function tokenRefreshedMsg(token: Token): TokenRefreshedMsg {\n return { type: MsgType.TokenRefreshed, token };\n}\n\n/**\n * The Msg the refresh port dispatches on failure — carries the rejection so the\n * consumer's reducer can decide whether to back off, surface an error, or give\n * up (errors are data: it is dispatched, never swallowed). The slice itself is\n * left untouched by a failed refresh — the held token (if any) stays held and\n * stale stays set, so the consumer can retry; clearing it is the consumer's\n * policy, expressed in their reducer.\n */\nexport interface TokenRefreshFailedMsg {\n readonly type: typeof MsgType.TokenRefreshFailed;\n readonly error: unknown;\n}\n\n/** Construct a `token_refresh_failed` Msg carrying the rejection. */\nexport function tokenRefreshFailedMsg(error: unknown): TokenRefreshFailedMsg {\n return { type: MsgType.TokenRefreshFailed, error };\n}\n\n/** The two Msgs the refresh port can produce. Union for a consumer's Msg type. */\nexport type TokenRefreshMsg = TokenRefreshedMsg | TokenRefreshFailedMsg;\n\n/** The ports this knob's `handlers` splice needs — the single refresh DI seam. */\nexport interface TokenRefreshPorts {\n /**\n * Mint a fresh credential. Called ONLY from the `refresh_token` interpret\n * handler — never from a verb — so the network I/O and any clock the issuer\n * reads stay at the effect boundary. May reject; the rejection is routed to\n * `token_refresh_failed` (errors are data), never thrown into the runtime.\n */\n readonly refresh: () => Promise<Token>;\n}\n\n/**\n * The knob: hand it a config, get back the slice initializer, the pure verbs,\n * and the `handlers` splice. Mirrors every L2 composition's uniform contract so\n * the call sites read identically across bricks.\n *\n * @example\n * const tr = createTokenRefresh({ skewMs: 30_000 });\n * const s0 = tr.init();\n * const [s1, cmds] = tr.ensureFresh(s0, Date.now()); // cmds = [refreshTokenCmd()]\n */\nexport function createTokenRefresh(config: TokenRefreshConfig = {}) {\n // Clamp once at construction: a negative skew would push the refresh trigger\n // PAST expiry, which inverts the knob's intent (\"refresh ahead\"). NaN (a\n // miswired config) collapses to 0 so `needsRefresh`'s comparison stays total.\n const skewMs =\n config.skewMs !== undefined && config.skewMs > 0 ? config.skewMs : 0;\n\n /**\n * Whether the held token must be refreshed before the next guarded call, at\n * the injected instant `at` (epoch ms). PURE — reads only, no clock, no\n * mutation. True iff ANY of:\n *\n * - no token is held (`token === null`) — nothing to present;\n * - the token was marked `stale` by a 401 — the server disagrees with the\n * client's belief that it is valid;\n * - the token's `expiresAt` is not a finite number (`NaN` / `±Infinity`) —\n * a miswired issuer or a corrupted slice; we cannot reason about when it\n * lapses, so we fail SAFE toward minting a fresh one rather than letting\n * `at >= NaN` (always `false`) or `at >= Infinity` (always `false`) mask\n * a broken expiry as \"fresh forever\";\n * - the token is at or past its skewed expiry (`at >= expiresAt - skewMs`)\n * — the proactive refresh-ahead window has opened.\n *\n * The expiry cutoff is half-open (`>=`), matching the window edges in\n * `rate-limit` / `cache` / `circuit-breaker`: a token minted to expire at `t`\n * with zero skew is considered due at exactly `t`.\n */\n function needsRefresh(state: TokenState, at: number): boolean {\n if (state.token === null) return true;\n if (state.stale) return true;\n // Non-finite expiry (NaN/±Infinity) can never be compared into a \"due\"\n // verdict — `at >= NaN` and `at >= Infinity` are both `false`, which would\n // pin a broken token as fresh forever. Fail safe: treat it as needing a\n // refresh so the next call mints a credential with a real deadline.\n if (!Number.isFinite(state.token.expiresAt)) return true;\n return at >= state.token.expiresAt - skewMs;\n }\n\n /**\n * Mark the held token unusable because a request the client believed valid\n * came back 401. PURE — returns a new slice; the input is never mutated.\n * Sets `stale` regardless of `expiresAt`, so the next `needsRefresh` returns\n * true even when the clock still says the token is fine. The token VALUE is\n * kept (not nulled) — it is the consumer's last-known credential until a\n * refresh replaces it, and keeping it lets observability show what was\n * rejected.\n *\n * Idempotent: a second 401 on an already-stale slice returns the same shape.\n * A 401 with no token held is a no-op (already needs a refresh).\n */\n function on401(state: TokenState): TokenState {\n if (state.stale) return state;\n return { ...state, stale: true };\n }\n\n /**\n * Install a freshly minted `token`, clearing `stale`. PURE — returns a new\n * slice; the input is never mutated. This is the fold for the\n * `token_refreshed` Msg: after it, `needsRefresh(state, at)` is false for any\n * `at` below the new skewed expiry, and the stale flag is gone (the new\n * credential supersedes whatever the 401 rejected).\n *\n * This is the slice's WRITE boundary for a credential, so it is where the\n * durability invariant is paid for: it normalizes a negative-zero `expiresAt`\n * to `+0` before the value enters the slice. `-0` is the one finite double\n * JSON cannot round-trip — `JSON.stringify(-0)` is `\"0\"`, which parses back as\n * `+0`, so a slice carrying `-0` would diverge from its persisted form across\n * a Durable Object eviction / page reload (invariant: the boundary parses, the\n * core trusts). `-0` and `+0` denote the same epoch-ms instant, so collapsing\n * them loses no information and makes the un-round-trippable state\n * unrepresentable in the slice by construction.\n */\n function refreshed(state: TokenState, token: Token): TokenState {\n const normalized: Token = Object.is(token.expiresAt, -0)\n ? { ...token, expiresAt: 0 }\n : token;\n return { ...state, token: normalized, stale: false };\n }\n\n /**\n * The verb a consumer calls at the call boundary: \"make sure I have a fresh\n * credential before the next request.\" PURE — returns `[next, cmds]`. When a\n * refresh is due (`needsRefresh(state, at)`), emits a single `refresh_token`\n * Cmd; otherwise emits none. The slice is returned UNCHANGED in both branches\n * — the refresh itself is async and lands later via `refreshed`, so there is\n * no in-flight phase to record here. (A consumer that wants single-flight —\n * \"don't fire a second refresh while one is pending\" — layers that in their\n * own reducer, e.g. by tracking a pending flag; this brick stays minimal and\n * lets the consumer own that policy.)\n *\n * The `at` is the caller's stamped clock reading (epoch ms), never read here —\n * keeping the verb pure and `replay`-able.\n */\n function ensureFresh(\n state: TokenState,\n at: number,\n ): readonly [TokenState, readonly Cmd[]] {\n if (needsRefresh(state, at)) {\n return [state, [refreshTokenCmd()]];\n }\n return [state, []];\n }\n\n /**\n * The interpret splice: wraps the injected `refresh` port and routes its\n * Ok/Err to `token_refreshed` / `token_refresh_failed` Msgs via the core's\n * `tryInterpret` Railway helper. Spread the result into a consumer's\n * `interpret` map:\n *\n * interpret: { ...other, ...tr.handlers({ refresh: () => sdk.mintToken() }) }\n *\n * The port is the ONLY place the network (and any clock the issuer reads) is\n * touched — every verb above stays pure. `tryInterpret` guarantees the\n * handler never rejects: a port rejection becomes a `token_refresh_failed`\n * Msg (errors are data), so the runtime's dispatch loop is never poisoned by\n * an unhandled rejection.\n */\n function handlers(\n ports: TokenRefreshPorts,\n ): Interpret<TokenRefreshMsg, RefreshTokenCmd, unknown> {\n return {\n refresh_token: tryInterpret<\n RefreshTokenCmd,\n Token,\n TokenRefreshMsg,\n unknown\n >(\n () => ports.refresh(),\n (token) => tokenRefreshedMsg(token),\n (error) => tokenRefreshFailedMsg(error),\n ),\n };\n }\n\n return {\n /** The slice initializer — spread into the consumer's `init`. */\n init: initTokenRefresh,\n needsRefresh,\n on401,\n refreshed,\n ensureFresh,\n handlers,\n };\n}\n\n/** The shape `createTokenRefresh` returns — useful for typing a held knob. */\nexport type TokenRefresh = ReturnType<typeof createTokenRefresh>;\n"]}
@@ -1,5 +1,5 @@
1
1
  import { dispatchIfPresent } from './chunk-ZJ5AOYBH.js';
2
- import { subId, structuralHash } from './chunk-PBDYW2TU.js';
2
+ import { structuralHash, subId } from './chunk-O7ED34JH.js';
3
3
 
4
4
  // src/subs/define-listener.ts
5
5
  function defineListener(target) {
@@ -215,16 +215,7 @@ function defineManagedResource(opts) {
215
215
  held.set(k, handle);
216
216
  return () => {
217
217
  held.delete(k);
218
- try {
219
- const result = opts.release(handle);
220
- if (result instanceof Promise) {
221
- result.catch((err) => {
222
- console.warn(`[managed:${opts.name}] release failed`, err);
223
- });
224
- }
225
- } catch (err) {
226
- console.warn(`[managed:${opts.name}] release threw`, err);
227
- }
218
+ return opts.release(handle);
228
219
  };
229
220
  };
230
221
  return {
@@ -306,20 +297,36 @@ function fromTransport(opts) {
306
297
  const subscribe = (s, ctx, dispatch) => {
307
298
  const t = opts.openTransport(s.key, ctx);
308
299
  const k = keyString(s.key);
300
+ let offMessage;
301
+ let offClose;
302
+ try {
303
+ offMessage = t.onMessage((raw) => {
304
+ const parsed = opts.parseInbound(raw, s.key);
305
+ if (parsed === null) return;
306
+ const msg = opts.onInbound(parsed, s.key);
307
+ if (msg === null) return;
308
+ dispatch(msg);
309
+ });
310
+ offClose = t.onClose(() => {
311
+ dispatch(opts.lostMsg(s.key));
312
+ });
313
+ } catch (err) {
314
+ offMessage?.();
315
+ offClose?.();
316
+ try {
317
+ t.close();
318
+ } catch (closeErr) {
319
+ console.warn(
320
+ `[fromTransport:${opts.name}] close during failed wiring failed`,
321
+ closeErr
322
+ );
323
+ }
324
+ throw err;
325
+ }
309
326
  live.set(k, t);
310
- const offMessage = t.onMessage((raw) => {
311
- const parsed = opts.parseInbound(raw, s.key);
312
- if (parsed === null) return;
313
- const msg = opts.onInbound(parsed, s.key);
314
- if (msg === null) return;
315
- dispatch(msg);
316
- });
317
- const offClose = t.onClose(() => {
318
- dispatch(opts.lostMsg(s.key));
319
- });
320
327
  return () => {
321
- offMessage();
322
- offClose();
328
+ offMessage?.();
329
+ offClose?.();
323
330
  live.delete(k);
324
331
  try {
325
332
  t.close();
@@ -361,5 +368,5 @@ function fromTransport(opts) {
361
368
  }
362
369
 
363
370
  export { combineManagedResources, defineListener, defineManagedResource, fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromTransport, fromWebSocket };
364
- //# sourceMappingURL=chunk-LYLDCIPH.js.map
365
- //# sourceMappingURL=chunk-LYLDCIPH.js.map
371
+ //# sourceMappingURL=chunk-2UGTDYOK.js.map
372
+ //# sourceMappingURL=chunk-2UGTDYOK.js.map