@demlik/tea 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/index.d.ts +151 -0
- package/dist/cache/index.js +53 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/chunk-2YFLOCWX.js +15 -0
- package/dist/chunk-2YFLOCWX.js.map +1 -0
- package/dist/chunk-CMG2KMWX.js +458 -0
- package/dist/chunk-CMG2KMWX.js.map +1 -0
- package/dist/chunk-PYBQREFU.js +95 -0
- package/dist/chunk-PYBQREFU.js.map +1 -0
- package/dist/chunk-PYOCUBQJ.js +196 -0
- package/dist/chunk-PYOCUBQJ.js.map +1 -0
- package/dist/chunk-Y5JM3UUH.js +126 -0
- package/dist/chunk-Y5JM3UUH.js.map +1 -0
- package/dist/chunk-ZRUST6BK.js +62 -0
- package/dist/chunk-ZRUST6BK.js.map +1 -0
- package/dist/circuit-breaker/index.d.ts +196 -0
- package/dist/circuit-breaker/index.js +53 -0
- package/dist/circuit-breaker/index.js.map +1 -0
- package/dist/deadline/index.d.ts +113 -0
- package/dist/deadline/index.js +20 -0
- package/dist/deadline/index.js.map +1 -0
- package/dist/debounce/index.d.ts +114 -0
- package/dist/debounce/index.js +56 -0
- package/dist/debounce/index.js.map +1 -0
- package/dist/devtools/index.d.ts +137 -0
- package/dist/devtools/index.js +217 -0
- package/dist/devtools/index.js.map +1 -0
- package/dist/devtools/styles.css +218 -0
- package/dist/do/index.d.ts +135 -0
- package/dist/do/index.js +54 -0
- package/dist/do/index.js.map +1 -0
- package/dist/extension/index.d.ts +207 -0
- package/dist/extension/index.js +26 -0
- package/dist/extension/index.js.map +1 -0
- package/dist/extension/react.d.ts +121 -0
- package/dist/extension/react.js +69 -0
- package/dist/extension/react.js.map +1 -0
- package/dist/extension/subs/index.d.ts +44 -0
- package/dist/extension/subs/index.js +83 -0
- package/dist/extension/subs/index.js.map +1 -0
- package/dist/extension/test-utils.d.ts +128 -0
- package/dist/extension/test-utils.js +269 -0
- package/dist/extension/test-utils.js.map +1 -0
- package/dist/idempotency/index.d.ts +131 -0
- package/dist/idempotency/index.js +58 -0
- package/dist/idempotency/index.js.map +1 -0
- package/dist/index.d.ts +364 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/machine-viz/index.d.ts +78 -0
- package/dist/machine-viz/index.js +139 -0
- package/dist/machine-viz/index.js.map +1 -0
- package/dist/mem/index.d.ts +45 -0
- package/dist/mem/index.js +19 -0
- package/dist/mem/index.js.map +1 -0
- package/dist/node/index.d.ts +134 -0
- package/dist/node/index.js +89 -0
- package/dist/node/index.js.map +1 -0
- package/dist/pbt/arbitraries/index.d.ts +135 -0
- package/dist/pbt/arbitraries/index.js +3 -0
- package/dist/pbt/arbitraries/index.js.map +1 -0
- package/dist/pbt/index.d.ts +28 -0
- package/dist/pbt/index.js +18 -0
- package/dist/pbt/index.js.map +1 -0
- package/dist/pbt/runners/index.d.ts +117 -0
- package/dist/pbt/runners/index.js +4 -0
- package/dist/pbt/runners/index.js.map +1 -0
- package/dist/rate-limit/index.d.ts +115 -0
- package/dist/rate-limit/index.js +45 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/react/index.d.ts +72 -0
- package/dist/react/index.js +33 -0
- package/dist/react/index.js.map +1 -0
- package/dist/recorder/index.d.ts +287 -0
- package/dist/recorder/index.js +157 -0
- package/dist/recorder/index.js.map +1 -0
- package/dist/retry-backoff/index.d.ts +131 -0
- package/dist/retry-backoff/index.js +37 -0
- package/dist/retry-backoff/index.js.map +1 -0
- package/dist/subs/index.d.ts +57 -0
- package/dist/subs/index.js +4 -0
- package/dist/subs/index.js.map +1 -0
- package/dist/testing/index.d.ts +220 -0
- package/dist/testing/index.js +105 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/throttle/index.d.ts +125 -0
- package/dist/throttle/index.js +54 -0
- package/dist/throttle/index.js.map +1 -0
- package/dist/trace-replay/index.d.ts +52 -0
- package/dist/trace-replay/index.js +57 -0
- package/dist/trace-replay/index.js.map +1 -0
- package/dist/types-CwBC5wMn.d.ts +5 -0
- package/dist/work-queue/index.d.ts +56 -0
- package/dist/work-queue/index.js +143 -0
- package/dist/work-queue/index.js.map +1 -0
- package/package.json +184 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/throttle/index.ts
|
|
2
|
+
function throttle(fn, ms, opts) {
|
|
3
|
+
const leading = opts?.leading ?? true;
|
|
4
|
+
const trailing = opts?.trailing ?? true;
|
|
5
|
+
let handle = null;
|
|
6
|
+
let pendingArgs = null;
|
|
7
|
+
function clearHandle() {
|
|
8
|
+
if (handle !== null) {
|
|
9
|
+
clearTimeout(handle);
|
|
10
|
+
handle = null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function onWindowExpired() {
|
|
14
|
+
if (trailing && pendingArgs !== null) {
|
|
15
|
+
const args = pendingArgs;
|
|
16
|
+
pendingArgs = null;
|
|
17
|
+
handle = setTimeout(onWindowExpired, ms);
|
|
18
|
+
fn(...args);
|
|
19
|
+
} else {
|
|
20
|
+
handle = null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const throttled = (...args) => {
|
|
24
|
+
if (handle === null) {
|
|
25
|
+
if (leading) {
|
|
26
|
+
fn(...args);
|
|
27
|
+
} else if (trailing) {
|
|
28
|
+
pendingArgs = args;
|
|
29
|
+
}
|
|
30
|
+
handle = setTimeout(onWindowExpired, ms);
|
|
31
|
+
} else if (trailing) {
|
|
32
|
+
pendingArgs = args;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
throttled.cancel = () => {
|
|
36
|
+
clearHandle();
|
|
37
|
+
pendingArgs = null;
|
|
38
|
+
};
|
|
39
|
+
throttled.flush = () => {
|
|
40
|
+
if (pendingArgs !== null) {
|
|
41
|
+
const args = pendingArgs;
|
|
42
|
+
pendingArgs = null;
|
|
43
|
+
clearHandle();
|
|
44
|
+
fn(...args);
|
|
45
|
+
} else {
|
|
46
|
+
clearHandle();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return throttled;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { throttle };
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/throttle/index.ts"],"names":[],"mappings":";AAyHO,SAAS,QAAA,CACd,EAAA,EACA,EAAA,EACA,IAAA,EACc;AACd,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,IAAA;AACjC,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,IAAA;AAMnC,EAAA,IAAI,MAAA,GAA+C,IAAA;AAOnD,EAAA,IAAI,WAAA,GAAwB,IAAA;AAE5B,EAAA,SAAS,WAAA,GAAoB;AAC3B,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,YAAA,CAAa,MAAM,CAAA;AACnB,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,EACF;AAOA,EAAA,SAAS,eAAA,GAAwB;AAC/B,IAAA,IAAI,QAAA,IAAY,gBAAgB,IAAA,EAAM;AACpC,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AAId,MAAA,MAAA,GAAS,UAAA,CAAW,iBAAiB,EAAE,CAAA;AACvC,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,CAAA,MAAO;AAEL,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,IAAI,IAAA,KAAkB;AACtC,IAAA,IAAI,WAAW,IAAA,EAAM;AAEnB,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,MACZ,WAAW,QAAA,EAAU;AAInB,QAAA,WAAA,GAAc,IAAA;AAAA,MAChB;AACA,MAAA,MAAA,GAAS,UAAA,CAAW,iBAAiB,EAAE,CAAA;AAAA,IACzC,WAAW,QAAA,EAAU;AAGnB,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAAA,EAEF,CAAA;AAEA,EAAA,SAAA,CAAU,SAAS,MAAY;AAC7B,IAAA,WAAA,EAAY;AACZ,IAAA,WAAA,GAAc,IAAA;AAAA,EAChB,CAAA;AAEA,EAAA,SAAA,CAAU,QAAQ,MAAY;AAI5B,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,WAAA,EAAY;AACZ,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,CAAA,MAAO;AAGL,MAAA,WAAA,EAAY;AAAA,IACd;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,SAAA;AACT","file":"index.js","sourcesContent":["/**\n * @demlik/tea/throttle — a timer-based call transformer that caps invocation\n * to at most once per `ms` window.\n *\n * Sibling of `@demlik/tea/debounce` — the two share a SYMMETRIC surface\n * (`Throttled<A>` / `Debounced<A>`, both callable + `.cancel()` + `.flush()`,\n * both `{ leading?, trailing? }`) so they read as a pair. They are kept in\n * separate files with NO cross-import: each is a self-contained timer, and the\n * coalesce-vs-drop policies differ enough that sharing code would hide the\n * distinction. The contrast IS the lesson:\n *\n * - **debounce** waits for quiet — fires once AFTER a burst settles.\n * - **throttle** enforces a rate — fires AT MOST once per `ms` while the\n * burst is ongoing, dropping the calls in between.\n *\n * WHAT THIS IS NOT — and why it lives outside `update`:\n *\n * - NOT a reducer / Transitions cell. Reducers are PURE (invariant 2 — same\n * inputs → same outputs, no `Date.now`, no timers). A throttle holds a live\n * `setTimeout` handle and a window cursor; folding it into `update` would\n * smuggle a timer into the pure core.\n * - NOT a Sub. A Sub is a continuous SOURCE of Msgs the substrate reconciles\n * by id every transition (invariant 4). A throttle is a SINK transformer:\n * it wraps a `void`-returning handler (the canonical one is\n * `Runtime.dispatch`) so a high-frequency HOST event is rate-limited BEFORE\n * it ever becomes a Msg.\n *\n * It is a higher-order function at the host boundary (pattern 14 — TEA owns\n * state, JS owns timing). The host attaches the wrapped function to a\n * high-frequency event source (`mousemove`, `scroll`, `pointermove`, a game\n * loop) and the substrate downstream only ever sees the rate-limited Msg:\n *\n * const onMoved = throttle(\n * (e: PointerEvent) =>\n * runtime.dispatch({ type: \"UserMovedCursor\", x: e.clientX, y: e.clientY }),\n * 16, // ~60fps cap\n * );\n * window.addEventListener(\"pointermove\", onMoved);\n * // cleanup: onMoved.cancel(); window.removeEventListener(\"pointermove\", onMoved);\n *\n * NOT exported from the package root — reached via the `@demlik/tea/throttle`\n * subpath, same one-shape-per-package rule as `rate-limit`, `deadline`, and\n * `work-queue`.\n *\n * Uses the universal `setTimeout` / `clearTimeout` timer idiom (same discipline\n * as `subs/from-timeout.ts`): exactly one live handle at a time, the window\n * timer clears itself on expiry, `cancel` / `flush` clear and null it so a\n * stale callback can never run.\n */\n\n/**\n * A throttled wrapper around `fn`. Callable with the SAME argument tuple as\n * `fn` — `(...args: A) => void` — plus two control methods that share names and\n * shapes with `Debounced<A>` so the two modules read as a symmetric pair.\n */\nexport interface Throttled<A extends readonly unknown[]> {\n /** Invoke `fn` if the window is open, else record args for the trailing fire. */\n (...args: A): void;\n /**\n * Drop any pending trailing fire WITHOUT invoking `fn`, and close the active\n * window. Clears the timer, forgets the captured trailing args, and resets\n * the cursor so the next call fires immediately on the leading edge (if\n * `leading` is enabled). Idempotent.\n *\n * The host-cleanup partner of `removeEventListener`: cancel the pending fire\n * when the component unmounts / the listener detaches, so a queued\n * `dispatch` can't land after teardown.\n */\n cancel(): void;\n /**\n * Fire any pending trailing call IMMEDIATELY with its latest captured args,\n * then close the window. No-op when nothing is pending. Use to force the last\n * dropped call out early — e.g. flush the final cursor position when a drag\n * ends, so the rest state isn't a stale mid-drag sample.\n */\n flush(): void;\n}\n\n/**\n * Wrap `fn` so it runs AT MOST once per `ms` window, no matter how often it is\n * called.\n *\n * Leading + trailing (the default): the first call fires `fn` IMMEDIATELY (the\n * leading edge) and opens a `ms` window. Calls during the window are dropped\n * from immediate firing but their args are remembered; when the window closes,\n * `fn` fires ONCE more with the LATEST dropped args (the trailing edge) and a\n * new window opens. A call after the window has already closed leads a fresh\n * window immediately. Spacing every call at least `ms` apart therefore fires\n * each one on its own leading edge.\n *\n * @param fn The handler to rate-limit. Typically `(...) => runtime.dispatch(...)`,\n * but any `(...args: A) => void` works. The return value is\n * ignored — throttle is a fire transformer, not a value pipe.\n * @param ms Window length in milliseconds. `fn` fires at most once per `ms`.\n * @param opts.leading Fire on the FIRST call of a window (the leading edge),\n * immediately. Default `true`.\n * @param opts.trailing Fire once at window END with the latest args dropped\n * during the window. Default `true`.\n *\n * Edge combinations (matching lodash's settled semantics, the de-facto\n * standard the ecosystem rediscovered):\n *\n * - `{ leading: true, trailing: true }` (default): immediate fire + a\n * trailing fire per window while calls keep arriving. The trailing fire is\n * SUPPRESSED when no extra call arrived during the window (a lone call\n * already fired on the leading edge — no stale trailing double-fire).\n * - `{ leading: true, trailing: false }`: only the immediate fire; everything\n * in the window is dropped with no catch-up at the end.\n * - `{ leading: false, trailing: true }`: no immediate fire; the FIRST call\n * opens a window and `fn` fires at the window's end with the latest args.\n * Steady \"sample every `ms`\" behavior with no instant response.\n * - `{ leading: false, trailing: false }`: never fires. Degenerate but legal;\n * we don't throw — the caller asked for a no-op transformer.\n *\n * WHY a default of leading + trailing both `true`: a throttle's job is \"respond\n * now, then keep responding at a steady rate.\" Leading gives the instant\n * response (the first `mousemove` updates immediately); trailing guarantees the\n * FINAL sample of a window isn't lost (the cursor ends where the user actually\n * left it, not one window-width behind). Dropping either loses one of those two\n * guarantees, so both default on.\n */\nexport function throttle<A extends readonly unknown[]>(\n fn: (...args: A) => void,\n ms: number,\n opts?: { leading?: boolean; trailing?: boolean },\n): Throttled<A> {\n const leading = opts?.leading ?? true;\n const trailing = opts?.trailing ?? true;\n\n // Exactly one live handle at a time (the from-timeout discipline). `null`\n // when no window is open — the single source of truth for \"is a window\n // active?\". The window timer arms on a fire and clears itself on expiry; the\n // call path never stacks overlapping timers.\n let handle: ReturnType<typeof setTimeout> | null = null;\n // The latest args seen DURING an open window, held for the trailing fire.\n // Overwritten by every dropped call (the trailing edge fires with the LAST\n // args), cleared the instant we fire / cancel. `null` (not `undefined`) is\n // the empty sentinel because `A` may itself contain `undefined` elements — a\n // present-but-empty tuple is still a pending call, distinct from \"nothing\n // pending\".\n let pendingArgs: A | null = null;\n\n function clearHandle(): void {\n if (handle !== null) {\n clearTimeout(handle);\n handle = null;\n }\n }\n\n // Window expiry: the `ms` window just closed. If a call arrived during the\n // window (`pendingArgs !== null`) and trailing is enabled, fire it now and\n // open a FRESH window so the rate cap continues across a sustained burst.\n // If nothing was dropped, let the window close (handle → null) so the next\n // call leads a new window immediately.\n function onWindowExpired(): void {\n if (trailing && pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n // Re-arm BEFORE firing so a re-entrant call from inside `fn` sees an open\n // window (and is correctly dropped/queued) rather than leading a second\n // window — keeps the \"at most once per `ms`\" invariant under re-entry.\n handle = setTimeout(onWindowExpired, ms);\n fn(...args);\n } else {\n // No trailing fire owed — close the window.\n handle = null;\n }\n }\n\n const throttled = (...args: A): void => {\n if (handle === null) {\n // Window is closed → this call leads a new window.\n if (leading) {\n fn(...args);\n } else if (trailing) {\n // No leading fire, but trailing wants the first call's args so the\n // window's end has something to fire. Without this, a leading:false\n // throttle would swallow a lone call entirely.\n pendingArgs = args;\n }\n handle = setTimeout(onWindowExpired, ms);\n } else if (trailing) {\n // Window is open → drop this call from immediate firing, but remember its\n // args as the latest candidate for the trailing edge.\n pendingArgs = args;\n }\n // Window open + trailing disabled → pure drop, nothing to remember.\n };\n\n throttled.cancel = (): void => {\n clearHandle();\n pendingArgs = null;\n };\n\n throttled.flush = (): void => {\n // Force the pending trailing fire out now and close the window. Mirrors\n // `onWindowExpired`'s fire branch but is caller-driven, so it tears the\n // timer down rather than re-arming.\n if (pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n clearHandle();\n fn(...args);\n } else {\n // Nothing pending — still close a lingering window so a post-flush call\n // leads cleanly.\n clearHandle();\n }\n };\n\n return throttled;\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Cmd, Sub, Machine } from '../index.js';
|
|
2
|
+
import { Trace } from '../recorder/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The outcome of {@link replayTrace}.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam S - the machine's State type.
|
|
8
|
+
*/
|
|
9
|
+
interface ReplayResult<S> {
|
|
10
|
+
/** `true` iff the recomputed final state deep-equals the trace's `finalState`. */
|
|
11
|
+
matches: boolean;
|
|
12
|
+
/** The trace's recorded `finalState` — what the run originally produced. */
|
|
13
|
+
expected: S;
|
|
14
|
+
/** The state the current reducer produced for the same input. */
|
|
15
|
+
actual: S;
|
|
16
|
+
/**
|
|
17
|
+
* Present iff `matches` is `false`. The FIRST point of divergence found by
|
|
18
|
+
* a deterministic pre-order walk:
|
|
19
|
+
* - `path` — a dotted/indexed access path from the state root, e.g.
|
|
20
|
+
* `state.queue[2].status` or `state` for a root-level
|
|
21
|
+
* scalar mismatch.
|
|
22
|
+
* - `expected` — the recorded value at that path.
|
|
23
|
+
* - `actual` — the recomputed value at that path.
|
|
24
|
+
*/
|
|
25
|
+
divergence?: {
|
|
26
|
+
path: string;
|
|
27
|
+
expected: unknown;
|
|
28
|
+
actual: unknown;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Replay a recorded {@link Trace} against `machine` and diff the recomputed
|
|
33
|
+
* final state against the recorded one.
|
|
34
|
+
*
|
|
35
|
+
* Runs the core pure fold `replay(machine, { msgs: trace.msgs, ctx,
|
|
36
|
+
* loaded: trace.loaded })` — init + update only, never `interpret`, never a
|
|
37
|
+
* `Store`, never a live subscription (see `replay` in ../index). The `ctx` is
|
|
38
|
+
* supplied fresh by the caller: the trace is the INPUT (loaded + msgs); ctx is
|
|
39
|
+
* the ENVIRONMENT the reducer runs against now.
|
|
40
|
+
*
|
|
41
|
+
* @param machine - the machine to replay against. Mutating its reducer (the
|
|
42
|
+
* whole point of a regression check) is what makes a recorded
|
|
43
|
+
* trace diverge.
|
|
44
|
+
* @param trace - a Trace from `../recorder` (live `dump()` or a re-hydrated
|
|
45
|
+
* `parseJSONL(...)`).
|
|
46
|
+
* @param ctx - the runtime ctx, supplied fresh at replay time.
|
|
47
|
+
*/
|
|
48
|
+
declare function replayTrace<S, M extends {
|
|
49
|
+
type: string;
|
|
50
|
+
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, trace: Trace<S, M>, ctx: Ctx): ReplayResult<S>;
|
|
51
|
+
|
|
52
|
+
export { type ReplayResult, replayTrace };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { replay } from '../chunk-CMG2KMWX.js';
|
|
2
|
+
|
|
3
|
+
// src/trace-replay/index.ts
|
|
4
|
+
function replayTrace(machine, trace, ctx) {
|
|
5
|
+
const result = replay(machine, {
|
|
6
|
+
msgs: trace.msgs,
|
|
7
|
+
ctx,
|
|
8
|
+
loaded: trace.loaded
|
|
9
|
+
});
|
|
10
|
+
const actual = result.state;
|
|
11
|
+
const expected = trace.finalState;
|
|
12
|
+
const divergence = firstDivergence(expected, actual, "state");
|
|
13
|
+
if (divergence === null) {
|
|
14
|
+
return { matches: true, expected, actual };
|
|
15
|
+
}
|
|
16
|
+
return { matches: false, expected, actual, divergence };
|
|
17
|
+
}
|
|
18
|
+
function firstDivergence(expected, actual, path) {
|
|
19
|
+
if (Object.is(expected, actual)) return null;
|
|
20
|
+
const te = typeOf(expected);
|
|
21
|
+
const ta = typeOf(actual);
|
|
22
|
+
if (te !== ta) return { path, expected, actual };
|
|
23
|
+
if (te === "array") {
|
|
24
|
+
const ae = expected;
|
|
25
|
+
const aa = actual;
|
|
26
|
+
if (ae.length !== aa.length) {
|
|
27
|
+
return { path, expected, actual };
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < ae.length; i++) {
|
|
30
|
+
const d = firstDivergence(ae[i], aa[i], `${path}[${i}]`);
|
|
31
|
+
if (d !== null) return d;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (te === "object") {
|
|
36
|
+
const oe = expected;
|
|
37
|
+
const oa = actual;
|
|
38
|
+
const keys = [.../* @__PURE__ */ new Set([...Object.keys(oe), ...Object.keys(oa)])].sort();
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
const d = firstDivergence(oe[key], oa[key], `${path}.${key}`);
|
|
41
|
+
if (d !== null) return d;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return { path, expected, actual };
|
|
46
|
+
}
|
|
47
|
+
function typeOf(v) {
|
|
48
|
+
if (v === null) return "null";
|
|
49
|
+
if (v === void 0) return "undefined";
|
|
50
|
+
if (Array.isArray(v)) return "array";
|
|
51
|
+
if (typeof v === "object") return "object";
|
|
52
|
+
return "primitive";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { replayTrace };
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/trace-replay/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// trace-replay — replay a recorded Trace against a machine and diff the state.\n//\n// This is the back half of the record/replay feature. `../recorder` captures\n// a run (`loaded` + `msgs` + `finalState`); this module feeds that input\n// through the core `replay(machine, { msgs, ctx, loaded })` fold and asks:\n//\n// does the current reducer still produce the SAME final state for this\n// recorded input?\n//\n// Equal ⇒ the reducer is behavior-compatible with the recording. Diverged ⇒\n// the reducer changed for that input, and the divergence path\n// (`state.queue[2].status`) points at exactly the cell that drifted. This is\n// the regression / production-repro tool: record a prod incident, replay it\n// against the fixed reducer locally, confirm the fix changes the state at the\n// path you expected — or catch an unintended drift before it ships.\n//\n// Standalone by design. It imports ONLY the `Trace` type from `../recorder`\n// and the `replay` engine from `../index`. The deep-equal + first-divergence\n// path finder is self-contained (no external diff lib) — a richer pretty-diff\n// lands in `../devtools` later; trace-replay stays dependency-free so it can\n// run anywhere a machine runs (CI, a Worker, a Node script).\n// ---------------------------------------------------------------------------\n\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\nimport type { Trace } from \"../recorder\";\n\n/**\n * The outcome of {@link replayTrace}.\n *\n * @typeParam S - the machine's State type.\n */\nexport interface ReplayResult<S> {\n /** `true` iff the recomputed final state deep-equals the trace's `finalState`. */\n matches: boolean;\n /** The trace's recorded `finalState` — what the run originally produced. */\n expected: S;\n /** The state the current reducer produced for the same input. */\n actual: S;\n /**\n * Present iff `matches` is `false`. The FIRST point of divergence found by\n * a deterministic pre-order walk:\n * - `path` — a dotted/indexed access path from the state root, e.g.\n * `state.queue[2].status` or `state` for a root-level\n * scalar mismatch.\n * - `expected` — the recorded value at that path.\n * - `actual` — the recomputed value at that path.\n */\n divergence?: { path: string; expected: unknown; actual: unknown };\n}\n\n/**\n * Replay a recorded {@link Trace} against `machine` and diff the recomputed\n * final state against the recorded one.\n *\n * Runs the core pure fold `replay(machine, { msgs: trace.msgs, ctx,\n * loaded: trace.loaded })` — init + update only, never `interpret`, never a\n * `Store`, never a live subscription (see `replay` in ../index). The `ctx` is\n * supplied fresh by the caller: the trace is the INPUT (loaded + msgs); ctx is\n * the ENVIRONMENT the reducer runs against now.\n *\n * @param machine - the machine to replay against. Mutating its reducer (the\n * whole point of a regression check) is what makes a recorded\n * trace diverge.\n * @param trace - a Trace from `../recorder` (live `dump()` or a re-hydrated\n * `parseJSONL(...)`).\n * @param ctx - the runtime ctx, supplied fresh at replay time.\n */\nexport function replayTrace<\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 trace: Trace<S, M>,\n ctx: Ctx,\n): ReplayResult<S> {\n const result = replay(machine, {\n msgs: trace.msgs,\n ctx,\n loaded: trace.loaded,\n });\n const actual = result.state;\n const expected = trace.finalState;\n\n const divergence = firstDivergence(expected, actual, \"state\");\n if (divergence === null) {\n return { matches: true, expected, actual };\n }\n return { matches: false, expected, actual, divergence };\n}\n\n// === self-contained deep-equal + first-divergence path finder ===\n//\n// One walk does both jobs: it returns the FIRST point where `expected` and\n// `actual` differ (as a path + the two values), or `null` when they are\n// deeply equal. \"First\" is defined by a deterministic pre-order traversal:\n// the node itself, then its children in a stable key order (array index\n// order; object keys sorted so the result does not depend on insertion\n// order — two states with the same data but different key order are equal,\n// and the reported path for a real mismatch is reproducible run-to-run).\n//\n// Scope: TEA state is plain JSON-shaped data (the substrate's __DEV__ guards\n// reject closures in Cmds, and states are conventionally serializable). So\n// the walk handles: primitives, null/undefined, arrays, and plain objects.\n// It treats other object kinds (Date, Map, Set, class instances) by reference\n// identity + a structural fallback via Object.entries — sufficient for the\n// JSON-shaped states this tool targets; a richer differ lives in ../devtools.\n\n/** A divergence record, or `null` when the two values are deeply equal. */\ntype Divergence = { path: string; expected: unknown; actual: unknown } | null;\n\nfunction firstDivergence(\n expected: unknown,\n actual: unknown,\n path: string,\n): Divergence {\n // Identity / primitive fast path. Handles same-reference, and equal\n // primitives. `Object.is` so NaN === NaN and +0 ≠ -0 (matches structural\n // equality intent better than ===).\n if (Object.is(expected, actual)) return null;\n\n const te = typeOf(expected);\n const ta = typeOf(actual);\n // Different kinds at this node (e.g. object vs array, number vs string,\n // present vs null) — this node IS the divergence.\n if (te !== ta) return { path, expected, actual };\n\n if (te === \"array\") {\n const ae = expected as unknown[];\n const aa = actual as unknown[];\n if (ae.length !== aa.length) {\n // Length mismatch reported at the array node itself — the shape differs,\n // which is more informative than an arbitrary element index.\n return { path, expected, actual };\n }\n for (let i = 0; i < ae.length; i++) {\n const d = firstDivergence(ae[i], aa[i], `${path}[${i}]`);\n if (d !== null) return d;\n }\n return null;\n }\n\n if (te === \"object\") {\n const oe = expected as Record<string, unknown>;\n const oa = actual as Record<string, unknown>;\n // Union of keys, sorted for deterministic traversal. A key present in one\n // but absent in the other diverges at that child path (its value is\n // `undefined` on the missing side).\n const keys = [...new Set([...Object.keys(oe), ...Object.keys(oa)])].sort();\n for (const key of keys) {\n const d = firstDivergence(oe[key], oa[key], `${path}.${key}`);\n if (d !== null) return d;\n }\n return null;\n }\n\n // Primitives (string/number/boolean/bigint/symbol/function) that failed the\n // Object.is fast path, or null/undefined that differ — this node diverges.\n return { path, expected, actual };\n}\n\n/** Coarse type tag used by the divergence walk to decide how to recurse. */\nfunction typeOf(\n v: unknown,\n): \"null\" | \"undefined\" | \"array\" | \"object\" | \"primitive\" {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n if (Array.isArray(v)) return \"array\";\n if (typeof v === \"object\") return \"object\";\n return \"primitive\";\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Store } from '../index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @demlik/tea/work-queue — substrate-agnostic work-queue lifecycle on `Store<S>`.
|
|
5
|
+
*
|
|
6
|
+
* The pure ops live in `./ops` and are NOT exported. Tests and consumers reach
|
|
7
|
+
* them only through `createQueue(store)`. This mirrors `@demlik/tea/do`'s
|
|
8
|
+
* one-shape-per-package rule: a single adapter is the public surface.
|
|
9
|
+
*
|
|
10
|
+
* The package is generic in the input payload `I` (audit-specific fields go
|
|
11
|
+
* here in apps/extension). `output` is a free-form side-channel correlation
|
|
12
|
+
* token (a backend id, URL, trace id) — typed as `string` because that's its
|
|
13
|
+
* only honest type. Consumers that need structured per-item data persist it in
|
|
14
|
+
* their own store and put the foreign-key string in `output`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type QueueItemStatus = "pending" | "running" | "done" | "failed" | "cancelled";
|
|
18
|
+
interface QueueItem<I> {
|
|
19
|
+
id: string;
|
|
20
|
+
status: QueueItemStatus;
|
|
21
|
+
input: I;
|
|
22
|
+
enqueuedAt: number;
|
|
23
|
+
startedAt?: number;
|
|
24
|
+
finishedAt?: number;
|
|
25
|
+
output?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Caller-facing enqueue payload. Just `I` today — kept named so future
|
|
30
|
+
* variants (priority, deferUntil, …) can grow without breaking callers.
|
|
31
|
+
*/
|
|
32
|
+
type EnqueueInput<I> = I;
|
|
33
|
+
/**
|
|
34
|
+
* Adapter that binds the pure ops to an injected `Store<QueueItem<I>[]>`.
|
|
35
|
+
*
|
|
36
|
+
* `load → mutate → save` is the only durability discipline this package
|
|
37
|
+
* imposes. The store decides what "save" means (chrome.storage, KV, DO
|
|
38
|
+
* storage, in-memory) — that decision belongs at the caller.
|
|
39
|
+
*
|
|
40
|
+
* Ops that don't change the queue (e.g. `markDone` on an unknown id) skip the
|
|
41
|
+
* save call so the underlying store doesn't observe a write churn.
|
|
42
|
+
*/
|
|
43
|
+
declare function createQueue<I>(store: Store<QueueItem<I>[]>): {
|
|
44
|
+
enqueue: (input: EnqueueInput<I>) => Promise<QueueItem<I>>;
|
|
45
|
+
claimNext: () => Promise<QueueItem<I> | null>;
|
|
46
|
+
markDone: (id: string) => Promise<void>;
|
|
47
|
+
markFailed: (id: string, error: string) => Promise<void>;
|
|
48
|
+
markCancelled: (id: string) => Promise<void>;
|
|
49
|
+
resetRunningToPending: () => Promise<void>;
|
|
50
|
+
removeItem: (id: string) => Promise<void>;
|
|
51
|
+
retryItem: (id: string) => Promise<void>;
|
|
52
|
+
bindOutput: (id: string, value: string) => Promise<void>;
|
|
53
|
+
list: () => Promise<QueueItem<I>[]>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { type EnqueueInput, type QueueItem, type QueueItemStatus, createQueue };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/work-queue/ops.ts
|
|
2
|
+
function enqueueOp(queue, input, now, id) {
|
|
3
|
+
const item = {
|
|
4
|
+
id,
|
|
5
|
+
status: "pending",
|
|
6
|
+
input,
|
|
7
|
+
enqueuedAt: now
|
|
8
|
+
};
|
|
9
|
+
return { next: [...queue, item], item };
|
|
10
|
+
}
|
|
11
|
+
function claimNextOp(queue, now) {
|
|
12
|
+
const idx = queue.findIndex((i) => i.status === "pending");
|
|
13
|
+
if (idx === -1) return null;
|
|
14
|
+
const claimed = {
|
|
15
|
+
...queue[idx],
|
|
16
|
+
status: "running",
|
|
17
|
+
startedAt: now
|
|
18
|
+
};
|
|
19
|
+
const next = queue.slice();
|
|
20
|
+
next[idx] = claimed;
|
|
21
|
+
return { next, claimed };
|
|
22
|
+
}
|
|
23
|
+
function markDoneOp(queue, id) {
|
|
24
|
+
const next = queue.filter((i) => i.id !== id);
|
|
25
|
+
return { next, changed: next.length !== queue.length };
|
|
26
|
+
}
|
|
27
|
+
function patchItemOp(queue, id, patch) {
|
|
28
|
+
const idx = queue.findIndex((i) => i.id === id);
|
|
29
|
+
if (idx === -1) return { next: queue.slice(), changed: false };
|
|
30
|
+
const next = queue.slice();
|
|
31
|
+
next[idx] = patch(queue[idx]);
|
|
32
|
+
return { next, changed: true };
|
|
33
|
+
}
|
|
34
|
+
function resetRunningOp(queue) {
|
|
35
|
+
let changed = false;
|
|
36
|
+
const next = queue.map((i) => {
|
|
37
|
+
if (i.status !== "running") return i;
|
|
38
|
+
changed = true;
|
|
39
|
+
return { ...i, status: "pending", startedAt: void 0 };
|
|
40
|
+
});
|
|
41
|
+
return { next, changed };
|
|
42
|
+
}
|
|
43
|
+
function removeOp(queue, id) {
|
|
44
|
+
const next = queue.filter((i) => i.id !== id);
|
|
45
|
+
return { next, changed: next.length !== queue.length };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/work-queue/index.ts
|
|
49
|
+
function genId() {
|
|
50
|
+
return globalThis.crypto?.randomUUID?.() ?? `q_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
51
|
+
}
|
|
52
|
+
function createQueue(store) {
|
|
53
|
+
async function loadParsed() {
|
|
54
|
+
const raw = await store.load();
|
|
55
|
+
return store.migrate(raw);
|
|
56
|
+
}
|
|
57
|
+
async function list() {
|
|
58
|
+
return await loadParsed() ?? [];
|
|
59
|
+
}
|
|
60
|
+
async function enqueue(input) {
|
|
61
|
+
const queue = await list();
|
|
62
|
+
const { next, item } = enqueueOp(queue, input, Date.now(), genId());
|
|
63
|
+
await store.save(next);
|
|
64
|
+
return item;
|
|
65
|
+
}
|
|
66
|
+
async function claimNext() {
|
|
67
|
+
const queue = await list();
|
|
68
|
+
const result = claimNextOp(queue, Date.now());
|
|
69
|
+
if (!result) return null;
|
|
70
|
+
await store.save(result.next);
|
|
71
|
+
return result.claimed;
|
|
72
|
+
}
|
|
73
|
+
async function markDone(id) {
|
|
74
|
+
const queue = await list();
|
|
75
|
+
const { next, changed } = markDoneOp(queue, id);
|
|
76
|
+
if (changed) await store.save(next);
|
|
77
|
+
}
|
|
78
|
+
async function markFailed(id, error) {
|
|
79
|
+
const queue = await list();
|
|
80
|
+
const { next, changed } = patchItemOp(queue, id, (item) => ({
|
|
81
|
+
...item,
|
|
82
|
+
status: "failed",
|
|
83
|
+
finishedAt: Date.now(),
|
|
84
|
+
error
|
|
85
|
+
}));
|
|
86
|
+
if (changed) await store.save(next);
|
|
87
|
+
}
|
|
88
|
+
async function markCancelled(id) {
|
|
89
|
+
const queue = await list();
|
|
90
|
+
const { next, changed } = patchItemOp(queue, id, (item) => ({
|
|
91
|
+
...item,
|
|
92
|
+
status: "cancelled",
|
|
93
|
+
finishedAt: Date.now()
|
|
94
|
+
}));
|
|
95
|
+
if (changed) await store.save(next);
|
|
96
|
+
}
|
|
97
|
+
async function resetRunningToPending() {
|
|
98
|
+
const queue = await list();
|
|
99
|
+
const { next, changed } = resetRunningOp(queue);
|
|
100
|
+
if (changed) await store.save(next);
|
|
101
|
+
}
|
|
102
|
+
async function removeItem(id) {
|
|
103
|
+
const queue = await list();
|
|
104
|
+
const { next, changed } = removeOp(queue, id);
|
|
105
|
+
if (changed) await store.save(next);
|
|
106
|
+
}
|
|
107
|
+
async function retryItem(id) {
|
|
108
|
+
const queue = await list();
|
|
109
|
+
const { next, changed } = patchItemOp(queue, id, (item) => ({
|
|
110
|
+
...item,
|
|
111
|
+
status: "pending",
|
|
112
|
+
startedAt: void 0,
|
|
113
|
+
finishedAt: void 0,
|
|
114
|
+
error: void 0,
|
|
115
|
+
output: void 0
|
|
116
|
+
}));
|
|
117
|
+
if (changed) await store.save(next);
|
|
118
|
+
}
|
|
119
|
+
async function bindOutput(id, value) {
|
|
120
|
+
const queue = await list();
|
|
121
|
+
const { next, changed } = patchItemOp(queue, id, (item) => ({
|
|
122
|
+
...item,
|
|
123
|
+
output: value
|
|
124
|
+
}));
|
|
125
|
+
if (changed) await store.save(next);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
enqueue,
|
|
129
|
+
claimNext,
|
|
130
|
+
markDone,
|
|
131
|
+
markFailed,
|
|
132
|
+
markCancelled,
|
|
133
|
+
resetRunningToPending,
|
|
134
|
+
removeItem,
|
|
135
|
+
retryItem,
|
|
136
|
+
bindOutput,
|
|
137
|
+
list
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { createQueue };
|
|
142
|
+
//# sourceMappingURL=index.js.map
|
|
143
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/work-queue/ops.ts","../../src/work-queue/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Pure queue-lifecycle ops over `QueueItem<I>[]`.\n *\n * Each op is `(queue, ...args) => { next, result? }`. No I/O, no Date.now\n * indirection beyond a plain call site — the adapter in `./index.ts` injects\n * `Store<QueueItem<I>[]>` and orchestrates `load → mutate → save`.\n *\n * Substrate-agnostic by construction: nothing here references chrome.storage,\n * Cloudflare, or any concrete persistence layer. The audit-specific shape\n * (journeyId, projectId, ...) is lifted into the caller's `I` parameter.\n *\n * NOT exported from package root. Tests reach this file only through the\n * `createQueue(memoryStore(...))` adapter — same one-shape-per-package rule as\n * `@demlik/tea/do`.\n */\n\nimport type { QueueItem, QueueItemStatus } from \"./index\";\n\n/** Append a new pending item. Returns the new queue and the created item. */\nexport function enqueueOp<I>(\n queue: readonly QueueItem<I>[],\n input: I,\n now: number,\n id: string,\n): { next: QueueItem<I>[]; item: QueueItem<I> } {\n const item: QueueItem<I> = {\n id,\n status: \"pending\",\n input,\n enqueuedAt: now,\n };\n return { next: [...queue, item], item };\n}\n\n/**\n * Claim the first pending item by flipping it to `running` and stamping\n * `startedAt`. Returns the new queue and the claimed item, or `null` if no\n * pending item exists.\n */\nexport function claimNextOp<I>(\n queue: readonly QueueItem<I>[],\n now: number,\n): { next: QueueItem<I>[]; claimed: QueueItem<I> } | null {\n const idx = queue.findIndex((i) => i.status === \"pending\");\n if (idx === -1) return null;\n // `idx !== -1` guarantees the slot is present.\n const claimed: QueueItem<I> = {\n ...queue[idx]!,\n status: \"running\",\n startedAt: now,\n };\n const next = queue.slice();\n next[idx] = claimed;\n return { next, claimed };\n}\n\n/**\n * Done items leave the queue — completed runs live wherever the caller\n * persists their output. Returns the new queue (or the original reference if\n * no item matched, so the adapter can skip a save).\n */\nexport function markDoneOp<I>(\n queue: readonly QueueItem<I>[],\n id: string,\n): { next: QueueItem<I>[]; changed: boolean } {\n const next = queue.filter((i) => i.id !== id);\n return { next, changed: next.length !== queue.length };\n}\n\n/**\n * Generic single-item status transition. Used by `markFailed`,\n * `markCancelled`, `retryItem`, `bindOutput`. The patch closure receives the\n * matched item and returns its replacement; returning the same reference is\n * fine (the adapter only saves on `changed`).\n */\nexport function patchItemOp<I>(\n queue: readonly QueueItem<I>[],\n id: string,\n patch: (item: QueueItem<I>) => QueueItem<I>,\n): { next: QueueItem<I>[]; changed: boolean } {\n const idx = queue.findIndex((i) => i.id === id);\n if (idx === -1) return { next: queue.slice(), changed: false };\n const next = queue.slice();\n // `idx !== -1` guarantees the slot is present.\n next[idx] = patch(queue[idx]!);\n return { next, changed: true };\n}\n\n/** Reset every `running` item back to `pending`, clearing `startedAt`. */\nexport function resetRunningOp<I>(queue: readonly QueueItem<I>[]): {\n next: QueueItem<I>[];\n changed: boolean;\n} {\n let changed = false;\n const next = queue.map((i) => {\n if (i.status !== \"running\") return i;\n changed = true;\n return { ...i, status: \"pending\" as QueueItemStatus, startedAt: undefined };\n });\n return { next, changed };\n}\n\n/** Hard-delete an item regardless of status. */\nexport function removeOp<I>(\n queue: readonly QueueItem<I>[],\n id: string,\n): { next: QueueItem<I>[]; changed: boolean } {\n const next = queue.filter((i) => i.id !== id);\n return { next, changed: next.length !== queue.length };\n}\n","/**\n * @demlik/tea/work-queue — substrate-agnostic work-queue lifecycle on `Store<S>`.\n *\n * The pure ops live in `./ops` and are NOT exported. Tests and consumers reach\n * them only through `createQueue(store)`. This mirrors `@demlik/tea/do`'s\n * one-shape-per-package rule: a single adapter is the public surface.\n *\n * The package is generic in the input payload `I` (audit-specific fields go\n * here in apps/extension). `output` is a free-form side-channel correlation\n * token (a backend id, URL, trace id) — typed as `string` because that's its\n * only honest type. Consumers that need structured per-item data persist it in\n * their own store and put the foreign-key string in `output`.\n */\n\nimport type { Store } from \"../index\";\nimport { claimNextOp, enqueueOp, markDoneOp, patchItemOp, removeOp, resetRunningOp } from \"./ops\";\n\n/**\n * Generate a queue-item id at the adapter boundary. Reads `Date.now()` and\n * `Math.random()` — impure helpers belong in the adapter (`index.ts`),\n * never in `ops.ts` (which is purely-functional by contract). Uses native\n * UUID when available, falls back to timestamp + random for environments\n * without `crypto.randomUUID`.\n */\nfunction genId(): string {\n return (\n globalThis.crypto?.randomUUID?.() ?? `q_${Date.now()}_${Math.random().toString(36).slice(2)}`\n );\n}\n\nexport type QueueItemStatus = \"pending\" | \"running\" | \"done\" | \"failed\" | \"cancelled\";\n\nexport interface QueueItem<I> {\n id: string;\n status: QueueItemStatus;\n input: I;\n enqueuedAt: number;\n startedAt?: number;\n finishedAt?: number;\n output?: string;\n error?: string;\n}\n\n/**\n * Caller-facing enqueue payload. Just `I` today — kept named so future\n * variants (priority, deferUntil, …) can grow without breaking callers.\n */\nexport type EnqueueInput<I> = I;\n\n/**\n * Adapter that binds the pure ops to an injected `Store<QueueItem<I>[]>`.\n *\n * `load → mutate → save` is the only durability discipline this package\n * imposes. The store decides what \"save\" means (chrome.storage, KV, DO\n * storage, in-memory) — that decision belongs at the caller.\n *\n * Ops that don't change the queue (e.g. `markDone` on an unknown id) skip the\n * save call so the underlying store doesn't observe a write churn.\n */\nexport function createQueue<I>(store: Store<QueueItem<I>[]>) {\n // `Store<S>.load()` returns `unknown` (invariant 8 boundary). Route\n // through `store.migrate(raw)` to get the typed value before any\n // queue operation reads it — same wiring the substrate's `run()` uses at\n // boot. The work queue is a substrate-agnostic primitive on top of\n // `Store<S>`, so it honors the same contract.\n async function loadParsed(): Promise<QueueItem<I>[] | null> {\n const raw = await store.load();\n return store.migrate(raw);\n }\n\n async function list(): Promise<QueueItem<I>[]> {\n return (await loadParsed()) ?? [];\n }\n\n async function enqueue(input: EnqueueInput<I>): Promise<QueueItem<I>> {\n const queue = await list();\n const { next, item } = enqueueOp<I>(queue, input, Date.now(), genId());\n await store.save(next);\n return item;\n }\n\n async function claimNext(): Promise<QueueItem<I> | null> {\n const queue = await list();\n const result = claimNextOp<I>(queue, Date.now());\n if (!result) return null;\n await store.save(result.next);\n return result.claimed;\n }\n\n // `markDone` ignores the optional second arg by design: completed runs leave\n // the queue, and the consumer is expected to persist any output (auditId,\n // etc.) wherever those completed records belong. Kept in the signature for\n // call-site readability and forward-compat with `bindOutput`.\n async function markDone(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = markDoneOp<I>(queue, id);\n if (changed) await store.save(next);\n }\n\n async function markFailed(id: string, error: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"failed\",\n finishedAt: Date.now(),\n error,\n }));\n if (changed) await store.save(next);\n }\n\n async function markCancelled(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"cancelled\",\n finishedAt: Date.now(),\n }));\n if (changed) await store.save(next);\n }\n\n async function resetRunningToPending(): Promise<void> {\n const queue = await list();\n const { next, changed } = resetRunningOp<I>(queue);\n if (changed) await store.save(next);\n }\n\n async function removeItem(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = removeOp<I>(queue, id);\n if (changed) await store.save(next);\n }\n\n async function retryItem(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"pending\",\n startedAt: undefined,\n finishedAt: undefined,\n error: undefined,\n output: undefined,\n }));\n if (changed) await store.save(next);\n }\n\n // `bindOutput` attaches a side-channel correlation token (string) to a\n // running item without changing its status. The queue can't reach into\n // `I` to mutate it (the substrate is domain-agnostic), so callers that\n // need to thread a backend id / URL / trace id through the lifecycle\n // route it here. Consumers needing structured payloads should persist\n // them in their own store and put the foreign-key string in `output`.\n async function bindOutput(id: string, value: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n output: value,\n }));\n if (changed) await store.save(next);\n }\n\n return {\n enqueue,\n claimNext,\n markDone,\n markFailed,\n markCancelled,\n resetRunningToPending,\n removeItem,\n retryItem,\n bindOutput,\n list,\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@demlik/tea",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TEA-faithful state machine substrate — one package, every host adapter. Core + subs + testing, plus React, Durable Object, Chrome extension, Node, in-memory, work-queue, devtools, and property-testing modules as subpaths.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tea",
|
|
7
|
+
"elm-architecture",
|
|
8
|
+
"state-machine",
|
|
9
|
+
"reducer",
|
|
10
|
+
"cloudflare",
|
|
11
|
+
"durable-objects",
|
|
12
|
+
"react",
|
|
13
|
+
"effects-as-data"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://demlik.run",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/cansirin/monorepo.git",
|
|
19
|
+
"directory": "packages/tea"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Can Sirin",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"**/*.css"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./subs": {
|
|
36
|
+
"types": "./dist/subs/index.d.ts",
|
|
37
|
+
"import": "./dist/subs/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./testing": {
|
|
40
|
+
"types": "./dist/testing/index.d.ts",
|
|
41
|
+
"import": "./dist/testing/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./do": {
|
|
44
|
+
"types": "./dist/do/index.d.ts",
|
|
45
|
+
"import": "./dist/do/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./mem": {
|
|
48
|
+
"types": "./dist/mem/index.d.ts",
|
|
49
|
+
"import": "./dist/mem/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./react": {
|
|
52
|
+
"types": "./dist/react/index.d.ts",
|
|
53
|
+
"import": "./dist/react/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./devtools": {
|
|
56
|
+
"types": "./dist/devtools/index.d.ts",
|
|
57
|
+
"import": "./dist/devtools/index.js"
|
|
58
|
+
},
|
|
59
|
+
"./devtools/styles.css": "./dist/devtools/styles.css",
|
|
60
|
+
"./extension": {
|
|
61
|
+
"types": "./dist/extension/index.d.ts",
|
|
62
|
+
"import": "./dist/extension/index.js"
|
|
63
|
+
},
|
|
64
|
+
"./extension/react": {
|
|
65
|
+
"types": "./dist/extension/react.d.ts",
|
|
66
|
+
"import": "./dist/extension/react.js"
|
|
67
|
+
},
|
|
68
|
+
"./extension/subs": {
|
|
69
|
+
"types": "./dist/extension/subs/index.d.ts",
|
|
70
|
+
"import": "./dist/extension/subs/index.js"
|
|
71
|
+
},
|
|
72
|
+
"./extension/test-utils": {
|
|
73
|
+
"types": "./dist/extension/test-utils.d.ts",
|
|
74
|
+
"import": "./dist/extension/test-utils.js"
|
|
75
|
+
},
|
|
76
|
+
"./work-queue": {
|
|
77
|
+
"types": "./dist/work-queue/index.d.ts",
|
|
78
|
+
"import": "./dist/work-queue/index.js"
|
|
79
|
+
},
|
|
80
|
+
"./node": {
|
|
81
|
+
"types": "./dist/node/index.d.ts",
|
|
82
|
+
"import": "./dist/node/index.js"
|
|
83
|
+
},
|
|
84
|
+
"./pbt": {
|
|
85
|
+
"types": "./dist/pbt/index.d.ts",
|
|
86
|
+
"import": "./dist/pbt/index.js"
|
|
87
|
+
},
|
|
88
|
+
"./pbt/arbitraries": {
|
|
89
|
+
"types": "./dist/pbt/arbitraries/index.d.ts",
|
|
90
|
+
"import": "./dist/pbt/arbitraries/index.js"
|
|
91
|
+
},
|
|
92
|
+
"./pbt/runners": {
|
|
93
|
+
"types": "./dist/pbt/runners/index.d.ts",
|
|
94
|
+
"import": "./dist/pbt/runners/index.js"
|
|
95
|
+
},
|
|
96
|
+
"./retry-backoff": {
|
|
97
|
+
"types": "./dist/retry-backoff/index.d.ts",
|
|
98
|
+
"import": "./dist/retry-backoff/index.js"
|
|
99
|
+
},
|
|
100
|
+
"./rate-limit": {
|
|
101
|
+
"types": "./dist/rate-limit/index.d.ts",
|
|
102
|
+
"import": "./dist/rate-limit/index.js"
|
|
103
|
+
},
|
|
104
|
+
"./idempotency": {
|
|
105
|
+
"types": "./dist/idempotency/index.d.ts",
|
|
106
|
+
"import": "./dist/idempotency/index.js"
|
|
107
|
+
},
|
|
108
|
+
"./circuit-breaker": {
|
|
109
|
+
"types": "./dist/circuit-breaker/index.d.ts",
|
|
110
|
+
"import": "./dist/circuit-breaker/index.js"
|
|
111
|
+
},
|
|
112
|
+
"./cache": {
|
|
113
|
+
"types": "./dist/cache/index.d.ts",
|
|
114
|
+
"import": "./dist/cache/index.js"
|
|
115
|
+
},
|
|
116
|
+
"./deadline": {
|
|
117
|
+
"types": "./dist/deadline/index.d.ts",
|
|
118
|
+
"import": "./dist/deadline/index.js"
|
|
119
|
+
},
|
|
120
|
+
"./machine-viz": {
|
|
121
|
+
"types": "./dist/machine-viz/index.d.ts",
|
|
122
|
+
"import": "./dist/machine-viz/index.js"
|
|
123
|
+
},
|
|
124
|
+
"./debounce": {
|
|
125
|
+
"types": "./dist/debounce/index.d.ts",
|
|
126
|
+
"import": "./dist/debounce/index.js"
|
|
127
|
+
},
|
|
128
|
+
"./throttle": {
|
|
129
|
+
"types": "./dist/throttle/index.d.ts",
|
|
130
|
+
"import": "./dist/throttle/index.js"
|
|
131
|
+
},
|
|
132
|
+
"./recorder": {
|
|
133
|
+
"types": "./dist/recorder/index.d.ts",
|
|
134
|
+
"import": "./dist/recorder/index.js"
|
|
135
|
+
},
|
|
136
|
+
"./trace-replay": {
|
|
137
|
+
"types": "./dist/trace-replay/index.d.ts",
|
|
138
|
+
"import": "./dist/trace-replay/index.js"
|
|
139
|
+
},
|
|
140
|
+
"./package.json": "./package.json"
|
|
141
|
+
},
|
|
142
|
+
"scripts": {
|
|
143
|
+
"build": "tsup",
|
|
144
|
+
"test": "vitest run",
|
|
145
|
+
"typecheck": "tsc --noEmit",
|
|
146
|
+
"typecheck:examples": "tsc -p examples/tsconfig.json",
|
|
147
|
+
"prepublishOnly": "tsup"
|
|
148
|
+
},
|
|
149
|
+
"dependencies": {
|
|
150
|
+
"better-result": "^2.5.2"
|
|
151
|
+
},
|
|
152
|
+
"peerDependencies": {
|
|
153
|
+
"fast-check": "^3 || ^4",
|
|
154
|
+
"react": "^18 || ^19",
|
|
155
|
+
"react-dom": "^18 || ^19",
|
|
156
|
+
"vitest": "^2 || ^3",
|
|
157
|
+
"ws": "^8"
|
|
158
|
+
},
|
|
159
|
+
"peerDependenciesMeta": {
|
|
160
|
+
"fast-check": { "optional": true },
|
|
161
|
+
"react": { "optional": true },
|
|
162
|
+
"react-dom": { "optional": true },
|
|
163
|
+
"vitest": { "optional": true },
|
|
164
|
+
"ws": { "optional": true }
|
|
165
|
+
},
|
|
166
|
+
"devDependencies": {
|
|
167
|
+
"@cloudflare/workers-types": "catalog:",
|
|
168
|
+
"@types/chrome": "catalog:",
|
|
169
|
+
"@types/node": "catalog:",
|
|
170
|
+
"@types/react": "catalog:",
|
|
171
|
+
"@types/react-dom": "catalog:",
|
|
172
|
+
"@types/ws": "catalog:",
|
|
173
|
+
"fast-check": "^3.23.0",
|
|
174
|
+
"react": "catalog:",
|
|
175
|
+
"react-dom": "catalog:",
|
|
176
|
+
"tsup": "^8.3.5",
|
|
177
|
+
"typescript": "catalog:",
|
|
178
|
+
"vitest": "catalog:",
|
|
179
|
+
"ws": "catalog:"
|
|
180
|
+
},
|
|
181
|
+
"publishConfig": {
|
|
182
|
+
"access": "public"
|
|
183
|
+
}
|
|
184
|
+
}
|