@vereda/http 1.0.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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +604 -0
  3. package/dist/adapters/zod.d.ts +14 -0
  4. package/dist/adapters/zod.d.ts.map +1 -0
  5. package/dist/adapters/zod.js +14 -0
  6. package/dist/adapters/zod.js.map +1 -0
  7. package/dist/core/backoff.d.ts +9 -0
  8. package/dist/core/backoff.d.ts.map +1 -0
  9. package/dist/core/backoff.js +23 -0
  10. package/dist/core/backoff.js.map +1 -0
  11. package/dist/core/client.d.ts +98 -0
  12. package/dist/core/client.d.ts.map +1 -0
  13. package/dist/core/client.js +781 -0
  14. package/dist/core/client.js.map +1 -0
  15. package/dist/core/errors.d.ts +87 -0
  16. package/dist/core/errors.d.ts.map +1 -0
  17. package/dist/core/errors.js +140 -0
  18. package/dist/core/errors.js.map +1 -0
  19. package/dist/core/index.d.ts +15 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +10 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/listeners.d.ts +14 -0
  24. package/dist/core/listeners.d.ts.map +1 -0
  25. package/dist/core/listeners.js +27 -0
  26. package/dist/core/listeners.js.map +1 -0
  27. package/dist/core/metrics.d.ts +33 -0
  28. package/dist/core/metrics.d.ts.map +1 -0
  29. package/dist/core/metrics.js +24 -0
  30. package/dist/core/metrics.js.map +1 -0
  31. package/dist/core/nanoid.d.ts +2 -0
  32. package/dist/core/nanoid.d.ts.map +1 -0
  33. package/dist/core/nanoid.js +11 -0
  34. package/dist/core/nanoid.js.map +1 -0
  35. package/dist/core/redact.d.ts +12 -0
  36. package/dist/core/redact.d.ts.map +1 -0
  37. package/dist/core/redact.js +42 -0
  38. package/dist/core/redact.js.map +1 -0
  39. package/dist/core/types.d.ts +261 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/types.js +41 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/core/validate.d.ts +19 -0
  44. package/dist/core/validate.d.ts.map +1 -0
  45. package/dist/core/validate.js +135 -0
  46. package/dist/core/validate.js.map +1 -0
  47. package/dist/middleware/index.d.ts +26 -0
  48. package/dist/middleware/index.d.ts.map +1 -0
  49. package/dist/middleware/index.js +55 -0
  50. package/dist/middleware/index.js.map +1 -0
  51. package/dist/queue/bulkhead.d.ts +63 -0
  52. package/dist/queue/bulkhead.d.ts.map +1 -0
  53. package/dist/queue/bulkhead.js +192 -0
  54. package/dist/queue/bulkhead.js.map +1 -0
  55. package/dist/queue/circuit-breaker.d.ts +81 -0
  56. package/dist/queue/circuit-breaker.d.ts.map +1 -0
  57. package/dist/queue/circuit-breaker.js +283 -0
  58. package/dist/queue/circuit-breaker.js.map +1 -0
  59. package/dist/queue/executor.d.ts +67 -0
  60. package/dist/queue/executor.d.ts.map +1 -0
  61. package/dist/queue/executor.js +273 -0
  62. package/dist/queue/executor.js.map +1 -0
  63. package/dist/queue/policy.d.ts +26 -0
  64. package/dist/queue/policy.d.ts.map +1 -0
  65. package/dist/queue/policy.js +37 -0
  66. package/dist/queue/policy.js.map +1 -0
  67. package/dist/queue/retry.d.ts +58 -0
  68. package/dist/queue/retry.d.ts.map +1 -0
  69. package/dist/queue/retry.js +259 -0
  70. package/dist/queue/retry.js.map +1 -0
  71. package/dist/queue/semaphore.d.ts +32 -0
  72. package/dist/queue/semaphore.d.ts.map +1 -0
  73. package/dist/queue/semaphore.js +83 -0
  74. package/dist/queue/semaphore.js.map +1 -0
  75. package/dist/ticket/ticket.d.ts +77 -0
  76. package/dist/ticket/ticket.d.ts.map +1 -0
  77. package/dist/ticket/ticket.js +186 -0
  78. package/dist/ticket/ticket.js.map +1 -0
  79. package/package.json +85 -0
  80. package/src/adapters/zod.ts +16 -0
  81. package/src/core/backoff.ts +26 -0
  82. package/src/core/client.ts +1048 -0
  83. package/src/core/errors.ts +194 -0
  84. package/src/core/index.ts +56 -0
  85. package/src/core/listeners.ts +28 -0
  86. package/src/core/metrics.ts +42 -0
  87. package/src/core/nanoid.ts +11 -0
  88. package/src/core/redact.ts +46 -0
  89. package/src/core/types.ts +306 -0
  90. package/src/core/validate.ts +163 -0
  91. package/src/middleware/index.ts +63 -0
  92. package/src/queue/bulkhead.ts +243 -0
  93. package/src/queue/circuit-breaker.ts +373 -0
  94. package/src/queue/executor.ts +355 -0
  95. package/src/queue/policy.ts +49 -0
  96. package/src/queue/retry.ts +380 -0
  97. package/src/queue/semaphore.ts +91 -0
  98. package/src/ticket/ticket.ts +246 -0
