@demlik/tea 0.15.0 → 0.17.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 (115) hide show
  1. package/README.md +30 -7
  2. package/dist/agent/index.d.ts +49 -37
  3. package/dist/agent/index.js +12 -8
  4. package/dist/chunk-4LPY67E4.js +18 -0
  5. package/dist/chunk-4LPY67E4.js.map +1 -0
  6. package/dist/chunk-4NTCPTNQ.js +64 -0
  7. package/dist/chunk-4NTCPTNQ.js.map +1 -0
  8. package/dist/{chunk-3B5X7SBB.js → chunk-4PXNZ5UY.js} +11 -9
  9. package/dist/chunk-4PXNZ5UY.js.map +1 -0
  10. package/dist/{chunk-AYS5N6WY.js → chunk-6XBEYZME.js} +62 -172
  11. package/dist/chunk-6XBEYZME.js.map +1 -0
  12. package/dist/chunk-C4U5D5HP.js +380 -0
  13. package/dist/chunk-C4U5D5HP.js.map +1 -0
  14. package/dist/{chunk-QPEVCJ5Q.js → chunk-DFUEAA6L.js} +13 -6
  15. package/dist/chunk-DFUEAA6L.js.map +1 -0
  16. package/dist/{chunk-22POWGFA.js → chunk-GWWCCSDH.js} +46 -20
  17. package/dist/chunk-GWWCCSDH.js.map +1 -0
  18. package/dist/chunk-K4ETBNKS.js +733 -0
  19. package/dist/chunk-K4ETBNKS.js.map +1 -0
  20. package/dist/chunk-KWNCM6XM.js +539 -0
  21. package/dist/chunk-KWNCM6XM.js.map +1 -0
  22. package/dist/{chunk-V46HSVCK.js → chunk-LYVOGOBC.js} +3 -3
  23. package/dist/{chunk-V46HSVCK.js.map → chunk-LYVOGOBC.js.map} +1 -1
  24. package/dist/{chunk-VTQI2NHP.js → chunk-OPH6Y4UT.js} +9 -8
  25. package/dist/chunk-OPH6Y4UT.js.map +1 -0
  26. package/dist/{chunk-Q5CQTBFA.js → chunk-OYR7ENKU.js} +20 -30
  27. package/dist/chunk-OYR7ENKU.js.map +1 -0
  28. package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
  29. package/dist/chunk-QNGYQMZB.js.map +1 -0
  30. package/dist/chunk-QWIPH4YH.js +98 -0
  31. package/dist/chunk-QWIPH4YH.js.map +1 -0
  32. package/dist/{chunk-TCLBCERH.js → chunk-XBZQT2TD.js} +238 -212
  33. package/dist/chunk-XBZQT2TD.js.map +1 -0
  34. package/dist/{core-DnzqWy6u.d.ts → core-rDbOaJTN.d.ts} +236 -140
  35. package/dist/do/index.d.ts +37 -33
  36. package/dist/do/index.js +21 -14
  37. package/dist/do/index.js.map +1 -1
  38. package/dist/{durable-effects-CWzA-o2B.d.ts → durable-effects-CNaw97Be.d.ts} +1 -1
  39. package/dist/effect/index.d.ts +151 -0
  40. package/dist/effect/index.js +98 -0
  41. package/dist/effect/index.js.map +1 -0
  42. package/dist/extension/index.d.ts +6 -7
  43. package/dist/extension/index.js +3 -2
  44. package/dist/extension/index.js.map +1 -1
  45. package/dist/flow/index.d.ts +68 -59
  46. package/dist/flow/index.js +32 -27
  47. package/dist/flow/index.js.map +1 -1
  48. package/dist/idempotency/index.d.ts +6 -7
  49. package/dist/idempotency/index.js +4 -2
  50. package/dist/{index-KXqTRJyb.d.ts → index-BOrsbdAf.d.ts} +19 -96
  51. package/dist/{index-DrIRb_Ch.d.ts → index-B_BxplWi.d.ts} +1 -1
  52. package/dist/index-CWmsXNdX.d.ts +207 -0
  53. package/dist/{index-C2DwkjlP.d.ts → index-Cberb5Rr.d.ts} +57 -45
  54. package/dist/{index-CSyMYDAB.d.ts → index-CcrKLtse.d.ts} +1 -1
  55. package/dist/{index-CuNO9m8i.d.ts → index-Cx0F2M34.d.ts} +14 -16
  56. package/dist/{index-HSYgG2Yt.d.ts → index-DaX_P35Q.d.ts} +247 -211
  57. package/dist/{index-CA_7I5g9.d.ts → index-q1PoukOZ.d.ts} +137 -246
  58. package/dist/index.d.ts +121 -385
  59. package/dist/index.js +2 -1
  60. package/dist/jev/index.d.ts +133 -97
  61. package/dist/jev/index.js +205 -161
  62. package/dist/jev/index.js.map +1 -1
  63. package/dist/{machine-DGlPWCWI.d.ts → machine-BcfCFMYF.d.ts} +171 -288
  64. package/dist/machine-viz/index.d.ts +5 -5
  65. package/dist/machine-viz/index.js +8 -4
  66. package/dist/machine-viz/index.js.map +1 -1
  67. package/dist/mem/index.d.ts +3 -4
  68. package/dist/mem/index.js +2 -1
  69. package/dist/mem/index.js.map +1 -1
  70. package/dist/node/index.d.ts +119 -78
  71. package/dist/node/index.js +52 -38
  72. package/dist/node/index.js.map +1 -1
  73. package/dist/paginate/index.d.ts +27 -22
  74. package/dist/paginate/index.js +6 -4
  75. package/dist/parity/index.d.ts +4 -5
  76. package/dist/parity/index.js +3 -2
  77. package/dist/parity/index.js.map +1 -1
  78. package/dist/pbt/index.d.ts +2 -2
  79. package/dist/pbt/index.js +2 -1
  80. package/dist/pbt/index.js.map +1 -1
  81. package/dist/persistence/index.d.ts +10 -12
  82. package/dist/persistence/index.js +5 -3
  83. package/dist/promise/index.d.ts +186 -0
  84. package/dist/promise/index.js +5 -0
  85. package/dist/promise/index.js.map +1 -0
  86. package/dist/{protocol-D8VbH-VH.d.ts → protocol-ClRZBhHQ.d.ts} +7 -8
  87. package/dist/react/index.d.ts +36 -17
  88. package/dist/react/index.js +27 -4
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/resilience/index.d.ts +63 -456
  91. package/dist/resilience/index.js +37 -483
  92. package/dist/resilience/index.js.map +1 -1
  93. package/dist/{runtime-types-CMF13BBh.d.ts → runtime-types-B62Ucnxr.d.ts} +132 -389
  94. package/dist/testing/index.d.ts +33 -113
  95. package/dist/testing/index.js +39 -181
  96. package/dist/testing/index.js.map +1 -1
  97. package/dist/timing/index.d.ts +25 -22
  98. package/dist/timing/index.js +4 -3
  99. package/dist/timing/index.js.map +1 -1
  100. package/dist/{types-BseNPIhr.d.ts → types-DPm5YCDQ.d.ts} +2 -2
  101. package/dist/work-queue/index.d.ts +5 -6
  102. package/package.json +20 -7
  103. package/dist/chunk-22POWGFA.js.map +0 -1
  104. package/dist/chunk-3B5X7SBB.js.map +0 -1
  105. package/dist/chunk-66PY5WYF.js +0 -1816
  106. package/dist/chunk-66PY5WYF.js.map +0 -1
  107. package/dist/chunk-AYS5N6WY.js.map +0 -1
  108. package/dist/chunk-L3TND7LA.js +0 -97
  109. package/dist/chunk-L3TND7LA.js.map +0 -1
  110. package/dist/chunk-MYXFNWAJ.js.map +0 -1
  111. package/dist/chunk-Q5CQTBFA.js.map +0 -1
  112. package/dist/chunk-QPEVCJ5Q.js.map +0 -1
  113. package/dist/chunk-TCLBCERH.js.map +0 -1
  114. package/dist/chunk-VTQI2NHP.js.map +0 -1
  115. package/dist/index-ZCMhnVKN.d.ts +0 -272
