@volter/world-core 3.0.37 → 3.0.39
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/app-route.cjs +26 -25
- package/app-route.d.cts +1 -0
- package/dist/app-route.cjs +26 -25
- package/dist/app-route.d.cts +1 -0
- package/dist/generated/pack-facts.json +409 -352
- package/dist/inject.cjs +474 -95
- package/dist/network-policy.cjs +108 -13
- package/dist/network-policy.d.cts +8 -3
- package/dist/src/actions.js +2 -1
- package/dist/src/anthropic-wire.d.ts +2 -0
- package/dist/src/anthropic-wire.js +5 -0
- package/dist/src/attestation.d.ts +6 -0
- package/dist/src/attestation.js +27 -0
- package/dist/src/bytes.d.ts +3 -0
- package/dist/src/bytes.js +17 -0
- package/dist/src/changeset.js +2 -1
- package/dist/src/clickhouse/sql.js +73 -14
- package/dist/src/derived-core.d.ts +210 -32
- package/dist/src/derived-core.js +510 -244
- package/dist/src/derived-real.d.ts +1 -1
- package/dist/src/derived-real.js +704 -93
- package/dist/src/derived.d.ts +18 -2
- package/dist/src/derived.js +21 -2
- package/dist/src/events.d.ts +28 -45
- package/dist/src/events.js +40 -58
- package/dist/src/exact-json.d.ts +10 -0
- package/dist/src/exact-json.js +98 -0
- package/dist/src/executor.d.ts +17 -0
- package/dist/src/executor.js +91 -30
- package/dist/src/file-response.js +1 -1
- package/dist/src/form.d.ts +8 -0
- package/dist/src/form.js +76 -0
- package/dist/src/git/format.d.ts +16 -0
- package/dist/src/git/format.js +110 -0
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +1 -0
- package/dist/src/git/lfs.d.ts +1 -1
- package/dist/src/git/lfs.js +4 -3
- package/dist/src/graphql-wire.d.ts +6 -0
- package/dist/src/graphql-wire.js +6 -3
- package/dist/src/grpc-wire.js +2 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.js +15 -4
- package/dist/src/held-reads.d.ts +6 -0
- package/dist/src/held-reads.js +60 -0
- package/dist/src/history.js +5 -4
- package/dist/src/host-port.d.ts +5 -0
- package/dist/src/host-port.js +14 -0
- package/dist/src/index.d.ts +11 -3
- package/dist/src/index.js +9 -4
- package/dist/src/log.js +12 -7
- package/dist/src/machines.d.ts +81 -5
- package/dist/src/machines.js +125 -9
- package/dist/src/managed-database.d.ts +15 -11
- package/dist/src/managed-database.js +38 -37
- package/dist/src/multipart.d.ts +21 -3
- package/dist/src/multipart.js +112 -38
- package/dist/src/observe.d.ts +3 -0
- package/dist/src/observe.js +6 -2
- package/dist/src/openai-wire.d.ts +16 -0
- package/dist/src/openai-wire.js +43 -12
- package/dist/src/pack-fetch.d.ts +4 -6
- package/dist/src/pack-fetch.js +189 -57
- package/dist/src/packRegistry.d.ts +7 -1
- package/dist/src/packRegistry.js +20 -3
- package/dist/src/private-transport.d.ts +1 -0
- package/dist/src/private-transport.js +11 -0
- package/dist/src/protobuf.js +10 -4
- package/dist/src/rateBudget.d.ts +8 -1
- package/dist/src/rateBudget.js +1 -0
- package/dist/src/redis/engine.d.ts +14 -5
- package/dist/src/redis/engine.js +271 -72
- package/dist/src/redis/frames.d.ts +17 -0
- package/dist/src/redis/frames.js +123 -0
- package/dist/src/redis/index.d.ts +6 -0
- package/dist/src/redis/index.js +6 -0
- package/dist/src/redis/json.d.ts +2 -0
- package/dist/src/redis/json.js +14 -0
- package/dist/src/redis/lua-libs.d.ts +4 -0
- package/dist/src/redis/lua-libs.js +173 -0
- package/dist/src/redis/lua.d.ts +1 -0
- package/dist/src/redis/lua.js +36 -0
- package/dist/src/redis/member-storage.d.ts +5 -0
- package/dist/src/redis/member-storage.js +75 -0
- package/dist/src/redis/resp2.d.ts +4 -0
- package/dist/src/redis/resp2.js +29 -0
- package/dist/src/redis/stream.d.ts +14 -0
- package/dist/src/redis/stream.js +335 -0
- package/dist/src/remote-execute.d.ts +2 -2
- package/dist/src/request-body.d.ts +11 -0
- package/dist/src/request-body.js +11 -0
- package/dist/src/runtime.d.ts +6 -5
- package/dist/src/runtime.js +5 -5
- package/dist/src/s3/wire.d.ts +4 -4
- package/dist/src/s3/wire.js +5 -3
- package/dist/src/scenario.d.ts +22 -0
- package/dist/src/scenario.js +35 -31
- package/dist/src/serve-http.d.ts +2 -1
- package/dist/src/serve-http.js +10 -1
- package/dist/src/serve.js +7 -4
- package/dist/src/signing.d.ts +23 -5
- package/dist/src/signing.js +74 -11
- package/dist/src/sigv4.d.ts +3 -1
- package/dist/src/sigv4.js +4 -2
- package/dist/src/smtp.js +2 -1
- package/dist/src/sockets.js +8 -3
- package/dist/src/state-system.d.ts +3 -0
- package/dist/src/storage.js +5 -2
- package/dist/src/twin-fetch.d.ts +3 -1
- package/dist/src/twin-fetch.js +3 -2
- package/dist/src/vendor-call.d.ts +10 -3
- package/dist/src/vendor-call.js +58 -14
- package/dist/test-fixtures/attestation.SOURCE.md +1 -0
- package/dist/test-fixtures/attestation.json +50 -0
- package/generated/pack-facts.json +409 -352
- package/inject.cjs +474 -95
- package/network-policy.cjs +108 -13
- package/network-policy.d.cts +8 -3
- package/package.json +11 -4
- package/src/actions.ts +2 -1
- package/src/anthropic-wire.ts +6 -0
- package/src/attestation.ts +23 -0
- package/src/bytes.ts +21 -0
- package/src/changeset.ts +2 -1
- package/src/clickhouse/sql.ts +50 -16
- package/src/derived-core.ts +573 -249
- package/src/derived-real.ts +552 -42
- package/src/derived.ts +23 -4
- package/src/events.ts +54 -85
- package/src/exact-json.ts +71 -0
- package/src/executor.ts +86 -27
- package/src/file-response.ts +1 -1
- package/src/form.ts +66 -0
- package/src/git/format.ts +89 -0
- package/src/git/index.ts +1 -0
- package/src/git/lfs.ts +5 -4
- package/src/graphql-wire.ts +8 -3
- package/src/grpc-wire.ts +2 -1
- package/src/head.ts +15 -5
- package/src/held-reads.ts +54 -0
- package/src/history.ts +5 -4
- package/src/host-port.ts +9 -0
- package/src/index.ts +10 -4
- package/src/log.ts +11 -7
- package/src/machines.ts +135 -13
- package/src/managed-database.ts +48 -40
- package/src/multipart.ts +110 -35
- package/src/observe.ts +6 -3
- package/src/openai-wire.ts +45 -14
- package/src/pack-fetch.ts +173 -50
- package/src/packRegistry.ts +27 -4
- package/src/private-transport.ts +8 -0
- package/src/protobuf.ts +8 -4
- package/src/rateBudget.ts +6 -1
- package/src/redis/engine.ts +168 -63
- package/src/redis/frames.ts +53 -0
- package/src/redis/index.ts +6 -0
- package/src/redis/json.ts +13 -0
- package/src/redis/lua-libs.ts +62 -0
- package/src/redis/lua.ts +20 -0
- package/src/redis/member-storage.ts +58 -0
- package/src/redis/resp2.ts +25 -0
- package/src/redis/stream.ts +102 -0
- package/src/remote-execute.ts +2 -2
- package/src/request-body.ts +12 -0
- package/src/runtime.ts +6 -5
- package/src/s3/wire.ts +8 -5
- package/src/scenario.ts +34 -13
- package/src/serve-http.ts +11 -2
- package/src/serve.ts +4 -3
- package/src/signing.ts +71 -17
- package/src/sigv4.ts +5 -3
- package/src/smtp.ts +3 -2
- package/src/sockets.ts +8 -3
- package/src/state-system.ts +3 -0
- package/src/storage.ts +5 -2
- package/src/twin-fetch.ts +4 -3
- package/src/vendor-call.ts +59 -12
- package/test-fixtures/attestation.json +50 -0
package/dist/src/derived.d.ts
CHANGED
|
@@ -41,13 +41,20 @@ export type DerivedOperation = {
|
|
|
41
41
|
name: string;
|
|
42
42
|
type: string;
|
|
43
43
|
required?: boolean;
|
|
44
|
-
minimum?: number;
|
|
45
|
-
maximum?: number;
|
|
44
|
+
minimum?: number | string;
|
|
45
|
+
maximum?: number | string;
|
|
46
46
|
}>;
|
|
47
47
|
};
|
|
48
48
|
/** `basePath` is what the vendor's server URL puts before every operation path; `spanning` names the
|
|
49
49
|
* path parameters whose values may hold slashes (a git ref, a file path), which no spec says. */
|
|
50
50
|
export type DerivedSurface = {
|
|
51
|
+
graphql?: {
|
|
52
|
+
sdl: string;
|
|
53
|
+
};
|
|
54
|
+
format?: string;
|
|
55
|
+
commands?: ReadonlyArray<{
|
|
56
|
+
id: string;
|
|
57
|
+
}>;
|
|
51
58
|
version: string;
|
|
52
59
|
basePath?: string;
|
|
53
60
|
spanning?: ReadonlyArray<string>;
|
|
@@ -102,6 +109,10 @@ export declare function matchOperation(routes: Map<string, Route[]>, method: str
|
|
|
102
109
|
operation: DerivedOperation;
|
|
103
110
|
params: Record<string, string>;
|
|
104
111
|
} | undefined;
|
|
112
|
+
/** The path's template parameters for a request no operation matches (a CORS preflight, a method the path is not
|
|
113
|
+
* served by): read from the most literal route of any method whose path the request's path fits, discriminators
|
|
114
|
+
* aside, so `around` and `gap` know the resource the URL names without parsing it (S3's OPTIONS on `/{Bucket}/{Key+}`). */
|
|
115
|
+
export declare function pathParams(routes: Map<string, Route[]>, pathname: string): Record<string, string>;
|
|
105
116
|
export type DerivedOwner = 'handler' | 'core' | 'gap';
|
|
106
117
|
/** The header the kernel carries a path prefix's named groups in, from routing to the call (never a client's). */
|
|
107
118
|
export declare const PREFIX_PARAMS_HEADER = "x-volter-path-prefix-params";
|
|
@@ -109,8 +120,13 @@ export type DerivedFetch = ((request: Request) => Promise<Response>) & {
|
|
|
109
120
|
/** Per operationId, who serves it: a semantics handler, the derived core, the pack's existing
|
|
110
121
|
* fetch while it moves, or nothing (the vendor's gap). */
|
|
111
122
|
owners(): Record<string, DerivedOwner>;
|
|
123
|
+
/** A vendor's owners by unit: its own API's under `''`, each lane's under the lane's name, each keyed by the unit's own
|
|
124
|
+
* operation ids (an id two units share is two operations, each served or the gap on its own). `owners()` is this map
|
|
125
|
+
* flat, a lane's ids written `<lane>:<id>`. A fetch with no lanes may leave it out: its one unit is `owners()`. */
|
|
126
|
+
ownersByUnit?(): Record<string, Record<string, DerivedOwner>>;
|
|
112
127
|
/** The vendor's sockets (sockets.ts), for the serve seam to upgrade to, when its manifest declares any. */
|
|
113
128
|
upgrade?: WebSocketUpgrade;
|
|
129
|
+
maxRequestBodySize?: number;
|
|
114
130
|
/** The workspace screens it draws, its lanes' too (`GET /twin`'s `screens`). */
|
|
115
131
|
workspaces?(): Workspace[];
|
|
116
132
|
/** Whether it lays out frames of the World's board (`semantics/board.ts`). */
|
package/dist/src/derived.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseExactJson } from "./exact-json.js";
|
|
1
2
|
import { runWithRequestTrace } from "./trace-context.js";
|
|
2
3
|
/**
|
|
3
4
|
* A request's body as text, read once per request: the dispatch reads it and tags the request with it, and every clone
|
|
@@ -29,7 +30,7 @@ export async function checkJsonBody(request) {
|
|
|
29
30
|
if (!(request.headers.get('content-type') ?? '').includes('json') && !/^\s*[[{]/.test(text))
|
|
30
31
|
return true;
|
|
31
32
|
try {
|
|
32
|
-
const value =
|
|
33
|
+
const value = parseExactJson(text);
|
|
33
34
|
const tag = request[BODY_TEXT] !== undefined ? BODY_PARSE_KEY.get(request) : undefined;
|
|
34
35
|
if (tag)
|
|
35
36
|
BODY_PARSED.set(tag, { value });
|
|
@@ -123,6 +124,24 @@ export function matchOperation(routes, method, pathname, query, headers) {
|
|
|
123
124
|
}
|
|
124
125
|
return undefined;
|
|
125
126
|
}
|
|
127
|
+
/** The path's template parameters for a request no operation matches (a CORS preflight, a method the path is not
|
|
128
|
+
* served by): read from the most literal route of any method whose path the request's path fits, discriminators
|
|
129
|
+
* aside, so `around` and `gap` know the resource the URL names without parsing it (S3's OPTIONS on `/{Bucket}/{Key+}`). */
|
|
130
|
+
export function pathParams(routes, pathname) {
|
|
131
|
+
const fits = [...routes.values()].flat().filter((r) => r.pattern.test(pathname)).sort((a, b) => b.literals - a.literals || b.names.length - a.names.length);
|
|
132
|
+
const route = fits[0];
|
|
133
|
+
const m = route?.pattern.exec(pathname);
|
|
134
|
+
if (!route || !m)
|
|
135
|
+
return {};
|
|
136
|
+
const params = {};
|
|
137
|
+
try {
|
|
138
|
+
route.names.forEach((name, i) => { params[name] = decodeURIComponent(m[i + 1]); });
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
return params;
|
|
144
|
+
}
|
|
126
145
|
/** The header the kernel carries a path prefix's named groups in, from routing to the call (never a client's). */
|
|
127
146
|
export const PREFIX_PARAMS_HEADER = 'x-volter-path-prefix-params';
|
|
128
147
|
export function createDerivedFetch(options) {
|
|
@@ -147,7 +166,7 @@ export function createDerivedFetch(options) {
|
|
|
147
166
|
tagBody(request, await request.clone().text());
|
|
148
167
|
// the path prefix's named groups (pack-fetch.ts, `pathPrefix`) are the call's parameters too
|
|
149
168
|
const prefixed = request.headers.get(PREFIX_PARAMS_HEADER);
|
|
150
|
-
const call = { request, operation: matched.operation, params: prefixed ? { ...
|
|
169
|
+
const call = { request, operation: matched.operation, params: prefixed ? { ...parseExactJson(prefixed), ...matched.params } : matched.params };
|
|
151
170
|
const around = options.around ?? ((_c, next) => next());
|
|
152
171
|
const handler = handlers[matched.operation.id];
|
|
153
172
|
if (handler)
|
package/dist/src/events.d.ts
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/** How the vendor signs a delivery. */
|
|
2
2
|
export type EventScheme =
|
|
3
|
+
/** One header carrying the destination's secret as it is, no signature over the body (Cloudflare Notifications'
|
|
4
|
+
* `cf-webhook-auth`, the secret a webhook destination was made with). */
|
|
5
|
+
{
|
|
6
|
+
kind: 'secret';
|
|
7
|
+
header: string;
|
|
8
|
+
}
|
|
3
9
|
/** A message id, a timestamp and a signature header (the Standard Webhooks shape): `<prefix>-id`,
|
|
4
10
|
* `<prefix>-timestamp` (seconds) and `<prefix>-signature: v1,<base64 HMAC-SHA256>` over `${id}.${timestamp}.${body}`,
|
|
5
11
|
* keyed by the base64 part of the secret after `secretPrefix`, or by the secret's own UTF-8 bytes (`secretKey: 'utf8'`:
|
|
6
12
|
* Polar's, whose SDK base64-encodes the secret as it is before verifying). */
|
|
7
|
-
{
|
|
13
|
+
| {
|
|
8
14
|
kind: 'webhook-id';
|
|
9
15
|
prefix: string;
|
|
10
16
|
secretPrefix?: string;
|
|
11
17
|
secretKey?: 'base64' | 'utf8';
|
|
12
18
|
}
|
|
13
19
|
/** One header of a timestamp and its signatures: `<header>: t=<seconds>,v1=<hex HMAC-SHA256>` over `${t}.${body}`,
|
|
14
|
-
* keyed by the secret as it is
|
|
20
|
+
* keyed by the secret as it is; `version` names the signature's key when the vendor's is not `v1` (ElevenLabs'
|
|
21
|
+
* `ElevenLabs-Signature: t=…,v0=…`). */
|
|
15
22
|
| {
|
|
16
23
|
kind: 'timestamp-v1';
|
|
17
24
|
header: string;
|
|
25
|
+
version?: string;
|
|
18
26
|
}
|
|
19
27
|
/** One HMAC header over the body (`X-Hub-Signature-256: sha256=<hex>`, GitHub's), or over `${timestamp}.${body}` when
|
|
20
28
|
* the vendor sends its timestamp in `timestampHeader`; `signed` names what is signed otherwise, `{t}` the timestamp
|
|
@@ -28,15 +36,15 @@ export type EventScheme =
|
|
|
28
36
|
timestampHeader?: string;
|
|
29
37
|
signed?: string;
|
|
30
38
|
algorithm?: 'sha1' | 'sha256';
|
|
31
|
-
secretKey?: 'base64' | 'utf8';
|
|
32
39
|
}
|
|
33
40
|
/** One header of a plain hash (not an HMAC) of the secret and the body (HubSpot's v1 `X-HubSpot-Signature`: the SHA-256
|
|
34
|
-
* of the app's client secret followed by the request body); `signed` names the input, `{secret}` and `{body}
|
|
41
|
+
* of the app's client secret followed by the request body); `signed` names the input, `{secret}` and `{body}`;
|
|
42
|
+
* `base64-unpadded` is base64 with its trailing `=` removed (E2B's `e2b-signature`) */
|
|
35
43
|
| {
|
|
36
44
|
kind: 'hash';
|
|
37
45
|
header: string;
|
|
38
46
|
signed: string;
|
|
39
|
-
encoding: 'hex' | 'base64';
|
|
47
|
+
encoding: 'hex' | 'base64' | 'base64-unpadded';
|
|
40
48
|
algorithm?: 'sha256';
|
|
41
49
|
};
|
|
42
50
|
/** Where the World's endpoints of one kind are kept (a door the vendor's dashboard stands in for writes them): their
|
|
@@ -79,34 +87,6 @@ export type EndpointsDecl = {
|
|
|
79
87
|
* when a message mentions it, and every `message` its subscription names). */
|
|
80
88
|
types?: ReadonlyArray<string>;
|
|
81
89
|
skip?: ReadonlyArray<string>;
|
|
82
|
-
/** What each delivery's answer writes on the endpoint, when the vendor keeps score (Daily's webhook): a success (2xx)
|
|
83
|
-
* resets `failures` and writes `success` (templates: `$time.iso`, `$time.s`), a failure adds one to `failures`, and
|
|
84
|
-
* `after` failures in a row make `move`, the vendor's transition of a state field, asked of the endpoint's machine,
|
|
85
|
-
* on every endpoint but one matching `unless` (Daily's `exponential` retry never breaks). */
|
|
86
|
-
outcome?: EndpointOutcome;
|
|
87
|
-
};
|
|
88
|
-
/** An endpoint kind's delivery outcome (EndpointsDecl.outcome). */
|
|
89
|
-
export type EndpointOutcome = {
|
|
90
|
-
failures: string;
|
|
91
|
-
success?: Record<string, string>;
|
|
92
|
-
after?: number;
|
|
93
|
-
move?: {
|
|
94
|
-
field: string;
|
|
95
|
-
to: string;
|
|
96
|
-
operation: string;
|
|
97
|
-
};
|
|
98
|
-
unless?: {
|
|
99
|
-
field: string;
|
|
100
|
-
equals: unknown;
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
/** A delivery's answer as the kernel reads it: the endpoint kind and row it went to, and the receiver's status (0 when
|
|
104
|
-
* the World refused it or nothing answered). */
|
|
105
|
-
export type DeliveryAnswer = {
|
|
106
|
-
kind: EndpointsDecl;
|
|
107
|
-
row: Record<string, unknown>;
|
|
108
|
-
status: number;
|
|
109
|
-
occurredAt: string;
|
|
110
90
|
};
|
|
111
91
|
/** The events a pack's writes send. */
|
|
112
92
|
export type EventsDecl = {
|
|
@@ -132,10 +112,6 @@ export type EventsDecl = {
|
|
|
132
112
|
endpoints: EndpointsDecl | ReadonlyArray<EndpointsDecl>;
|
|
133
113
|
/** The bookkeeping type each delivery is kept as (`_webhook_message`): what a World reads to see what was sent. */
|
|
134
114
|
record?: string;
|
|
135
|
-
/** The bookkeeping type a World states its receivers' answers in (`{url, status}`, Daily's `_receiver`), for receivers
|
|
136
|
-
* it runs no server for: a delivery whose URL starts with a stated `url` (the longest) is answered that status, and
|
|
137
|
-
* nothing is sent. */
|
|
138
|
-
receivers?: string;
|
|
139
115
|
};
|
|
140
116
|
/** An event's further envelope values (`$`-named) the pack computes for a write, from the vendor's own state (the
|
|
141
117
|
* connected account it is about, the API version it is rendered in): the pack's `semantics/events.ts` `values`. A
|
|
@@ -187,14 +163,18 @@ export declare function eventTypeOf(decl: Pick<EventsDecl, 'types' | 'known'>, o
|
|
|
187
163
|
/** Whether an endpoint's list of event types takes a type: every type when the list is empty, `*` or `<family>.*`
|
|
188
164
|
* its patterns, else the type itself. */
|
|
189
165
|
export declare function takesEvent(list: unknown, type: string): boolean;
|
|
190
|
-
/**
|
|
191
|
-
*
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
166
|
+
/** Who watches what the World sends (a walk reading each delivery as its receiver would): told of every delivery as it
|
|
167
|
+
* is made, before its transport. Set and restored by the walk, as its World store is; unset, nothing watches. */
|
|
168
|
+
export type DeliveryObserver = (delivery: EventDelivery & {
|
|
169
|
+
service: string;
|
|
170
|
+
}) => void;
|
|
171
|
+
export declare function setDeliveryObserver(observer: DeliveryObserver | undefined): DeliveryObserver | undefined;
|
|
172
|
+
/** What the application's server answered a message the vendor waits on (its status, its headers by lower-cased name,
|
|
173
|
+
* its body), or why it did not answer: `missed` when the World let no request out to it or it could not be reached
|
|
174
|
+
* (`unreachable`), or it did not answer in time (`timeout`). */
|
|
196
175
|
export type ApplicationAnswer = {
|
|
197
176
|
status: number;
|
|
177
|
+
headers: Record<string, string>;
|
|
198
178
|
body: string;
|
|
199
179
|
missed?: 'timeout' | 'unreachable';
|
|
200
180
|
};
|
|
@@ -203,10 +183,13 @@ export type ApplicationAnswer = {
|
|
|
203
183
|
* application's; `'unreachable'` is a question nobody answers (a walk's life, a sealed World, takes no other); undefined
|
|
204
184
|
* is a question it does not take, which goes on as any other does: the application route, the World's egress rule, the
|
|
205
185
|
* network. A walk's stand-in answers every question (its life's answers, else unreachable); a hosted World's router
|
|
206
|
-
* answers only what it can answer truthfully (WorldDoors.askInWorld) and leaves the rest.
|
|
186
|
+
* answers only what it can answer truthfully (WorldDoors.askInWorld) and leaves the rest. `bodyError` models a
|
|
187
|
+
* disconnect after the response headers; a caller needing the complete body receives no complete answer. */
|
|
207
188
|
export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{
|
|
208
189
|
status: number;
|
|
190
|
+
headers?: Record<string, string>;
|
|
209
191
|
body: string;
|
|
192
|
+
bodyError?: string;
|
|
210
193
|
} | 'unreachable' | undefined>;
|
|
211
194
|
/** A message the vendor sends the application's server and decides by its answer (Stripe's real-time authorization
|
|
212
195
|
* request, answered within its window): over the same transport, waited on for at most `within` milliseconds, the
|
|
@@ -214,4 +197,4 @@ export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{
|
|
|
214
197
|
export declare function askApplication(url: string, init: RequestInit, within: number, standIn?: ApplicationStandIn): Promise<ApplicationAnswer>;
|
|
215
198
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
216
199
|
* that takes it, and recorded. */
|
|
217
|
-
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown
|
|
200
|
+
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown>>): Promise<EventDelivery[]>;
|
package/dist/src/events.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// delivery is made, never when a browser client imports the kernel.
|
|
9
9
|
import { createHash, createHmac } from 'node:crypto';
|
|
10
10
|
import { applyTwinWrite, twinResources } from "./serve.js";
|
|
11
|
-
import { equalSecrets as same } from "./signing.js";
|
|
11
|
+
import { equalSecrets as same, ksuidFrom } from "./signing.js";
|
|
12
12
|
import { deliveryTraceHeaders } from "./trace-context.js";
|
|
13
13
|
/** Whether an endpoint takes an event by its `match`: every field of one match (any, when several) equal to the event's
|
|
14
14
|
* value, or, a list, holding it. */
|
|
@@ -33,23 +33,28 @@ const webhookKey = (scheme, secret) => scheme.secretKey === 'utf8' ? Buffer.from
|
|
|
33
33
|
* made before this one, so a receiver that deduplicates by id drops neither. */
|
|
34
34
|
export function signEvent(scheme, secret, body, seconds, url, seq = 0) {
|
|
35
35
|
if (scheme.kind === 'webhook-id') {
|
|
36
|
-
|
|
36
|
+
// Svix's `msg_` and a KSUID (its ids' form), derived from the delivery itself
|
|
37
|
+
const id = `msg_${ksuidFrom(seconds, seq, `${url}|${seconds}|${seq}|${body}`)}`;
|
|
37
38
|
const p = scheme.prefix.toLowerCase();
|
|
38
39
|
const signature = createHmac('sha256', webhookKey(scheme, secret)).update(`${id}.${seconds}.${body}`, 'utf8').digest('base64');
|
|
39
40
|
return { [`${p}-id`]: id, [`${p}-timestamp`]: String(seconds), [`${p}-signature`]: `v1,${signature}` };
|
|
40
41
|
}
|
|
41
42
|
if (scheme.kind === 'timestamp-v1') {
|
|
42
43
|
const signature = createHmac('sha256', secret).update(`${seconds}.${body}`, 'utf8').digest('hex');
|
|
43
|
-
return { [scheme.header.toLowerCase()]: `t=${seconds}
|
|
44
|
+
return { [scheme.header.toLowerCase()]: `t=${seconds},${scheme.version ?? 'v1'}=${signature}` };
|
|
44
45
|
}
|
|
46
|
+
if (scheme.kind === 'secret')
|
|
47
|
+
return { [scheme.header.toLowerCase()]: secret };
|
|
45
48
|
if (scheme.kind === 'hash')
|
|
46
49
|
return { [scheme.header.toLowerCase()]: hashOf(scheme, secret, body) };
|
|
47
|
-
|
|
48
|
-
const mac = createHmac(scheme.algorithm ?? 'sha256', scheme.secretKey === 'base64' ? Buffer.from(secret, 'base64') : secret).update(hmacBase(scheme, seconds, body), 'utf8').digest(scheme.encoding);
|
|
50
|
+
const mac = createHmac(scheme.algorithm ?? 'sha256', secret).update(hmacBase(scheme, seconds, body), 'utf8').digest(scheme.encoding);
|
|
49
51
|
return { [scheme.header.toLowerCase()]: `${scheme.prefix ?? ''}${mac}`, ...(scheme.timestampHeader ? { [scheme.timestampHeader.toLowerCase()]: String(seconds) } : {}) };
|
|
50
52
|
}
|
|
51
53
|
/** A `hash` scheme's value: the digest of its `signed` template with the secret and the body put in. */
|
|
52
|
-
const hashOf = (scheme, secret, body) =>
|
|
54
|
+
const hashOf = (scheme, secret, body) => {
|
|
55
|
+
const digest = createHash(scheme.algorithm ?? 'sha256').update(scheme.signed.replaceAll('{secret}', secret).replaceAll('{body}', body), 'utf8');
|
|
56
|
+
return scheme.encoding === 'base64-unpadded' ? digest.digest('base64').replace(/=+$/, '') : digest.digest(scheme.encoding);
|
|
57
|
+
};
|
|
53
58
|
/** What an `hmac` scheme signs: its `signed` template, else `${t}.${body}` with a timestamp header, else the body. */
|
|
54
59
|
const hmacBase = (scheme, seconds, body) => scheme.signed !== undefined ? scheme.signed.replaceAll('{t}', String(seconds)).replaceAll('{body}', body) : scheme.timestampHeader ? `${seconds}.${body}` : body;
|
|
55
60
|
/** Every scheme's headers for one delivery, merged. */
|
|
@@ -81,7 +86,11 @@ export function verifyEvent(scheme, secret, body, headers, opts = {}) {
|
|
|
81
86
|
if (!Number.isFinite(seconds) || !fresh(seconds))
|
|
82
87
|
return false;
|
|
83
88
|
const expected = createHmac('sha256', secret).update(`${seconds}.${body}`, 'utf8').digest('hex');
|
|
84
|
-
return parts.some(([k, v]) => k === 'v1' && v !== undefined && same(v, expected));
|
|
89
|
+
return parts.some(([k, v]) => k === (scheme.version ?? 'v1') && v !== undefined && same(v, expected));
|
|
90
|
+
}
|
|
91
|
+
if (scheme.kind === 'secret') {
|
|
92
|
+
const given = h[scheme.header.toLowerCase()];
|
|
93
|
+
return given !== undefined && same(given, secret);
|
|
85
94
|
}
|
|
86
95
|
if (scheme.kind === 'hash') {
|
|
87
96
|
const given = h[scheme.header.toLowerCase()];
|
|
@@ -133,11 +142,7 @@ function at(row, path) {
|
|
|
133
142
|
function headersFor(templates, values, endpoint) {
|
|
134
143
|
const out = {};
|
|
135
144
|
for (const [name, template] of Object.entries(templates)) {
|
|
136
|
-
|
|
137
|
-
const prefixed = /^(.+?)\$endpoint\.([\w.]+)$/.exec(template);
|
|
138
|
-
const field = prefixed ? at(endpoint, prefixed[2]) : undefined;
|
|
139
|
-
const value = prefixed ? (field === undefined || field === null || field === '' ? undefined : `${prefixed[1]}${String(field)}`)
|
|
140
|
-
: template.startsWith('$endpoint.') ? at(endpoint, template.slice('$endpoint.'.length)) : template.startsWith('$') ? values[template] : template;
|
|
145
|
+
const value = template.startsWith('$endpoint.') ? at(endpoint, template.slice('$endpoint.'.length)) : template.startsWith('$') ? values[template] : template;
|
|
141
146
|
if (value !== undefined && value !== null)
|
|
142
147
|
out[name.toLowerCase()] = String(value);
|
|
143
148
|
}
|
|
@@ -191,7 +196,7 @@ const worldTransport = async (url, body, headers) => {
|
|
|
191
196
|
import('../app-route.cjs'),
|
|
192
197
|
import('../network-policy.cjs'),
|
|
193
198
|
]);
|
|
194
|
-
if (!appDestination(url) && worldEgressRefusal(url) !== null)
|
|
199
|
+
if (!appDestination(url) && worldEgressRefusal(url, process.env, 'POST') !== null)
|
|
195
200
|
return 0;
|
|
196
201
|
try {
|
|
197
202
|
return (await appFetch(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body })).status;
|
|
@@ -200,27 +205,11 @@ const worldTransport = async (url, body, headers) => {
|
|
|
200
205
|
return 0;
|
|
201
206
|
}
|
|
202
207
|
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
import('../app-route.cjs'),
|
|
209
|
-
import('../network-policy.cjs'),
|
|
210
|
-
]);
|
|
211
|
-
if (!appDestination(url) && worldEgressRefusal(url) !== null)
|
|
212
|
-
return 0;
|
|
213
|
-
// the request that caused the message is its trace's parent (trace-context.ts)
|
|
214
|
-
const headers = new Headers(init.headers);
|
|
215
|
-
for (const [k, v] of Object.entries(deliveryTraceHeaders()))
|
|
216
|
-
if (!headers.has(k))
|
|
217
|
-
headers.set(k, v);
|
|
218
|
-
try {
|
|
219
|
-
return (await appFetch(url, { ...init, headers })).status;
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return 0;
|
|
223
|
-
}
|
|
208
|
+
let deliveryObserver;
|
|
209
|
+
export function setDeliveryObserver(observer) {
|
|
210
|
+
const previous = deliveryObserver;
|
|
211
|
+
deliveryObserver = observer;
|
|
212
|
+
return previous;
|
|
224
213
|
}
|
|
225
214
|
/** A message the vendor sends the application's server and decides by its answer (Stripe's real-time authorization
|
|
226
215
|
* request, answered within its window): over the same transport, waited on for at most `within` milliseconds, the
|
|
@@ -231,42 +220,44 @@ export async function askApplication(url, init, within, standIn) {
|
|
|
231
220
|
const late = new Promise((done) => { timer = setTimeout(() => done('late'), within); });
|
|
232
221
|
const stood = await Promise.race([standIn(url, init), late]).finally(() => clearTimeout(timer));
|
|
233
222
|
if (stood === 'late')
|
|
234
|
-
return { status: 0, body: '', missed: 'timeout' };
|
|
223
|
+
return { status: 0, headers: {}, body: '', missed: 'timeout' };
|
|
235
224
|
if (stood === 'unreachable')
|
|
236
|
-
return { status: 0, body: '', missed: 'unreachable' };
|
|
225
|
+
return { status: 0, headers: {}, body: '', missed: 'unreachable' };
|
|
226
|
+
if (stood !== undefined && stood.bodyError !== undefined)
|
|
227
|
+
return { status: 0, headers: {}, body: '', missed: 'unreachable' };
|
|
237
228
|
if (stood !== undefined)
|
|
238
|
-
return stood;
|
|
229
|
+
return { status: stood.status, headers: Object.fromEntries(Object.entries(stood.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v])), body: stood.body };
|
|
239
230
|
}
|
|
240
231
|
const [{ appDestination, appFetch }, { worldEgressRefusal }] = await Promise.all([
|
|
241
232
|
import('../app-route.cjs'),
|
|
242
233
|
import('../network-policy.cjs'),
|
|
243
234
|
]);
|
|
244
|
-
if (!appDestination(url) && worldEgressRefusal(url) !== null)
|
|
245
|
-
return { status: 0, body: '', missed: 'unreachable' };
|
|
235
|
+
if (!appDestination(url) && worldEgressRefusal(url, process.env, init.method ?? 'GET') !== null)
|
|
236
|
+
return { status: 0, headers: {}, body: '', missed: 'unreachable' };
|
|
246
237
|
const headers = new Headers(init.headers);
|
|
247
238
|
for (const [k, v] of Object.entries(deliveryTraceHeaders()))
|
|
248
239
|
if (!headers.has(k))
|
|
249
240
|
headers.set(k, v);
|
|
250
241
|
try {
|
|
251
242
|
const res = await appFetch(url, { ...init, headers, signal: AbortSignal.timeout(within) });
|
|
252
|
-
return { status: res.status, body: await res.text() };
|
|
243
|
+
return { status: res.status, headers: Object.fromEntries(res.headers), body: await res.text() };
|
|
253
244
|
}
|
|
254
245
|
catch (e) {
|
|
255
|
-
return { status: 0, body: '', missed: e instanceof Error && (e.name === 'TimeoutError' || e.name === 'AbortError') ? 'timeout' : 'unreachable' };
|
|
246
|
+
return { status: 0, headers: {}, body: '', missed: e instanceof Error && (e.name === 'TimeoutError' || e.name === 'AbortError') ? 'timeout' : 'unreachable' };
|
|
256
247
|
}
|
|
257
248
|
}
|
|
258
249
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
259
250
|
* that takes it, and recorded. */
|
|
260
|
-
export async function deliverEvents(service, decl, write, data, transport = worldTransport, values
|
|
251
|
+
export async function deliverEvents(service, decl, write, data, transport = worldTransport, values) {
|
|
261
252
|
if (write.storedType === decl.store)
|
|
262
253
|
return [];
|
|
263
254
|
const sent = [];
|
|
264
255
|
for (const type of eventTypesOf(decl, write.operation))
|
|
265
|
-
sent.push(...(await deliverEventType(service, decl, write, data, transport, values, type
|
|
256
|
+
sent.push(...(await deliverEventType(service, decl, write, data, transport, values, type)));
|
|
266
257
|
return sent;
|
|
267
258
|
}
|
|
268
259
|
/** One event type a write sends: to the live endpoints that take it, stored when the vendor keeps its events. */
|
|
269
|
-
async function deliverEventType(service, decl, write, data, transport, values, type
|
|
260
|
+
async function deliverEventType(service, decl, write, data, transport, values, type) {
|
|
270
261
|
const all = twinResources(service, write.root);
|
|
271
262
|
const kinds = Array.isArray(decl.endpoints) ? decl.endpoints : [decl.endpoints];
|
|
272
263
|
// an endpoint's row with its joined rows under their names (`app.hook_url` reads the installation's App)
|
|
@@ -295,7 +286,7 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
295
286
|
const kept = decl.store !== undefined ? all.filter((r) => r.type === decl.store).length : 0;
|
|
296
287
|
const filled = {
|
|
297
288
|
$type: type, $data: eventData,
|
|
298
|
-
$id: `
|
|
289
|
+
$id: `evt_${ksuidFrom(seconds, kept, `${type}|${write.occurredAt}|${kept}|${JSON.stringify(eventData)}`)}`,
|
|
299
290
|
'$time.ms': millis, '$time.s': seconds, '$time.iso': write.occurredAt,
|
|
300
291
|
...requestValues(write.request),
|
|
301
292
|
...extra,
|
|
@@ -314,7 +305,6 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
314
305
|
const shared = JSON.stringify(envelope);
|
|
315
306
|
const each = perEndpoint(decl.envelope);
|
|
316
307
|
const sent = [];
|
|
317
|
-
const to = [];
|
|
318
308
|
const before = decl.record !== undefined ? all.filter((r) => r.type === decl.record).length : 0;
|
|
319
309
|
for (const [i, { row, kind }] of endpoints.entries()) {
|
|
320
310
|
const body = each ? JSON.stringify(fillEnvelope(decl.envelope, filled, row)) : shared;
|
|
@@ -323,7 +313,6 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
323
313
|
const extraHeaders = decl.headers ? headersFor(decl.headers, filled, row) : {};
|
|
324
314
|
const headers = { ...extraHeaders, ...signEventAll(decl.scheme, String(at(row, kind.secret)), body, seconds, url, seq) };
|
|
325
315
|
sent.push({ url, body, headers });
|
|
326
|
-
to.push({ kind, row });
|
|
327
316
|
// kept with the write, by an id of the delivery itself (two made at once never take the same one)
|
|
328
317
|
if (decl.record !== undefined) {
|
|
329
318
|
await applyTwinWrite(service, {
|
|
@@ -336,18 +325,11 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
336
325
|
// slow or down fails no request
|
|
337
326
|
// the write's trace is read now, inside its request, and carried on each delivery (W3C trace continuation)
|
|
338
327
|
const trace = deliveryTraceHeaders();
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
.sort((x, y) => String(y.url).length - String(x.url).length)[0];
|
|
343
|
-
return r === undefined ? undefined : Number(r.status);
|
|
344
|
-
};
|
|
345
|
-
for (const [i, { url, body: payload, headers }] of sent.entries())
|
|
328
|
+
for (const d of sent)
|
|
329
|
+
deliveryObserver?.({ ...d, service });
|
|
330
|
+
for (const { url, body: payload, headers } of sent)
|
|
346
331
|
setTimeout(() => {
|
|
347
|
-
|
|
348
|
-
(answered !== undefined ? Promise.resolve(answered) : transport(url, payload, { ...headers, ...trace }))
|
|
349
|
-
.then((status) => (onAnswer && to[i].kind.outcome ? onAnswer({ ...to[i], status: typeof status === 'number' ? status : 0, occurredAt: write.occurredAt }) : undefined))
|
|
350
|
-
.catch(() => { });
|
|
332
|
+
void transport(url, payload, { ...headers, ...trace }).catch(() => { });
|
|
351
333
|
}, 0);
|
|
352
334
|
return sent;
|
|
353
335
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** JSON and wire integers outside JavaScript's safe range retain their decimal digits.
|
|
2
|
+
* Safe integers stay numbers; floating-point tokens keep their wire's double semantics.
|
|
3
|
+
* Quoted strings are consumed whole, so their contents are never rewritten.
|
|
4
|
+
*/
|
|
5
|
+
export declare function exactInteger(digits: string): number | string;
|
|
6
|
+
export declare function parseExactJson(text: string, integer?: (digits: string) => number | string | bigint): any;
|
|
7
|
+
/** Ordering integer versions and stored fields must not round adjacent int64 values. */
|
|
8
|
+
export declare function compareExactNumbers(a: unknown, b: unknown): number;
|
|
9
|
+
/** Unsafe integer tokens use decimal strings at kernel interfaces; schema checks retain that meaning. */
|
|
10
|
+
export declare function isExactIntegerValue(value: unknown): boolean;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/** JSON and wire integers outside JavaScript's safe range retain their decimal digits.
|
|
2
|
+
* Safe integers stay numbers; floating-point tokens keep their wire's double semantics.
|
|
3
|
+
* Quoted strings are consumed whole, so their contents are never rewritten.
|
|
4
|
+
*/
|
|
5
|
+
export function exactInteger(digits) {
|
|
6
|
+
const n = BigInt(digits);
|
|
7
|
+
return n >= -9007199254740991n && n <= 9007199254740991n ? Number(n) : n.toString();
|
|
8
|
+
}
|
|
9
|
+
export function parseExactJson(text, integer = exactInteger) {
|
|
10
|
+
let at = 0;
|
|
11
|
+
const number = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
12
|
+
const space = () => { while (/[\x20\t\r\n]/.test(text[at] ?? '') && at < text.length)
|
|
13
|
+
at++; };
|
|
14
|
+
const invalid = () => { throw new SyntaxError(`Invalid JSON at position ${at}`); };
|
|
15
|
+
const quoted = () => {
|
|
16
|
+
const begin = at++;
|
|
17
|
+
for (; at < text.length; at++) {
|
|
18
|
+
if (text[at] === '\\') {
|
|
19
|
+
at++;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (text[at] === '"') {
|
|
23
|
+
const token = text.slice(begin, ++at);
|
|
24
|
+
// JSON.parse reads only a quoted string, never an integer token.
|
|
25
|
+
return JSON.parse(token);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return invalid();
|
|
29
|
+
};
|
|
30
|
+
const value = () => {
|
|
31
|
+
space();
|
|
32
|
+
if (text[at] === '"')
|
|
33
|
+
return quoted();
|
|
34
|
+
if (text[at] === '[' || text[at] === '{') {
|
|
35
|
+
const array = text[at++] === '[';
|
|
36
|
+
const out = array ? [] : {};
|
|
37
|
+
const end = array ? ']' : '}';
|
|
38
|
+
space();
|
|
39
|
+
if (text[at] === end) {
|
|
40
|
+
at++;
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
for (;;) {
|
|
44
|
+
space();
|
|
45
|
+
let key;
|
|
46
|
+
if (!array) {
|
|
47
|
+
if (text[at] !== '"')
|
|
48
|
+
return invalid();
|
|
49
|
+
key = quoted();
|
|
50
|
+
space();
|
|
51
|
+
if (text[at++] !== ':')
|
|
52
|
+
return invalid();
|
|
53
|
+
}
|
|
54
|
+
const item = value();
|
|
55
|
+
if (array)
|
|
56
|
+
out.push(item);
|
|
57
|
+
else
|
|
58
|
+
Object.defineProperty(out, key, { value: item, enumerable: true, writable: true, configurable: true });
|
|
59
|
+
space();
|
|
60
|
+
if (text[at] === end) {
|
|
61
|
+
at++;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
if (text[at++] !== ',')
|
|
65
|
+
return invalid();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const [literal, decoded] of [['true', true], ['false', false], ['null', null]]) {
|
|
69
|
+
if (text.startsWith(literal, at)) {
|
|
70
|
+
at += literal.length;
|
|
71
|
+
return decoded;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
number.lastIndex = at;
|
|
75
|
+
const token = number.exec(text)?.[0];
|
|
76
|
+
if (token === undefined)
|
|
77
|
+
return invalid();
|
|
78
|
+
at += token.length;
|
|
79
|
+
return /^-?\d+$/.test(token) && !Number.isSafeInteger(Number(token)) ? integer(token) : Number(token);
|
|
80
|
+
};
|
|
81
|
+
const out = value();
|
|
82
|
+
space();
|
|
83
|
+
if (at !== text.length)
|
|
84
|
+
return invalid();
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** Ordering integer versions and stored fields must not round adjacent int64 values. */
|
|
88
|
+
export function compareExactNumbers(a, b) {
|
|
89
|
+
if (/^-?\d+$/.test(String(a)) && /^-?\d+$/.test(String(b))) {
|
|
90
|
+
const x = BigInt(String(a)), y = BigInt(String(b));
|
|
91
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
92
|
+
}
|
|
93
|
+
return Number(a) - Number(b);
|
|
94
|
+
}
|
|
95
|
+
/** Unsafe integer tokens use decimal strings at kernel interfaces; schema checks retain that meaning. */
|
|
96
|
+
export function isExactIntegerValue(value) {
|
|
97
|
+
return Number.isInteger(value) || typeof value === 'string' && /^-?\d+$/.test(value) && !Number.isSafeInteger(Number(value));
|
|
98
|
+
}
|
package/dist/src/executor.d.ts
CHANGED
|
@@ -17,12 +17,26 @@ export type TwinAuthStrategy =
|
|
|
17
17
|
{
|
|
18
18
|
in: 'lanes';
|
|
19
19
|
lanes: Record<string, TwinAuthStrategy>;
|
|
20
|
+
} | {
|
|
21
|
+
in: 'choice';
|
|
22
|
+
choices: Array<{
|
|
23
|
+
credential: string;
|
|
24
|
+
strategy: TwinAuthStrategy;
|
|
25
|
+
}>;
|
|
26
|
+
} | {
|
|
27
|
+
in: 'signature';
|
|
28
|
+
algorithm: 'oauth1-hmac-sha1';
|
|
20
29
|
}
|
|
21
30
|
/** the key rides in the query string (`?key=…`, `?appid=…`) — no header will do */
|
|
22
31
|
| {
|
|
23
32
|
in: 'query';
|
|
24
33
|
name: string;
|
|
25
34
|
}
|
|
35
|
+
/** the key rides in the path (Telegram's `/bot<token>/METHOD_NAME`): `pattern`'s first group is its place */
|
|
36
|
+
| {
|
|
37
|
+
in: 'path';
|
|
38
|
+
pattern: string;
|
|
39
|
+
}
|
|
26
40
|
/** AWS Signature Version 4 — a signature computed per request over the whole canonical request.
|
|
27
41
|
* `scope` answers which region and service THIS request is for, because a pack may route several
|
|
28
42
|
* services over one executor (aws routes six); it is pure and is handed no secret. */
|
|
@@ -125,8 +139,11 @@ export type CredentialCustody = {
|
|
|
125
139
|
};
|
|
126
140
|
/** `vendor`: the vendor whose rate budget every call is charged to (D8): the call is priced and reserved before it is
|
|
127
141
|
* made, refused when the budget is spent, and settled with the vendor's answer (its back-off honoured). */
|
|
142
|
+
/** `now`: the instant a signature is made at (the wall clock unless given: an acceptance whose vendor is a fixture World
|
|
143
|
+
* signs at that World's clock, which its twin checks a signature's date against). */
|
|
128
144
|
export type RemoteExecuteOptions = {
|
|
129
145
|
custody?: CredentialCustody;
|
|
130
146
|
vendor?: string;
|
|
147
|
+
now?: () => Date;
|
|
131
148
|
};
|
|
132
149
|
export declare function buildRemoteExecute(origin: URL, credential: CredentialPayload, auth?: TwinAuthStrategy, vendorHosts?: readonly HostRule[], options?: RemoteExecuteOptions): RemoteExecute;
|