@@ -0,0 +1,186 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { CancelledError } from "../core/errors.js";
3
+ import { emitIsolated } from "../core/listeners.js";
4
+ // Allowed transitions between ticket states. `done` and `cancelled` are both
5
+ // terminal: `cancel()` resolves the ticket directly with a CancelledError
6
+ // result, so a cancelled ticket is never transitioned to `done` by the retry
7
+ // loop (its `_markDone` becomes a no-op). The `retrying -> retrying` self-loop
8
+ // permits re-entering `retrying` with a new attempt number (a fresh status
9
+ // object is assigned, not mutated in place). `pending -> done` is the
10
+ // first-attempt success path: client.ts calls `_markDone` directly from
11
+ // `pending` when the initial request succeeds, so it is an intentional
12
+ // shortcut, not a gap in the lifecycle.
13
+ const ALLOWED_TRANSITIONS = {
14
+ pending: ["queued", "done", "cancelled"],
15
+ queued: ["retrying", "done", "cancelled"],
16
+ retrying: ["retrying", "done", "cancelled"],
17
+ cancelled: [],
18
+ done: [],
19
+ };
20
+ // ---------------------------------------------------------------------------
21
+ // Ticket
22
+ // ---------------------------------------------------------------------------
23
+ export class Ticket {
24
+ id;
25
+ emitter = new EventEmitter();
26
+ _status = { state: "pending" };
27
+ _cancelled = false;
28
+ _abortController = new AbortController();
29
+ _resolve;
30
+ _promise;
31
+ /** @internal — use `createTicket()` to obtain a ticket and its controller. */
32
+ constructor(id) {
33
+ this.id = id;
34
+ this._promise = new Promise((resolve) => {
35
+ this._resolve = resolve;
36
+ });
37
+ // Prevent Node from throwing on unhandled "error" events
38
+ this.emitter.on("error", () => { });
39
+ }
40
+ get status() {
41
+ return this._status;
42
+ }
43
+ /** Validate and apply a state transition. Returns false (and leaves the
44
+ * current state untouched) when `next` is not reachable from the current
45
+ * state, so illegal transitions are ignored rather than corrupting state. */
46
+ applyTransition(next) {
47
+ if (!ALLOWED_TRANSITIONS[this._status.state].includes(next.state)) {
48
+ return false;
49
+ }
50
+ this._status = next;
51
+ return true;
52
+ }
53
+ get signal() {
54
+ return this._abortController.signal;
55
+ }
56
+ // biome-ignore lint/suspicious/noExplicitAny: overload implementation signature must accept every declared overload; callers only see the typed overloads.
57
+ on(event, listener) {
58
+ // biome-ignore lint/suspicious/noExplicitAny: EventEmitter's own listener signature.
59
+ this.emitter.on(event, listener);
60
+ return this;
61
+ }
62
+ // biome-ignore lint/suspicious/noExplicitAny: overload implementation signature must accept every declared overload; callers only see the typed overloads.
63
+ off(event, listener) {
64
+ // biome-ignore lint/suspicious/noExplicitAny: EventEmitter's own listener signature.
65
+ this.emitter.off(event, listener);
66
+ return this;
67
+ }
68
+ // -- Public API ------------------------------------------------------------
69
+ cancel() {
70
+ if (this._cancelled)
71
+ return;
72
+ // `cancelled` is terminal — if the ticket already resolved, this is a no-op.
73
+ if (!this.applyTransition({ state: "cancelled" }))
74
+ return;
75
+ this._cancelled = true;
76
+ this._abortController.abort();
77
+ const result = { success: false, error: new CancelledError() };
78
+ emitIsolated(this.emitter, "update", { type: "cancelled" });
79
+ emitIsolated(this.emitter, "done", result);
80
+ emitIsolated(this.emitter, "error", result.error);
81
+ this._resolve(result);
82
+ }
83
+ toPromise() {
84
+ return this._promise;
85
+ }
86
+ async *subscribe() {
87
+ // Already terminal — yield synthetic update and return
88
+ const current = this._status;
89
+ if (current.state === "done") {
90
+ yield { type: "done", result: current.result };
91
+ return;
92
+ }
93
+ if (current.state === "cancelled") {
94
+ yield { type: "cancelled" };
95
+ return;
96
+ }
97
+ const updates = [];
98
+ let notify = null;
99
+ let isDone = false;
100
+ const onUpdate = (update) => {
101
+ updates.push(update);
102
+ notify?.();
103
+ notify = null;
104
+ if (update.type === "done" || update.type === "cancelled") {
105
+ isDone = true;
106
+ }
107
+ };
108
+ this.emitter.on("update", onUpdate);
109
+ try {
110
+ while (!isDone || updates.length > 0) {
111
+ if (updates.length > 0) {
112
+ yield updates.shift();
113
+ }
114
+ else {
115
+ await new Promise((r) => {
116
+ notify = r;
117
+ });
118
+ }
119
+ }
120
+ }
121
+ finally {
122
+ this.emitter.off("update", onUpdate);
123
+ }
124
+ }
125
+ get isCancelled() {
126
+ return this._cancelled;
127
+ }
128
+ /** True once this ticket has reached a terminal state by any path — a
129
+ * normal `done` transition or `cancel()` (which resolves the ticket
130
+ * directly, bypassing `done`). A terminal-event emitter must check this
131
+ * before firing, or a result racing a concurrent cancel() can emit a
132
+ * second, contradictory terminal event for the same ticket. */
133
+ get isSettled() {
134
+ return this._status.state === "done" || this._cancelled;
135
+ }
136
+ // -- Internal mutators (private, accessed via TicketController) ------------
137
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: reached via bracket notation from createTicket(); Biome cannot see that access.
138
+ markQueued() {
139
+ if (!this.applyTransition({ state: "queued" }))
140
+ return;
141
+ emitIsolated(this.emitter, "update", { type: "queued" });
142
+ }
143
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: reached via bracket notation from createTicket(); Biome cannot see that access.
144
+ markRetrying(attempt, delayMs) {
145
+ if (!this.applyTransition({ state: "retrying", attempt }))
146
+ return;
147
+ emitIsolated(this.emitter, "update", {
148
+ type: "retrying",
149
+ attempt,
150
+ delayMs,
151
+ });
152
+ }
153
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: reached via bracket notation from createTicket(); Biome cannot see that access.
154
+ markDone(result) {
155
+ if (!this.applyTransition({ state: "done", result }))
156
+ return;
157
+ emitIsolated(this.emitter, "update", { type: "done", result });
158
+ emitIsolated(this.emitter, "done", result);
159
+ if (result.success === false) {
160
+ emitIsolated(this.emitter, "error", result.error);
161
+ }
162
+ this._resolve(result);
163
+ }
164
+ }
165
+ // ---------------------------------------------------------------------------
166
+ // Factory — the only public way to get a ticket and its controller
167
+ // ---------------------------------------------------------------------------
168
+ export function createTicket(id) {
169
+ const ticket = new Ticket(id);
170
+ // Bind the private methods to the ticket instance and expose them
171
+ // through the controller interface.
172
+ const controller = {
173
+ // Bracket notation accesses private methods — the type boundary prevents
174
+ // external callers from reaching these, while the controller provides
175
+ // a clean compile-time API for internal use.
176
+ // biome-ignore-start lint/complexity/useLiteralKeys: these members are private; dot
177
+ // access is a compile error, bracket notation is the deliberate escape hatch.
178
+ markQueued: () => ticket["markQueued"](),
179
+ markRetrying: (attempt, delayMs) => ticket["markRetrying"](attempt, delayMs),
180
+ markDone: (result) => ticket["markDone"](result),
181
+ abortSignal: () => ticket["_abortController"].abort(),
182
+ // biome-ignore-end lint/complexity/useLiteralKeys: restore the rule.
183
+ };
184
+ return { ticket, controller };
185
+ }
186
+ //# sourceMappingURL=ticket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket.js","sourceRoot":"","sources":["../../src/ticket/ticket.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@vereda/http",
3
+ "version": "1.0.0",
4
+ "description": "A resilient HTTP client with automatic queuing, retries, and bulkhead isolation",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/core/index.d.ts",
10
+ "import": "./dist/core/index.js"
11
+ },
12
+ "./middleware": {
13
+ "types": "./dist/middleware/index.d.ts",
14
+ "import": "./dist/middleware/index.js"
15
+ },
16
+ "./zod": {
17
+ "types": "./dist/adapters/zod.d.ts",
18
+ "import": "./dist/adapters/zod.js"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "prepublishOnly": "npm run build && npm test",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage",
27
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json && tsc -p examples/tsconfig.json && tsc -p performance/tsconfig.json",
28
+ "example:checkout": "tsx --tsconfig examples/tsconfig.json examples/checkout/index.ts",
29
+ "lint": "biome lint",
30
+ "lint:fix": "biome lint --write",
31
+ "format": "biome format --write",
32
+ "format:check": "biome format",
33
+ "check": "biome check --error-on-warnings",
34
+ "check:fix": "biome check --write",
35
+ "ci": "biome ci --error-on-warnings",
36
+ "docs": "typedoc"
37
+ },
38
+ "keywords": [
39
+ "http",
40
+ "client",
41
+ "queue",
42
+ "retry",
43
+ "bulkhead",
44
+ "resilience"
45
+ ],
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/riosgabriel/vereda.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/riosgabriel/vereda/issues"
53
+ },
54
+ "homepage": "https://github.com/riosgabriel/vereda#readme",
55
+ "engines": {
56
+ "node": ">=22"
57
+ },
58
+ "files": [
59
+ "dist",
60
+ "src",
61
+ "README.md",
62
+ "LICENSE"
63
+ ],
64
+ "peerDependencies": {
65
+ "zod": "^4.6.5"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "zod": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "devDependencies": {
73
+ "@arethetypeswrong/cli": "^0.18.5",
74
+ "@biomejs/biome": "^2.5.14",
75
+ "@types/node": "^26.6.2",
76
+ "@vitest/coverage-v8": "5.0.1",
77
+ "husky": "^9.1.7",
78
+ "publint": "^0.3.24",
79
+ "tsx": "^4.23.15",
80
+ "typedoc": "^0.28.20",
81
+ "typescript": "^6.0.0",
82
+ "vitest": "^5.0.1",
83
+ "zod": "^4.6.5"
84
+ }
85
+ }
@@ -0,0 +1,16 @@
1
+ import type { ZodType } from "zod";
2
+ import type { ParseFn } from "../core/types.ts";
3
+
4
+ /**
5
+ * Wraps a Zod schema into a ParseFn for use with vereda's `parse` option.
6
+ *
7
+ * @example
8
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
9
+ *
10
+ * const ticket = client.get('/users/1', {
11
+ * parse: withZod(UserSchema),
12
+ * });
13
+ */
14
+ export function withZod<T>(schema: ZodType<T>): ParseFn<T> {
15
+ return (data: unknown): T => schema.parse(data);
16
+ }
@@ -0,0 +1,26 @@
1
+ import type { BackoffFn, BackoffOptions } from "./types.ts";
2
+
3
+ /** Default base delay in ms for exponential backoff. */
4
+ export const DEFAULT_BASE_DELAY_MS = 200;
5
+ /** Default max delay cap in ms for exponential backoff. */
6
+ export const DEFAULT_MAX_DELAY_MS = 30_000;
7
+ /** Default jitter setting for exponential backoff. */
8
+ export const DEFAULT_JITTER = true;
9
+
10
+ export function buildBackoffFn(config?: BackoffFn | BackoffOptions): BackoffFn {
11
+ if (typeof config === "function") {
12
+ return config;
13
+ }
14
+
15
+ const baseDelayMs = config?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
16
+ const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
17
+ const jitter = config?.jitter ?? DEFAULT_JITTER;
18
+
19
+ return (attempt: number): number => {
20
+ const exponential = baseDelayMs * 2 ** attempt;
21
+ const capped = Math.min(exponential, maxDelayMs);
22
+ if (!jitter) return capped;
23
+ // Full jitter: random value in [0, capped]
24
+ return Math.random() * capped;
25
+ };
26
+ }