@@ -1,12 +1,12 @@
1
- import { a as CmdOf, b as CmdDef, C as Cmd, I as Interpret, l as CmdValue, g as PortEmitter, i as NoCtx, S as Sub, M as Machine } from '../core-DnzqWy6u.js';
2
- import { M as MsgType } from '../protocol-D8VbH-VH.js';
3
- import { e as ResilientConfig, a as ResilientState, R as RunCmd, S as SucceedMsg, F as FailMsg, f as ResilientTimerMsg, c as ResilientPorts } from '../index-CA_7I5g9.js';
4
- export { g as CacheConfig, h as CallBudget, i as CallPhase, C as CircuitConfig, j as CircuitPolicy, k as CircuitState, l as DEFAULT_RESILIENT_NAME, n as DeadlineExceededError, o as DeadlineNameOf, p as DeadlineSettled, q as DefaultResilientName, M as MountConfig, r as MountableKnob, s as MountedCell, t as MountedResilientCall, b as RateLimitConfig, D as ResilientCallDeadlineConfig, u as ResilientDeadlineType, v as ResilientErrType, d as ResilientHandlers, w as ResilientOkType, x as ResilientRunType, y as RunCmdDef, z as Settle, A as SettleFold, B as SlidingWindow, T as TokenBucket, E as canPass, G as createResilientCall, H as defaultCircuitPolicy, I as initBucket, J as initCircuit, K as initWindow, L as liftResilience, m as mountResilientCall, N as onFailure, O as onSuccess, P as record, Q as refill, U as remaining, V as runCmdDef, W as tryConsume } from '../index-CA_7I5g9.js';
5
- import { a as DeadlineSub } from '../index-HSYgG2Yt.js';
6
- export { A as ArmTimer, e as CacheEntry, c as CacheEvictMsg, C as CacheEvictionSub, D as DeadlineExceeded, f as DeadlineMsgType, g as DeadlineOpts, T as TtlCache, h as cacheEvictMsg, i as cacheEvictionSub, j as cacheEvictionSubscribe, d as deadlineExceeded, k as deadlineMsgType, b as deadlineSub, l as evictExpired, m as get, n as has, o as initCache, r as remove, p as set, q as setTimeoutArmTimer, s as subscribeDeadline, t as subscribeWith } from '../index-HSYgG2Yt.js';
1
+ import { c as CmdOf, b as CmdDef, f as SettledErr, g as TaggedError, e as SettledOk, T as TimerDeps, C as Cmd } from '../core-rDbOaJTN.js';
2
+ import { M as MsgType } from '../protocol-ClRZBhHQ.js';
3
+ import { a as DeadlineSub } from '../index-DaX_P35Q.js';
4
+ export { A as ArmTimer, g as CacheEntry, f as CacheEvictMsg, h as CacheEvictionDeps, C as CacheEvictionSub, D as DeadlineExceeded, i as DeadlineMsgType, j as DeadlineOpts, b as DeadlinesSub, T as TtlCache, k as cacheEvictMsg, l as cacheEvictionSub, m as cacheEvictionSubscribe, d as deadlineExceeded, n as deadlineMsgType, c as deadlineSub, o as deadlines, e as deadlinesSub, p as evictExpired, q as get, r as has, t as initCache, u as nextTimer, v as remove, w as set, x as setTimeoutArmTimer, s as subscribeDeadline, y as subscribeWith } from '../index-DaX_P35Q.js';
5
+ import { c as ResilientConfig, a as ResilientState, R as RunCmd, S as SucceedMsg, F as FailMsg, b as ResilientTimerMsg } from '../index-q1PoukOZ.js';
6
+ export { C as CacheConfig, d as CallBudget, e as CallPhase, f as CircuitConfig, g as CircuitPolicy, h as CircuitState, D as DEFAULT_RESILIENT_NAME, i as DeadlineExceededError, j as DeadlineNameOf, k as DefaultResilientName, l as RateLimitConfig, m as ResilientCallDeadlineConfig, n as ResilientDeadlineType, o as ResilientErrType, p as ResilientOkType, q as ResilientRunType, r as RunCmdDef, s as RunErr, t as SettleMsg, u as SettleOutcome, v as SettleResult, w as SlidingWindow, T as TokenBucket, x as canPass, y as createResilientCall, z as defaultCircuitPolicy, A as initBucket, B as initCircuit, E as initWindow, G as liftResilience, H as onFailure, I as onSuccess, J as record, K as refill, L as remaining, M as runCmdDef, N as tryConsume } from '../index-q1PoukOZ.js';
7
7
  import { Rng, RetryPolicy } from '../retry-backoff/index.js';
