@cplieger/actions 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/jsr.json +8 -12
  4. package/package.json +7 -9
  5. package/src/__test-helpers__/action-test-setup.ts +34 -0
  6. package/src/api-idempotency.test.ts +163 -0
  7. package/src/api.test.ts +96 -0
  8. package/src/api.ts +0 -0
  9. package/src/cleanup.test.ts +102 -0
  10. package/src/cleanup.ts +0 -0
  11. package/src/configure-api.test.ts +219 -0
  12. package/src/cross-action-coordination.test.ts +176 -0
  13. package/src/cross-action-races-extra.test.ts +231 -0
  14. package/src/cross-action-races.test.ts +213 -0
  15. package/src/cross-action-scope-serial.test.ts +121 -0
  16. package/src/debounce.test.ts +121 -0
  17. package/src/debounce.ts +0 -0
  18. package/src/define-helpers.ts +0 -0
  19. package/src/define.property.test.ts +133 -0
  20. package/src/define.test.ts +476 -0
  21. package/src/define.ts +0 -0
  22. package/src/dispatch-callbacks.test.ts +141 -0
  23. package/src/dispatch-options.test.ts +197 -0
  24. package/src/edge-cases.test.ts +81 -0
  25. package/src/error.test.ts +268 -0
  26. package/src/error.ts +0 -0
  27. package/src/index.ts +0 -0
  28. package/src/leak-stress.test.ts +117 -0
  29. package/src/loading.test.ts +146 -0
  30. package/src/loading.ts +0 -0
  31. package/src/new-features.test.ts +246 -0
  32. package/src/notifier.ts +0 -0
  33. package/src/poll.test.ts +385 -0
  34. package/src/poll.ts +0 -0
  35. package/src/primitive-edge-cases.test.ts +105 -0
  36. package/src/primitive-interactions.test.ts +112 -0
  37. package/src/primitives.test.ts +215 -0
  38. package/src/race-dedupe-cancel.test.ts +88 -0
  39. package/src/redteam.test.ts +254 -0
  40. package/src/redteam2.test.ts +337 -0
  41. package/src/redteam3.test.ts +454 -0
  42. package/src/registry.test.ts +271 -0
  43. package/src/registry.ts +0 -0
  44. package/src/retry-network-delay.test.ts +155 -0
  45. package/src/retry.ts +0 -0
  46. package/src/transport.test.ts +139 -0
  47. package/src/transport.ts +0 -0
  48. package/src/types.ts +0 -0
  49. package/.editorconfig +0 -19
  50. package/.htmlvalidate.json +0 -28
  51. package/.stylelintrc.json +0 -67
  52. package/dist/src/api.d.ts +0 -56
  53. package/dist/src/api.js +0 -158
  54. package/dist/src/cleanup.d.ts +0 -11
  55. package/dist/src/cleanup.js +0 -63
  56. package/dist/src/debounce.d.ts +0 -28
  57. package/dist/src/debounce.js +0 -91
  58. package/dist/src/define-helpers.d.ts +0 -20
  59. package/dist/src/define-helpers.js +0 -83
  60. package/dist/src/define.d.ts +0 -14
  61. package/dist/src/define.js +0 -627
  62. package/dist/src/error.d.ts +0 -42
  63. package/dist/src/error.js +0 -174
  64. package/dist/src/index.d.ts +0 -17
  65. package/dist/src/index.js +0 -22
  66. package/dist/src/loading.d.ts +0 -15
  67. package/dist/src/loading.js +0 -114
  68. package/dist/src/notifier.d.ts +0 -21
  69. package/dist/src/notifier.js +0 -21
  70. package/dist/src/poll.d.ts +0 -23
  71. package/dist/src/poll.js +0 -120
  72. package/dist/src/registry.d.ts +0 -18
  73. package/dist/src/registry.js +0 -210
  74. package/dist/src/retry.d.ts +0 -9
  75. package/dist/src/retry.js +0 -78
  76. package/dist/src/transport.d.ts +0 -46
  77. package/dist/src/transport.js +0 -70
  78. package/dist/src/types.d.ts +0 -157
  79. package/dist/src/types.js +0 -7
  80. package/eslint.config.base.mjs +0 -186
