@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.
- package/LICENSE +21 -0
- package/README.md +604 -0
- package/dist/adapters/zod.d.ts +14 -0
- package/dist/adapters/zod.d.ts.map +1 -0
- package/dist/adapters/zod.js +14 -0
- package/dist/adapters/zod.js.map +1 -0
- package/dist/core/backoff.d.ts +9 -0
- package/dist/core/backoff.d.ts.map +1 -0
- package/dist/core/backoff.js +23 -0
- package/dist/core/backoff.js.map +1 -0
- package/dist/core/client.d.ts +98 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +781 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/errors.d.ts +87 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +140 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/listeners.d.ts +14 -0
- package/dist/core/listeners.d.ts.map +1 -0
- package/dist/core/listeners.js +27 -0
- package/dist/core/listeners.js.map +1 -0
- package/dist/core/metrics.d.ts +33 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +24 -0
- package/dist/core/metrics.js.map +1 -0
- package/dist/core/nanoid.d.ts +2 -0
- package/dist/core/nanoid.d.ts.map +1 -0
- package/dist/core/nanoid.js +11 -0
- package/dist/core/nanoid.js.map +1 -0
- package/dist/core/redact.d.ts +12 -0
- package/dist/core/redact.d.ts.map +1 -0
- package/dist/core/redact.js +42 -0
- package/dist/core/redact.js.map +1 -0
- package/dist/core/types.d.ts +261 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +41 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validate.d.ts +19 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +135 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/middleware/index.d.ts +26 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +55 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/queue/bulkhead.d.ts +63 -0
- package/dist/queue/bulkhead.d.ts.map +1 -0
- package/dist/queue/bulkhead.js +192 -0
- package/dist/queue/bulkhead.js.map +1 -0
- package/dist/queue/circuit-breaker.d.ts +81 -0
- package/dist/queue/circuit-breaker.d.ts.map +1 -0
- package/dist/queue/circuit-breaker.js +283 -0
- package/dist/queue/circuit-breaker.js.map +1 -0
- package/dist/queue/executor.d.ts +67 -0
- package/dist/queue/executor.d.ts.map +1 -0
- package/dist/queue/executor.js +273 -0
- package/dist/queue/executor.js.map +1 -0
- package/dist/queue/policy.d.ts +26 -0
- package/dist/queue/policy.d.ts.map +1 -0
- package/dist/queue/policy.js +37 -0
- package/dist/queue/policy.js.map +1 -0
- package/dist/queue/retry.d.ts +58 -0
- package/dist/queue/retry.d.ts.map +1 -0
- package/dist/queue/retry.js +259 -0
- package/dist/queue/retry.js.map +1 -0
- package/dist/queue/semaphore.d.ts +32 -0
- package/dist/queue/semaphore.d.ts.map +1 -0
- package/dist/queue/semaphore.js +83 -0
- package/dist/queue/semaphore.js.map +1 -0
- package/dist/ticket/ticket.d.ts +77 -0
- package/dist/ticket/ticket.d.ts.map +1 -0
- package/dist/ticket/ticket.js +186 -0
- package/dist/ticket/ticket.js.map +1 -0
- package/package.json +85 -0
- package/src/adapters/zod.ts +16 -0
- package/src/core/backoff.ts +26 -0
- package/src/core/client.ts +1048 -0
- package/src/core/errors.ts +194 -0
- package/src/core/index.ts +56 -0
- package/src/core/listeners.ts +28 -0
- package/src/core/metrics.ts +42 -0
- package/src/core/nanoid.ts +11 -0
- package/src/core/redact.ts +46 -0
- package/src/core/types.ts +306 -0
- package/src/core/validate.ts +163 -0
- package/src/middleware/index.ts +63 -0
- package/src/queue/bulkhead.ts +243 -0
- package/src/queue/circuit-breaker.ts +373 -0
- package/src/queue/executor.ts +355 -0
- package/src/queue/policy.ts +49 -0
- package/src/queue/retry.ts +380 -0
- package/src/queue/semaphore.ts +91 -0
- 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
|
+
}
|