8
- import 'zod';
9
- import '../types-BseNPIhr.js';
8
+ import '@standard-schema/spec';
9
+ import '../types-DPm5YCDQ.js';
10
10
 
11
11
  /**
12
12
  * A minted credential: the opaque `value` to send on the wire, and the absolute
@@ -75,59 +75,38 @@ interface TokenState {
75
75
  declare function initTokenRefresh(): TokenState;
76
76
  /**
77
77
  * The Cmd this knob emits to ask the runtime to mint a fresh token. Plain data
78
- * (no closure — invariant 1) carrying nothing: the refresh port takes no input,
79
- * and the slice the result installs is read off the follow-up Msg, not this
80
- * Cmd. The consumer unions this into their machine's Cmd type and lets
81
- * `handlers` interpret it.
78
+ * (no closure — invariant 1) carrying nothing: the refresh takes no input, and
79
+ * the slice the result installs is read off the settled Msg, not this Cmd.
80
+ * List it in the machine's `cmds`; its handler returns `ok(token)` or
81
+ * `err({ _tag: "token_refresh_failed", … })`, and the engine mints
82
+ * `refresh_token_ok` / `refresh_token_err`.
82
83
  */
83
84
  declare const refreshToken: CmdDef<"refresh_token", Record<string, never>, Token, {
84
85
  readonly [detail: string]: unknown;
85
86
  readonly _tag: "token_refresh_failed";
86
- }, unknown>;
87
+ }>;
87
88
  type RefreshTokenCmd = CmdOf<typeof refreshToken>;
88
89
  /** Construct a `refresh_token` Cmd. */
89
90
  declare function refreshTokenCmd(): RefreshTokenCmd;
90
91
  /**
91
- * The Msg the refresh port dispatches on success — carries the freshly minted
92
- * `Token`. The consumer unions this into their Msg type and handles it by
93
- * folding `refreshed(state, msg.token)` into the slice.
92
+ * The Msg the engine mints when the `refresh_token` handler returns
93
+ * `ok(token)`: `value` is the freshly minted `Token`. Fold it with
94
+ * `refreshed(state, msg.value)`.
94
95
  */
95
- interface TokenRefreshedMsg {
96
- readonly type: typeof MsgType.TokenRefreshed;
97
- readonly token: Token;
98
- }
99
- /** Construct a `token_refreshed` Msg carrying the new token. */
100
- declare function tokenRefreshedMsg(token: Token): TokenRefreshedMsg;
96
+ type TokenRefreshedMsg = SettledOk<"refresh_token", RefreshTokenCmd, Token>;
101
97
  /**
102
- * The Msg the refresh port dispatches on failure — carries the rejection so the
103
- * consumer's reducer can decide whether to back off, surface an error, or give
104
- * up (errors are data: it is dispatched, never swallowed). The slice itself is
105
- * left untouched by a failed refresh — the held token (if any) stays held and
106
- * stale stays set, so the consumer can retry; clearing it is the consumer's
107
- * policy, expressed in their reducer.
98
+ * The Msg the engine mints when the handler returns
99
+ * `err({ _tag: "token_refresh_failed", … })` — errors are data, never
100
+ * swallowed. The slice is left untouched by a failed refresh: the held token
101
+ * (if any) stays held and stale stays set, so the consumer can retry; clearing
102
+ * it is the consumer's policy, expressed in their reducer.
108
103
  */
109
- interface TokenRefreshFailedMsg {
110
- readonly type: typeof MsgType.TokenRefreshFailed;
111
- readonly error: unknown;
112
- }
113
- /** Construct a `token_refresh_failed` Msg carrying the rejection. */
114
- declare function tokenRefreshFailedMsg(error: unknown): TokenRefreshFailedMsg;
115
- /** The two Msgs the refresh port can produce. Union for a consumer's Msg type. */
104
+ type TokenRefreshFailedMsg = SettledErr<"refresh_token", RefreshTokenCmd, TaggedError<"token_refresh_failed">>;
105
+ /** The two Msgs a refresh can settle with. Union for a consumer's Msg type. */
116
106
  type TokenRefreshMsg = TokenRefreshedMsg | TokenRefreshFailedMsg;
117
- /** The ports this knob's `handlers` splice needs — the single refresh DI seam. */
118
- interface TokenRefreshPorts {
119
- /**
120
- * Mint a fresh credential. Called ONLY from the `refresh_token` interpret
121
- * handler — never from a verb — so the network I/O and any clock the issuer
122
- * reads stay at the effect boundary. May reject; the rejection is routed to
123
- * `token_refresh_failed` (errors are data), never thrown into the runtime.
124
- */
125
- readonly refresh: () => Promise<Token>;
126
- }
127
107
  /**
128
- * The knob: hand it a config, get back the slice initializer, the pure verbs,
129
- * and the `handlers` splice. Mirrors every L2 composition's uniform contract so
130
- * the call sites read identically across bricks.
108
+ * The knob: hand it a config, get back the slice initializer and the pure
109
+ * verbs, plus the `refresh_token` Cmd def (`run`) to list in `cmds`.
131
110
  *
132
111
  * @example
133
112
  * const tr = createTokenRefresh({ skewMs: 30_000 });
@@ -135,13 +114,17 @@ interface TokenRefreshPorts {
135
114
  * const [s1, cmds] = tr.ensureFresh(s0, Date.now()); // cmds = [refreshTokenCmd()]
136
115
  */