package/dist/src/api.d.ts DELETED
@@ -1,56 +0,0 @@
1
- import type { Action, ActionDefinition, RequestSpec } from "./types.js";
2
- /** Default request timeout in milliseconds. */
3
- export declare const API_TIMEOUT_MS = 30000;
4
- /**
5
- * Compose an optional caller signal with a fresh timeout signal.
6
- * If the caller provides an existing signal, the result aborts when
7
- * either the caller signal or the timeout fires — whichever comes first.
8
- *
9
- * @param signal - Existing signal to compose with (may be undefined).
10
- * @param ms - Timeout in milliseconds.
11
- * @returns A composed AbortSignal.
12
- */
13
- export declare function withTimeout(signal: AbortSignal | undefined, ms: number): AbortSignal;
14
- /** Configuration for the global API fetch layer. Set via `configureApi()`. */
15
- export interface ApiConfig {
16
- /** Base URL prepended to every RequestSpec.path (e.g. "https://api.example.com/v1"). */
17
- readonly baseUrl?: string;
18
- /** Inject headers on every request. Receives current headers + the request spec.
19
- * Mutate and/or return the headers object. May be async (e.g. to read a token store). */
20
- readonly prepareHeaders?: (headers: Headers, context: {
21
- spec: RequestSpec;
22
- }) => Headers | undefined | Promise<Headers | undefined>;
23
- /** RequestInit.credentials mode applied to every request (e.g. "include" for cookies). */
24
- readonly credentials?: RequestCredentials;
25
- /** Custom fetch implementation. Useful for SSR (isomorphic-fetch) or testing. */
26
- readonly fetchFn?: typeof fetch;
27
- }
28
- /**
29
- * Configure the global HTTP layer used by all `apiAction` instances.
30
- * Call once at app boot. Subsequent calls replace the previous config.
31
- *
32
- * @example
33
- * ```ts
34
- * configureApi({
35
- * baseUrl: "https://api.example.com",
36
- * credentials: "include",
37
- * prepareHeaders: (headers) => {
38
- * headers.set("Authorization", `Bearer ${getToken()}`);
39
- * },
40
- * });
41
- * ```
42
- */
43
- export declare function configureApi(config: ApiConfig): void;
44
- /** Reset API config. @internal Test-only. */
45
- export declare function _resetApiConfigForTest(): void;
46
- /** Caller-facing shape of an apiAction definition. Replaces `run` with
47
- * a `request` function that returns an HTTP {@link RequestSpec}. */
48
- export interface ApiActionDefinition<TArgs, TResult, TOp = unknown> extends Omit<ActionDefinition<TArgs, TResult, TOp>, "run"> {
49
- request: (args: TArgs) => RequestSpec;
50
- }
51
- /**
52
- * Build an Action from an HTTP request descriptor.
53
- * Wraps `defineAction` with a generated `run()` that calls `fetch`
54
- * via the global {@link ApiConfig} layer configured with {@link configureApi}.
55
- */
56
- export declare function apiAction<TArgs, TResult = unknown, TOp = unknown>(def: ApiActionDefinition<TArgs, TResult, TOp>): Action<TArgs, TResult>;
package/dist/src/api.js DELETED
@@ -1,158 +0,0 @@
1
- // apiAction: factory for HTTP-backed actions. Wraps fetch so the run()
2
- // implementation is just the request descriptor.
3
- // ---------------------------------------------------------------------------
4
- import { defineAction, IDEMPOTENCY_HEADER } from "./define.js";
5
- import { ActionError, classifyFetchError, hasErrorString } from "./error.js";
6
- /** Default request timeout in milliseconds. */
7
- export const API_TIMEOUT_MS = 30_000;
8
- /**
9
- * Compose an optional caller signal with a fresh timeout signal.
10
- * If the caller provides an existing signal, the result aborts when
11
- * either the caller signal or the timeout fires — whichever comes first.
12
- *
13
- * @param signal - Existing signal to compose with (may be undefined).
14
- * @param ms - Timeout in milliseconds.
15
- * @returns A composed AbortSignal.
16
- */
17
- export function withTimeout(signal, ms) {
18
- return signal !== undefined
19
- ? AbortSignal.any([signal, AbortSignal.timeout(ms)])
20
- : AbortSignal.timeout(ms);
21
- }
22
- const JSON_CT = "application/json";
23
- let _apiConfig = {};
24
- /**
25
- * Configure the global HTTP layer used by all `apiAction` instances.
26
- * Call once at app boot. Subsequent calls replace the previous config.
27
- *
28
- * @example
29
- * ```ts
30
- * configureApi({
31
- * baseUrl: "https://api.example.com",
32
- * credentials: "include",
33
- * prepareHeaders: (headers) => {
34
- * headers.set("Authorization", `Bearer ${getToken()}`);
35
- * },
36
- * });
37
- * ```
38
- */
39
- export function configureApi(config) {
40
- _apiConfig = config;
41
- }
42
- /** Reset API config. @internal Test-only. */
43
- export function _resetApiConfigForTest() {
44
- _apiConfig = {};
45
- }
46
- /**
47
- * Build an Action from an HTTP request descriptor.
48
- * Wraps `defineAction` with a generated `run()` that calls `fetch`
49
- * via the global {@link ApiConfig} layer configured with {@link configureApi}.
50
- */
51
- export function apiAction(def) {
52
- const { request, ...rest } = def;
53
- return defineAction({
54
- ...rest,
55
- run: async (args, signal, ctx) => {
56
- const spec = request(args);
57
- return executeRequest(spec, signal, ctx);
58
- },
59
- });
60
- }
61
- async function executeRequest(spec, signal, ctx) {
62
- const cfg = _apiConfig;
63
- const init = { method: spec.method };
64
- // Build headers via Headers API for prepareHeaders compatibility
65
- const headers = new Headers();
66
- if (spec.method !== "GET" && spec.body !== undefined) {
67
- headers.set("Content-Type", JSON_CT);
68
- init.body = JSON.stringify(spec.body);
69
- }
70
- if (ctx?.idempotencyKey !== undefined) {
71
- headers.set(IDEMPOTENCY_HEADER, ctx.idempotencyKey);
72
- }
73
- // Per-request headers from RequestSpec
74
- if (spec.headers !== undefined) {
75
- for (const [k, v] of Object.entries(spec.headers)) {
76
- headers.set(k, v);
77
- }
78
- }
79
- // Global prepareHeaders hook
80
- if (cfg.prepareHeaders !== undefined) {
81
- await cfg.prepareHeaders(headers, { spec });
82
- }
83
- // Convert Headers to plain object for RequestInit
84
- const headerObj = {};
85
- headers.forEach((v, k) => {
86
- headerObj[k.toLowerCase()] = v;
87
- });
88
- if (Object.keys(headerObj).length > 0) {
89
- init.headers = headerObj;
90
- }
91
- // Credentials
92
- if (cfg.credentials !== undefined) {
93
- init.credentials = cfg.credentials;
94
- }
95
- init.signal = withTimeout(signal, API_TIMEOUT_MS);
96
- // Resolve URL: prepend baseUrl if configured, normalizing double slashes at the join
97
- let url;
98
- if (cfg.baseUrl !== undefined) {
99
- const base = cfg.baseUrl.endsWith("/") ? cfg.baseUrl.slice(0, -1) : cfg.baseUrl;
100
- const path = spec.path.startsWith("/") ? spec.path : `/${spec.path}`;
101
- url = `${base}${path}`;
102
- }
103
- else {
104
- url = spec.path;
105
- }
106
- // Use custom fetchFn or global fetch
107
- const fetchImpl = cfg.fetchFn ?? fetch;
108
- let r;
109
- try {
110
- r = await fetchImpl(url, init);
111
- }
112
- catch (e) {
113
- throw classifyFetchError(e, signal);
114
- }
115
- if (!r.ok) {
116
- let serverError = "";
117
- let serverCode;
118
- try {
119
- const body = await r.json();
120
- if (hasErrorString(body)) {
121
- serverError = body.error;
122
- }
123
- if (typeof body === "object" && body !== null && "code" in body) {
124
- const code = body.code;
125
- if (typeof code === "string") {
126
- serverCode = code;
127
- }
128
- }
129
- }
130
- catch {
131
- // Body wasn't JSON — leave serverError empty.
132
- }
133
- const opts = { status: r.status };
134
- if (serverCode !== undefined) {
135
- opts.code = serverCode;
136
- }
137
- throw new ActionError(serverError !== "" ? serverError : `HTTP ${String(r.status)}`, opts);
138
- }
139
- if (r.status === 204) {
140
- return undefined;
141
- }
142
- const text = await r.text();
143
- if (text === "") {
144
- if (spec.method !== "DELETE") {
145
- console.warn(`[actions] ${spec.method} ${spec.path} returned empty body — callers expecting data will receive undefined`);
146
- }
147
- return undefined;
148
- }
149
- try {
150
- return JSON.parse(text);
151
- }
152
- catch (e) {
153
- throw new ActionError(`response not JSON: ${e instanceof Error ? e.message : String(e)}`, {
154
- status: r.status,
155
- cause: e,
156
- });
157
- }
158
- }
@@ -1,11 +0,0 @@
1
- import type { Action } from "./types.js";
2
- /** Internal: register an Action so cancelAllPending() can iterate it. */
3
- export declare function _registerAction<TArgs, TResult>(action: Action<TArgs, TResult>): void;
4
- /**
5
- * Register a cleanup function to run on page unload (or test invoke).
6
- */
7
- export declare function registerCleanup(fn: () => void): () => void;
8
- /** Test-only: invoke the same cleanup logic that beforeunload runs. */
9
- export declare function _cancelAllForTest(): void;
10
- /** Test-only: clear both registries + uninstall the listener. */
11
- export declare function _resetForTest(): void;
@@ -1,63 +0,0 @@
1
- // Global cleanup: cancel all in-flight actions + run registered
2
- // cleanup hooks. Wired to window.beforeunload so navigation away
3
- // from the page aborts everything cleanly.
4
- //
5
- // Cleanup hooks must be idempotent. Cancellation is allowed to fire
6
- // multiple times (e.g., cancelled navigation followed by confirmed).
7
- // ---------------------------------------------------------------------------
8
- const trackedActions = new Set();
9
- const cleanupHooks = new Set();
10
- let beforeunloadInstalled = false;
11
- /** Internal: register an Action so cancelAllPending() can iterate it. */
12
- export function _registerAction(action) {
13
- trackedActions.add(action);
14
- installBeforeunloadOnce();
15
- }
16
- /**
17
- * Register a cleanup function to run on page unload (or test invoke).
18
- */
19
- export function registerCleanup(fn) {
20
- cleanupHooks.add(fn);
21
- installBeforeunloadOnce();
22
- return () => cleanupHooks.delete(fn);
23
- }
24
- function cancelAllPending() {
25
- for (const action of trackedActions) {
26
- try {
27
- action.cancel();
28
- }
29
- catch (e) {
30
- console.error(`[actions] cancel for ${action.name} threw`, e);
31
- }
32
- }
33
- for (const fn of [...cleanupHooks]) {
34
- try {
35
- fn();
36
- }
37
- catch (e) {
38
- console.error("[actions] cleanup hook threw", e);
39
- }
40
- }
41
- }
42
- function installBeforeunloadOnce() {
43
- if (beforeunloadInstalled) {
44
- return;
45
- }
46
- beforeunloadInstalled = true;
47
- if (typeof window !== "undefined") {
48
- window.addEventListener("beforeunload", cancelAllPending);
49
- }
50
- }
51
- /** Test-only: invoke the same cleanup logic that beforeunload runs. */
52
- export function _cancelAllForTest() {
53
- cancelAllPending();
54
- }
55
- /** Test-only: clear both registries + uninstall the listener. */
56
- export function _resetForTest() {
57
- trackedActions.clear();
58
- cleanupHooks.clear();
59
- if (beforeunloadInstalled && typeof window !== "undefined") {
60
- window.removeEventListener("beforeunload", cancelAllPending);
61
- }
62
- beforeunloadInstalled = false;
63
- }
@@ -1,28 +0,0 @@
1
- import type { Action } from "./types.js";
2
- /** A debounced action dispatcher. Callable to schedule a dispatch,
3
- * with `flush`, `cancel`, and `isPending` control methods. */
4
- export interface DebouncedDispatch<TArgs> {
5
- /** Schedule a dispatch with the given args. Replaces any pending
6
- * dispatch's args. */
7
- (args: TArgs): void;
8
- /** Fire immediately with the most-recent args (or args supplied
9
- * here, overriding the pending). No-op if nothing is pending and
10
- * no args supplied. */
11
- flush(args?: TArgs): Promise<unknown> | undefined;
12
- /** Discard any pending dispatch without firing. */
13
- cancel(): void;
14
- /** True if there's a scheduled dispatch waiting for the timer. */
15
- isPending(): boolean;
16
- }
17
- interface DebounceOptions {
18
- /** Quiet window in ms. */
19
- readonly wait: number;
20
- /** Fire on the leading edge instead of the trailing edge. Default false. */
21
- readonly leading?: boolean;
22
- }
23
- /**
24
- * Wrap an action with a debounce timer so rapid calls coalesce into a
25
- * single dispatch after a quiet window.
26
- */
27
- export declare function debouncedDispatch<TArgs, TResult>(action: Action<TArgs, TResult>, opts: DebounceOptions): DebouncedDispatch<TArgs>;
28
- export {};
@@ -1,91 +0,0 @@
1
- // debouncedDispatch: wrap an Action so that rapid calls coalesce into
2
- // a single dispatch after a quiet window. Replaces ad-hoc setTimeout
3
- // + clearTimeout chains with a single helper that adds flush/cancel.
4
- // ---------------------------------------------------------------------------
5
- /**
6
- * Wrap an action with a debounce timer so rapid calls coalesce into a
7
- * single dispatch after a quiet window.
8
- */
9
- export function debouncedDispatch(action, opts) {
10
- let timer;
11
- let lastArgs;
12
- let pending = false;
13
- let lastFiredAt = 0;
14
- const fn = ((args) => {
15
- if (opts.leading === true) {
16
- const now = Date.now();
17
- if (now - lastFiredAt < opts.wait) {
18
- lastArgs = args;
19
- pending = true;
20
- if (timer === undefined) {
21
- const remaining = Math.max(0, opts.wait - (now - lastFiredAt));
22
- timer = setTimeout(fireTrailing, remaining);
23
- }
24
- return;
25
- }
26
- void action.dispatch(args);
27
- lastFiredAt = now;
28
- lastArgs = undefined;
29
- pending = true;
30
- if (timer !== undefined) {
31
- clearTimeout(timer);
32
- }
33
- timer = setTimeout(fireTrailing, opts.wait);
34
- return;
35
- }
36
- lastArgs = args;
37
- pending = true;
38
- if (timer !== undefined) {
39
- clearTimeout(timer);
40
- }
41
- timer = setTimeout(() => {
42
- timer = undefined;
43
- pending = false;
44
- const a = lastArgs;
45
- lastArgs = undefined;
46
- if (a !== undefined) {
47
- void action.dispatch(a);
48
- }
49
- }, opts.wait);
50
- });
51
- function fireTrailing() {
52
- timer = undefined;
53
- const a = lastArgs;
54
- lastArgs = undefined;
55
- if (a !== undefined) {
56
- lastFiredAt = Date.now();
57
- pending = true;
58
- timer = setTimeout(fireTrailing, opts.wait);
59
- void action.dispatch(a);
60
- }
61
- else {
62
- pending = false;
63
- }
64
- }
65
- fn.flush = (args) => {
66
- if (timer !== undefined) {
67
- clearTimeout(timer);
68
- timer = undefined;
69
- }
70
- const a = args ?? lastArgs;
71
- lastArgs = undefined;
72
- pending = false;
73
- if (a !== undefined) {
74
- if (opts.leading === true) {
75
- lastFiredAt = Date.now();
76
- }
77
- return action.dispatch(a);
78
- }
79
- return undefined;
80
- };
81
- fn.cancel = () => {
82
- if (timer !== undefined) {
83
- clearTimeout(timer);
84
- timer = undefined;
85
- }
86
- lastArgs = undefined;
87
- pending = false;
88
- };
89
- fn.isPending = () => pending;
90
- return fn;
91
- }
@@ -1,20 +0,0 @@
1
- import type { NotificationSpec } from "./types.js";
2
- /** Invoke a callback safely — errors are caught and logged without
3
- * disrupting the dispatch lifecycle. */
4
- export declare function safeInvoke(actionName: string, hookName: string, fn: () => void): void;
5
- export declare const _symbolMap: Map<symbol, number>;
6
- export declare function symbolId(sym: symbol): number;
7
- /** Reset symbol state — test-only. */
8
- export declare function _resetSymbols(): void;
9
- /** Defensive JSON.stringify — falls back to String(args) on cycles
10
- * or non-serializable values (DOM elements, functions). Used by
11
- * the default dedupe key computation. */
12
- export declare function safeStringify(args: unknown): string;
13
- /** Resolve a NotificationSpec to its message string. Returns null when
14
- * the spec is `false` (suppressed) or undefined and no fallback. */
15
- export declare function resolveNotification<TArgs, TPayload>(spec: NotificationSpec<TArgs, TPayload> | undefined, args: TArgs, payload: TPayload, fallback?: string): string | null;
16
- /** @deprecated Use {@link resolveNotification} instead. */
17
- export declare const resolveToast: typeof resolveNotification;
18
- /** Build a default error notification prefix from the action name.
19
- * Converts "chat.delete" -> "Delete failed". */
20
- export declare function defaultErrorPrefix(name: string): string;
@@ -1,83 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Pure utility helpers extracted from define.ts — independently testable
3
- // without instantiating the full action framework.
4
- // ---------------------------------------------------------------------------
5
- /** Invoke a callback safely — errors are caught and logged without
6
- * disrupting the dispatch lifecycle. */
7
- export function safeInvoke(actionName, hookName, fn) {
8
- try {
9
- fn();
10
- }
11
- catch (e) {
12
- console.error(`[actions] ${hookName} callback for ${actionName} threw`, e);
13
- }
14
- }
15
- /** Monotonic counter for symbol identity in dedupe keys. Symbols with
16
- * the same description are distinct values but String(sym) is identical,
17
- * so we assign each unique symbol a stable numeric ID. */
18
- let _symbolCounter = 0;
19
- export const _symbolMap = new Map();
20
- export function symbolId(sym) {
21
- let id = _symbolMap.get(sym);
22
- if (id === undefined) {
23
- id = ++_symbolCounter;
24
- _symbolMap.set(sym, id);
25
- }
26
- return id;
27
- }
28
- /** Reset symbol state — test-only. */
29
- export function _resetSymbols() {
30
- _symbolCounter = 0;
31
- _symbolMap.clear();
32
- }
33
- /** Defensive JSON.stringify — falls back to String(args) on cycles
34
- * or non-serializable values (DOM elements, functions). Used by
35
- * the default dedupe key computation. */
36
- export function safeStringify(args) {
37
- if (args === undefined) {
38
- return "undefined";
39
- }
40
- if (args === null || typeof args === "number" || typeof args === "boolean") {
41
- return String(args);
42
- }
43
- if (typeof args === "string") {
44
- return JSON.stringify(args);
45
- }
46
- if (typeof args === "bigint") {
47
- return `${String(args)}n`;
48
- }
49
- if (typeof args === "symbol") {
50
- return `@@sym${String(symbolId(args))}`;
51
- }
52
- try {
53
- return JSON.stringify(args, (_key, value) => value === undefined ? "__undef__" : value);
54
- }
55
- catch {
56
- // eslint-disable-next-line @typescript-eslint/no-base-to-string -- intentional fallback for cyclic objects
57
- return String(args);
58
- }
59
- }
60
- /** Resolve a NotificationSpec to its message string. Returns null when
61
- * the spec is `false` (suppressed) or undefined and no fallback. */
62
- export function resolveNotification(spec, args, payload, fallback) {
63
- if (spec === false) {
64
- return null;
65
- }
66
- if (spec === undefined) {
67
- return fallback ?? null;
68
- }
69
- if (typeof spec === "string") {
70
- return spec;
71
- }
72
- return spec(args, payload);
73
- }
74
- /** @deprecated Use {@link resolveNotification} instead. */
75
- export const resolveToast = resolveNotification;
76
- /** Build a default error notification prefix from the action name.
77
- * Converts "chat.delete" -> "Delete failed". */
78
- export function defaultErrorPrefix(name) {
79
- const parts = name.split(".");
80
- const tail = parts[parts.length - 1] ?? name;
81
- const readable = tail.replace(/[_-]/g, " ");
82
- return readable.charAt(0).toUpperCase() + readable.slice(1) + " failed";
83
- }
@@ -1,14 +0,0 @@
1
- import type { Action, ActionDefinition } from "./types.js";
2
- /** Header name used by apiAction when an idempotency key is generated. */
3
- export declare const IDEMPOTENCY_HEADER = "Idempotency-Key";
4
- /**
5
- * Create an action from a declarative definition.
6
- */
7
- export declare function defineAction<TArgs, TResult, TOp = unknown>(def: ActionDefinition<TArgs, TResult, TOp>): Action<TArgs, TResult>;
8
- /** Test-only: reset the instance counter + scope chains + dedupe map. */
9
- export declare function _resetForTest(): void;
10
- /** Test-only: expose internal map sizes for leak verification. */
11
- export declare function _internalsForTest(): {
12
- scopeChains: number;
13
- activeDedupes: number;
14
- };