swarpc 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -11
- package/dist/nodes.d.ts +3 -0
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -4
- package/dist/standardschema.d.ts +56 -0
- package/dist/standardschema.d.ts.map +1 -0
- package/dist/standardschema.js +1 -0
- package/dist/types.d.ts +20 -194
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +28 -8
- package/package.json +20 -20
- package/src/client.ts +27 -11
- package/src/nodes.ts +8 -0
- package/src/server.ts +4 -9
- package/src/standardschema.ts +70 -0
- package/src/types.ts +67 -31
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ RPC for Service Workers -- move that heavy computation off of your UI thread!
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- Fully typesafe
|
|
15
|
+
- Supports any [Standard Schema](https://standardschema.dev)-compliant validation library (ArkType, Zod, Valibot, etc.)
|
|
15
16
|
- Cancelable requests
|
|
16
17
|
- Parallelization with multiple worker instances
|
|
17
18
|
- Automatic [transfer](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) of transferable values from- and to- worker code
|
|
@@ -21,13 +22,19 @@ RPC for Service Workers -- move that heavy computation off of your UI thread!
|
|
|
21
22
|
## Installation
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
npm add swarpc
|
|
25
|
+
npm add swarpc
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Also add a [Standard-Schema-compliant validation library](https://standardschema.dev/#what-schema-libraries-implement-the-spec) of your choosing
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# For example
|
|
32
|
+
npm add arktype
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
<details>
|
|
28
36
|
<summary>
|
|
29
37
|
Bleeding edge
|
|
30
|
-
|
|
31
38
|
</summary>
|
|
32
39
|
|
|
33
40
|
If you want to use the latest commit instead of a published version, you can, either by using the Git URL:
|
|
@@ -50,6 +57,10 @@ This works thanks to the fact that `dist/` is published on the repository (and k
|
|
|
50
57
|
|
|
51
58
|
## Usage
|
|
52
59
|
|
|
60
|
+
> [!NOTE]
|
|
61
|
+
> We use [ArkType](https://arktype.io) in the following examples, but, as stated above, any validation library
|
|
62
|
+
> is a-okay (provided that it is [Standard Schema v1](https://standardschema.dev)-compliant)
|
|
63
|
+
|
|
53
64
|
### 1. Declare your procedures in a shared file
|
|
54
65
|
|
|
55
66
|
```typescript
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,kBAAkB,EAClB,KAAK,MAAM,EACX,KAAK,QAAQ,EACd,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,KAAK,EACL,OAAO,EAEP,iBAAiB,EACjB,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;CAC3B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAC;AAEF;;GAEG;AACH,KAAK,OAAO,CAAC,UAAU,SAAS,aAAa,IAAI;IAC/C,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,kCAAkC;IAClC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACzB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACnC,CAAC;AAQF,MAAM,MAAM,cAAc,GAAG;IAC3B,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7B,UAAU,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChC,CAAC;AAKF,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,KAAK,EAAE,SAAS,EAChB,QAAkB,EAClB,eAAuB,EACvB,KAAU,EACV,YAAiB,GAClB,GAAE;IACD,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B,GACL,YAAY,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,kBAAkB,EAClB,KAAK,MAAM,EACX,KAAK,QAAQ,EACd,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,KAAK,EACL,OAAO,EAEP,iBAAiB,EACjB,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;CAC3B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAC;AAEF;;GAEG;AACH,KAAK,OAAO,CAAC,UAAU,SAAS,aAAa,IAAI;IAC/C,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,kCAAkC;IAClC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACzB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACnC,CAAC;AAQF,MAAM,MAAM,cAAc,GAAG;IAC3B,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7B,UAAU,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChC,CAAC;AAKF,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,KAAK,EAAE,SAAS,EAChB,QAAkB,EAClB,eAAuB,EACvB,KAAU,EACV,YAAiB,GAClB,GAAE;IACD,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B,GACL,YAAY,CAAC,UAAU,CAAC,CAgL1B;AAiCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,UAAU,SAAS,aAAa,EAC9D,CAAC,EAAE,kBAAkB,EACrB,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,EACzC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,EAC5B,OAAO,CAAC,EAAE,0BAA0B,GACnC,IAAI,CAiBN;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,SAAS,aAAa,EACxE,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,iBAsFzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/client.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
5
|
import { createLogger, } from "./log.js";
|
|
6
|
-
import { makeNodeId, whoToSendTo } from "./nodes.js";
|
|
6
|
+
import { makeNodeId, nodeIdOrSW, whoToSendTo } from "./nodes.js";
|
|
7
7
|
import { zProcedures, } from "./types.js";
|
|
8
8
|
import { findTransferables } from "./utils.js";
|
|
9
9
|
/**
|
|
@@ -75,12 +75,16 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
|
|
|
75
75
|
// Set the method on the instance
|
|
76
76
|
const _runProcedure = async (input, onProgress = () => { }, reqid, nodeId) => {
|
|
77
77
|
// Validate the input against the procedure's input schema
|
|
78
|
-
procedures[functionName].input.
|
|
78
|
+
const validation = procedures[functionName].input["~standard"].validate(input);
|
|
79
|
+
if (validation instanceof Promise)
|
|
80
|
+
throw new Error("Validations must not be async");
|
|
81
|
+
if (validation.issues)
|
|
82
|
+
throw new Error(`Invalid input: ${validation.issues}`);
|
|
79
83
|
const requestId = reqid ?? makeRequestId();
|
|
80
84
|
// Choose which node to use
|
|
81
85
|
nodeId ??= whoToSendTo(nodes, pendingRequests);
|
|
82
86
|
const node = nodes && nodeId ? nodes[nodeId] : undefined;
|
|
83
|
-
const l = createLogger("client", loglevel, nodeId
|
|
87
|
+
const l = createLogger("client", loglevel, nodeIdOrSW(nodeId), requestId);
|
|
84
88
|
return new Promise((resolve, reject) => {
|
|
85
89
|
// Store promise handlers (as well as progress updates handler)
|
|
86
90
|
// so the client listener can resolve/reject the promise (and react to progress updates)
|
|
@@ -104,19 +108,28 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
|
|
|
104
108
|
};
|
|
105
109
|
// @ts-expect-error
|
|
106
110
|
instance[functionName] = _runProcedure;
|
|
107
|
-
instance[functionName].broadcast = async (input,
|
|
111
|
+
instance[functionName].broadcast = async (input, onProgresses, nodesCount) => {
|
|
108
112
|
let nodesToUse = [undefined];
|
|
109
113
|
if (nodes)
|
|
110
114
|
nodesToUse = Object.keys(nodes);
|
|
111
115
|
if (nodesCount)
|
|
112
116
|
nodesToUse = nodesToUse.slice(0, nodesCount);
|
|
113
|
-
const
|
|
114
|
-
|
|
117
|
+
const progresses = new Map();
|
|
118
|
+
function onProgress(nodeId) {
|
|
119
|
+
if (!onProgresses)
|
|
120
|
+
return (_) => { };
|
|
121
|
+
return (progress) => {
|
|
122
|
+
progresses.set(nodeIdOrSW(nodeId), progress);
|
|
123
|
+
onProgresses(progresses);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const results = await Promise.allSettled(nodesToUse.map(async (id) => _runProcedure(input, onProgress(id), undefined, id)));
|
|
127
|
+
return results.map((r, i) => ({ ...r, node: nodeIdOrSW(nodesToUse[i]) }));
|
|
115
128
|
};
|
|
116
129
|
instance[functionName].cancelable = (input, onProgress) => {
|
|
117
130
|
const requestId = makeRequestId();
|
|
118
131
|
const nodeId = whoToSendTo(nodes, pendingRequests);
|
|
119
|
-
const l = createLogger("client", loglevel, nodeId
|
|
132
|
+
const l = createLogger("client", loglevel, nodeIdOrSW(nodeId), requestId);
|
|
120
133
|
return {
|
|
121
134
|
request: _runProcedure(input, onProgress, requestId, nodeId),
|
|
122
135
|
cancel(reason) {
|
|
@@ -186,7 +199,7 @@ export function postMessageSync(l, worker, message, options) {
|
|
|
186
199
|
* @returns
|
|
187
200
|
*/
|
|
188
201
|
export async function startClientListener(ctx) {
|
|
189
|
-
if (_clientListenerStarted.has(ctx.nodeId
|
|
202
|
+
if (_clientListenerStarted.has(nodeIdOrSW(ctx.nodeId)))
|
|
190
203
|
return;
|
|
191
204
|
const { logger: l, node: worker } = ctx;
|
|
192
205
|
// Get service worker registration if no worker is provided
|
|
@@ -208,7 +221,7 @@ export async function startClientListener(ctx) {
|
|
|
208
221
|
// Ignore other messages that aren't for us
|
|
209
222
|
if (eventData?.by !== "sw&rpc")
|
|
210
223
|
return;
|
|
211
|
-
// We don't use a
|
|
224
|
+
// We don't use a schema here, we trust the server to send valid data
|
|
212
225
|
const payload = eventData;
|
|
213
226
|
// Ignore #initialize request, it's client->server only
|
|
214
227
|
if ("isInitializeRequest" in payload) {
|
|
@@ -249,14 +262,14 @@ export async function startClientListener(ctx) {
|
|
|
249
262
|
else {
|
|
250
263
|
w.addEventListener("message", listener);
|
|
251
264
|
}
|
|
252
|
-
_clientListenerStarted.add(ctx.nodeId
|
|
265
|
+
_clientListenerStarted.add(nodeIdOrSW(ctx.nodeId));
|
|
253
266
|
// Recursive terminal case is ensured by calling this *after* _clientListenerStarted is set to true: startClientListener() will therefore not be called in postMessage() again.
|
|
254
267
|
await postMessage(ctx, {
|
|
255
268
|
by: "sw&rpc",
|
|
256
269
|
functionName: "#initialize",
|
|
257
270
|
isInitializeRequest: true,
|
|
258
271
|
localStorageData: ctx.localStorage,
|
|
259
|
-
nodeId: ctx.nodeId
|
|
272
|
+
nodeId: nodeIdOrSW(ctx.nodeId),
|
|
260
273
|
});
|
|
261
274
|
}
|
|
262
275
|
/**
|
package/dist/nodes.d.ts
CHANGED
|
@@ -9,4 +9,7 @@ export declare function nodeIdFromScope(scope: WorkerGlobalScope, _scopeType?: "
|
|
|
9
9
|
* @source
|
|
10
10
|
*/
|
|
11
11
|
export declare function makeNodeId(): string;
|
|
12
|
+
declare const serviceWorkerNodeId: "(SW)";
|
|
13
|
+
export declare function nodeIdOrSW(id: string | undefined): string | typeof serviceWorkerNodeId;
|
|
14
|
+
export {};
|
|
12
15
|
//# sourceMappingURL=nodes.d.ts.map
|
package/dist/nodes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACpC,SAAS,GAAG,MAAM,CA0BpB;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,iBAAiB,EACxB,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,GAC9C,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
|
|
1
|
+
{"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACpC,SAAS,GAAG,MAAM,CA0BpB;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,iBAAiB,EACxB,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,GAC9C,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,QAAA,MAAM,mBAAmB,EAAG,MAAe,CAAC;AAE5C,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,GAAG,SAAS,GACrB,MAAM,GAAG,OAAO,mBAAmB,CAErC"}
|
package/dist/nodes.js
CHANGED
|
@@ -34,3 +34,7 @@ export function nodeIdFromScope(scope, _scopeType) {
|
|
|
34
34
|
export function makeNodeId() {
|
|
35
35
|
return "N" + Math.random().toString(16).substring(2, 5).toUpperCase();
|
|
36
36
|
}
|
|
37
|
+
const serviceWorkerNodeId = "(SW)"; // Fixed ID for the service worker, as there's only one
|
|
38
|
+
export function nodeIdOrSW(id) {
|
|
39
|
+
return id ?? serviceWorkerNodeId;
|
|
40
|
+
}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAyC,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EACL,kBAAkB,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAyC,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EACL,kBAAkB,EAKlB,uBAAuB,EAEvB,gBAAgB,EAChB,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAMpB;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IAC1B,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,CACvB,IAAI,EAAE,uBAAuB,CAC3B,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EACtB,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EACzB,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CACzB,KACE,IAAI;CACV,CAAC;AAKF;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,QAAkB,EAClB,KAAK,EACL,UAAU,GACX,GAAE;IACD,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC5C,GACL,YAAY,CAAC,UAAU,CAAC,CAwM1B"}
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/// <reference lib="webworker" />
|
|
6
6
|
import { type } from "arktype";
|
|
7
7
|
import { createLogger, injectIntoConsoleGlobal } from "./log.js";
|
|
8
|
-
import { PayloadHeaderSchema, PayloadInitializeSchema,
|
|
8
|
+
import { PayloadHeaderSchema, PayloadInitializeSchema, validatePayloadCore as validatePayloadCore, zImplementations, zProcedures, } from "./types.js";
|
|
9
9
|
import { findTransferables } from "./utils.js";
|
|
10
10
|
import { FauxLocalStorage } from "./localstorage.js";
|
|
11
11
|
import { scopeIsDedicated, scopeIsShared, scopeIsService } from "./scopes.js";
|
|
@@ -120,11 +120,11 @@ export function Server(procedures, { loglevel = "debug", scope, _scopeType, } =
|
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
// Define payload schema for incoming messages
|
|
123
|
-
const payload =
|
|
123
|
+
const payload = validatePayloadCore(schemas, event.data);
|
|
124
124
|
if ("isInitializeRequest" in payload)
|
|
125
125
|
throw "Unreachable: #initialize request payload should've been handled already";
|
|
126
126
|
// Handle abortion requests (pro-choice ftw!!)
|
|
127
|
-
if (payload
|
|
127
|
+
if ("abort" in payload) {
|
|
128
128
|
const controller = abortControllers.get(requestId);
|
|
129
129
|
if (!controller)
|
|
130
130
|
await postError("No abort controller found for request");
|
|
@@ -133,7 +133,7 @@ export function Server(procedures, { loglevel = "debug", scope, _scopeType, } =
|
|
|
133
133
|
}
|
|
134
134
|
// Set up the abort controller for this request
|
|
135
135
|
abortControllers.set(requestId, new AbortController());
|
|
136
|
-
if (!payload
|
|
136
|
+
if (!("input" in payload)) {
|
|
137
137
|
await postError("No input provided");
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** The Standard Schema interface. */
|
|
2
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
3
|
+
/** The Standard Schema properties. */
|
|
4
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
5
|
+
}
|
|
6
|
+
export declare namespace StandardSchemaV1 {
|
|
7
|
+
/** The Standard Schema properties interface. */
|
|
8
|
+
interface Props<Input = unknown, Output = Input> {
|
|
9
|
+
/** The version number of the standard. */
|
|
10
|
+
readonly version: 1;
|
|
11
|
+
/** The vendor name of the schema library. */
|
|
12
|
+
readonly vendor: string;
|
|
13
|
+
/** Validates unknown input values. */
|
|
14
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
15
|
+
/** Inferred types associated with the schema. */
|
|
16
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The result interface of the validate function. */
|
|
19
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
20
|
+
/** The result interface if validation succeeds. */
|
|
21
|
+
interface SuccessResult<Output> {
|
|
22
|
+
/** The typed output value. */
|
|
23
|
+
readonly value: Output;
|
|
24
|
+
/** The non-existent issues. */
|
|
25
|
+
readonly issues?: undefined;
|
|
26
|
+
}
|
|
27
|
+
/** The result interface if validation fails. */
|
|
28
|
+
interface FailureResult {
|
|
29
|
+
/** The issues of failed validation. */
|
|
30
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
31
|
+
}
|
|
32
|
+
/** The issue interface of the failure output. */
|
|
33
|
+
interface Issue {
|
|
34
|
+
/** The error message of the issue. */
|
|
35
|
+
readonly message: string;
|
|
36
|
+
/** The path of the issue, if any. */
|
|
37
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** The path segment interface of the issue. */
|
|
40
|
+
interface PathSegment {
|
|
41
|
+
/** The key representing a path segment. */
|
|
42
|
+
readonly key: PropertyKey;
|
|
43
|
+
}
|
|
44
|
+
/** The Standard Schema types interface. */
|
|
45
|
+
interface Types<Input = unknown, Output = Input> {
|
|
46
|
+
/** The input type of the schema. */
|
|
47
|
+
readonly input: Input;
|
|
48
|
+
/** The output type of the schema. */
|
|
49
|
+
readonly output: Output;
|
|
50
|
+
}
|
|
51
|
+
/** Infers the input type of a Standard Schema. */
|
|
52
|
+
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
53
|
+
/** Infers the output type of a Standard Schema. */
|
|
54
|
+
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=standardschema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standardschema.d.ts","sourceRoot":"","sources":["../src/standardschema.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAC/D,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;CAC7D;AAED,MAAM,CAAC,OAAO,WAAW,gBAAgB,CAAC;IACxC,gDAAgD;IAChD,UAAiB,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;QACpD,0CAA0C;QAC1C,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,6CAA6C;QAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,sCAAsC;QACtC,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,OAAO,KACX,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9C,iDAAiD;QACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;KACnD;IAED,qDAAqD;IACrD,KAAY,MAAM,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC;IAEnE,mDAAmD;IACnD,UAAiB,aAAa,CAAC,MAAM;QACnC,8BAA8B;QAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,+BAA+B;QAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;KAC7B;IAED,gDAAgD;IAChD,UAAiB,aAAa;QAC5B,uCAAuC;QACvC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;KACvC;IAED,iDAAiD;IACjD,UAAiB,KAAK;QACpB,sCAAsC;QACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,qCAAqC;QACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC;KACtE;IAED,+CAA+C;IAC/C,UAAiB,WAAW;QAC1B,2CAA2C;QAC3C,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;KAC3B;IAED,2CAA2C;IAC3C,UAAiB,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;QACpD,oCAAoC;QACpC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;QACtB,qCAAqC;QACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB;IAED,kDAAkD;IAClD,KAAY,UAAU,CAAC,MAAM,SAAS,gBAAgB,IAAI,WAAW,CACnE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAC7B,CAAC,OAAO,CAAC,CAAC;IAEX,mDAAmD;IACnD,KAAY,WAAW,CAAC,MAAM,SAAS,gBAAgB,IAAI,WAAW,CACpE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAC7B,CAAC,QAAQ,CAAC,CAAC;CACb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import type { StandardSchemaV1 as Schema } from "./standardschema.js";
|
|
6
6
|
import { RequestBoundLogger } from "./log.js";
|
|
7
7
|
/**
|
|
8
8
|
* A procedure declaration
|
|
9
9
|
*/
|
|
10
|
-
export type Procedure<I extends
|
|
10
|
+
export type Procedure<I extends Schema, P extends Schema, S extends Schema> = {
|
|
11
11
|
/**
|
|
12
12
|
* ArkType type for the input (first argument) of the procedure, when calling it from the client.
|
|
13
13
|
*/
|
|
@@ -53,15 +53,15 @@ export type CancelablePromise<T = unknown> = {
|
|
|
53
53
|
/**
|
|
54
54
|
* An implementation of a procedure
|
|
55
55
|
*/
|
|
56
|
-
export type ProcedureImplementation<I extends
|
|
56
|
+
export type ProcedureImplementation<I extends Schema, P extends Schema, S extends Schema> = (
|
|
57
57
|
/**
|
|
58
58
|
* Input data for the procedure
|
|
59
59
|
*/
|
|
60
|
-
input: I
|
|
60
|
+
input: Schema.InferInput<I>,
|
|
61
61
|
/**
|
|
62
62
|
* Callback to call with progress updates.
|
|
63
63
|
*/
|
|
64
|
-
onProgress: (progress: P
|
|
64
|
+
onProgress: (progress: Schema.InferInput<P>) => void,
|
|
65
65
|
/**
|
|
66
66
|
* Additional tools useful when implementing the procedure.
|
|
67
67
|
*/
|
|
@@ -78,14 +78,14 @@ tools: {
|
|
|
78
78
|
* ID of the Node the request is being processed on.
|
|
79
79
|
*/
|
|
80
80
|
nodeId: string;
|
|
81
|
-
}) => Promise<S
|
|
81
|
+
}) => Promise<Schema.InferInput<S>>;
|
|
82
82
|
/**
|
|
83
83
|
* Declarations of procedures by name.
|
|
84
84
|
*
|
|
85
85
|
* An example of declaring procedures:
|
|
86
86
|
* {@includeCode ../example/src/lib/procedures.ts}
|
|
87
87
|
*/
|
|
88
|
-
export type ProceduresMap = Record<string, Procedure<
|
|
88
|
+
export type ProceduresMap = Record<string, Procedure<Schema, Schema, Schema>>;
|
|
89
89
|
/**
|
|
90
90
|
* Implementations of procedures by name
|
|
91
91
|
*/
|
|
@@ -99,7 +99,7 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
99
99
|
/**
|
|
100
100
|
* Called when a procedure call has been successful.
|
|
101
101
|
*/
|
|
102
|
-
success?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Procedures[Procedure]["success"]
|
|
102
|
+
success?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Schema.InferOutput<Procedures[Procedure]["success"]>) => void;
|
|
103
103
|
/**
|
|
104
104
|
* Called when a procedure call has failed.
|
|
105
105
|
*/
|
|
@@ -107,9 +107,9 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
107
107
|
/**
|
|
108
108
|
* Called when a procedure call sends progress updates.
|
|
109
109
|
*/
|
|
110
|
-
progress?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Procedures[Procedure]["progress"]
|
|
110
|
+
progress?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Schema.InferOutput<Procedures[Procedure]["progress"]>) => void;
|
|
111
111
|
};
|
|
112
|
-
export declare const PayloadInitializeSchema: import("arktype/internal/
|
|
112
|
+
export declare const PayloadInitializeSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
113
113
|
by: "sw&rpc";
|
|
114
114
|
functionName: "#initialize";
|
|
115
115
|
isInitializeRequest: true;
|
|
@@ -145,11 +145,11 @@ export declare const PayloadCoreSchema: import("arktype").Generic<[["I", unknown
|
|
|
145
145
|
};
|
|
146
146
|
}, {}, {}>;
|
|
147
147
|
export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
|
|
148
|
-
input: PM[Name]["input"]
|
|
148
|
+
input: Schema.InferOutput<PM[Name]["input"]>;
|
|
149
149
|
} | {
|
|
150
|
-
progress: PM[Name]["progress"]
|
|
150
|
+
progress: Schema.InferOutput<PM[Name]["progress"]>;
|
|
151
151
|
} | {
|
|
152
|
-
result: PM[Name]["success"]
|
|
152
|
+
result: Schema.InferOutput<PM[Name]["success"]>;
|
|
153
153
|
} | {
|
|
154
154
|
abort: {
|
|
155
155
|
reason: string;
|
|
@@ -162,183 +162,7 @@ export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof
|
|
|
162
162
|
/**
|
|
163
163
|
* @source
|
|
164
164
|
*/
|
|
165
|
-
export declare
|
|
166
|
-
PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
167
|
-
readonly "input?": "I";
|
|
168
|
-
readonly "progress?": "P";
|
|
169
|
-
readonly "result?": "S";
|
|
170
|
-
readonly "abort?": {
|
|
171
|
-
readonly reason: "string";
|
|
172
|
-
};
|
|
173
|
-
readonly "error?": {
|
|
174
|
-
readonly message: "string";
|
|
175
|
-
};
|
|
176
|
-
}, {}, {}>, {
|
|
177
|
-
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
178
|
-
readonly "input?": "I";
|
|
179
|
-
readonly "progress?": "P";
|
|
180
|
-
readonly "result?": "S";
|
|
181
|
-
readonly "abort?": {
|
|
182
|
-
readonly reason: "string";
|
|
183
|
-
};
|
|
184
|
-
readonly "error?": {
|
|
185
|
-
readonly message: "string";
|
|
186
|
-
};
|
|
187
|
-
}, {}, {}>;
|
|
188
|
-
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
189
|
-
readonly by: "\"sw&rpc\"";
|
|
190
|
-
readonly functionName: "Name";
|
|
191
|
-
readonly requestId: "string >= 1";
|
|
192
|
-
}, {}, {}>;
|
|
193
|
-
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
194
|
-
by: "sw&rpc";
|
|
195
|
-
functionName: "#initialize";
|
|
196
|
-
isInitializeRequest: true;
|
|
197
|
-
localStorageData: Record<string, unknown>;
|
|
198
|
-
nodeId: string;
|
|
199
|
-
}, {}> & {
|
|
200
|
-
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
201
|
-
by: "sw&rpc";
|
|
202
|
-
functionName: "#initialize";
|
|
203
|
-
isInitializeRequest: true;
|
|
204
|
-
localStorageData: Record<string, unknown>;
|
|
205
|
-
nodeId: string;
|
|
206
|
-
}, {}>, "unparsed"];
|
|
207
|
-
};
|
|
208
|
-
} & {}>;
|
|
209
|
-
PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
|
|
210
|
-
readonly by: "\"sw&rpc\"";
|
|
211
|
-
readonly functionName: "Name";
|
|
212
|
-
readonly requestId: "string >= 1";
|
|
213
|
-
}, {}, {}>, {
|
|
214
|
-
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
215
|
-
readonly "input?": "I";
|
|
216
|
-
readonly "progress?": "P";
|
|
217
|
-
readonly "result?": "S";
|
|
218
|
-
readonly "abort?": {
|
|
219
|
-
readonly reason: "string";
|
|
220
|
-
};
|
|
221
|
-
readonly "error?": {
|
|
222
|
-
readonly message: "string";
|
|
223
|
-
};
|
|
224
|
-
}, {}, {}>;
|
|
225
|
-
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
226
|
-
readonly by: "\"sw&rpc\"";
|
|
227
|
-
readonly functionName: "Name";
|
|
228
|
-
readonly requestId: "string >= 1";
|
|
229
|
-
}, {}, {}>;
|
|
230
|
-
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
231
|
-
by: "sw&rpc";
|
|
232
|
-
functionName: "#initialize";
|
|
233
|
-
isInitializeRequest: true;
|
|
234
|
-
localStorageData: Record<string, unknown>;
|
|
235
|
-
nodeId: string;
|
|
236
|
-
}, {}> & {
|
|
237
|
-
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
238
|
-
by: "sw&rpc";
|
|
239
|
-
functionName: "#initialize";
|
|
240
|
-
isInitializeRequest: true;
|
|
241
|
-
localStorageData: Record<string, unknown>;
|
|
242
|
-
nodeId: string;
|
|
243
|
-
}, {}>, "unparsed"];
|
|
244
|
-
};
|
|
245
|
-
} & {}>;
|
|
246
|
-
PayloadInitializeSchema: {
|
|
247
|
-
by: "sw&rpc";
|
|
248
|
-
functionName: "#initialize";
|
|
249
|
-
isInitializeRequest: true;
|
|
250
|
-
localStorageData: Record<string, unknown>;
|
|
251
|
-
nodeId: string;
|
|
252
|
-
};
|
|
253
|
-
}, {
|
|
254
|
-
PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
255
|
-
readonly "input?": "I";
|
|
256
|
-
readonly "progress?": "P";
|
|
257
|
-
readonly "result?": "S";
|
|
258
|
-
readonly "abort?": {
|
|
259
|
-
readonly reason: "string";
|
|
260
|
-
};
|
|
261
|
-
readonly "error?": {
|
|
262
|
-
readonly message: "string";
|
|
263
|
-
};
|
|
264
|
-
}, {}, {}>, {
|
|
265
|
-
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
266
|
-
readonly "input?": "I";
|
|
267
|
-
readonly "progress?": "P";
|
|
268
|
-
readonly "result?": "S";
|
|
269
|
-
readonly "abort?": {
|
|
270
|
-
readonly reason: "string";
|
|
271
|
-
};
|
|
272
|
-
readonly "error?": {
|
|
273
|
-
readonly message: "string";
|
|
274
|
-
};
|
|
275
|
-
}, {}, {}>;
|
|
276
|
-
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
277
|
-
readonly by: "\"sw&rpc\"";
|
|
278
|
-
readonly functionName: "Name";
|
|
279
|
-
readonly requestId: "string >= 1";
|
|
280
|
-
}, {}, {}>;
|
|
281
|
-
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
282
|
-
by: "sw&rpc";
|
|
283
|
-
functionName: "#initialize";
|
|
284
|
-
isInitializeRequest: true;
|
|
285
|
-
localStorageData: Record<string, unknown>;
|
|
286
|
-
nodeId: string;
|
|
287
|
-
}, {}> & {
|
|
288
|
-
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
289
|
-
by: "sw&rpc";
|
|
290
|
-
functionName: "#initialize";
|
|
291
|
-
isInitializeRequest: true;
|
|
292
|
-
localStorageData: Record<string, unknown>;
|
|
293
|
-
nodeId: string;
|
|
294
|
-
}, {}>, "unparsed"];
|
|
295
|
-
};
|
|
296
|
-
} & {}>;
|
|
297
|
-
PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
|
|
298
|
-
readonly by: "\"sw&rpc\"";
|
|
299
|
-
readonly functionName: "Name";
|
|
300
|
-
readonly requestId: "string >= 1";
|
|
301
|
-
}, {}, {}>, {
|
|
302
|
-
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
303
|
-
readonly "input?": "I";
|
|
304
|
-
readonly "progress?": "P";
|
|
305
|
-
readonly "result?": "S";
|
|
306
|
-
readonly "abort?": {
|
|
307
|
-
readonly reason: "string";
|
|
308
|
-
};
|
|
309
|
-
readonly "error?": {
|
|
310
|
-
readonly message: "string";
|
|
311
|
-
};
|
|
312
|
-
}, {}, {}>;
|
|
313
|
-
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
314
|
-
readonly by: "\"sw&rpc\"";
|
|
315
|
-
readonly functionName: "Name";
|
|
316
|
-
readonly requestId: "string >= 1";
|
|
317
|
-
}, {}, {}>;
|
|
318
|
-
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
319
|
-
by: "sw&rpc";
|
|
320
|
-
functionName: "#initialize";
|
|
321
|
-
isInitializeRequest: true;
|
|
322
|
-
localStorageData: Record<string, unknown>;
|
|
323
|
-
nodeId: string;
|
|
324
|
-
}, {}> & {
|
|
325
|
-
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
326
|
-
by: "sw&rpc";
|
|
327
|
-
functionName: "#initialize";
|
|
328
|
-
isInitializeRequest: true;
|
|
329
|
-
localStorageData: Record<string, unknown>;
|
|
330
|
-
nodeId: string;
|
|
331
|
-
}, {}>, "unparsed"];
|
|
332
|
-
};
|
|
333
|
-
} & {}>;
|
|
334
|
-
PayloadInitializeSchema: {
|
|
335
|
-
by: "sw&rpc";
|
|
336
|
-
functionName: "#initialize";
|
|
337
|
-
isInitializeRequest: true;
|
|
338
|
-
localStorageData: Record<string, unknown>;
|
|
339
|
-
nodeId: string;
|
|
340
|
-
};
|
|
341
|
-
}>;
|
|
165
|
+
export declare function validatePayloadCore<PM extends ProceduresMap, Name extends keyof PM>(procedure: PM[Name], payload: unknown): PayloadCore<PM, keyof PM>;
|
|
342
166
|
/**
|
|
343
167
|
* The effective payload as sent by the server to the client
|
|
344
168
|
*/
|
|
@@ -346,19 +170,21 @@ export type Payload<PM extends ProceduresMap, Name extends keyof PM = keyof PM>
|
|
|
346
170
|
/**
|
|
347
171
|
* A procedure's corresponding method on the client instance -- used to call the procedure. If you want to be able to cancel the request, you can use the `cancelable` method instead of running the procedure directly.
|
|
348
172
|
*/
|
|
349
|
-
export type ClientMethod<P extends Procedure<
|
|
173
|
+
export type ClientMethod<P extends Procedure<Schema, Schema, Schema>> = ((input: Schema.InferInput<P["input"]>, onProgress?: (progress: Schema.InferOutput<P["progress"]>) => void) => Promise<Schema.InferOutput<P["success"]>>) & {
|
|
350
174
|
/**
|
|
351
175
|
* A method that returns a `CancelablePromise`. Cancel it by calling `.cancel(reason)` on it, and wait for the request to resolve by awaiting the `request` property on the returned object.
|
|
352
176
|
*/
|
|
353
|
-
cancelable: (input: P["input"]
|
|
177
|
+
cancelable: (input: Schema.InferInput<P["input"]>, onProgress?: (progress: Schema.InferOutput<P["progress"]>) => void, requestId?: string) => CancelablePromise<Schema.InferOutput<P["success"]>>;
|
|
354
178
|
/**
|
|
355
179
|
* Send the request to specific nodes, or all nodes.
|
|
356
180
|
* Returns an array of results, one for each node the request was sent to.
|
|
357
181
|
* Each result is a {@link PromiseSettledResult}, with also an additional property, the node ID of the request
|
|
358
182
|
*/
|
|
359
|
-
broadcast: (input: P["input"]
|
|
183
|
+
broadcast: (input: Schema.InferInput<P["input"]>, onProgress?: (
|
|
184
|
+
/** Map of node IDs to their progress updates */
|
|
185
|
+
progresses: Map<string, Schema.InferOutput<P["progress"]>>) => void,
|
|
360
186
|
/** Number of nodes to send the request to. Leave undefined to send to all nodes */
|
|
361
|
-
nodes?: number) => Promise<Array<PromiseSettledResult<P["success"]
|
|
187
|
+
nodes?: number) => Promise<Array<PromiseSettledResult<Schema.InferOutput<P["success"]>> & {
|
|
362
188
|
node: string;
|
|
363
189
|
}>>;
|
|
364
190
|
};
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
|
-
import { type } from "arktype";
|
|
5
|
+
import { ArkErrors, type } from "arktype";
|
|
6
6
|
export const PayloadInitializeSchema = type({
|
|
7
7
|
by: '"sw&rpc"',
|
|
8
8
|
functionName: '"#initialize"',
|
|
@@ -28,16 +28,36 @@ export const PayloadCoreSchema = type("<I, P, S>", {
|
|
|
28
28
|
"abort?": { reason: "string" },
|
|
29
29
|
"error?": { message: "string" },
|
|
30
30
|
});
|
|
31
|
+
const AbortOrError = type.or({ abort: { reason: "string" } }, { error: { message: "string" } });
|
|
31
32
|
/**
|
|
32
33
|
* @source
|
|
33
34
|
*/
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
]);
|
|
35
|
+
export function validatePayloadCore(procedure, payload) {
|
|
36
|
+
if (typeof payload !== "object")
|
|
37
|
+
throw new Error("payload is not an object");
|
|
38
|
+
if (payload === null)
|
|
39
|
+
throw new Error("payload is null");
|
|
40
|
+
if ("input" in payload) {
|
|
41
|
+
const input = procedure.input["~standard"].validate(payload.input);
|
|
42
|
+
if ("value" in input)
|
|
43
|
+
return { input: input.value };
|
|
44
|
+
}
|
|
45
|
+
if ("progress" in payload) {
|
|
46
|
+
const progress = procedure.progress["~standard"].validate(payload.progress);
|
|
47
|
+
if ("value" in progress)
|
|
48
|
+
return { progress: progress.value };
|
|
49
|
+
}
|
|
50
|
+
if ("result" in payload) {
|
|
51
|
+
const result = procedure.success["~standard"].validate(payload.result);
|
|
52
|
+
if ("value" in result)
|
|
53
|
+
return { result: result.value };
|
|
54
|
+
}
|
|
55
|
+
const abortOrError = AbortOrError(payload);
|
|
56
|
+
if (!(abortOrError instanceof ArkErrors)) {
|
|
57
|
+
return abortOrError;
|
|
58
|
+
}
|
|
59
|
+
throw new Error("invalid payload");
|
|
60
|
+
}
|
|
41
61
|
/**
|
|
42
62
|
* Symbol used as the key for the procedures map on the server instance
|
|
43
63
|
* @internal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Full type-safe RPC library for service worker -- move things off of the UI thread with ease!",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"service-workers",
|
|
@@ -40,42 +40,42 @@
|
|
|
40
40
|
"typedoc:withplugins": "npm run typedoc:plugins && npm run typedoc",
|
|
41
41
|
"prepare": "husky"
|
|
42
42
|
},
|
|
43
|
-
"peerDependencies": {
|
|
44
|
-
"arktype": "^2.1.22"
|
|
45
|
-
},
|
|
46
43
|
"devDependencies": {
|
|
47
44
|
"@8hobbies/typedoc-plugin-plausible": "^2.2.0",
|
|
48
|
-
"@playwright/test": "^1.
|
|
49
|
-
"@vitest/web-worker": "^
|
|
45
|
+
"@playwright/test": "^1.56.1",
|
|
46
|
+
"@vitest/web-worker": "^4.0.6",
|
|
50
47
|
"date-fns": "^4.1.0",
|
|
51
48
|
"husky": "^9.1.7",
|
|
52
49
|
"kacl": "^1.1.1",
|
|
53
|
-
"knip": "^5.
|
|
54
|
-
"lint-staged": "^16.
|
|
50
|
+
"knip": "^5.67.0",
|
|
51
|
+
"lint-staged": "^16.2.6",
|
|
55
52
|
"nodemon": "^3.1.10",
|
|
56
|
-
"oxlint": "^1.
|
|
57
|
-
"pkg-pr-new": "^0.0.
|
|
53
|
+
"oxlint": "^1.25.0",
|
|
54
|
+
"pkg-pr-new": "^0.0.60",
|
|
58
55
|
"prettier": "^3.6.2",
|
|
59
56
|
"sirv-cli": "^3.0.1",
|
|
60
|
-
"typedoc": "^0.28.
|
|
61
|
-
"typedoc-material-theme": "^1.4.
|
|
62
|
-
"typedoc-plugin-dt-links": "^2.0.
|
|
57
|
+
"typedoc": "^0.28.14",
|
|
58
|
+
"typedoc-material-theme": "^1.4.1",
|
|
59
|
+
"typedoc-plugin-dt-links": "^2.0.27",
|
|
63
60
|
"typedoc-plugin-extras": "^4.0.1",
|
|
64
61
|
"typedoc-plugin-inline-sources": "^1.3.0",
|
|
65
|
-
"typedoc-plugin-mdn-links": "^5.0.
|
|
66
|
-
"typedoc-plugin-redirect": "^1.2.
|
|
67
|
-
"typescript": "^5.9.
|
|
68
|
-
"vite": "^7.1.
|
|
69
|
-
"vitest": "^
|
|
62
|
+
"typedoc-plugin-mdn-links": "^5.0.10",
|
|
63
|
+
"typedoc-plugin-redirect": "^1.2.1",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"vite": "^7.1.12",
|
|
66
|
+
"vitest": "^4.0.6"
|
|
70
67
|
},
|
|
71
68
|
"volta": {
|
|
72
|
-
"node": "24.
|
|
73
|
-
"npm": "11.6.
|
|
69
|
+
"node": "24.11.0",
|
|
70
|
+
"npm": "11.6.2"
|
|
74
71
|
},
|
|
75
72
|
"lint-staged": {
|
|
76
73
|
"*.{ts,js,md,json,yaml,yml}": [
|
|
77
74
|
"oxlint --fix",
|
|
78
75
|
"prettier --write"
|
|
79
76
|
]
|
|
77
|
+
},
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"arktype": "^2.1.25"
|
|
80
80
|
}
|
|
81
81
|
}
|
package/src/client.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type Logger,
|
|
10
10
|
type LogLevel,
|
|
11
11
|
} from "./log.js";
|
|
12
|
-
import { makeNodeId, whoToSendTo } from "./nodes.js";
|
|
12
|
+
import { makeNodeId, nodeIdOrSW, whoToSendTo } from "./nodes.js";
|
|
13
13
|
import {
|
|
14
14
|
ClientMethod,
|
|
15
15
|
Hooks,
|
|
@@ -178,7 +178,12 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
178
178
|
nodeId?: string,
|
|
179
179
|
) => {
|
|
180
180
|
// Validate the input against the procedure's input schema
|
|
181
|
-
|
|
181
|
+
const validation =
|
|
182
|
+
procedures[functionName].input["~standard"].validate(input);
|
|
183
|
+
if (validation instanceof Promise)
|
|
184
|
+
throw new Error("Validations must not be async");
|
|
185
|
+
if (validation.issues)
|
|
186
|
+
throw new Error(`Invalid input: ${validation.issues}`);
|
|
182
187
|
|
|
183
188
|
const requestId = reqid ?? makeRequestId();
|
|
184
189
|
|
|
@@ -186,7 +191,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
186
191
|
nodeId ??= whoToSendTo(nodes, pendingRequests);
|
|
187
192
|
const node = nodes && nodeId ? nodes[nodeId] : undefined;
|
|
188
193
|
|
|
189
|
-
const l = createLogger("client", loglevel, nodeId
|
|
194
|
+
const l = createLogger("client", loglevel, nodeIdOrSW(nodeId), requestId);
|
|
190
195
|
|
|
191
196
|
return new Promise((resolve, reject) => {
|
|
192
197
|
// Store promise handlers (as well as progress updates handler)
|
|
@@ -217,26 +222,37 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
217
222
|
instance[functionName] = _runProcedure;
|
|
218
223
|
instance[functionName]!.broadcast = async (
|
|
219
224
|
input,
|
|
220
|
-
|
|
225
|
+
onProgresses,
|
|
221
226
|
nodesCount,
|
|
222
227
|
) => {
|
|
223
228
|
let nodesToUse: Array<string | undefined> = [undefined];
|
|
224
229
|
if (nodes) nodesToUse = Object.keys(nodes);
|
|
225
230
|
if (nodesCount) nodesToUse = nodesToUse.slice(0, nodesCount);
|
|
226
231
|
|
|
232
|
+
const progresses = new Map<string, unknown>();
|
|
233
|
+
|
|
234
|
+
function onProgress(nodeId: string | undefined) {
|
|
235
|
+
if (!onProgresses) return (_: unknown) => {};
|
|
236
|
+
|
|
237
|
+
return (progress: unknown) => {
|
|
238
|
+
progresses.set(nodeIdOrSW(nodeId), progress);
|
|
239
|
+
onProgresses(progresses);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
227
243
|
const results = await Promise.allSettled(
|
|
228
244
|
nodesToUse.map(async (id) =>
|
|
229
|
-
_runProcedure(input, onProgress, undefined, id),
|
|
245
|
+
_runProcedure(input, onProgress(id), undefined, id),
|
|
230
246
|
),
|
|
231
247
|
);
|
|
232
248
|
|
|
233
|
-
return results.map((r, i) => ({ ...r, node: nodesToUse[i]
|
|
249
|
+
return results.map((r, i) => ({ ...r, node: nodeIdOrSW(nodesToUse[i]) }));
|
|
234
250
|
};
|
|
235
251
|
instance[functionName]!.cancelable = (input, onProgress) => {
|
|
236
252
|
const requestId = makeRequestId();
|
|
237
253
|
const nodeId = whoToSendTo(nodes, pendingRequests);
|
|
238
254
|
|
|
239
|
-
const l = createLogger("client", loglevel, nodeId
|
|
255
|
+
const l = createLogger("client", loglevel, nodeIdOrSW(nodeId), requestId);
|
|
240
256
|
|
|
241
257
|
return {
|
|
242
258
|
request: _runProcedure(input, onProgress, requestId, nodeId),
|
|
@@ -336,7 +352,7 @@ export function postMessageSync<Procedures extends ProceduresMap>(
|
|
|
336
352
|
export async function startClientListener<Procedures extends ProceduresMap>(
|
|
337
353
|
ctx: Context<Procedures>,
|
|
338
354
|
) {
|
|
339
|
-
if (_clientListenerStarted.has(ctx.nodeId
|
|
355
|
+
if (_clientListenerStarted.has(nodeIdOrSW(ctx.nodeId))) return;
|
|
340
356
|
|
|
341
357
|
const { logger: l, node: worker } = ctx;
|
|
342
358
|
|
|
@@ -363,7 +379,7 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
363
379
|
// Ignore other messages that aren't for us
|
|
364
380
|
if (eventData?.by !== "sw&rpc") return;
|
|
365
381
|
|
|
366
|
-
// We don't use a
|
|
382
|
+
// We don't use a schema here, we trust the server to send valid data
|
|
367
383
|
const payload = eventData as Payload<Procedures>;
|
|
368
384
|
|
|
369
385
|
// Ignore #initialize request, it's client->server only
|
|
@@ -410,7 +426,7 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
410
426
|
w.addEventListener("message", listener);
|
|
411
427
|
}
|
|
412
428
|
|
|
413
|
-
_clientListenerStarted.add(ctx.nodeId
|
|
429
|
+
_clientListenerStarted.add(nodeIdOrSW(ctx.nodeId));
|
|
414
430
|
|
|
415
431
|
// Recursive terminal case is ensured by calling this *after* _clientListenerStarted is set to true: startClientListener() will therefore not be called in postMessage() again.
|
|
416
432
|
await postMessage(ctx, {
|
|
@@ -418,7 +434,7 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
418
434
|
functionName: "#initialize",
|
|
419
435
|
isInitializeRequest: true,
|
|
420
436
|
localStorageData: ctx.localStorage,
|
|
421
|
-
nodeId: ctx.nodeId
|
|
437
|
+
nodeId: nodeIdOrSW(ctx.nodeId),
|
|
422
438
|
});
|
|
423
439
|
}
|
|
424
440
|
|
package/src/nodes.ts
CHANGED
|
@@ -53,3 +53,11 @@ export function nodeIdFromScope(
|
|
|
53
53
|
export function makeNodeId(): string {
|
|
54
54
|
return "N" + Math.random().toString(16).substring(2, 5).toUpperCase();
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
const serviceWorkerNodeId = "(SW)" as const; // Fixed ID for the service worker, as there's only one
|
|
58
|
+
|
|
59
|
+
export function nodeIdOrSW(
|
|
60
|
+
id: string | undefined,
|
|
61
|
+
): string | typeof serviceWorkerNodeId {
|
|
62
|
+
return id ?? serviceWorkerNodeId;
|
|
63
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
PayloadCore,
|
|
13
13
|
PayloadHeaderSchema,
|
|
14
14
|
PayloadInitializeSchema,
|
|
15
|
-
PayloadSchema,
|
|
16
15
|
ProcedureImplementation,
|
|
16
|
+
validatePayloadCore as validatePayloadCore,
|
|
17
17
|
zImplementations,
|
|
18
18
|
zProcedures,
|
|
19
19
|
type ProceduresMap,
|
|
@@ -188,18 +188,13 @@ export function Server<Procedures extends ProceduresMap>(
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// Define payload schema for incoming messages
|
|
191
|
-
const payload =
|
|
192
|
-
type(`"${functionName}"`),
|
|
193
|
-
schemas.input,
|
|
194
|
-
schemas.progress,
|
|
195
|
-
schemas.success,
|
|
196
|
-
).assert(event.data);
|
|
191
|
+
const payload = validatePayloadCore(schemas, event.data);
|
|
197
192
|
|
|
198
193
|
if ("isInitializeRequest" in payload)
|
|
199
194
|
throw "Unreachable: #initialize request payload should've been handled already";
|
|
200
195
|
|
|
201
196
|
// Handle abortion requests (pro-choice ftw!!)
|
|
202
|
-
if (payload
|
|
197
|
+
if ("abort" in payload) {
|
|
203
198
|
const controller = abortControllers.get(requestId);
|
|
204
199
|
|
|
205
200
|
if (!controller)
|
|
@@ -212,7 +207,7 @@ export function Server<Procedures extends ProceduresMap>(
|
|
|
212
207
|
// Set up the abort controller for this request
|
|
213
208
|
abortControllers.set(requestId, new AbortController());
|
|
214
209
|
|
|
215
|
-
if (!payload
|
|
210
|
+
if (!("input" in payload)) {
|
|
216
211
|
await postError("No input provided");
|
|
217
212
|
return;
|
|
218
213
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** The Standard Schema interface. */
|
|
2
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
3
|
+
/** The Standard Schema properties. */
|
|
4
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export declare namespace StandardSchemaV1 {
|
|
8
|
+
/** The Standard Schema properties interface. */
|
|
9
|
+
export interface Props<Input = unknown, Output = Input> {
|
|
10
|
+
/** The version number of the standard. */
|
|
11
|
+
readonly version: 1;
|
|
12
|
+
/** The vendor name of the schema library. */
|
|
13
|
+
readonly vendor: string;
|
|
14
|
+
/** Validates unknown input values. */
|
|
15
|
+
readonly validate: (
|
|
16
|
+
value: unknown,
|
|
17
|
+
) => Result<Output> | Promise<Result<Output>>;
|
|
18
|
+
/** Inferred types associated with the schema. */
|
|
19
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The result interface of the validate function. */
|
|
23
|
+
export type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
24
|
+
|
|
25
|
+
/** The result interface if validation succeeds. */
|
|
26
|
+
export interface SuccessResult<Output> {
|
|
27
|
+
/** The typed output value. */
|
|
28
|
+
readonly value: Output;
|
|
29
|
+
/** The non-existent issues. */
|
|
30
|
+
readonly issues?: undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The result interface if validation fails. */
|
|
34
|
+
export interface FailureResult {
|
|
35
|
+
/** The issues of failed validation. */
|
|
36
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The issue interface of the failure output. */
|
|
40
|
+
export interface Issue {
|
|
41
|
+
/** The error message of the issue. */
|
|
42
|
+
readonly message: string;
|
|
43
|
+
/** The path of the issue, if any. */
|
|
44
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The path segment interface of the issue. */
|
|
48
|
+
export interface PathSegment {
|
|
49
|
+
/** The key representing a path segment. */
|
|
50
|
+
readonly key: PropertyKey;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The Standard Schema types interface. */
|
|
54
|
+
export interface Types<Input = unknown, Output = Input> {
|
|
55
|
+
/** The input type of the schema. */
|
|
56
|
+
readonly input: Input;
|
|
57
|
+
/** The output type of the schema. */
|
|
58
|
+
readonly output: Output;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Infers the input type of a Standard Schema. */
|
|
62
|
+
export type InferInput<Schema extends StandardSchemaV1> = NonNullable<
|
|
63
|
+
Schema["~standard"]["types"]
|
|
64
|
+
>["input"];
|
|
65
|
+
|
|
66
|
+
/** Infers the output type of a Standard Schema. */
|
|
67
|
+
export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
|
|
68
|
+
Schema["~standard"]["types"]
|
|
69
|
+
>["output"];
|
|
70
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import type {
|
|
7
|
+
StandardSchemaV1 as Schema,
|
|
8
|
+
StandardSchemaV1,
|
|
9
|
+
} from "./standardschema.js";
|
|
10
|
+
import { ArkErrors, type } from "arktype";
|
|
7
11
|
import { RequestBoundLogger } from "./log.js";
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* A procedure declaration
|
|
11
15
|
*/
|
|
12
|
-
export type Procedure<I extends
|
|
16
|
+
export type Procedure<I extends Schema, P extends Schema, S extends Schema> = {
|
|
13
17
|
/**
|
|
14
18
|
* ArkType type for the input (first argument) of the procedure, when calling it from the client.
|
|
15
19
|
*/
|
|
@@ -58,18 +62,18 @@ export type CancelablePromise<T = unknown> = {
|
|
|
58
62
|
* An implementation of a procedure
|
|
59
63
|
*/
|
|
60
64
|
export type ProcedureImplementation<
|
|
61
|
-
I extends
|
|
62
|
-
P extends
|
|
63
|
-
S extends
|
|
65
|
+
I extends Schema,
|
|
66
|
+
P extends Schema,
|
|
67
|
+
S extends Schema,
|
|
64
68
|
> = (
|
|
65
69
|
/**
|
|
66
70
|
* Input data for the procedure
|
|
67
71
|
*/
|
|
68
|
-
input: I
|
|
72
|
+
input: Schema.InferInput<I>,
|
|
69
73
|
/**
|
|
70
74
|
* Callback to call with progress updates.
|
|
71
75
|
*/
|
|
72
|
-
onProgress: (progress: P
|
|
76
|
+
onProgress: (progress: Schema.InferInput<P>) => void,
|
|
73
77
|
/**
|
|
74
78
|
* Additional tools useful when implementing the procedure.
|
|
75
79
|
*/
|
|
@@ -87,7 +91,7 @@ export type ProcedureImplementation<
|
|
|
87
91
|
*/
|
|
88
92
|
nodeId: string;
|
|
89
93
|
},
|
|
90
|
-
) => Promise<S
|
|
94
|
+
) => Promise<Schema.InferInput<S>>;
|
|
91
95
|
|
|
92
96
|
/**
|
|
93
97
|
* Declarations of procedures by name.
|
|
@@ -95,7 +99,7 @@ export type ProcedureImplementation<
|
|
|
95
99
|
* An example of declaring procedures:
|
|
96
100
|
* {@includeCode ../example/src/lib/procedures.ts}
|
|
97
101
|
*/
|
|
98
|
-
export type ProceduresMap = Record<string, Procedure<
|
|
102
|
+
export type ProceduresMap = Record<string, Procedure<Schema, Schema, Schema>>;
|
|
99
103
|
|
|
100
104
|
/**
|
|
101
105
|
* Implementations of procedures by name
|
|
@@ -117,7 +121,7 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
117
121
|
*/
|
|
118
122
|
success?: <Procedure extends keyof ProceduresMap>(
|
|
119
123
|
procedure: Procedure,
|
|
120
|
-
data: Procedures[Procedure]["success"]
|
|
124
|
+
data: Schema.InferOutput<Procedures[Procedure]["success"]>,
|
|
121
125
|
) => void;
|
|
122
126
|
/**
|
|
123
127
|
* Called when a procedure call has failed.
|
|
@@ -131,7 +135,7 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
131
135
|
*/
|
|
132
136
|
progress?: <Procedure extends keyof ProceduresMap>(
|
|
133
137
|
procedure: Procedure,
|
|
134
|
-
data: Procedures[Procedure]["progress"]
|
|
138
|
+
data: Schema.InferOutput<Procedures[Procedure]["progress"]>,
|
|
135
139
|
) => void;
|
|
136
140
|
};
|
|
137
141
|
|
|
@@ -179,13 +183,13 @@ export type PayloadCore<
|
|
|
179
183
|
Name extends keyof PM = keyof PM,
|
|
180
184
|
> =
|
|
181
185
|
| {
|
|
182
|
-
input: PM[Name]["input"]
|
|
186
|
+
input: Schema.InferOutput<PM[Name]["input"]>;
|
|
183
187
|
}
|
|
184
188
|
| {
|
|
185
|
-
progress: PM[Name]["progress"]
|
|
189
|
+
progress: Schema.InferOutput<PM[Name]["progress"]>;
|
|
186
190
|
}
|
|
187
191
|
| {
|
|
188
|
-
result: PM[Name]["success"]
|
|
192
|
+
result: Schema.InferOutput<PM[Name]["success"]>;
|
|
189
193
|
}
|
|
190
194
|
| {
|
|
191
195
|
abort: { reason: string };
|
|
@@ -194,16 +198,43 @@ export type PayloadCore<
|
|
|
194
198
|
error: { message: string };
|
|
195
199
|
};
|
|
196
200
|
|
|
201
|
+
const AbortOrError = type.or(
|
|
202
|
+
{ abort: { reason: "string" } },
|
|
203
|
+
{ error: { message: "string" } },
|
|
204
|
+
);
|
|
205
|
+
|
|
197
206
|
/**
|
|
198
207
|
* @source
|
|
199
208
|
*/
|
|
200
|
-
export
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
export function validatePayloadCore<
|
|
210
|
+
PM extends ProceduresMap,
|
|
211
|
+
Name extends keyof PM,
|
|
212
|
+
>(procedure: PM[Name], payload: unknown): PayloadCore<PM, keyof PM> {
|
|
213
|
+
if (typeof payload !== "object") throw new Error("payload is not an object");
|
|
214
|
+
if (payload === null) throw new Error("payload is null");
|
|
215
|
+
|
|
216
|
+
if ("input" in payload) {
|
|
217
|
+
const input = procedure.input["~standard"].validate(payload.input);
|
|
218
|
+
if ("value" in input) return { input: input.value };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if ("progress" in payload) {
|
|
222
|
+
const progress = procedure.progress["~standard"].validate(payload.progress);
|
|
223
|
+
if ("value" in progress) return { progress: progress.value };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if ("result" in payload) {
|
|
227
|
+
const result = procedure.success["~standard"].validate(payload.result);
|
|
228
|
+
if ("value" in result) return { result: result.value };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const abortOrError = AbortOrError(payload);
|
|
232
|
+
if (!(abortOrError instanceof ArkErrors)) {
|
|
233
|
+
return abortOrError;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
throw new Error("invalid payload");
|
|
237
|
+
}
|
|
207
238
|
|
|
208
239
|
/**
|
|
209
240
|
* The effective payload as sent by the server to the client
|
|
@@ -216,30 +247,35 @@ export type Payload<
|
|
|
216
247
|
/**
|
|
217
248
|
* A procedure's corresponding method on the client instance -- used to call the procedure. If you want to be able to cancel the request, you can use the `cancelable` method instead of running the procedure directly.
|
|
218
249
|
*/
|
|
219
|
-
export type ClientMethod<P extends Procedure<
|
|
220
|
-
input: P["input"]
|
|
221
|
-
onProgress?: (progress: P["progress"]
|
|
222
|
-
) => Promise<P["success"]
|
|
250
|
+
export type ClientMethod<P extends Procedure<Schema, Schema, Schema>> = ((
|
|
251
|
+
input: Schema.InferInput<P["input"]>,
|
|
252
|
+
onProgress?: (progress: Schema.InferOutput<P["progress"]>) => void,
|
|
253
|
+
) => Promise<Schema.InferOutput<P["success"]>>) & {
|
|
223
254
|
/**
|
|
224
255
|
* A method that returns a `CancelablePromise`. Cancel it by calling `.cancel(reason)` on it, and wait for the request to resolve by awaiting the `request` property on the returned object.
|
|
225
256
|
*/
|
|
226
257
|
cancelable: (
|
|
227
|
-
input: P["input"]
|
|
228
|
-
onProgress?: (progress: P["progress"]
|
|
258
|
+
input: Schema.InferInput<P["input"]>,
|
|
259
|
+
onProgress?: (progress: Schema.InferOutput<P["progress"]>) => void,
|
|
229
260
|
requestId?: string,
|
|
230
|
-
) => CancelablePromise<P["success"]
|
|
261
|
+
) => CancelablePromise<Schema.InferOutput<P["success"]>>;
|
|
231
262
|
/**
|
|
232
263
|
* Send the request to specific nodes, or all nodes.
|
|
233
264
|
* Returns an array of results, one for each node the request was sent to.
|
|
234
265
|
* Each result is a {@link PromiseSettledResult}, with also an additional property, the node ID of the request
|
|
235
266
|
*/
|
|
236
267
|
broadcast: (
|
|
237
|
-
input: P["input"]
|
|
238
|
-
onProgress?: (
|
|
268
|
+
input: Schema.InferInput<P["input"]>,
|
|
269
|
+
onProgress?: (
|
|
270
|
+
/** Map of node IDs to their progress updates */
|
|
271
|
+
progresses: Map<string, Schema.InferOutput<P["progress"]>>,
|
|
272
|
+
) => void,
|
|
239
273
|
/** Number of nodes to send the request to. Leave undefined to send to all nodes */
|
|
240
274
|
nodes?: number,
|
|
241
275
|
) => Promise<
|
|
242
|
-
Array<
|
|
276
|
+
Array<
|
|
277
|
+
PromiseSettledResult<Schema.InferOutput<P["success"]>> & { node: string }
|
|
278
|
+
>
|
|
243
279
|
>;
|
|
244
280
|
};
|
|
245
281
|
|