137
116
  declare function createTokenRefresh(config?: TokenRefreshConfig): {
117
+ /** The `refresh_token` Cmd def — list it in the machine's `cmds`. */
118
+ run: CmdDef<"refresh_token", Record<string, never>, Token, {
119
+ readonly [detail: string]: unknown;
120
+ readonly _tag: "token_refresh_failed";
121
+ }>;
138
122
  /** The slice initializer — spread into the consumer's `init`. */
139
123
  init: typeof initTokenRefresh;
140
124
  needsRefresh: (state: TokenState, at: number) => boolean;
141
125
  on401: (state: TokenState) => TokenState;
142
126
  refreshed: (state: TokenState, token: Token) => TokenState;
143
- ensureFresh: (state: TokenState, at: number) => readonly [TokenState, readonly Cmd[]];
144
- handlers: (ports: TokenRefreshPorts) => Interpret<TokenRefreshMsg, RefreshTokenCmd, unknown>;
127
+ ensureFresh: (state: TokenState, at: number) => readonly [TokenState, readonly RefreshTokenCmd[]];
145
128
  };
146
129
  /** The shape `createTokenRefresh` returns — useful for typing a held knob. */
147
130
  type TokenRefresh = ReturnType<typeof createTokenRefresh>;
@@ -149,19 +132,15 @@ type TokenRefresh = ReturnType<typeof createTokenRefresh>;
149
132
  /**
150
133
  * The authed-call knob. It is `ResilientConfig` widened by the token dimension:
151
134
  *
152
- * - `refresh` — the ONLY required field: the port that mints a fresh `Token`.
153
- * Identical to token-refresh's `TokenRefreshPorts.refresh`; it is hoisted
154
- * onto the config (not the ports) so the consumer spells the credential
155
- * source once, the same way `rng` is curried onto the factory.
156
135
  * - `skewMs` — token-refresh's refresh-ahead skew (optional, defaults to 0).
157
136
  * - every `ResilientConfig` field (`retry` / `circuit` / `rateLimit` /
158
137
  * `cache` / `deadline`) — passed straight through to the resilient-call
159
138
  * knob, each still optional, each still "omit a brick → omit its gate".
139
+ *
140
+ * The token mint is not on the config: it is the `refresh_token` Cmd, and its
141
+ * handler is yours.
160
142
  */
161
- interface AuthedConfig extends ResilientConfig, TokenRefreshConfig {
162
- /** Mint a fresh credential. The single DI seam the auth dimension adds. */
163
- readonly refresh: () => Promise<Token>;
164
- }
143
+ type AuthedConfig = ResilientConfig & TokenRefreshConfig;
165
144
  /**
166
145
  * The composed slice. Three plain-data fields, no closures:
167
146
  *
@@ -209,11 +188,6 @@ type UnauthorizedError = {
209
188
  readonly _tag: "unauthorized";
210
189
  readonly key: string;
211
190
  };
212
- /** Ports `handlers` needs — resilient-call's run port AND token-refresh's refresh port. */
213
- interface AuthedPorts<I, R> extends ResilientPorts<I, R> {
214
- /** Mint a fresh credential. Mirrors `AuthedConfig.refresh` for symmetry at the splice site. */
215
- readonly refresh: () => Promise<Token>;
216
- }
217
191
  /**
218
192
  * Build an authed-call knob from `config`. `rng` is injected for retry jitter
219
193
  * and threaded straight into the underlying resilient-call knob — pass a fixed
@@ -223,34 +197,38 @@ interface AuthedPorts<I, R> extends ResilientPorts<I, R> {
223
197
  * Returns the uniform L2 knob contract, widened by the auth verbs (`on401`,
224
198
  * `onRefreshed`, `installToken`). `I` is the port input type, `R` the result.
225
199
  */
