@procedures/http 0.0.0-stage → 0.3.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/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +118 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/address.d.ts +31 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +214 -0
- package/dist/address.js.map +1 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/exchange.d.ts +102 -0
- package/dist/exchange.d.ts.map +1 -0
- package/dist/exchange.js +322 -0
- package/dist/exchange.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +41 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +205 -0
- package/dist/module.js.map +1 -0
- package/dist/parsers.d.ts +37 -0
- package/dist/parsers.d.ts.map +1 -0
- package/dist/parsers.js +175 -0
- package/dist/parsers.js.map +1 -0
- package/dist/policy.d.ts +94 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +140 -0
- package/dist/policy.js.map +1 -0
- package/dist/procedures/http/index.d.ts +5 -0
- package/dist/procedures/http/index.d.ts.map +1 -0
- package/dist/procedures/http/index.js +5 -0
- package/dist/procedures/http/index.js.map +1 -0
- package/dist/procedures/http/json.d.ts +14 -0
- package/dist/procedures/http/json.d.ts.map +1 -0
- package/dist/procedures/http/json.js +56 -0
- package/dist/procedures/http/json.js.map +1 -0
- package/dist/procedures/http/ndjson.d.ts +18 -0
- package/dist/procedures/http/ndjson.d.ts.map +1 -0
- package/dist/procedures/http/ndjson.js +65 -0
- package/dist/procedures/http/ndjson.js.map +1 -0
- package/dist/procedures/http/request.d.ts +14 -0
- package/dist/procedures/http/request.d.ts.map +1 -0
- package/dist/procedures/http/request.js +79 -0
- package/dist/procedures/http/request.js.map +1 -0
- package/dist/procedures/http/sse.d.ts +19 -0
- package/dist/procedures/http/sse.d.ts.map +1 -0
- package/dist/procedures/http/sse.js +66 -0
- package/dist/procedures/http/sse.js.map +1 -0
- package/dist/register.d.ts +6 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +8 -0
- package/dist/register.js.map +1 -0
- package/dist/scope.d.ts +53 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +107 -0
- package/dist/scope.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -3
- package/src/address.ts +206 -0
- package/src/errors.ts +44 -0
- package/src/exchange.ts +381 -0
- package/src/index.ts +11 -0
- package/src/module.ts +257 -0
- package/src/parsers.ts +184 -0
- package/src/policy.ts +192 -0
- package/src/procedures/http/index.ts +4 -0
- package/src/procedures/http/json.ts +52 -0
- package/src/procedures/http/ndjson.ts +64 -0
- package/src/procedures/http/request.ts +72 -0
- package/src/procedures/http/sse.ts +65 -0
- package/src/register.ts +10 -0
- package/src/scope.ts +129 -0
- package/src/types.ts +127 -0
package/src/scope.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The time limits and the stop of one call (roadmap R38).
|
|
3
|
+
*
|
|
4
|
+
* A call stops at the first of these events:
|
|
5
|
+
* - the signal of the call aborts (`ctx.signal`): the caller went away, or the server stops;
|
|
6
|
+
* - the deadline of the call passes (`ctx.capability.deadline`);
|
|
7
|
+
* - the time of the input passes (`timeoutMs`);
|
|
8
|
+
* - a stream gets no data for the idle time of the input (`idleTimeoutMs`).
|
|
9
|
+
*
|
|
10
|
+
* The earliest event wins. The stop aborts the request, and the call fails with `TIMEOUT` or
|
|
11
|
+
* `ABORTED`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { InvocationError, type ProcedurePath } from "@procedures/client";
|
|
15
|
+
|
|
16
|
+
/** The cause of a stop. */
|
|
17
|
+
export type StopReason = "timeout" | "idle" | "deadline" | "aborted";
|
|
18
|
+
|
|
19
|
+
/** The longest delay of `setTimeout`. A later time gets no timer: the call cannot last so long. */
|
|
20
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
21
|
+
|
|
22
|
+
/** The parts of the context of a handler that a scope reads. */
|
|
23
|
+
export interface ScopeContext {
|
|
24
|
+
readonly signal?: AbortSignal | undefined;
|
|
25
|
+
readonly capability?: { readonly deadline?: number | undefined } | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The time limits of a scope, in milliseconds. */
|
|
29
|
+
export interface ScopeLimits {
|
|
30
|
+
/** The time of the whole call. */
|
|
31
|
+
readonly timeoutMs?: number | undefined;
|
|
32
|
+
/** The longest time without data. */
|
|
33
|
+
readonly idleTimeoutMs?: number | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The stop of one call: one abort signal for the request, and the error of the stop. */
|
|
37
|
+
export class CallScope {
|
|
38
|
+
readonly #controller = new AbortController();
|
|
39
|
+
readonly #timers: ReturnType<typeof setTimeout>[] = [];
|
|
40
|
+
readonly #parent: AbortSignal | undefined;
|
|
41
|
+
readonly #onAbort = (): void => this.stop("aborted");
|
|
42
|
+
#idle: ReturnType<typeof setTimeout> | undefined;
|
|
43
|
+
#reason: StopReason | undefined;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
readonly path: ProcedurePath,
|
|
47
|
+
context: ScopeContext,
|
|
48
|
+
readonly limits: ScopeLimits = {},
|
|
49
|
+
) {
|
|
50
|
+
this.#parent = context.signal;
|
|
51
|
+
if (this.#parent?.aborted) {
|
|
52
|
+
this.stop("aborted");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.#parent?.addEventListener("abort", this.#onAbort, { once: true });
|
|
56
|
+
if (limits.timeoutMs !== undefined) this.#later(limits.timeoutMs, () => this.stop("timeout"));
|
|
57
|
+
const deadline = context.capability?.deadline;
|
|
58
|
+
if (deadline !== undefined) this.#later(Math.max(0, deadline - Date.now()), () => this.stop("deadline"));
|
|
59
|
+
this.touch();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#later(ms: number, stop: () => void): void {
|
|
63
|
+
if (ms <= MAX_TIMER_MS) this.#timers.push(setTimeout(stop, ms));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The signal that aborts the request at the stop. */
|
|
67
|
+
get signal(): AbortSignal {
|
|
68
|
+
return this.#controller.signal;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The cause of the stop, or undefined while the call runs. */
|
|
72
|
+
get reason(): StopReason | undefined {
|
|
73
|
+
return this.#reason;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** This function stops the call. The first cause wins. */
|
|
77
|
+
stop(reason: StopReason): void {
|
|
78
|
+
if (this.#reason !== undefined) return;
|
|
79
|
+
this.#reason = reason;
|
|
80
|
+
this.#clearIdle();
|
|
81
|
+
this.#controller.abort(this.error());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** This function starts the idle time again: data arrived. */
|
|
85
|
+
touch(): void {
|
|
86
|
+
const idle = this.limits.idleTimeoutMs;
|
|
87
|
+
if (idle === undefined || this.#reason !== undefined) return;
|
|
88
|
+
this.#clearIdle();
|
|
89
|
+
if (idle <= MAX_TIMER_MS) this.#idle = setTimeout(() => this.stop("idle"), idle);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** This function holds the idle time while the reader of a stream has an item and reads no data. */
|
|
93
|
+
pause(): void {
|
|
94
|
+
this.#clearIdle();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#clearIdle(): void {
|
|
98
|
+
if (this.#idle !== undefined) clearTimeout(this.#idle);
|
|
99
|
+
this.#idle = undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The error of the stop. Before a stop, an `ABORTED` error. */
|
|
103
|
+
error(): InvocationError {
|
|
104
|
+
const key = this.path.join(".");
|
|
105
|
+
switch (this.#reason) {
|
|
106
|
+
case "timeout":
|
|
107
|
+
return new InvocationError("TIMEOUT", `${key}: the call took more than ${this.limits.timeoutMs} ms (timeoutMs)`, this.path);
|
|
108
|
+
case "idle":
|
|
109
|
+
return new InvocationError("TIMEOUT", `${key}: no data arrived in ${this.limits.idleTimeoutMs} ms (idleTimeoutMs)`, this.path);
|
|
110
|
+
case "deadline":
|
|
111
|
+
return new InvocationError("TIMEOUT", `${key}: the deadline of the call passed`, this.path);
|
|
112
|
+
default:
|
|
113
|
+
return new InvocationError("ABORTED", `${key}: the caller aborted the call`, this.path);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The error to throw for a failure: the error of the stop after a stop, or the failure. */
|
|
118
|
+
translate(error: unknown): unknown {
|
|
119
|
+
return this.#reason !== undefined ? this.error() : error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** This function removes the timers and the listener. */
|
|
123
|
+
dispose(): void {
|
|
124
|
+
for (const timer of this.#timers) clearTimeout(timer);
|
|
125
|
+
this.#timers.length = 0;
|
|
126
|
+
this.#clearIdle();
|
|
127
|
+
this.#parent?.removeEventListener("abort", this.#onAbort);
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions of the procedures of this package
|
|
3
|
+
*
|
|
4
|
+
* mark procedure new adds the types of each new procedure here. The zod schemas are in
|
|
5
|
+
* `module.ts`: an exported schema needs a written type (isolatedDeclarations).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A JSON value. */
|
|
9
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
10
|
+
|
|
11
|
+
/** The methods of `http.request`. */
|
|
12
|
+
export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS";
|
|
13
|
+
|
|
14
|
+
/** The fields of a request that each procedure has. */
|
|
15
|
+
export interface HttpTarget {
|
|
16
|
+
/** The URL: `http:` or `https:`. */
|
|
17
|
+
url: string;
|
|
18
|
+
/** The headers of the request. */
|
|
19
|
+
headers?: Record<string, string> | undefined;
|
|
20
|
+
/** More parameters for the query of the URL. A list gives the parameter more than one time. */
|
|
21
|
+
query?: Record<string, string | number | boolean | Array<string | number | boolean>> | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// http.request Types - Send an HTTP request and give the status, the headers and the body
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
export interface HttpRequestInput extends HttpTarget {
|
|
29
|
+
method?: HttpMethod | undefined;
|
|
30
|
+
/** A body of text. */
|
|
31
|
+
body?: string | undefined;
|
|
32
|
+
/** A body of JSON: the procedure sets `content-type: application/json`. */
|
|
33
|
+
json?: unknown;
|
|
34
|
+
/** A body of bytes in base64. */
|
|
35
|
+
bodyBase64?: string | undefined;
|
|
36
|
+
/** The form of the body of the response: text (the default), parsed JSON, or bytes in base64. */
|
|
37
|
+
responseType?: "text" | "json" | "bytes" | undefined;
|
|
38
|
+
/** The time of the whole call, in milliseconds. The default is 30000. */
|
|
39
|
+
timeoutMs?: number | undefined;
|
|
40
|
+
/** The largest body of the response, in bytes. The limit of the module is the maximum. */
|
|
41
|
+
maxResponseBytes?: number | undefined;
|
|
42
|
+
/** What to do with a redirect: follow it (the default), give it (`manual`), or fail (`error`). */
|
|
43
|
+
redirect?: "follow" | "manual" | "error" | undefined;
|
|
44
|
+
/** The most redirects that the call follows. The default is 5. */
|
|
45
|
+
maxRedirects?: number | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface HttpRequestOutput {
|
|
49
|
+
status: number;
|
|
50
|
+
statusText: string;
|
|
51
|
+
/** True for a status in 200 to 299. */
|
|
52
|
+
ok: boolean;
|
|
53
|
+
/** The URL of the response: the last URL after the redirects. */
|
|
54
|
+
url: string;
|
|
55
|
+
/** True when the call followed one or more redirects. */
|
|
56
|
+
redirected: boolean;
|
|
57
|
+
/** The headers of the response, with names in lowercase. `set-cookie` is a list. */
|
|
58
|
+
headers: Record<string, string | string[]>;
|
|
59
|
+
/** The body: text, parsed JSON (null for an empty body), or bytes in base64. */
|
|
60
|
+
body: unknown;
|
|
61
|
+
/** The size of the body in bytes, after the content encoding. */
|
|
62
|
+
bytes: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// http.json Types - Send a GET or a POST of JSON and give the parsed JSON response
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
export interface HttpJsonInput extends HttpTarget {
|
|
70
|
+
/** GET, or POST. The default is POST with `json`, and GET without it. */
|
|
71
|
+
method?: "GET" | "POST" | undefined;
|
|
72
|
+
/** The JSON body of a POST. */
|
|
73
|
+
json?: unknown;
|
|
74
|
+
/** The time of the whole call, in milliseconds. The default is 30000. */
|
|
75
|
+
timeoutMs?: number | undefined;
|
|
76
|
+
/** The largest body of the response, in bytes. The limit of the module is the maximum. */
|
|
77
|
+
maxResponseBytes?: number | undefined;
|
|
78
|
+
/** True (the default): a status outside 200 to 299 is an error. False: the output has it. */
|
|
79
|
+
failOnStatus?: boolean | undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HttpJsonOutput {
|
|
83
|
+
status: number;
|
|
84
|
+
ok: boolean;
|
|
85
|
+
url: string;
|
|
86
|
+
headers: Record<string, string | string[]>;
|
|
87
|
+
/** The parsed body. An empty body gives null. An error status with a body that is not JSON gives its text. */
|
|
88
|
+
data: unknown;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// http.ndjson Types - Read an NDJSON response as a stream of parsed lines
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
export interface HttpStreamInput extends HttpTarget {
|
|
96
|
+
/** The time of the whole stream, in milliseconds. The default is no limit. */
|
|
97
|
+
timeoutMs?: number | undefined;
|
|
98
|
+
/** The longest time without data, in milliseconds. The default is no limit. */
|
|
99
|
+
idleTimeoutMs?: number | undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface HttpNdjsonInput extends HttpStreamInput {
|
|
103
|
+
/** The largest line, in bytes. The default is 1 MiB. */
|
|
104
|
+
maxLineBytes?: number | undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** One item of `http.ndjson`: the JSON value of one line. */
|
|
108
|
+
export type HttpNdjsonOutput = unknown;
|
|
109
|
+
|
|
110
|
+
// =============================================================================
|
|
111
|
+
// http.sse Types - Read a stream of server-sent events
|
|
112
|
+
// =============================================================================
|
|
113
|
+
|
|
114
|
+
export interface HttpSseInput extends HttpStreamInput {
|
|
115
|
+
/** The `Last-Event-ID` header: the server continues after this event. */
|
|
116
|
+
lastEventId?: string | undefined;
|
|
117
|
+
/** The largest event, in bytes. The default is 1 MiB. */
|
|
118
|
+
maxEventBytes?: number | undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** One item of `http.sse`: one event. */
|
|
122
|
+
export interface HttpSseOutput {
|
|
123
|
+
event: string;
|
|
124
|
+
data: string;
|
|
125
|
+
id?: string | undefined;
|
|
126
|
+
retry?: number | undefined;
|
|
127
|
+
}
|