@pellux/goodvibes-transport-http 0.18.3
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 +18 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +39 -0
- package/dist/backoff.d.ts +16 -0
- package/dist/backoff.d.ts.map +1 -0
- package/dist/backoff.js +37 -0
- package/dist/contract-client.d.ts +21 -0
- package/dist/contract-client.d.ts.map +1 -0
- package/dist/contract-client.js +31 -0
- package/dist/http-core.d.ts +55 -0
- package/dist/http-core.d.ts.map +1 -0
- package/dist/http-core.js +217 -0
- package/dist/http.d.ts +12 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +51 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/paths.d.ts +32 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +42 -0
- package/dist/reconnect.d.ts +11 -0
- package/dist/reconnect.d.ts.map +1 -0
- package/dist/reconnect.js +19 -0
- package/dist/retry.d.ts +18 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +45 -0
- package/dist/sse-stream.d.ts +22 -0
- package/dist/sse-stream.d.ts.map +1 -0
- package/dist/sse-stream.js +212 -0
- package/dist/sse.d.ts +7 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +17 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @pellux/goodvibes-transport-http
|
|
2
|
+
|
|
3
|
+
HTTP, JSON, path, and SSE helpers for GoodVibes SDK clients.
|
|
4
|
+
|
|
5
|
+
Install:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @pellux/goodvibes-transport-http
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Exports include:
|
|
12
|
+
- contract route invocation helpers
|
|
13
|
+
- HTTP transport creation
|
|
14
|
+
- header and auth token resolution helpers
|
|
15
|
+
- retry/backoff helpers
|
|
16
|
+
- SSE streaming helpers
|
|
17
|
+
|
|
18
|
+
Use this package when you need lower-level HTTP/SSE control or when you are building a custom GoodVibes client on top of the synced contracts.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
2
|
+
export type AuthTokenResolver = () => MaybePromise<string | null | undefined>;
|
|
3
|
+
export type HeaderResolver = () => MaybePromise<HeadersInit | undefined>;
|
|
4
|
+
export declare function mergeHeaders(...sources: Array<HeadersInit | undefined>): Headers;
|
|
5
|
+
export declare function resolveAuthToken(authToken: string | null | undefined, getAuthToken?: AuthTokenResolver): Promise<string | null>;
|
|
6
|
+
export declare function resolveHeaders(headers: HeadersInit | undefined, getHeaders?: HeaderResolver): Promise<Headers>;
|
|
7
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,MAAM,YAAY,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAE9E,MAAM,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;AAuBzE,wBAAgB,YAAY,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAMhF;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,YAAY,CAAC,EAAE,iBAAiB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,UAAU,CAAC,EAAE,cAAc,GAC1B,OAAO,CAAC,OAAO,CAAC,CAGlB"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function appendHeaders(target, headers) {
|
|
2
|
+
if (!headers)
|
|
3
|
+
return;
|
|
4
|
+
if (headers instanceof Headers) {
|
|
5
|
+
headers.forEach((value, key) => {
|
|
6
|
+
target.set(key, value);
|
|
7
|
+
});
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(headers)) {
|
|
11
|
+
for (const [key, value] of headers) {
|
|
12
|
+
target.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
17
|
+
if (value !== undefined) {
|
|
18
|
+
target.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function mergeHeaders(...sources) {
|
|
23
|
+
const headers = new Headers();
|
|
24
|
+
for (const source of sources) {
|
|
25
|
+
appendHeaders(headers, source);
|
|
26
|
+
}
|
|
27
|
+
return headers;
|
|
28
|
+
}
|
|
29
|
+
export async function resolveAuthToken(authToken, getAuthToken) {
|
|
30
|
+
if (getAuthToken) {
|
|
31
|
+
const resolved = await getAuthToken();
|
|
32
|
+
return resolved ?? null;
|
|
33
|
+
}
|
|
34
|
+
return authToken ?? null;
|
|
35
|
+
}
|
|
36
|
+
export async function resolveHeaders(headers, getHeaders) {
|
|
37
|
+
const resolved = getHeaders ? await getHeaders() : undefined;
|
|
38
|
+
return mergeHeaders(headers, resolved);
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface BackoffPolicy {
|
|
2
|
+
readonly maxAttempts?: number;
|
|
3
|
+
readonly baseDelayMs?: number;
|
|
4
|
+
readonly maxDelayMs?: number;
|
|
5
|
+
readonly backoffFactor?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvedBackoffPolicy {
|
|
8
|
+
readonly maxAttempts: number;
|
|
9
|
+
readonly baseDelayMs: number;
|
|
10
|
+
readonly maxDelayMs: number;
|
|
11
|
+
readonly backoffFactor: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function normalizeBackoffPolicy(policy: BackoffPolicy | undefined, defaults: ResolvedBackoffPolicy): ResolvedBackoffPolicy;
|
|
14
|
+
export declare function computeBackoffDelay(attempt: number, policy: ResolvedBackoffPolicy): number;
|
|
15
|
+
export declare function sleepWithSignal(delayMs: number, signal?: AbortSignal): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=backoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,QAAQ,EAAE,qBAAqB,GAC9B,qBAAqB,CAOvB;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,GAC5B,MAAM,CAKR;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAoBf"}
|
package/dist/backoff.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function normalizeBackoffPolicy(policy, defaults) {
|
|
2
|
+
return {
|
|
3
|
+
maxAttempts: Math.max(1, Math.floor(policy?.maxAttempts ?? defaults.maxAttempts)),
|
|
4
|
+
baseDelayMs: Math.max(0, Math.floor(policy?.baseDelayMs ?? defaults.baseDelayMs)),
|
|
5
|
+
maxDelayMs: Math.max(0, Math.floor(policy?.maxDelayMs ?? defaults.maxDelayMs)),
|
|
6
|
+
backoffFactor: Math.max(1, policy?.backoffFactor ?? defaults.backoffFactor),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function computeBackoffDelay(attempt, policy) {
|
|
10
|
+
if (attempt <= 1)
|
|
11
|
+
return 0;
|
|
12
|
+
const exponent = Math.max(0, attempt - 2);
|
|
13
|
+
const delay = policy.baseDelayMs * (policy.backoffFactor ** exponent);
|
|
14
|
+
return Math.min(policy.maxDelayMs, Math.max(0, Math.floor(delay)));
|
|
15
|
+
}
|
|
16
|
+
export async function sleepWithSignal(delayMs, signal) {
|
|
17
|
+
if (delayMs <= 0)
|
|
18
|
+
return;
|
|
19
|
+
if (signal?.aborted) {
|
|
20
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
21
|
+
}
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
cleanup();
|
|
25
|
+
resolve();
|
|
26
|
+
}, delayMs);
|
|
27
|
+
const onAbort = () => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
cleanup();
|
|
30
|
+
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
|
31
|
+
};
|
|
32
|
+
const cleanup = () => {
|
|
33
|
+
signal?.removeEventListener('abort', onAbort);
|
|
34
|
+
};
|
|
35
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HttpTransport } from './http.js';
|
|
2
|
+
import { type ServerSentEventHandlers } from './sse-stream.js';
|
|
3
|
+
export interface ContractRouteDefinition {
|
|
4
|
+
readonly method: string;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ContractRouteLike {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ContractInvokeOptions {
|
|
11
|
+
readonly signal?: AbortSignal;
|
|
12
|
+
readonly headers?: HeadersInit;
|
|
13
|
+
}
|
|
14
|
+
export interface ContractStreamOptions extends ContractInvokeOptions {
|
|
15
|
+
readonly handlers: ServerSentEventHandlers;
|
|
16
|
+
}
|
|
17
|
+
export declare function buildContractInput(primaryKey: string, primaryValue: string, input?: Record<string, unknown>): Record<string, unknown>;
|
|
18
|
+
export declare function requireContractRoute<TRoute extends ContractRouteLike>(routes: readonly TRoute[], routeId: string, kind: string): TRoute;
|
|
19
|
+
export declare function invokeContractRoute<T = unknown>(transport: HttpTransport, route: ContractRouteDefinition, input?: Record<string, unknown>, options?: ContractInvokeOptions): Promise<T>;
|
|
20
|
+
export declare function openContractRouteStream(transport: HttpTransport, route: ContractRouteDefinition, input: Record<string, unknown> | undefined, options: ContractStreamOptions): Promise<() => void>;
|
|
21
|
+
//# sourceMappingURL=contract-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-client.d.ts","sourceRoot":"","sources":["../src/contract-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAA6B,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAChC;AAED,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IAClE,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;CAC5C;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,oBAAoB,CAAC,MAAM,SAAS,iBAAiB,EACnE,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EAC7C,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,uBAAuB,EAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,CAAC,CAAC,CAQZ;AAED,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,IAAI,CAAC,CAYrB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { openServerSentEventStream } from './sse-stream.js';
|
|
2
|
+
export function buildContractInput(primaryKey, primaryValue, input) {
|
|
3
|
+
return {
|
|
4
|
+
[primaryKey]: primaryValue,
|
|
5
|
+
...(input ?? {}),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function requireContractRoute(routes, routeId, kind) {
|
|
9
|
+
const route = routes.find((candidate) => candidate.id === routeId);
|
|
10
|
+
if (!route) {
|
|
11
|
+
throw new Error(`Unknown ${kind} "${routeId}"`);
|
|
12
|
+
}
|
|
13
|
+
return route;
|
|
14
|
+
}
|
|
15
|
+
export function invokeContractRoute(transport, route, input, options = {}) {
|
|
16
|
+
const resolved = transport.resolveContractRequest(route.method, route.path, input);
|
|
17
|
+
return transport.requestJson(resolved.url, {
|
|
18
|
+
method: resolved.method,
|
|
19
|
+
body: resolved.body,
|
|
20
|
+
headers: options.headers,
|
|
21
|
+
signal: options.signal,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export async function openContractRouteStream(transport, route, input, options) {
|
|
25
|
+
const resolved = transport.resolveContractRequest(route.method, route.path, input);
|
|
26
|
+
return await openServerSentEventStream(transport.fetchImpl, resolved.url, options.handlers, {
|
|
27
|
+
authToken: transport.authToken,
|
|
28
|
+
headers: options.headers,
|
|
29
|
+
signal: options.signal,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type AuthTokenResolver, type HeaderResolver } from './auth.js';
|
|
2
|
+
import { type HttpRetryPolicy } from './retry.js';
|
|
3
|
+
import { type TransportPaths } from './paths.js';
|
|
4
|
+
export type { HttpRetryPolicy } from './retry.js';
|
|
5
|
+
export type JsonValue = string | number | boolean | null | {
|
|
6
|
+
readonly [key: string]: JsonValue;
|
|
7
|
+
} | readonly JsonValue[];
|
|
8
|
+
export type JsonObject = {
|
|
9
|
+
readonly [key: string]: JsonValue;
|
|
10
|
+
};
|
|
11
|
+
export interface HttpJsonTransportOptions {
|
|
12
|
+
readonly baseUrl: string;
|
|
13
|
+
readonly authToken?: string | null;
|
|
14
|
+
readonly getAuthToken?: AuthTokenResolver;
|
|
15
|
+
readonly fetch?: typeof fetch;
|
|
16
|
+
readonly fetchImpl?: typeof fetch;
|
|
17
|
+
readonly headers?: HeadersInit;
|
|
18
|
+
readonly getHeaders?: HeaderResolver;
|
|
19
|
+
readonly retry?: HttpRetryPolicy;
|
|
20
|
+
}
|
|
21
|
+
export interface HttpJsonRequestOptions {
|
|
22
|
+
readonly method?: string;
|
|
23
|
+
readonly body?: unknown;
|
|
24
|
+
readonly headers?: HeadersInit;
|
|
25
|
+
readonly signal?: AbortSignal;
|
|
26
|
+
readonly retry?: false | HttpRetryPolicy;
|
|
27
|
+
}
|
|
28
|
+
export interface ResolvedContractRequest {
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly method: string;
|
|
31
|
+
readonly body?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export interface TransportJsonError {
|
|
34
|
+
readonly status: number;
|
|
35
|
+
readonly body: unknown;
|
|
36
|
+
readonly url: string;
|
|
37
|
+
readonly method: string;
|
|
38
|
+
}
|
|
39
|
+
export interface HttpJsonTransport {
|
|
40
|
+
readonly baseUrl: string;
|
|
41
|
+
readonly authToken?: string | null;
|
|
42
|
+
readonly fetchImpl: typeof fetch;
|
|
43
|
+
readonly paths: TransportPaths;
|
|
44
|
+
buildUrl(path: string): string;
|
|
45
|
+
getAuthToken(): Promise<string | null>;
|
|
46
|
+
requestJson<T>(pathOrUrl: string, options?: HttpJsonRequestOptions): Promise<T>;
|
|
47
|
+
resolveContractRequest(method: string, path: string, input?: Record<string, unknown>): ResolvedContractRequest;
|
|
48
|
+
}
|
|
49
|
+
export declare function createJsonRequestInit(token: string | null | undefined, body?: unknown, method?: string, headers?: HeadersInit, signal?: AbortSignal, defaultHeaders?: HeadersInit): RequestInit;
|
|
50
|
+
export declare const createJsonInit: typeof createJsonRequestInit;
|
|
51
|
+
export declare function createFetch(fetchImpl?: typeof fetch, fallbackFetch?: typeof fetch): typeof fetch;
|
|
52
|
+
export declare function readJsonBody(response: Response): Promise<unknown>;
|
|
53
|
+
export declare function requestJson<T>(fetchImpl: typeof fetch, url: string, init?: RequestInit): Promise<T>;
|
|
54
|
+
export declare function createHttpJsonTransport(options: HttpJsonTransportOptions): HttpJsonTransport;
|
|
55
|
+
//# sourceMappingURL=http-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-core.d.ts","sourceRoot":"","sources":["../src/http-core.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkD,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACxH,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjF,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACrC,SAAS,SAAS,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;CAC1C;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChF,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,uBAAuB,CAAC;CAChH;AA8HD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,SAAQ,EACd,OAAO,GAAE,WAAgB,EACzB,MAAM,CAAC,EAAE,WAAW,EACpB,cAAc,GAAE,WAAgB,GAC/B,WAAW,CAab;AAED,eAAO,MAAM,cAAc,8BAAwB,CAAC;AAEpD,wBAAgB,WAAW,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,EAAE,aAAa,CAAC,EAAE,OAAO,KAAK,GAAG,OAAO,KAAK,CAMhG;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvE;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,SAAS,EAAE,OAAO,KAAK,EACvB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC,CAYZ;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAmF5F"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Synced from goodvibes-tui/src/runtime/transports/http-json-transport.ts
|
|
2
|
+
import { sleepWithSignal } from './backoff.js';
|
|
3
|
+
import { mergeHeaders, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
4
|
+
import { getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, resolveHttpRetryPolicy, } from './retry.js';
|
|
5
|
+
import { buildUrl, createTransportPaths } from './paths.js';
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null;
|
|
11
|
+
}
|
|
12
|
+
function applyHeaderSource(target, source) {
|
|
13
|
+
if (!source)
|
|
14
|
+
return;
|
|
15
|
+
if (source instanceof Headers) {
|
|
16
|
+
source.forEach((value, key) => {
|
|
17
|
+
target[key] = value;
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(source)) {
|
|
22
|
+
for (const [key, value] of source) {
|
|
23
|
+
target[key] = value;
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const [key, value] of Object.entries(source)) {
|
|
28
|
+
if (value !== undefined) {
|
|
29
|
+
target[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function mergeHeaderRecord(...sources) {
|
|
34
|
+
const merged = {};
|
|
35
|
+
for (const source of sources) {
|
|
36
|
+
applyHeaderSource(merged, source);
|
|
37
|
+
}
|
|
38
|
+
return merged;
|
|
39
|
+
}
|
|
40
|
+
function readErrorMessage(status, url, body) {
|
|
41
|
+
if (isRecord(body) && typeof body.error === 'string' && body.error.trim()) {
|
|
42
|
+
return body.error.trim();
|
|
43
|
+
}
|
|
44
|
+
if (typeof body === 'string' && body.trim()) {
|
|
45
|
+
return body.trim();
|
|
46
|
+
}
|
|
47
|
+
return `Transport request failed with status ${status} for ${url}`;
|
|
48
|
+
}
|
|
49
|
+
function createTransportError(status, url, method, body) {
|
|
50
|
+
return Object.assign(new Error(readErrorMessage(status, url, body)), {
|
|
51
|
+
transport: {
|
|
52
|
+
status,
|
|
53
|
+
body,
|
|
54
|
+
url,
|
|
55
|
+
method,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function createNetworkTransportError(error, url, method) {
|
|
60
|
+
const message = error instanceof Error && error.message.trim()
|
|
61
|
+
? error.message.trim()
|
|
62
|
+
: `Transport request failed before receiving a response for ${url}`;
|
|
63
|
+
return Object.assign(new Error(message), {
|
|
64
|
+
cause: error,
|
|
65
|
+
transport: {
|
|
66
|
+
status: 0,
|
|
67
|
+
body: { error: message },
|
|
68
|
+
url,
|
|
69
|
+
method,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function toStringValue(value, key) {
|
|
74
|
+
if (value === undefined || value === null) {
|
|
75
|
+
throw new Error(`Missing required path parameter "${key}"`);
|
|
76
|
+
}
|
|
77
|
+
return String(value);
|
|
78
|
+
}
|
|
79
|
+
function addQueryValue(url, key, value) {
|
|
80
|
+
if (value === undefined)
|
|
81
|
+
return;
|
|
82
|
+
if (value === null) {
|
|
83
|
+
url.searchParams.append(key, 'null');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
for (const item of value) {
|
|
88
|
+
addQueryValue(url, key, item);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === 'object') {
|
|
93
|
+
url.searchParams.append(key, JSON.stringify(value));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
url.searchParams.append(key, String(value));
|
|
97
|
+
}
|
|
98
|
+
function splitContractInput(path, input = {}) {
|
|
99
|
+
const remaining = { ...input };
|
|
100
|
+
const interpolatedPath = path.replace(/\{([^}]+)\}/g, (_match, key) => {
|
|
101
|
+
const value = toStringValue(remaining[key], key);
|
|
102
|
+
delete remaining[key];
|
|
103
|
+
return encodeURIComponent(value);
|
|
104
|
+
});
|
|
105
|
+
return { interpolatedPath, remaining };
|
|
106
|
+
}
|
|
107
|
+
export function createJsonRequestInit(token, body, method = 'GET', headers = {}, signal, defaultHeaders = {}) {
|
|
108
|
+
return {
|
|
109
|
+
method,
|
|
110
|
+
credentials: 'include',
|
|
111
|
+
signal,
|
|
112
|
+
headers: mergeHeaderRecord(defaultHeaders, token ? { Authorization: `Bearer ${token}` } : undefined, body !== undefined ? { 'Content-Type': 'application/json' } : undefined, headers),
|
|
113
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export const createJsonInit = createJsonRequestInit;
|
|
117
|
+
export function createFetch(fetchImpl, fallbackFetch) {
|
|
118
|
+
const resolved = fetchImpl ?? fallbackFetch ?? globalThis.fetch;
|
|
119
|
+
if (typeof resolved !== 'function') {
|
|
120
|
+
throw new Error('Fetch implementation is required');
|
|
121
|
+
}
|
|
122
|
+
return resolved.bind(globalThis);
|
|
123
|
+
}
|
|
124
|
+
export async function readJsonBody(response) {
|
|
125
|
+
const text = await response.text();
|
|
126
|
+
if (!text.trim())
|
|
127
|
+
return null;
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(text);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return text;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function requestJson(fetchImpl, url, init = {}) {
|
|
136
|
+
let response;
|
|
137
|
+
try {
|
|
138
|
+
response = await fetchImpl(url, init);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw createNetworkTransportError(error, url, init.method ?? 'GET');
|
|
142
|
+
}
|
|
143
|
+
const body = await readJsonBody(response);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw createTransportError(response.status, url, init.method ?? 'GET', body);
|
|
146
|
+
}
|
|
147
|
+
return body;
|
|
148
|
+
}
|
|
149
|
+
export function createHttpJsonTransport(options) {
|
|
150
|
+
const baseUrl = options.baseUrl.trim();
|
|
151
|
+
const fetchImpl = createFetch(options.fetchImpl, options.fetch);
|
|
152
|
+
const authToken = options.authToken ?? null;
|
|
153
|
+
const defaultHeaders = options.headers;
|
|
154
|
+
const retryPolicy = options.retry;
|
|
155
|
+
const paths = createTransportPaths(baseUrl);
|
|
156
|
+
const requestJsonForTransport = async (pathOrUrl, requestOptions = {}) => {
|
|
157
|
+
const url = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')
|
|
158
|
+
? pathOrUrl
|
|
159
|
+
: buildUrl(baseUrl, pathOrUrl);
|
|
160
|
+
const method = requestOptions.method ?? (requestOptions.body === undefined ? 'GET' : 'POST');
|
|
161
|
+
const resolvedRetry = resolveHttpRetryPolicy(retryPolicy, requestOptions.retry);
|
|
162
|
+
let attempt = 0;
|
|
163
|
+
while (true) {
|
|
164
|
+
attempt += 1;
|
|
165
|
+
const token = await resolveAuthToken(authToken, options.getAuthToken);
|
|
166
|
+
const headers = await resolveHeaders(defaultHeaders, options.getHeaders);
|
|
167
|
+
try {
|
|
168
|
+
return await requestJson(fetchImpl, url, createJsonRequestInit(token, requestOptions.body, method, mergeHeaders(headers, requestOptions.headers), requestOptions.signal));
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const status = typeof error === 'object' && error !== null && 'transport' in error
|
|
172
|
+
? error.transport?.status
|
|
173
|
+
: undefined;
|
|
174
|
+
const shouldRetry = attempt < resolvedRetry.maxAttempts && ((typeof status === 'number' && status > 0 && isRetryableHttpStatus(method, status, resolvedRetry))
|
|
175
|
+
|| (typeof status === 'number' && status === 0 && isRetryableNetworkError(method, resolvedRetry)));
|
|
176
|
+
if (!shouldRetry) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
await sleepWithSignal(getHttpRetryDelay(attempt + 1, resolvedRetry), requestOptions.signal);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const resolveContractRequest = (method, path, input = {}) => {
|
|
184
|
+
const { interpolatedPath, remaining } = splitContractInput(path, input);
|
|
185
|
+
const upperMethod = method.toUpperCase();
|
|
186
|
+
const url = new URL(buildUrl(baseUrl, interpolatedPath));
|
|
187
|
+
if (upperMethod === 'GET' || upperMethod === 'HEAD') {
|
|
188
|
+
for (const [key, value] of Object.entries(remaining)) {
|
|
189
|
+
addQueryValue(url, key, value);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
url: url.toString(),
|
|
193
|
+
method: upperMethod,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const body = isPlainObject(remaining) && Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
197
|
+
return {
|
|
198
|
+
url: url.toString(),
|
|
199
|
+
method: upperMethod,
|
|
200
|
+
...(body ? { body } : {}),
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
baseUrl: paths.baseUrl,
|
|
205
|
+
authToken,
|
|
206
|
+
fetchImpl,
|
|
207
|
+
paths,
|
|
208
|
+
buildUrl(path) {
|
|
209
|
+
return buildUrl(baseUrl, path);
|
|
210
|
+
},
|
|
211
|
+
async getAuthToken() {
|
|
212
|
+
return await resolveAuthToken(authToken, options.getAuthToken);
|
|
213
|
+
},
|
|
214
|
+
requestJson: requestJsonForTransport,
|
|
215
|
+
resolveContractRequest,
|
|
216
|
+
};
|
|
217
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthTokenResolver, type HeaderResolver, type MaybePromise, mergeHeaders, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
2
|
+
import { type BackoffPolicy, type ResolvedBackoffPolicy, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
|
|
3
|
+
import { type HttpRetryPolicy, type ResolvedHttpRetryPolicy, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
4
|
+
import { type ResolvedStreamReconnectPolicy, type StreamReconnectPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
|
|
5
|
+
import { createFetch, createJsonInit, createJsonRequestInit, readJsonBody, type HttpJsonRequestOptions, type HttpJsonTransport, type HttpJsonTransportOptions, type JsonObject, type JsonValue, type ResolvedContractRequest } from './http-core.js';
|
|
6
|
+
export type { AuthTokenResolver, BackoffPolicy, HeaderResolver, HttpJsonRequestOptions, HttpRetryPolicy, JsonObject, JsonValue, MaybePromise, ResolvedBackoffPolicy, ResolvedContractRequest, ResolvedHttpRetryPolicy, ResolvedStreamReconnectPolicy, StreamReconnectPolicy, };
|
|
7
|
+
export type HttpTransportOptions = HttpJsonTransportOptions;
|
|
8
|
+
export type HttpTransport = HttpJsonTransport;
|
|
9
|
+
export { createFetch, createJsonInit, createJsonRequestInit, readJsonBody, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
|
|
10
|
+
export declare function normalizeTransportError(error: unknown): Error;
|
|
11
|
+
export declare function createHttpTransport(options: HttpTransportOptions): HttpTransport;
|
|
12
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EAEX,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC7B,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,qBAAqB,GACtB,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,GAC/B,CAAC;AAsBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAkB7D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAmBhF"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ConfigurationError, ContractError, createHttpStatusError } from '@pellux/goodvibes-errors';
|
|
2
|
+
import { mergeHeaders, resolveAuthToken, resolveHeaders, } from './auth.js';
|
|
3
|
+
import { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, } from './backoff.js';
|
|
4
|
+
import { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, } from './retry.js';
|
|
5
|
+
import { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, } from './reconnect.js';
|
|
6
|
+
import { createFetch, createHttpJsonTransport, createJsonInit, createJsonRequestInit, readJsonBody, } from './http-core.js';
|
|
7
|
+
export { createFetch, createJsonInit, createJsonRequestInit, readJsonBody, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
|
|
8
|
+
function isTransportError(error) {
|
|
9
|
+
return Boolean(error
|
|
10
|
+
&& typeof error === 'object'
|
|
11
|
+
&& 'transport' in error
|
|
12
|
+
&& error.transport
|
|
13
|
+
&& typeof error.transport.status === 'number'
|
|
14
|
+
&& typeof error.transport.url === 'string');
|
|
15
|
+
}
|
|
16
|
+
export function normalizeTransportError(error) {
|
|
17
|
+
if (isTransportError(error)) {
|
|
18
|
+
return createHttpStatusError(error.transport.status, error.transport.url, typeof error.transport.method === 'string' ? error.transport.method : 'GET', error.transport.body);
|
|
19
|
+
}
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
if (error.message === 'Fetch implementation is required' || error.message === 'Transport baseUrl is required') {
|
|
22
|
+
return new ConfigurationError(error.message);
|
|
23
|
+
}
|
|
24
|
+
if (error.message.startsWith('Missing required path parameter')) {
|
|
25
|
+
return new ContractError(error.message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
29
|
+
}
|
|
30
|
+
export function createHttpTransport(options) {
|
|
31
|
+
const transport = createHttpJsonTransport(options);
|
|
32
|
+
return {
|
|
33
|
+
...transport,
|
|
34
|
+
async requestJson(pathOrUrl, requestOptions = {}) {
|
|
35
|
+
try {
|
|
36
|
+
return await transport.requestJson(pathOrUrl, requestOptions);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw normalizeTransportError(error);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
resolveContractRequest(method, path, input = {}) {
|
|
43
|
+
try {
|
|
44
|
+
return transport.resolveContractRequest(method, path, input);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw normalizeTransportError(error);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type { ContractInvokeOptions, ContractRouteDefinition, ContractRouteLike, ContractStreamOptions, } from './contract-client.js';
|
|
2
|
+
export { buildContractInput, invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
|
|
3
|
+
export type { HttpJsonRequestOptions, HttpTransport, HttpTransportOptions, JsonObject, JsonValue, ResolvedContractRequest, } from './http.js';
|
|
4
|
+
export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, } from './http.js';
|
|
5
|
+
export type { ServerSentEventHandlers, ServerSentEventOptions } from './sse.js';
|
|
6
|
+
export { openServerSentEventStream } from './sse.js';
|
|
7
|
+
export type { ServerSentEventHandlers as RawServerSentEventHandlers, ServerSentEventOptions as RawServerSentEventOptions } from './sse-stream.js';
|
|
8
|
+
export { openServerSentEventStream as openRawServerSentEventStream } from './sse-stream.js';
|
|
9
|
+
export type { AuthTokenResolver, HeaderResolver, MaybePromise } from './auth.js';
|
|
10
|
+
export { mergeHeaders, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
11
|
+
export type { BackoffPolicy, ResolvedBackoffPolicy } from './backoff.js';
|
|
12
|
+
export { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
|
|
13
|
+
export type { HttpRetryPolicy, ResolvedHttpRetryPolicy } from './retry.js';
|
|
14
|
+
export { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
15
|
+
export type { StreamReconnectPolicy, ResolvedStreamReconnectPolicy } from './reconnect.js';
|
|
16
|
+
export { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
|
|
17
|
+
export type { TransportPaths } from './paths.js';
|
|
18
|
+
export { buildUrl, createTransportPaths, normalizeBaseUrl } from './paths.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,SAAS,EACT,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EAAE,uBAAuB,IAAI,0BAA0B,EAAE,sBAAsB,IAAI,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAClJ,OAAO,EAAE,yBAAyB,IAAI,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3E,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC5K,YAAY,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,+BAA+B,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC;AAC1H,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { buildContractInput, invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
|
|
2
|
+
export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, } from './http.js';
|
|
3
|
+
export { openServerSentEventStream } from './sse.js';
|
|
4
|
+
export { openServerSentEventStream as openRawServerSentEventStream } from './sse-stream.js';
|
|
5
|
+
export { mergeHeaders, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
6
|
+
export { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
|
|
7
|
+
export { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
8
|
+
export { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
|
|
9
|
+
export { buildUrl, createTransportPaths, normalizeBaseUrl } from './paths.js';
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface TransportPaths {
|
|
2
|
+
readonly baseUrl: string;
|
|
3
|
+
readonly statusUrl: string;
|
|
4
|
+
readonly controlPlaneUrl: string;
|
|
5
|
+
readonly controlPlaneAuthUrl: string;
|
|
6
|
+
readonly controlPlaneEventsUrl: string;
|
|
7
|
+
readonly controlPlaneMethodsUrl: string;
|
|
8
|
+
readonly sessionsUrl: string;
|
|
9
|
+
readonly tasksUrl: string;
|
|
10
|
+
readonly approvalsUrl: string;
|
|
11
|
+
readonly providersUrl: string;
|
|
12
|
+
readonly accountsUrl: string;
|
|
13
|
+
readonly localAuthUrl: string;
|
|
14
|
+
readonly telemetryUrl: string;
|
|
15
|
+
readonly telemetryEventsUrl: string;
|
|
16
|
+
readonly telemetryErrorsUrl: string;
|
|
17
|
+
readonly telemetryTracesUrl: string;
|
|
18
|
+
readonly telemetryMetricsUrl: string;
|
|
19
|
+
readonly telemetryStreamUrl: string;
|
|
20
|
+
readonly telemetryOtlpTracesUrl: string;
|
|
21
|
+
readonly telemetryOtlpLogsUrl: string;
|
|
22
|
+
readonly telemetryOtlpMetricsUrl: string;
|
|
23
|
+
readonly remoteUrl: string;
|
|
24
|
+
readonly remoteContractUrl: string;
|
|
25
|
+
readonly peerRequestsUrl: string;
|
|
26
|
+
readonly peerListUrl: string;
|
|
27
|
+
readonly remoteWorkUrl: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function normalizeBaseUrl(baseUrl: string): string;
|
|
30
|
+
export declare function buildUrl(baseUrl: string, path: string): string;
|
|
31
|
+
export declare function createTransportPaths(baseUrl: string): TransportPaths;
|
|
32
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8BpE"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function normalizeBaseUrl(baseUrl) {
|
|
2
|
+
const normalized = baseUrl.trim();
|
|
3
|
+
if (!normalized) {
|
|
4
|
+
throw new Error('Transport baseUrl is required');
|
|
5
|
+
}
|
|
6
|
+
return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;
|
|
7
|
+
}
|
|
8
|
+
export function buildUrl(baseUrl, path) {
|
|
9
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
10
|
+
return new URL(path, `${normalized}/`).toString();
|
|
11
|
+
}
|
|
12
|
+
export function createTransportPaths(baseUrl) {
|
|
13
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
14
|
+
return {
|
|
15
|
+
baseUrl: normalized,
|
|
16
|
+
statusUrl: buildUrl(normalized, '/status'),
|
|
17
|
+
controlPlaneUrl: buildUrl(normalized, '/api/control-plane'),
|
|
18
|
+
controlPlaneAuthUrl: buildUrl(normalized, '/api/control-plane/auth'),
|
|
19
|
+
controlPlaneEventsUrl: buildUrl(normalized, '/api/control-plane/events'),
|
|
20
|
+
controlPlaneMethodsUrl: buildUrl(normalized, '/api/control-plane/methods'),
|
|
21
|
+
sessionsUrl: buildUrl(normalized, '/api/sessions'),
|
|
22
|
+
tasksUrl: buildUrl(normalized, '/api/tasks'),
|
|
23
|
+
approvalsUrl: buildUrl(normalized, '/api/approvals'),
|
|
24
|
+
providersUrl: buildUrl(normalized, '/api/providers'),
|
|
25
|
+
accountsUrl: buildUrl(normalized, '/api/accounts'),
|
|
26
|
+
localAuthUrl: buildUrl(normalized, '/api/local-auth'),
|
|
27
|
+
telemetryUrl: buildUrl(normalized, '/api/v1/telemetry'),
|
|
28
|
+
telemetryEventsUrl: buildUrl(normalized, '/api/v1/telemetry/events'),
|
|
29
|
+
telemetryErrorsUrl: buildUrl(normalized, '/api/v1/telemetry/errors'),
|
|
30
|
+
telemetryTracesUrl: buildUrl(normalized, '/api/v1/telemetry/traces'),
|
|
31
|
+
telemetryMetricsUrl: buildUrl(normalized, '/api/v1/telemetry/metrics'),
|
|
32
|
+
telemetryStreamUrl: buildUrl(normalized, '/api/v1/telemetry/stream'),
|
|
33
|
+
telemetryOtlpTracesUrl: buildUrl(normalized, '/api/v1/telemetry/otlp/v1/traces'),
|
|
34
|
+
telemetryOtlpLogsUrl: buildUrl(normalized, '/api/v1/telemetry/otlp/v1/logs'),
|
|
35
|
+
telemetryOtlpMetricsUrl: buildUrl(normalized, '/api/v1/telemetry/otlp/v1/metrics'),
|
|
36
|
+
remoteUrl: buildUrl(normalized, '/api/remote'),
|
|
37
|
+
remoteContractUrl: buildUrl(normalized, '/api/remote/node-host/contract'),
|
|
38
|
+
peerRequestsUrl: buildUrl(normalized, '/api/remote/pair/requests'),
|
|
39
|
+
peerListUrl: buildUrl(normalized, '/api/remote/peers'),
|
|
40
|
+
remoteWorkUrl: buildUrl(normalized, '/api/remote/work'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type BackoffPolicy, type ResolvedBackoffPolicy } from './backoff.js';
|
|
2
|
+
export interface StreamReconnectPolicy extends BackoffPolicy {
|
|
3
|
+
readonly enabled?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ResolvedStreamReconnectPolicy extends ResolvedBackoffPolicy {
|
|
6
|
+
readonly enabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const DEFAULT_STREAM_RECONNECT_POLICY: ResolvedStreamReconnectPolicy;
|
|
9
|
+
export declare function normalizeStreamReconnectPolicy(policy?: StreamReconnectPolicy): ResolvedStreamReconnectPolicy;
|
|
10
|
+
export declare function getStreamReconnectDelay(attempt: number, policy: ResolvedStreamReconnectPolicy): number;
|
|
11
|
+
//# sourceMappingURL=reconnect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconnect.d.ts","sourceRoot":"","sources":["../src/reconnect.ts"],"names":[],"mappings":"AACA,OAAO,EAA+C,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3H,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,6BAA8B,SAAQ,qBAAqB;IAC1E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,+BAA+B,EAAE,6BAM7C,CAAC;AAEF,wBAAgB,8BAA8B,CAC5C,MAAM,CAAC,EAAE,qBAAqB,GAC7B,6BAA6B,CAM/B;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,6BAA6B,GACpC,MAAM,CAER"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Synced from goodvibes-tui/src/runtime/transports/stream-reconnect.ts
|
|
2
|
+
import { computeBackoffDelay, normalizeBackoffPolicy } from './backoff.js';
|
|
3
|
+
export const DEFAULT_STREAM_RECONNECT_POLICY = {
|
|
4
|
+
enabled: false,
|
|
5
|
+
maxAttempts: Number.POSITIVE_INFINITY,
|
|
6
|
+
baseDelayMs: 500,
|
|
7
|
+
maxDelayMs: 5_000,
|
|
8
|
+
backoffFactor: 2,
|
|
9
|
+
};
|
|
10
|
+
export function normalizeStreamReconnectPolicy(policy) {
|
|
11
|
+
const normalized = normalizeBackoffPolicy(policy, DEFAULT_STREAM_RECONNECT_POLICY);
|
|
12
|
+
return {
|
|
13
|
+
...normalized,
|
|
14
|
+
enabled: policy?.enabled ?? DEFAULT_STREAM_RECONNECT_POLICY.enabled,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function getStreamReconnectDelay(attempt, policy) {
|
|
18
|
+
return computeBackoffDelay(attempt, policy);
|
|
19
|
+
}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type BackoffPolicy, type ResolvedBackoffPolicy } from './backoff.js';
|
|
2
|
+
export interface HttpRetryPolicy extends BackoffPolicy {
|
|
3
|
+
readonly retryOnStatuses?: readonly number[];
|
|
4
|
+
readonly retryOnMethods?: readonly string[];
|
|
5
|
+
readonly retryOnNetworkError?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvedHttpRetryPolicy extends ResolvedBackoffPolicy {
|
|
8
|
+
readonly retryOnStatuses: readonly number[];
|
|
9
|
+
readonly retryOnMethods: readonly string[];
|
|
10
|
+
readonly retryOnNetworkError: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const DEFAULT_HTTP_RETRY_POLICY: ResolvedHttpRetryPolicy;
|
|
13
|
+
export declare function normalizeHttpRetryPolicy(policy?: HttpRetryPolicy): ResolvedHttpRetryPolicy;
|
|
14
|
+
export declare function resolveHttpRetryPolicy(defaultPolicy?: HttpRetryPolicy, override?: false | HttpRetryPolicy): ResolvedHttpRetryPolicy;
|
|
15
|
+
export declare function getHttpRetryDelay(attempt: number, policy: ResolvedHttpRetryPolicy): number;
|
|
16
|
+
export declare function isRetryableHttpStatus(method: string, status: number, policy: ResolvedHttpRetryPolicy): boolean;
|
|
17
|
+
export declare function isRetryableNetworkError(method: string, policy: ResolvedHttpRetryPolicy): boolean;
|
|
18
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AACA,OAAO,EAA+C,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3H,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC;AAED,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACvC;AAED,eAAO,MAAM,yBAAyB,EAAE,uBAQvC,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,MAAM,CAAC,EAAE,eAAe,GACvB,uBAAuB,CAQzB;AAED,wBAAgB,sBAAsB,CACpC,aAAa,CAAC,EAAE,eAAe,EAC/B,QAAQ,CAAC,EAAE,KAAK,GAAG,eAAe,GACjC,uBAAuB,CAazB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,uBAAuB,GAC9B,MAAM,CAER;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAGT;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAET"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Synced from goodvibes-tui/src/runtime/transports/http-retry.ts
|
|
2
|
+
import { computeBackoffDelay, normalizeBackoffPolicy } from './backoff.js';
|
|
3
|
+
export const DEFAULT_HTTP_RETRY_POLICY = {
|
|
4
|
+
maxAttempts: 1,
|
|
5
|
+
baseDelayMs: 250,
|
|
6
|
+
maxDelayMs: 2_000,
|
|
7
|
+
backoffFactor: 2,
|
|
8
|
+
retryOnStatuses: [408, 429, 500, 502, 503, 504],
|
|
9
|
+
retryOnMethods: ['GET', 'HEAD', 'OPTIONS'],
|
|
10
|
+
retryOnNetworkError: true,
|
|
11
|
+
};
|
|
12
|
+
export function normalizeHttpRetryPolicy(policy) {
|
|
13
|
+
const normalized = normalizeBackoffPolicy(policy, DEFAULT_HTTP_RETRY_POLICY);
|
|
14
|
+
return {
|
|
15
|
+
...normalized,
|
|
16
|
+
retryOnStatuses: [...(policy?.retryOnStatuses ?? DEFAULT_HTTP_RETRY_POLICY.retryOnStatuses)],
|
|
17
|
+
retryOnMethods: [...(policy?.retryOnMethods ?? DEFAULT_HTTP_RETRY_POLICY.retryOnMethods)].map((method) => method.toUpperCase()),
|
|
18
|
+
retryOnNetworkError: policy?.retryOnNetworkError ?? DEFAULT_HTTP_RETRY_POLICY.retryOnNetworkError,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function resolveHttpRetryPolicy(defaultPolicy, override) {
|
|
22
|
+
if (override === false) {
|
|
23
|
+
return normalizeHttpRetryPolicy({ maxAttempts: 1 });
|
|
24
|
+
}
|
|
25
|
+
const base = normalizeHttpRetryPolicy(defaultPolicy);
|
|
26
|
+
if (!override)
|
|
27
|
+
return base;
|
|
28
|
+
return {
|
|
29
|
+
...base,
|
|
30
|
+
...normalizeBackoffPolicy(override, base),
|
|
31
|
+
retryOnStatuses: override.retryOnStatuses ? [...override.retryOnStatuses] : base.retryOnStatuses,
|
|
32
|
+
retryOnMethods: override.retryOnMethods ? override.retryOnMethods.map((method) => method.toUpperCase()) : base.retryOnMethods,
|
|
33
|
+
retryOnNetworkError: override.retryOnNetworkError ?? base.retryOnNetworkError,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function getHttpRetryDelay(attempt, policy) {
|
|
37
|
+
return computeBackoffDelay(attempt, policy);
|
|
38
|
+
}
|
|
39
|
+
export function isRetryableHttpStatus(method, status, policy) {
|
|
40
|
+
return policy.retryOnMethods.includes(method.toUpperCase())
|
|
41
|
+
&& policy.retryOnStatuses.includes(status);
|
|
42
|
+
}
|
|
43
|
+
export function isRetryableNetworkError(method, policy) {
|
|
44
|
+
return policy.retryOnMethods.includes(method.toUpperCase()) && policy.retryOnNetworkError;
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AuthTokenResolver } from './auth.js';
|
|
2
|
+
import { type StreamReconnectPolicy } from './reconnect.js';
|
|
3
|
+
export interface ServerSentEventHandlers {
|
|
4
|
+
readonly onEvent?: (eventName: string, payload: unknown) => void;
|
|
5
|
+
readonly onReady?: (payload: unknown) => void;
|
|
6
|
+
readonly onError?: (error: unknown) => void;
|
|
7
|
+
readonly onReconnect?: (input: {
|
|
8
|
+
readonly attempt: number;
|
|
9
|
+
readonly delayMs: number;
|
|
10
|
+
}) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface ServerSentEventOptions {
|
|
13
|
+
readonly signal?: AbortSignal;
|
|
14
|
+
readonly headers?: HeadersInit;
|
|
15
|
+
readonly authToken?: string | null;
|
|
16
|
+
readonly getAuthToken?: AuthTokenResolver;
|
|
17
|
+
readonly lastEventId?: string | null;
|
|
18
|
+
readonly reconnect?: StreamReconnectPolicy;
|
|
19
|
+
}
|
|
20
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
21
|
+
export declare function openServerSentEventStream(fetchImpl: typeof fetch, url: string, handlers: ServerSentEventHandlers, options?: ServerSentEventOptions): Promise<() => void>;
|
|
22
|
+
//# sourceMappingURL=sse-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-stream.d.ts","sourceRoot":"","sources":["../src/sse-stream.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkC,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAChG;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,CAAC;CAC5C;AAWD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASpD;AA8BD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,OAAO,KAAK,EACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,IAAI,CAAC,CAkLrB"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Synced from goodvibes-tui/src/runtime/transports/sse-stream.ts
|
|
2
|
+
import { sleepWithSignal } from './backoff.js';
|
|
3
|
+
import { mergeHeaders, resolveAuthToken } from './auth.js';
|
|
4
|
+
import { getStreamReconnectDelay, normalizeStreamReconnectPolicy, } from './reconnect.js';
|
|
5
|
+
function readEventPayload(data) {
|
|
6
|
+
if (!data.trim())
|
|
7
|
+
return null;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(data);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function isAbortError(error) {
|
|
16
|
+
return (error instanceof DOMException && error.name === 'AbortError') || (typeof error === 'object'
|
|
17
|
+
&& error !== null
|
|
18
|
+
&& 'name' in error
|
|
19
|
+
&& error.name === 'AbortError');
|
|
20
|
+
}
|
|
21
|
+
function createStreamError(status, url, body) {
|
|
22
|
+
const message = body.trim()
|
|
23
|
+
? `Unable to open SSE stream: ${status} ${body}`.trim()
|
|
24
|
+
: `Unable to open SSE stream: ${status}`;
|
|
25
|
+
return Object.assign(new Error(message), {
|
|
26
|
+
transport: {
|
|
27
|
+
status,
|
|
28
|
+
body,
|
|
29
|
+
url,
|
|
30
|
+
method: 'GET',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function reportStreamError(error, handlers) {
|
|
35
|
+
if (handlers.onError) {
|
|
36
|
+
handlers.onError(error);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
queueMicrotask(() => {
|
|
40
|
+
throw error;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export async function openServerSentEventStream(fetchImpl, url, handlers, options = {}) {
|
|
44
|
+
const outerController = new AbortController();
|
|
45
|
+
const reconnectPolicy = normalizeStreamReconnectPolicy(options.reconnect);
|
|
46
|
+
let lastEventId = options.lastEventId ?? null;
|
|
47
|
+
let activeController = null;
|
|
48
|
+
let stopped = false;
|
|
49
|
+
let reconnectAttempts = 0;
|
|
50
|
+
let readySettled = false;
|
|
51
|
+
if (options.signal) {
|
|
52
|
+
options.signal.addEventListener('abort', () => outerController.abort(), { once: true });
|
|
53
|
+
}
|
|
54
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
55
|
+
const settleReady = (error) => {
|
|
56
|
+
if (readySettled)
|
|
57
|
+
return;
|
|
58
|
+
readySettled = true;
|
|
59
|
+
if (error) {
|
|
60
|
+
reject(error);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
resolve();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const runConnection = async () => {
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
activeController = controller;
|
|
69
|
+
outerController.signal.addEventListener('abort', () => controller.abort(), { once: true });
|
|
70
|
+
const token = await resolveAuthToken(options.authToken ?? null, options.getAuthToken);
|
|
71
|
+
const headers = mergeHeaders({ Accept: 'text/event-stream' }, options.headers, token ? { Authorization: `Bearer ${token}` } : undefined, lastEventId ? { 'Last-Event-ID': lastEventId } : undefined);
|
|
72
|
+
let response;
|
|
73
|
+
try {
|
|
74
|
+
response = await fetchImpl(url, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
credentials: 'include',
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
headers,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const wrapped = createStreamError(0, url, error instanceof Error ? error.message : String(error));
|
|
83
|
+
if (!readySettled) {
|
|
84
|
+
settleReady(wrapped);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
throw wrapped;
|
|
88
|
+
}
|
|
89
|
+
if (!response.ok || !response.body) {
|
|
90
|
+
const body = await response.text().catch(() => '');
|
|
91
|
+
const wrapped = createStreamError(response.status, url, body);
|
|
92
|
+
if (!readySettled) {
|
|
93
|
+
settleReady(wrapped);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
throw wrapped;
|
|
97
|
+
}
|
|
98
|
+
settleReady();
|
|
99
|
+
const reader = response.body.getReader();
|
|
100
|
+
const decoder = new TextDecoder();
|
|
101
|
+
let buffer = '';
|
|
102
|
+
let eventName = '';
|
|
103
|
+
let data = '';
|
|
104
|
+
const flush = () => {
|
|
105
|
+
if (!eventName && !data.trim()) {
|
|
106
|
+
eventName = '';
|
|
107
|
+
data = '';
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const payload = readEventPayload(data);
|
|
111
|
+
if (eventName === 'ready') {
|
|
112
|
+
handlers.onReady?.(payload);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
handlers.onEvent?.(eventName || 'message', payload);
|
|
116
|
+
}
|
|
117
|
+
eventName = '';
|
|
118
|
+
data = '';
|
|
119
|
+
};
|
|
120
|
+
const consumeLine = (line) => {
|
|
121
|
+
if (!line) {
|
|
122
|
+
flush();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (line.startsWith(':'))
|
|
126
|
+
return;
|
|
127
|
+
if (line.startsWith('id:')) {
|
|
128
|
+
const candidate = line.slice(3).trim();
|
|
129
|
+
if (candidate) {
|
|
130
|
+
lastEventId = candidate;
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (line.startsWith('event:')) {
|
|
135
|
+
eventName = line.slice(6).trim();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (line.startsWith('data:')) {
|
|
139
|
+
data += `${data ? '\n' : ''}${line.slice(5).trim()}`;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
while (true) {
|
|
144
|
+
const { done, value } = await reader.read();
|
|
145
|
+
if (done)
|
|
146
|
+
break;
|
|
147
|
+
buffer += decoder.decode(value, { stream: true });
|
|
148
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
149
|
+
while (newlineIndex >= 0) {
|
|
150
|
+
const line = buffer.slice(0, newlineIndex).replace(/\r$/, '');
|
|
151
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
152
|
+
consumeLine(line);
|
|
153
|
+
newlineIndex = buffer.indexOf('\n');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (buffer.trim()) {
|
|
157
|
+
consumeLine(buffer.replace(/\r$/, ''));
|
|
158
|
+
flush();
|
|
159
|
+
}
|
|
160
|
+
if (reconnectPolicy.enabled && !controller.signal.aborted && !outerController.signal.aborted && !stopped) {
|
|
161
|
+
throw createStreamError(response.status, url, 'Stream closed unexpectedly');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
controller.abort();
|
|
166
|
+
if (activeController === controller) {
|
|
167
|
+
activeController = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const loop = async () => {
|
|
172
|
+
while (!outerController.signal.aborted && !stopped) {
|
|
173
|
+
try {
|
|
174
|
+
await runConnection();
|
|
175
|
+
reconnectAttempts = 0;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (isAbortError(error) || outerController.signal.aborted || stopped) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const nextAttempt = reconnectAttempts + 1;
|
|
183
|
+
const shouldReconnect = reconnectPolicy.enabled && nextAttempt < reconnectPolicy.maxAttempts;
|
|
184
|
+
if (!shouldReconnect) {
|
|
185
|
+
reportStreamError(error, handlers);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
reconnectAttempts = nextAttempt;
|
|
189
|
+
const delayMs = getStreamReconnectDelay(nextAttempt + 1, reconnectPolicy);
|
|
190
|
+
handlers.onReconnect?.({ attempt: nextAttempt, delayMs });
|
|
191
|
+
handlers.onError?.(error);
|
|
192
|
+
try {
|
|
193
|
+
await sleepWithSignal(delayMs, outerController.signal);
|
|
194
|
+
}
|
|
195
|
+
catch (sleepError) {
|
|
196
|
+
if (!isAbortError(sleepError)) {
|
|
197
|
+
reportStreamError(sleepError, handlers);
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
void loop();
|
|
205
|
+
});
|
|
206
|
+
await readyPromise;
|
|
207
|
+
return () => {
|
|
208
|
+
stopped = true;
|
|
209
|
+
activeController?.abort();
|
|
210
|
+
outerController.abort();
|
|
211
|
+
};
|
|
212
|
+
}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ServerSentEventHandlers, type ServerSentEventOptions as CoreServerSentEventOptions } from './sse-stream.js';
|
|
2
|
+
import { type HttpTransport } from './http.js';
|
|
3
|
+
export type { ServerSentEventHandlers };
|
|
4
|
+
export interface ServerSentEventOptions extends Omit<CoreServerSentEventOptions, 'authToken'> {
|
|
5
|
+
}
|
|
6
|
+
export declare function openServerSentEventStream(transport: HttpTransport, pathOrUrl: string, handlers: ServerSentEventHandlers, options?: ServerSentEventOptions): Promise<() => void>;
|
|
7
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8D,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,IAAI,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AACtL,OAAO,EAAE,KAAK,aAAa,EAA2B,MAAM,WAAW,CAAC;AAExE,YAAY,EAAE,uBAAuB,EAAE,CAAC;AACxC,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,0BAA0B,EAAE,WAAW,CAAC;CAAG;AAEhG,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,IAAI,CAAC,CAarB"}
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { openServerSentEventStream as openServerSentEventStreamCore } from './sse-stream.js';
|
|
2
|
+
import { normalizeTransportError } from './http.js';
|
|
3
|
+
export async function openServerSentEventStream(transport, pathOrUrl, handlers, options = {}) {
|
|
4
|
+
const url = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')
|
|
5
|
+
? pathOrUrl
|
|
6
|
+
: transport.buildUrl(pathOrUrl);
|
|
7
|
+
try {
|
|
8
|
+
return await openServerSentEventStreamCore(transport.fetchImpl, url, handlers, {
|
|
9
|
+
...options,
|
|
10
|
+
authToken: transport.authToken,
|
|
11
|
+
getAuthToken: transport.getAuthToken.bind(transport),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
throw normalizeTransportError(error);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pellux/goodvibes-transport-http",
|
|
3
|
+
"version": "0.18.3",
|
|
4
|
+
"description": "HTTP, JSON, path, and SSE transport primitives for GoodVibes client integrations.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/mgd34msu/goodvibes-sdk/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/mgd34msu/goodvibes-sdk",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"goodvibes",
|
|
30
|
+
"sdk",
|
|
31
|
+
"http",
|
|
32
|
+
"sse",
|
|
33
|
+
"transport"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@pellux/goodvibes-errors": "0.18.3"
|
|
40
|
+
}
|
|
41
|
+
}
|