226
- declare function createAuthedCall<I, R>(config: AuthedConfig, rng?: () => number): {
200
+ declare function createAuthedCall<I, R>(config?: AuthedConfig, rng?: () => number): {
201
+ /** The call's `Cmd.define`d run Cmd — list it in `cmds`. */
202
+ run: CmdDef<"resilient_run", {
203
+ readonly key: string;
204
+ readonly input: I;
205
+ }, R, {
206
+ readonly [detail: string]: unknown;
207
+ readonly _tag: "deadline_exceeded";
208
+ } | {
209
+ readonly [detail: string]: unknown;
210
+ readonly _tag: "port_rejected";
211
+ }>;
212
+ /** token-refresh's `refresh_token` Cmd def — list it in `cmds`. */
213
+ refresh: CmdDef<"refresh_token", Record<string, never>, Token, {
214
+ readonly [detail: string]: unknown;
215
+ readonly _tag: "token_refresh_failed";
216
+ }>;
227
217
  init: () => AuthedState<I, R>;
228
218
  attempt: (s: AuthedState<I, R>, key: string, input: I, at: number) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
229
- succeed: (s: AuthedState<I, R>, key: string, msg: SucceedMsg<R>) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
230
- fail: (s: AuthedState<I, R>, key: string, msg: FailMsg) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
219
+ succeed: (s: AuthedState<I, R>, msg: SucceedMsg<R>) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
220
+ fail: (s: AuthedState<I, R>, msg: FailMsg) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
231
221
  on401: (s: AuthedState<I, R>, key: string, at: number) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
232
222
  installToken: (s: AuthedState<I, R>, token: Token) => AuthedState<I, R>;
233
223
  onRefreshed: (s: AuthedState<I, R>, at: number) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
234
- onTimer: (s: AuthedState<I, R>, msg: ResilientTimerMsg) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
224
+ onTimer: (s: AuthedState<I, R>, msg: ResilientTimerMsg | {
225
+ readonly id: string;
226
+ readonly atMs: number;
227
+ }) => readonly [AuthedState<I, R>, readonly AuthedCmd<I>[]];
235
228
  /** token-refresh's call-boundary verb, re-exposed for proactive expiry refresh. */
236
229
  needsRefresh: (s: AuthedState<I, R>, at: number) => boolean;
237
- subs: (s: AuthedState<I, R>) => readonly DeadlineSub[];
238
- handlers: (ports: AuthedPorts<I, R>) => {
239
- refresh_token: (cmd: CmdValue<"refresh_token", Record<string, never>, {
240
- readonly [detail: string]: unknown;
241
- readonly _tag: "token_refresh_failed";
242
- }, unknown>, ctx: PortEmitter, dispatch?: ((msg: TokenRefreshMsg) => void) | undefined) => Promise<void | TokenRefreshMsg>;
243
- resilient_run: (cmd: CmdValue<"resilient_run", {
244
- readonly key: string;
245
- readonly input: I;
246
- }, {
247
- readonly [detail: string]: unknown;
248
- readonly _tag: "deadline_exceeded";
249
- } | {
250
- readonly [detail: string]: unknown;
251
- readonly _tag: "port_rejected";
252
- }, unknown>, ctx: NoCtx) => Promise<SucceedMsg<R, "resilient"> | FailMsg<"resilient">>;
253
- };
230
+ deadlines: (s: AuthedState<I, R>) => readonly DeadlineSub[];
231
+ timer: (s: AuthedState<I, R>) => TimerDeps<ResilientTimerMsg<"resilient">> | null;
254
232
  };
255
233
  /** The shape `createAuthedCall` returns — useful for typing a held knob. */
256
234
  type AuthedCall<I, R> = ReturnType<typeof createAuthedCall<I, R>>;
@@ -370,375 +348,4 @@ interface RetryToSuccessOptions {
370
348
  */
371
349
  declare function retryToSuccess<R>(port: () => Promise<R>, policy: RetryPolicy, opts?: RetryToSuccessOptions): Promise<R>;
372
350
 
373
- /**
374
- * The wrapper's Model slice. `phase` is the lifecycle (`armed` while the
375
- * countdown runs, `exceeded` once the deadline fired); `seq` is the monotonic
376
- * re-arm counter that keys the timer Sub's id so each accepted progress Msg
377
- * retires the old timer and starts a fresh one.
378
- *
379
- * PURE and SERIALIZABLE: there is deliberately NO absolute timestamp here. A
380
- * deadline timestamp would force the merged `update` to read the wall clock to
381
- * compute it, which the conformance gate (property 2 — two replays under
382
- * distinct ambient clocks) is built to catch. Instead, the timer arms a fresh
383
- * full `ms` countdown per generation; no remaining-delay computation and no
384
- * clock read occur in the slice or `update` — the only clock is `setTimeout`
385
- * inside the Sub.
386
- */
387
- interface DeadlineSlice {
388
- /** `armed` while the countdown is live; `exceeded` once the deadline fired. */
389
- readonly phase: "armed" | "exceeded";
390
- /**
391
- * Monotonic re-arm counter. Starts at 0; each accepted progress Msg (while
392
- * armed) bumps it by one. Folds into the timer Sub's id, so a bump = a new id
393
- * = the substrate retires the old timer and arms a fresh `ms` countdown.
394
- */
395
- readonly seq: number;
396
- }
397
- /**
398
- * The composed Model. The base machine's state is nested under `base`; the
399
- * wrapper's state lives in the NAMED `$deadline` slice — never hidden in a
400
- * closure. Both fields are plain data, so the whole composed Model round-trips
401
- * through `JSON.parse(JSON.stringify(...))` iff the base state does.
402
- */
403
- interface DeadlineModel<S> {
404
- readonly base: S;
405
- readonly $deadline: DeadlineSlice;
406
- }
407
- /**
408
- * The relative-timeout Sub the wrapper arms while `phase === "armed"`. `delayMs`
409
- * is the window `config.ms`; the id is `$deadline:timeout:{seq}`, so each
410
- * progress bump produces a distinct id and the reconcile pass churns the timer.
411
- * Same `delayMs`-on-the-Sub shape `fromTimeout` consumes.
412
- */
413
- type DeadlineTimeoutSub = Sub<"$deadline:timeout"> & {
414
- readonly delayMs: number;
415
- };
416
- /**
417
- * The Msg the timeout Sub dispatches when `config.ms` elapses with no accepted
418
- * progress. `seq` echoes the re-arm generation the timer was armed against, so a
419
- * stale timer's Msg (one whose id was already retired) is identifiable; the
420
- * merged `update` only honors it when it matches the current slice `seq`.
421
- */
422
- interface DeadlineExceededMsg {
423
- readonly type: "$deadline:exceeded";
424
- /** The re-arm generation this timer was armed against. */
425
- readonly seq: number;
426
- }
427
- /**
428
- * Construct the deadline-exceeded Msg. Exported so the subscribe cell and tests
429
- * share one constructor rather than two literals that can drift.
430
- */
431
- declare function deadlineExceededMsg(seq: number): DeadlineExceededMsg;
432
- /**
433
- * The fire-and-forget decision Cmd the merged `update` appends on every
434
- * transition, so the wrapper's choice is visible in the replayed Cmd log
435
- * (rule 3) — `rearm` when an accepted progress Msg bumped `seq`, `expire` when
436
- * the deadline-exceeded Msg flipped the phase, `idle` otherwise (a non-progress
437
- * Msg, or a Msg after the machine already auto-failed). Plain data; its
438
- * interpret handler is a no-op (the decision is the Cmd's presence in the log,
439
- * not a side effect).
440
- */
441
- declare const deadlineDecision: CmdDef<"$deadline:decision", {
442
- decision: "idle" | "rearm" | "expire";
443
- seq: number;
444
- }, void, never, unknown>;
445
- type DeadlineDecisionCmd = CmdOf<typeof deadlineDecision>;
446
- /**
447
- * A predicate over a Msg that decides whether it re-arms the deadline.
448
- *
449
- * The argument is `Readonly<M>` and the result is `boolean`: this is the
450
- * STRONGEST purity TS can express here. `Readonly<M>` removes the most common
451
- * impurity at compile time — the predicate cannot reassign the Msg's own
452
- * fields — and `boolean` pins the return so a side-effecting `(msg) => { … }`
453
- * with an implicit `undefined`/`void` return is rejected.
454
- *
455
- * What the type system CANNOT encode (TS has no effect tracking): observational
456
- * purity. A body that reads `Date.now()`, calls `Math.random()`, performs IO,
457
- * or closes over and mutates external state still type-checks. Those remain a
458
- * CONVENTION enforced by review, not the compiler — the predicate runs inside
459
- * the pure merged `update` (invariant 2), so it must be a pure function of the
460
- * Msg alone: no clock, no RNG, no IO, no captured mutable state.
461
- */
462
- type ProgressPredicate<M extends {
463
- type: string;
464
- }> = (msg: Readonly<M>) => boolean;
465
- /**
466
- * The deadline knob. `ms` is the inactivity window; `progress` is an OPTIONAL
467
- * predicate picking which base Msgs count as activity (default: EVERY base Msg
468
- * re-arms). The predicate runs INSIDE the pure merged `update`, so it must be a
469
- * pure function of the Msg — see {@link ProgressPredicate} for what the type
470
- * enforces vs. what stays a convention.
471
- */
472
- interface DeadlineConfig<M extends {
473
- type: string;
474
- }> {
475
- /** The inactivity window in milliseconds. Auto-fail after `ms` with no progress. */
476
- readonly ms: number;
477
- /**
478
- * Predicate picking which base Msgs re-arm the timer. Default: every base Msg
479
- * counts as progress. Pure — a function of the Msg alone (see
480
- * {@link ProgressPredicate}).
481
- */
482
- readonly progress?: ProgressPredicate<M>;
483
- }
484
- /**
485
- * Wrap `base` with an inactivity deadline. Returns a NEW `Machine` over the
486
- * composed Model `{ base, $deadline }`, with the base's Msgs/Cmds/Subs extended
487
- * by the wrapper's own (`$deadline:exceeded` Msg, `$deadline:decision` Cmd,
488
- * `$deadline:timeout` Sub).
489
- *
490
- * The composed machine:
491
- * - `init` rehydrates the base (honoring the `[loaded, []]` contract) and
492
- * seeds `$deadline: { phase: "armed", seq: 0 }`. Base init Cmds pass
493
- * through unchanged.
494
- * - `update` (Reducer form) runs the base reducer for base Msgs — base Cmds
495
- * pass through UNCHANGED — and on an accepted progress Msg bumps `seq`
496
- * (re-arm). The `$deadline:exceeded` Msg cell flips `phase` to `exceeded`.
497
- * Every transition appends a `$deadline:decision` marker Cmd.
498
- * - `subscriptions` is the base's, plus a `$deadline:timeout` Sub keyed on
499
- * `seq` while `phase === "armed"`.
500
- * - `subscribe` is the base's, plus the `$deadline:timeout` cell.
501
- * - `interpret` is the base's, plus a no-op `$deadline:decision` handler.
502
- *
503
- * @param base any `Machine<S, M, C, U, Ctx>`.
504
- * @param config the deadline knob (`ms` window + optional `progress` predicate).
505
- */
506
- declare function withDeadline<S, M extends {
507
- type: string;
508
- }, C extends Cmd, U extends Sub, Ctx>(base: Machine<S, M, C, U, Ctx>, config: DeadlineConfig<M>): Machine<DeadlineModel<S>, M | DeadlineExceededMsg, C | DeadlineDecisionCmd, U | DeadlineTimeoutSub, Ctx>;
509
-
510
- /**
511
- * The `withResilience` knob. Extends resilient-call's `ResilientConfig` (every
512
- * brick optional — omit a brick, omit its gate) with the two fields the wrapper
513
- * needs that the bare knob does not:
514
- *
515
- * - `target` — the `Cmd.type` discriminant of the ONE base Cmd this wrapper
516
- * hardens. Every other base Cmd passes through untouched.
517
- * - `keyOf` — OPTIONAL. Derives the per-call resilience `key` from the target
518
- * base Cmd, so distinct logical calls (distinct urls, distinct ids) get
519
- * independent retry / cache state. Pure — it runs inside the merged
520
- * `update`, so it must NOT read the clock / RNG. Defaults to a single
521
- * shared key (`target`), i.e. one logical call at a time.
522
- */
523
- interface ResilienceConfig<TC extends Cmd, TM extends {
524
- type: string;
525
- }> extends ResilientConfig {
526
- /** The base `Cmd.type` to make resilient. */
527
- readonly target: TC["type"];
528
- /** Optional per-call key deriver. Pure. Defaults to the target type itself. */
529
- readonly keyOf?: (cmd: TC) => string;
530
- /**
531
- * Extracts the real wall-clock time (ms) the TRIGGERING base Msg carries as
532
- * data — the time-as-data convention `resilient-call` expects on `attempt`.
533
- *
534
- * The merged `update` is PURE: it cannot read `Date.now()` (rule 5). The COLD
535
- * attempt's gate (the very first `attempt` for a target Cmd in a transition)
536
- * still needs a real instant — the breaker compares `at` against its recorded
537
- * `openedAtMs`, the cache against its write `at`, the bucket against its last
538
- * refill `at`, and the deadline cap anchors at `at + deadline.ms`. Fabricating
539
- * `at = 0` is the betrayal: a cold attempt after a real-time breaker trip then
540
- * computes cooldown = `0 - realTripTime < 0` forever, so the breaker NEVER
541
- * recovers, and a `0`-anchored deadline brick fires immediately.
542
- *
543
- * So the base Msg that drove this transition MUST carry its own wall time as
544
- * data, and `at` reads it off that Msg. The merged update passes
545
- * `at(currentBaseMsg)` into `rc.attempt` for every target Cmd that transition
546
- * emitted — real time enters the cold gate as Msg data, never `0`.
547
- *
548
- * REQUIRED whenever any time-sensitive brick (`circuit`, `rateLimit`, `cache`,
549
- * `deadline`) is configured — `withResilience` THROWS at construction if such
550
- * a brick is set and `at` is absent (errors are data: the cold gate needs real
551
- * time, refuse to build a breaker that can never recover). OPTIONAL when only
552
- * `retry` is configured: retry backoff reads its time off the result / timer
553
- * Msgs (`$resilience:err.at`, `$resilience:timer.atMs`), never the cold attempt
554
- * — so a `0` cold `at` is harmless there. Pure: it runs inside the merged
555
- * `update`, so it must NOT read the clock / RNG.
556
- */
557
- readonly at?: (msg: TM) => number;
558
- }
559
- /**
560
- * The composed Model. The base machine's state nests under `base`; the wrapper
561
- * owns the NAMED `$resilience` slice — resilient-call's `ResilientState` keyed
562
- * by the per-call `key`, with `I = TC` (the original target base Cmd is the
563
- * port input) and `R = unknown` (the base interpret handler's resolution — the
564
- * wrapper never inspects it; it routes Ok/Err and delivers a Msg to the base).
565
- * Plain data → round-trips through `JSON.parse(JSON.stringify(...))` iff the
566
- * base state does.
567
- */
568
- interface ResilienceModel<S, TC extends Cmd> {
569
- readonly base: S;
570
- readonly $resilience: ResilientState<TC, unknown>;
571
- }
572
- /**
573
- * The carrier Cmd the wrapper emits when the gate ADMITS a target base Cmd. It
574
- * RETAGS the original base Cmd into `input` — the base Cmd is plain data, so no
575
- * closure (invariant 3). The interpret handler unwraps `input` and runs it
576
- * through the BASE interpret for that Cmd type. `key` indexes the resilient
577
- * call this run belongs to.
578
- */
579
- declare function resilienceRunCmdDef<TC extends Cmd, TM>(): CmdDef<"$resilience:run", {
580
- readonly key: string;
581
- /** The ORIGINAL base Cmd, retagged — never swallowed. */
582
- readonly input: TC;
583
- }, void | TM, {
584
- readonly [detail: string]: unknown;
585
- readonly _tag: "deadline_exceeded";
586
- } | {
587
- readonly [detail: string]: unknown;
588
- readonly _tag: "base_rejected";
589
- }, unknown>;
590
- type ResilienceRunCmd<TC extends Cmd> = CmdOf<ReturnType<typeof resilienceRunCmdDef<TC, unknown>>>;
591
- /**
592
- * The success Msg the `$resilience:run` handler dispatches back when the base
593
- * interpret resolved OK. Routes to `succeed`, then `result` — the base
594
- * handler's follow-up, when it is a Msg — is folded through the base reducer.
595
- * `at` is stamped at the interpret boundary (the one permitted clock read).
596
- */
597
- type ResilienceOkMsg = {
598
- readonly type: "$resilience:ok";
599
- readonly key: string;
600
- readonly result: unknown;
601
- readonly at: number;
602
- };
603
- /**
604
- * The error Msg the `$resilience:run` handler dispatches back when the base
605
- * interpret threw / rejected. Routes to `fail` (trip the breaker, then back off
606
- * or settle). `at` is stamped at the interpret boundary.
607
- */
608
- type ResilienceErrMsg = {
609
- readonly type: "$resilience:err";
610
- readonly key: string;
611
- readonly error: unknown;
612
- readonly at: number;
613
- };
614
- /**
615
- * The retry / deadline timer Msg the `$resilience:timer` Sub dispatches when
616
- * the wall clock crosses the armed instant. Routes to `onTimer`. `id` is the
617
- * `$resilience:`-namespaced timer id; `atMs` is the fire instant (Msg data, the
618
- * clock that drove it lives in the Sub).
619
- */
620
- type ResilienceTimerMsg = {
621
- readonly type: "$resilience:timer";
622
- readonly id: string;
623
- readonly atMs: number;
624
- };
625
- /** Every Msg the wrapper adds to the base's Msg union. */
626
- type ResilienceMsg = ResilienceOkMsg | ResilienceErrMsg | ResilienceTimerMsg;
627
- /** Every Cmd the wrapper adds to the base's Cmd union. */
628
- type ResilienceCmd<TC extends Cmd> = ResilienceRunCmd<TC>;
629
- /** The Sub the wrapper adds — a deadline-style timer in the `$resilience` family. */
630
- type ResilienceTimerSub = Sub<"$resilience:timer"> & {
631
- readonly atMs: number;
632
- };
633
- /**
634
- * Wrap `base` so its `config.target` Cmd is run through the resilient-call
635
- * concern (cache → circuit → rate-limit → retry, deadline-capped). Returns a
636
- * NEW `Machine` over the composed Model `{ base, $resilience }`, with the
637
- * base's Msgs/Cmds/Subs/Ctx extended by the wrapper's own.
638
- *
639
- * @param base any `Machine<S, M, C, U, Ctx>`.
640
- * @param config the resilience knob: `target`, the optional bricks, `keyOf`.
641
- * @param rng injected backoff jitter source. Pass a fixed `() => 0.5` in
642
- * tests to pin retry delays; defaults to `Math.random` (read only
643
- * at the verb boundary, never inside the merged `update`).
644
- */
645
- declare function withResilience<S, M extends {
646
- type: string;
647
- }, C extends Cmd, U extends Sub, Ctx>(base: Machine<S, M, C, U, Ctx>, config: ResilienceConfig<C, M>, rng?: () => number): Machine<ResilienceModel<S, C>, M | ResilienceMsg, C | ResilienceCmd<C>, U | ResilienceTimerSub, Ctx>;
648
-
649
- /**
650
- * The datum handed to the sink. The default projector emits `{ seq, msgType }`;
651
- * a custom `event` projector may return any extra fields. `at` is OPTIONAL and
652
- * plain data — it is stamped at the interpret boundary by the `clock` port, not
653
- * inside the (pure, clock-free) merged `update`. Every field is JSON-round-trip
654
- * serializable so a recorded telemetry stream stays durable.
655
- */
656
- interface TelemetryEvent {
657
- /** Monotonic event counter — equals `$telemetry.seq` after the transition. */
658
- readonly seq: number;
659
- /** The `Msg.type` discriminant that drove this transition. */
660
- readonly msgType: string;
661
- /**
662
- * Wall-clock instant the event was emitted, ms since epoch. Absent until the
663
- * interpret boundary stamps it (the `clock` port). Optional because the
664
- * merged `update` is clock-free — a verb must never read `Date.now()`.
665
- */
666
- readonly at?: number;
667
- }
668
- /**
669
- * The wrapper's Model slice. A monotonic event counter — minimal but real, so
670
- * the named-slice mechanism is exercised for the richer-slice wrappers that
671
- * follow. `seq` equals the number of base Msgs folded so far: after replaying
672
- * `N` msgs, `$telemetry.seq === N`. Plain data → serializes into a `Store<S>`.
673
- */
674
- interface TelemetrySlice {
675
- readonly seq: number;
676
- }
677
- /**
678
- * The composed Model. The base machine's state is nested under `base`; the
679
- * wrapper's state lives in the NAMED `$telemetry` slice — never hidden in a
680
- * closure. Both fields are plain data, so the whole composed Model round-trips
681
- * through `JSON.parse(JSON.stringify(...))` iff the base state does.
682
- */
683
- interface TelemetryModel<S> {
684
- readonly base: S;
685
- readonly $telemetry: TelemetrySlice;
686
- }
687
- /**
688
- * The fire-and-forget Cmd the merged `update` appends after every base
689
- * transition. It carries the projected `event` as plain data (invariant 3 —
690
- * no closure). The interpret handler hands `event` to the sink and dispatches
691
- * NOTHING back, so there is no feedback loop.
692
- */
693
- declare const telemetryEmit: CmdDef<"$telemetry:emit", {
694
- event: TelemetryEvent;
695
- }, void, never, TelemetryPorts>;
696
- type TelemetryEmitCmd = CmdOf<typeof telemetryEmit>;
697
- /**
698
- * The side-effecting ports the wrapper adds to `Ctx`. The sink (PostHog / Sentry
699
- * bridge) lives at the interpret boundary, never in a verb. `clock` is the
700
- * optional wall-clock source used to stamp `event.at` at that same boundary —
701
- * pass `Date.now` in production, a fixed `() => 1000` in tests; omit it to leave
702
- * `at` unset.
703
- */
704
- interface TelemetryPorts {
705
- /** The side-effecting sink. Fire-and-forget — its result is never awaited into a Msg. */
706
- readonly telemetrySink: (event: TelemetryEvent) => void | Promise<void>;
707
- /** Optional clock for stamping `event.at` at the interpret boundary. */
708
- readonly clock?: () => number;
709
- }
710
- /**
711
- * The telemetry knob. `event` is an OPTIONAL projector mapping
712
- * `(msg, prev, next)` to a `TelemetryEvent`; omit it and the default emits
713
- * `{ seq, msgType }` where `seq` is the post-transition counter. The projector
714
- * runs INSIDE the pure merged `update`, so it must not read the clock — stamp
715
- * `at` at the sink boundary via the `clock` port instead.
716
- */
717
- interface TelemetryConfig<S, M extends {
718
- type: string;
719
- }> {
720
- readonly event?: (msg: M, prev: TelemetryModel<S>, next: TelemetryModel<S>) => TelemetryEvent;
721
- }
722
- /**
723
- * Wrap `base` with observe-only telemetry. Returns a NEW `Machine` over the
724
- * composed Model `{ base, $telemetry }`, with the base's Msgs/Cmds/Subs/Ctx
725
- * extended by the wrapper's own.
726
- *
727
- * The composed machine:
728
- * - `init` rehydrates the base (honoring the `[loaded, []]` contract) and
729
- * seeds `$telemetry: { seq: 0 }`. Base init Cmds pass through unchanged.
730
- * - `update` (Reducer form, keyed by every base `Msg.type`) runs the base
731
- * reducer, increments `seq`, and appends a `$telemetry:emit` Cmd carrying
732
- * the projected event. Base Cmds pass through UNCHANGED — the observe-only
733
- * property.
734
- * - `subscriptions` / `subscribe` are the base's, verbatim (no timers).
735
- * - `interpret` is `{ ...base.interpret, "$telemetry:emit": sink-handler }`.
736
- *
737
- * @param base any `Machine<S, M, C, U, Ctx>`.
738
- * @param config the telemetry knob (optional `event` projector).
739
- */
740
- declare function withTelemetry<S, M extends {
741
- type: string;
742
- }, C extends Cmd, U extends Sub, Ctx>(base: Machine<S, M, C, U, Ctx>, config?: TelemetryConfig<S, M>): Machine<TelemetryModel<S>, M, C | TelemetryEmitCmd, U, Ctx & TelemetryPorts>;
743
-
744
- export { type AuthedCall, type AuthedCmd, type AuthedConfig, type AuthedPorts, type AuthedState, type DeadlineConfig, type DeadlineDecisionCmd, type DeadlineExceededMsg, type DeadlineModel, type DeadlineSlice, DeadlineSub, type DeadlineTimeoutSub, FailMsg, type ProgressPredicate, type RefreshTokenCmd, type ResilienceCmd, type ResilienceConfig, type ResilienceErrMsg, type ResilienceModel, type ResilienceMsg, type ResilienceOkMsg, type ResilienceRunCmd, type ResilienceTimerMsg, type ResilienceTimerSub, ResilientConfig, ResilientPorts, ResilientState, ResilientTimerMsg, RetryExhaustedError, type RetryToSuccessOptions, RunCmd, SucceedMsg, type TelemetryConfig, type TelemetryEmitCmd, type TelemetryEvent, type TelemetryModel, type TelemetryPorts, type TelemetrySlice, type Token, type TokenRefresh, type TokenRefreshConfig, type TokenRefreshFailedMsg, type TokenRefreshMsg, type TokenRefreshPorts, type TokenRefreshedMsg, type TokenState, type Unauthorized, type UnauthorizedError, createAuthedCall, createTokenRefresh, deadlineDecision, deadlineExceededMsg, initTokenRefresh, liftAuthed, refreshToken, refreshTokenCmd, resilienceRunCmdDef, retryToSuccess, telemetryEmit, tokenRefreshFailedMsg, tokenRefreshedMsg, withDeadline, withResilience, withTelemetry };
351
+ export { type AuthedCall, type AuthedCmd, type AuthedConfig, type AuthedState, DeadlineSub, FailMsg, type RefreshTokenCmd, ResilientConfig, ResilientState, ResilientTimerMsg, RetryExhaustedError, type RetryToSuccessOptions, RunCmd, SucceedMsg, type Token, type TokenRefresh, type TokenRefreshConfig, type TokenRefreshFailedMsg, type TokenRefreshMsg, type TokenRefreshedMsg, type TokenState, type Unauthorized, type UnauthorizedError, createAuthedCall, createTokenRefresh, initTokenRefresh, liftAuthed, refreshToken, refreshTokenCmd, retryToSuccess };