@zudojs/adapters 1.0.1 → 1.2.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/README.md +38 -1
- package/dist/adapter/adapter.health.d.ts +31 -0
- package/dist/adapter/adapter.health.js +94 -0
- package/dist/adapter/adapter.registry.d.ts +19 -0
- package/dist/adapter/adapter.registry.js +31 -0
- package/dist/adapter/adapter.retry.d.ts +35 -0
- package/dist/adapter/adapter.retry.js +74 -0
- package/dist/adapter/index.d.ts +2 -0
- package/dist/adapter/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/lifecycle/lifecycle.type.d.ts +8 -1
- package/dist/testing/adapterTesting.helper.d.ts +7 -3
- package/dist/testing/adapterTesting.helper.js +6 -0
- package/package.json +2 -5
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Boundary layer between Zudojs and external platforms with adapter contracts, registry, capabilities, and transport abstractions.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-adapters](https://zudojs.oyinlola.site/docs/packages-adapters) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-adapters.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -88,7 +94,38 @@ const health = createDegradedHealth("replica lag above threshold");
|
|
|
88
94
|
```
|
|
89
95
|
|
|
90
96
|
A `LifecycleAdapter` adds optional `configure(options)` and `health()` to the
|
|
91
|
-
base contract.
|
|
97
|
+
base contract. The registry calls them:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import {
|
|
101
|
+
AdapterRegistry,
|
|
102
|
+
createHealthyHealth,
|
|
103
|
+
createMockAdapter,
|
|
104
|
+
} from "@zudojs/adapters";
|
|
105
|
+
|
|
106
|
+
const registry = new AdapterRegistry();
|
|
107
|
+
registry.register(
|
|
108
|
+
createMockAdapter({
|
|
109
|
+
name: "db",
|
|
110
|
+
health: () => createHealthyHealth(),
|
|
111
|
+
configure: () => {},
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await registry.configure("db", { poolSize: 10 }); // throws if the adapter has no configure()
|
|
116
|
+
const report = await registry.healthAll({ timeout: 2_000 });
|
|
117
|
+
report.status; // worst of the per-adapter statuses: "healthy" | "degraded" | "unhealthy"
|
|
118
|
+
report.adapters.db; // AdapterHealth; throwing, timed-out or aborted checks are "unhealthy"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Adapters without `health()` are left out of `report.adapters`.
|
|
122
|
+
|
|
123
|
+
`healthAll({ retry: { attempts, delay } })` re-runs a check that reports
|
|
124
|
+
`unhealthy`, up to `attempts` tries in total, pausing `delay` ms between
|
|
125
|
+
them; `timeout` bounds each try and an aborted `signal` stops the retries.
|
|
126
|
+
Adapter names are checked on `register()`: `__proto__`, `constructor` and
|
|
127
|
+
`prototype` are refused, because a report keyed by adapter name cannot hold
|
|
128
|
+
them.
|
|
92
129
|
|
|
93
130
|
## Transport contracts
|
|
94
131
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/adapters/adapter
|
|
3
|
+
*
|
|
4
|
+
* Health aggregation and configuration for registered adapters.
|
|
5
|
+
*
|
|
6
|
+
* `LifecycleAdapter.health()` and `configure()` were part of the contract
|
|
7
|
+
* with nothing in the package that called them. `AdapterRegistry.healthAll()`
|
|
8
|
+
* and `AdapterRegistry.configure()` delegate here.
|
|
9
|
+
*/
|
|
10
|
+
import type { Adapter } from "./adapter.type.js";
|
|
11
|
+
import type { AdapterHealth, AdapterHealthStatus, AdapterOperationOptions } from "../lifecycle/lifecycle.type.js";
|
|
12
|
+
/** Aggregated health of every adapter that implements `health()`. */
|
|
13
|
+
export interface AdapterHealthReport {
|
|
14
|
+
/** The worst status across `adapters`; `"healthy"` when none report. */
|
|
15
|
+
readonly status: AdapterHealthStatus;
|
|
16
|
+
/** Per-adapter health, keyed by normalized adapter name. */
|
|
17
|
+
readonly adapters: Readonly<Record<string, AdapterHealth>>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Collects health from every adapter that implements `health()`. A check that
|
|
21
|
+
* throws, rejects, times out or is aborted is reported as `"unhealthy"`
|
|
22
|
+
* instead of failing the whole report.
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectAdapterHealth(entries: ReadonlyArray<readonly [string, Adapter]>, options?: AdapterOperationOptions): Promise<AdapterHealthReport>;
|
|
25
|
+
/**
|
|
26
|
+
* Passes options to an adapter's `configure()` hook.
|
|
27
|
+
*
|
|
28
|
+
* @throws {AdapterConfigurationError} If the adapter has no `configure()` hook.
|
|
29
|
+
*/
|
|
30
|
+
export declare function configureAdapter(name: string, adapter: Adapter, options: unknown): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=adapter.health.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/adapters/adapter
|
|
3
|
+
*
|
|
4
|
+
* Health aggregation and configuration for registered adapters.
|
|
5
|
+
*
|
|
6
|
+
* `LifecycleAdapter.health()` and `configure()` were part of the contract
|
|
7
|
+
* with nothing in the package that called them. `AdapterRegistry.healthAll()`
|
|
8
|
+
* and `AdapterRegistry.configure()` delegate here.
|
|
9
|
+
*/
|
|
10
|
+
import { AdapterConfigurationError } from "@zudojs/errors";
|
|
11
|
+
import { withRetry } from "./adapter.retry.js";
|
|
12
|
+
const RANK = {
|
|
13
|
+
healthy: 0,
|
|
14
|
+
degraded: 1,
|
|
15
|
+
unhealthy: 2,
|
|
16
|
+
};
|
|
17
|
+
function unhealthy(message) {
|
|
18
|
+
return { status: "unhealthy", message, timestamp: Date.now() };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Runs one health check, bounded by `timeout` and `signal`, retried according
|
|
22
|
+
* to `retry`.
|
|
23
|
+
*/
|
|
24
|
+
async function checkOne(adapter, options) {
|
|
25
|
+
return withRetry(() => attemptCheck(adapter, options), (health) => health.status !== "unhealthy", options);
|
|
26
|
+
}
|
|
27
|
+
/** Runs a single health-check attempt, bounded by `timeout` and `signal`. */
|
|
28
|
+
async function attemptCheck(adapter, options) {
|
|
29
|
+
if (options.signal?.aborted)
|
|
30
|
+
return unhealthy("Health check aborted.");
|
|
31
|
+
let timer;
|
|
32
|
+
let onAbort;
|
|
33
|
+
const guards = [];
|
|
34
|
+
if (options.timeout !== undefined) {
|
|
35
|
+
guards.push(new Promise((resolve) => {
|
|
36
|
+
timer = setTimeout(() => resolve(unhealthy(`Health check timed out after ${options.timeout} ms.`)), options.timeout);
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
if (options.signal) {
|
|
40
|
+
const signal = options.signal;
|
|
41
|
+
guards.push(new Promise((resolve) => {
|
|
42
|
+
onAbort = () => resolve(unhealthy("Health check aborted."));
|
|
43
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const check = Promise.resolve()
|
|
48
|
+
.then(() => adapter.health?.())
|
|
49
|
+
.catch((error) => unhealthy(error instanceof Error ? error.message : String(error)));
|
|
50
|
+
return await Promise.race([check, ...guards]);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (timer !== undefined)
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
if (onAbort)
|
|
56
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Collects health from every adapter that implements `health()`. A check that
|
|
61
|
+
* throws, rejects, times out or is aborted is reported as `"unhealthy"`
|
|
62
|
+
* instead of failing the whole report.
|
|
63
|
+
*/
|
|
64
|
+
export async function collectAdapterHealth(entries, options = {}) {
|
|
65
|
+
// `Object.create(null)`, not `{}`: assigning `adapters["__proto__"]` on an
|
|
66
|
+
// ordinary object runs the inherited prototype setter, so the entry would
|
|
67
|
+
// vanish from the report and an unhealthy adapter would be invisible.
|
|
68
|
+
const adapters = Object.create(null);
|
|
69
|
+
await Promise.all(entries
|
|
70
|
+
.filter(([, adapter]) => typeof adapter.health === "function")
|
|
71
|
+
.map(async ([name, adapter]) => {
|
|
72
|
+
adapters[name] = await checkOne(adapter, options);
|
|
73
|
+
}));
|
|
74
|
+
let status = "healthy";
|
|
75
|
+
for (const health of Object.values(adapters)) {
|
|
76
|
+
const rank = RANK[health.status] ?? RANK.unhealthy;
|
|
77
|
+
if (rank > RANK[status])
|
|
78
|
+
status = rank === RANK.unhealthy ? "unhealthy" : health.status;
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze({ status, adapters: Object.freeze(adapters) });
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Passes options to an adapter's `configure()` hook.
|
|
84
|
+
*
|
|
85
|
+
* @throws {AdapterConfigurationError} If the adapter has no `configure()` hook.
|
|
86
|
+
*/
|
|
87
|
+
export async function configureAdapter(name, adapter, options) {
|
|
88
|
+
const configurable = adapter;
|
|
89
|
+
if (typeof configurable.configure !== "function") {
|
|
90
|
+
throw new AdapterConfigurationError(name, new Error(`Adapter "${name}" does not implement configure().`));
|
|
91
|
+
}
|
|
92
|
+
await configurable.configure(options);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=adapter.health.js.map
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { Adapter } from "./adapter.type.js";
|
|
15
15
|
import type { AdapterCapabilities } from "../capabilities/capabilities.type.js";
|
|
16
|
+
import type { AdapterHealthReport } from "./adapter.health.js";
|
|
17
|
+
import type { AdapterOperationOptions } from "../lifecycle/lifecycle.type.js";
|
|
16
18
|
/** A capability an adapter can declare. */
|
|
17
19
|
export type AdapterCapabilityName = keyof AdapterCapabilities;
|
|
18
20
|
/**
|
|
@@ -28,6 +30,10 @@ export declare class AdapterRegistry {
|
|
|
28
30
|
* @throws {AdapterConfigurationError} If the adapter name is blank. A name
|
|
29
31
|
* that normalizes to the empty string is unaddressable — `get("")` is the
|
|
30
32
|
* only way back to it, and every other blank name collides with it.
|
|
33
|
+
* @throws {AdapterConfigurationError} If the adapter name is a prototype
|
|
34
|
+
* member (`__proto__`, `constructor`, `prototype`). Those names survive the
|
|
35
|
+
* registry's `Map`, but any consumer keying a plain object by adapter name
|
|
36
|
+
* loses or corrupts the entry, so they are refused at the door.
|
|
31
37
|
* @throws {AdapterAlreadyRegisteredError} If an adapter with the same name is already registered.
|
|
32
38
|
*/
|
|
33
39
|
register(adapter: Adapter): void;
|
|
@@ -113,6 +119,19 @@ export declare class AdapterRegistry {
|
|
|
113
119
|
* @throws {AggregateError} After attempting all adapters, if any failed.
|
|
114
120
|
*/
|
|
115
121
|
stopAll(): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Checks the health of every adapter that implements `health()`. Failures,
|
|
124
|
+
* timeouts (`options.timeout`) and aborts (`options.signal`) are reported
|
|
125
|
+
* as `"unhealthy"` entries rather than thrown.
|
|
126
|
+
*/
|
|
127
|
+
healthAll(options?: AdapterOperationOptions): Promise<AdapterHealthReport>;
|
|
128
|
+
/**
|
|
129
|
+
* Passes options to one adapter's `configure()` hook.
|
|
130
|
+
*
|
|
131
|
+
* @throws {AdapterNotFoundError} If no adapter with the name is registered.
|
|
132
|
+
* @throws {AdapterConfigurationError} If the adapter has no `configure()`.
|
|
133
|
+
*/
|
|
134
|
+
configure(name: string, options: unknown): Promise<void>;
|
|
116
135
|
/**
|
|
117
136
|
* Returns the number of registered adapters.
|
|
118
137
|
*/
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* `removeAndDispose()` / `disposeAll()` to also release adapter resources.
|
|
13
13
|
*/
|
|
14
14
|
import { AdapterAlreadyRegisteredError, AdapterCapabilityMissingError, AdapterConfigurationError, AdapterNotFoundError, } from "@zudojs/errors";
|
|
15
|
+
import { collectAdapterHealth, configureAdapter } from "./adapter.health.js";
|
|
16
|
+
/** Names that are unsafe as a plain-object key, so no adapter may claim them. */
|
|
17
|
+
const RESERVED_NAMES = new Set([
|
|
18
|
+
"__proto__",
|
|
19
|
+
"constructor",
|
|
20
|
+
"prototype",
|
|
21
|
+
]);
|
|
15
22
|
/**
|
|
16
23
|
* Registry for Zudojs adapters.
|
|
17
24
|
*
|
|
@@ -25,6 +32,10 @@ export class AdapterRegistry {
|
|
|
25
32
|
* @throws {AdapterConfigurationError} If the adapter name is blank. A name
|
|
26
33
|
* that normalizes to the empty string is unaddressable — `get("")` is the
|
|
27
34
|
* only way back to it, and every other blank name collides with it.
|
|
35
|
+
* @throws {AdapterConfigurationError} If the adapter name is a prototype
|
|
36
|
+
* member (`__proto__`, `constructor`, `prototype`). Those names survive the
|
|
37
|
+
* registry's `Map`, but any consumer keying a plain object by adapter name
|
|
38
|
+
* loses or corrupts the entry, so they are refused at the door.
|
|
28
39
|
* @throws {AdapterAlreadyRegisteredError} If an adapter with the same name is already registered.
|
|
29
40
|
*/
|
|
30
41
|
register(adapter) {
|
|
@@ -32,6 +43,9 @@ export class AdapterRegistry {
|
|
|
32
43
|
if (name === "") {
|
|
33
44
|
throw new AdapterConfigurationError(String(adapter.name), new Error("Adapter name cannot be blank."));
|
|
34
45
|
}
|
|
46
|
+
if (RESERVED_NAMES.has(name)) {
|
|
47
|
+
throw new AdapterConfigurationError(String(adapter.name), new Error(`Adapter name "${name}" is reserved.`));
|
|
48
|
+
}
|
|
35
49
|
if (this.adapters.has(name)) {
|
|
36
50
|
throw new AdapterAlreadyRegisteredError(name);
|
|
37
51
|
}
|
|
@@ -160,6 +174,23 @@ export class AdapterRegistry {
|
|
|
160
174
|
async stopAll() {
|
|
161
175
|
await this.forEachAdapter((adapter) => adapter.stop?.(), "One or more adapters failed to stop.");
|
|
162
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Checks the health of every adapter that implements `health()`. Failures,
|
|
179
|
+
* timeouts (`options.timeout`) and aborts (`options.signal`) are reported
|
|
180
|
+
* as `"unhealthy"` entries rather than thrown.
|
|
181
|
+
*/
|
|
182
|
+
async healthAll(options) {
|
|
183
|
+
return collectAdapterHealth([...this.adapters.entries()], options);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Passes options to one adapter's `configure()` hook.
|
|
187
|
+
*
|
|
188
|
+
* @throws {AdapterNotFoundError} If no adapter with the name is registered.
|
|
189
|
+
* @throws {AdapterConfigurationError} If the adapter has no `configure()`.
|
|
190
|
+
*/
|
|
191
|
+
async configure(name, options) {
|
|
192
|
+
await configureAdapter(this.normalizeName(name), this.require(name), options);
|
|
193
|
+
}
|
|
163
194
|
/**
|
|
164
195
|
* Returns the number of registered adapters.
|
|
165
196
|
*/
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/adapters/adapter
|
|
3
|
+
*
|
|
4
|
+
* Retry support for adapter operations.
|
|
5
|
+
*
|
|
6
|
+
* `AdapterOperationOptions.retry` was part of the contract with nothing that
|
|
7
|
+
* read it, so a caller asking for three attempts silently got one.
|
|
8
|
+
*/
|
|
9
|
+
import type { AdapterOperationOptions } from "../lifecycle/lifecycle.type.js";
|
|
10
|
+
/**
|
|
11
|
+
* Total attempts requested, clamped to at least one.
|
|
12
|
+
*
|
|
13
|
+
* A missing, non-finite, fractional or non-positive `attempts` means a single
|
|
14
|
+
* attempt: a retry policy nobody can express as "run fewer than once".
|
|
15
|
+
*/
|
|
16
|
+
export declare function retryAttempts(options: AdapterOperationOptions): number;
|
|
17
|
+
/** Delay between attempts in milliseconds; negative and non-finite mean none. */
|
|
18
|
+
export declare function retryDelay(options: AdapterOperationOptions): number;
|
|
19
|
+
/** Waits `ms`, resolving early when `signal` aborts so a retry cannot outlive it. */
|
|
20
|
+
export declare function wait(ms: number, signal?: AbortSignal): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Runs `attempt` until `accept` approves its result or the attempt budget from
|
|
23
|
+
* `options.retry` is spent, returning the last result either way.
|
|
24
|
+
*
|
|
25
|
+
* An aborted signal stops further attempts immediately — the caller has already
|
|
26
|
+
* said it no longer wants the answer.
|
|
27
|
+
*
|
|
28
|
+
* @typeParam T - The attempt's result type.
|
|
29
|
+
* @param attempt - The operation to run; it must not throw.
|
|
30
|
+
* @param accept - Whether a result is final.
|
|
31
|
+
* @param options - Operation options carrying the retry policy.
|
|
32
|
+
* @returns The first accepted result, or the last one produced.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withRetry<T>(attempt: () => Promise<T>, accept: (result: T) => boolean, options: AdapterOperationOptions): Promise<T>;
|
|
35
|
+
//# sourceMappingURL=adapter.retry.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/adapters/adapter
|
|
3
|
+
*
|
|
4
|
+
* Retry support for adapter operations.
|
|
5
|
+
*
|
|
6
|
+
* `AdapterOperationOptions.retry` was part of the contract with nothing that
|
|
7
|
+
* read it, so a caller asking for three attempts silently got one.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Total attempts requested, clamped to at least one.
|
|
11
|
+
*
|
|
12
|
+
* A missing, non-finite, fractional or non-positive `attempts` means a single
|
|
13
|
+
* attempt: a retry policy nobody can express as "run fewer than once".
|
|
14
|
+
*/
|
|
15
|
+
export function retryAttempts(options) {
|
|
16
|
+
const attempts = options.retry?.attempts;
|
|
17
|
+
if (typeof attempts !== "number" || !Number.isFinite(attempts))
|
|
18
|
+
return 1;
|
|
19
|
+
return Math.max(1, Math.floor(attempts));
|
|
20
|
+
}
|
|
21
|
+
/** Delay between attempts in milliseconds; negative and non-finite mean none. */
|
|
22
|
+
export function retryDelay(options) {
|
|
23
|
+
const delay = options.retry?.delay;
|
|
24
|
+
if (typeof delay !== "number" || !Number.isFinite(delay))
|
|
25
|
+
return 0;
|
|
26
|
+
return Math.max(0, delay);
|
|
27
|
+
}
|
|
28
|
+
/** Waits `ms`, resolving early when `signal` aborts so a retry cannot outlive it. */
|
|
29
|
+
export function wait(ms, signal) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const finish = () => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
signal?.removeEventListener("abort", finish);
|
|
34
|
+
resolve();
|
|
35
|
+
};
|
|
36
|
+
const timer = setTimeout(finish, ms);
|
|
37
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
38
|
+
// A signal that aborted before the listener was attached never fires it.
|
|
39
|
+
if (signal?.aborted === true)
|
|
40
|
+
finish();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Runs `attempt` until `accept` approves its result or the attempt budget from
|
|
45
|
+
* `options.retry` is spent, returning the last result either way.
|
|
46
|
+
*
|
|
47
|
+
* An aborted signal stops further attempts immediately — the caller has already
|
|
48
|
+
* said it no longer wants the answer.
|
|
49
|
+
*
|
|
50
|
+
* @typeParam T - The attempt's result type.
|
|
51
|
+
* @param attempt - The operation to run; it must not throw.
|
|
52
|
+
* @param accept - Whether a result is final.
|
|
53
|
+
* @param options - Operation options carrying the retry policy.
|
|
54
|
+
* @returns The first accepted result, or the last one produced.
|
|
55
|
+
*/
|
|
56
|
+
export async function withRetry(attempt, accept, options) {
|
|
57
|
+
const attempts = retryAttempts(options);
|
|
58
|
+
const delay = retryDelay(options);
|
|
59
|
+
const aborted = () => options.signal?.aborted === true;
|
|
60
|
+
let result = await attempt();
|
|
61
|
+
for (let remaining = attempts - 1; remaining > 0; remaining--) {
|
|
62
|
+
if (accept(result))
|
|
63
|
+
return result;
|
|
64
|
+
if (aborted())
|
|
65
|
+
return result;
|
|
66
|
+
if (delay > 0)
|
|
67
|
+
await wait(delay, options.signal);
|
|
68
|
+
if (aborted())
|
|
69
|
+
return result;
|
|
70
|
+
result = await attempt();
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=adapter.retry.js.map
|
package/dist/adapter/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@
|
|
|
6
6
|
export type { Adapter } from "./adapter.type.js";
|
|
7
7
|
export { AdapterRegistry } from "./adapter.registry.js";
|
|
8
8
|
export type { AdapterCapabilityName } from "./adapter.registry.js";
|
|
9
|
+
export { collectAdapterHealth, configureAdapter } from "./adapter.health.js";
|
|
10
|
+
export type { AdapterHealthReport } from "./adapter.health.js";
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/adapter/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
export type { Adapter } from "./adapter/index.js";
|
|
19
19
|
export { AdapterRegistry } from "./adapter/index.js";
|
|
20
20
|
export type { AdapterCapabilityName } from "./adapter/index.js";
|
|
21
|
+
export type { AdapterHealthReport } from "./adapter/index.js";
|
|
21
22
|
export type { AdapterCapabilities } from "./capabilities/index.js";
|
|
22
23
|
export type { AdapterMetadata } from "./metadata/index.js";
|
|
23
24
|
export type { AdapterHealthStatus, AdapterHealth, AdapterOperationOptions, LifecycleAdapter, } from "./lifecycle/index.js";
|
|
@@ -25,7 +25,14 @@ export interface AdapterOperationOptions {
|
|
|
25
25
|
readonly signal?: AbortSignal;
|
|
26
26
|
/** Timeout in milliseconds. */
|
|
27
27
|
readonly timeout?: number;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Retry configuration.
|
|
30
|
+
*
|
|
31
|
+
* `attempts` is the total number of tries, not the number of extra ones:
|
|
32
|
+
* `1` (or anything below it) runs the operation once. `delay` is the pause
|
|
33
|
+
* in milliseconds between tries, ended early by `signal`. A health check is
|
|
34
|
+
* retried only while it reports `"unhealthy"`; `timeout` bounds each try.
|
|
35
|
+
*/
|
|
29
36
|
readonly retry?: {
|
|
30
37
|
readonly attempts: number;
|
|
31
38
|
readonly delay?: number;
|
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { Adapter } from "../adapter/adapter.type.js";
|
|
7
7
|
import { AdapterRegistry } from "../adapter/adapter.registry.js";
|
|
8
|
-
import type { AdapterHealth } from "../lifecycle/lifecycle.type.js";
|
|
8
|
+
import type { AdapterHealth, LifecycleAdapter } from "../lifecycle/lifecycle.type.js";
|
|
9
9
|
/**
|
|
10
10
|
* Creates a minimal mock adapter for testing.
|
|
11
|
+
*
|
|
12
|
+
* Accepts the `LifecycleAdapter` hooks too: `health` and `configure` are
|
|
13
|
+
* forwarded when given, so the result can be exercised with
|
|
14
|
+
* `AdapterRegistry.healthAll()` and `configure()`.
|
|
11
15
|
*/
|
|
12
|
-
export declare function createMockAdapter(overrides?: Partial<
|
|
16
|
+
export declare function createMockAdapter(overrides?: Partial<LifecycleAdapter> & {
|
|
13
17
|
name: string;
|
|
14
|
-
}):
|
|
18
|
+
}): LifecycleAdapter;
|
|
15
19
|
/**
|
|
16
20
|
* Creates a mock adapter registry pre-populated with adapters.
|
|
17
21
|
*/
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import { AdapterRegistry } from "../adapter/adapter.registry.js";
|
|
7
7
|
/**
|
|
8
8
|
* Creates a minimal mock adapter for testing.
|
|
9
|
+
*
|
|
10
|
+
* Accepts the `LifecycleAdapter` hooks too: `health` and `configure` are
|
|
11
|
+
* forwarded when given, so the result can be exercised with
|
|
12
|
+
* `AdapterRegistry.healthAll()` and `configure()`.
|
|
9
13
|
*/
|
|
10
14
|
export function createMockAdapter(overrides = { name: "mock" }) {
|
|
11
15
|
const capabilities = {
|
|
@@ -31,6 +35,8 @@ export function createMockAdapter(overrides = { name: "mock" }) {
|
|
|
31
35
|
start: overrides.start,
|
|
32
36
|
stop: overrides.stop,
|
|
33
37
|
dispose: overrides.dispose,
|
|
38
|
+
...(overrides.health ? { health: overrides.health } : {}),
|
|
39
|
+
...(overrides.configure ? { configure: overrides.configure } : {}),
|
|
34
40
|
};
|
|
35
41
|
}
|
|
36
42
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/adapters",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Boundary layer between Zudojs and external platforms — adapter contracts, registry, capabilities, and transport abstractions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -27,10 +27,7 @@
|
|
|
27
27
|
"node": ">=24.0.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@zudojs/errors": "1.0
|
|
31
|
-
"@zudojs/constants": "1.0.1",
|
|
32
|
-
"@zudojs/types": "1.0.0",
|
|
33
|
-
"@zudojs/lifecycle": "1.1.0"
|
|
30
|
+
"@zudojs/errors": "1.2.0"
|
|
34
31
|
},
|
|
35
32
|
"devDependencies": {
|
|
36
33
|
"typescript": "7.0.2",
|