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 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 arktype
25
+ npm add swarpc
25
26
  ```
26
27
 
27
- <details>
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
@@ -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,CAgK1B;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"}
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.assert(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 ?? "(SW)", requestId);
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, onProgress, nodesCount) => {
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 results = await Promise.allSettled(nodesToUse.map(async (id) => _runProcedure(input, onProgress, undefined, id)));
114
- return results.map((r, i) => ({ ...r, node: nodesToUse[i] ?? "(SW)" }));
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 ?? "(SW)", requestId);
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 ?? "(SW)"))
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 arktype schema here, we trust the server to send valid data
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 ?? "(SW)");
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 ?? "(SW)",
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
@@ -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
+ }
@@ -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,EAMlB,uBAAuB,EACvB,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,CA6M1B"}
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, PayloadSchema, zImplementations, zProcedures, } from "./types.js";
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 = PayloadSchema(type(`"${functionName}"`), schemas.input, schemas.progress, schemas.success).assert(event.data);
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.abort) {
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.input) {
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 { type Type } from "arktype";
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 Type, P extends Type, S extends Type> = {
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 Type, P extends Type, S extends Type> = (
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["inferOut"],
60
+ input: Schema.InferInput<I>,
61
61
  /**
62
62
  * Callback to call with progress updates.
63
63
  */
64
- onProgress: (progress: P["inferIn"]) => void,
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["inferIn"]>;
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<Type, Type, Type>>;
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"]["inferOut"]) => void;
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"]["inferOut"]) => void;
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/methods/object.ts").ObjectType<{
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"]["inferOut"];
148
+ input: Schema.InferOutput<PM[Name]["input"]>;
149
149
  } | {
150
- progress: PM[Name]["progress"]["inferOut"];
150
+ progress: Schema.InferOutput<PM[Name]["progress"]>;
151
151
  } | {
152
- result: PM[Name]["success"]["inferOut"];
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 const PayloadSchema: import("arktype").Generic<[["Name", string], ["I", unknown], ["P", unknown], ["S", unknown]], readonly [readonly ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"], "|", "PayloadInitializeSchema"], {
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<Type, Type, Type>> = ((input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>) & {
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"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void, requestId?: string) => CancelablePromise<P["success"]["inferOut"]>;
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"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void,
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"]["inferOut"]> & {
187
+ nodes?: number) => Promise<Array<PromiseSettledResult<Schema.InferOutput<P["success"]>> & {
362
188
  node: string;
363
189
  }>>;
364
190
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,IAAI;IACtE;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC;IACT;;;OAGG;IACH,QAAQ,EAAE,CAAC,CAAC;IACZ;;OAEG;IACH,OAAO,EAAE,CAAC,CAAC;IACX;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAC;CACnD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC3C,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,CACjC,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,IAAI,IACZ;AACF;;GAEG;AACH,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC;AACpB;;GAEG;AACH,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK,IAAI;AAC5C;;GAEG;AACH,KAAK,EAAE;IACL;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;OAEG;IACH,MAAM,EAAE,kBAAkB,CAAC;IAC3B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,KACE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAE3B;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,UAAU,SAAS,aAAa,IAAI;KAChE,CAAC,IAAI,MAAM,UAAU,GAAG,uBAAuB,CAC9C,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EACtB,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EACzB,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CACzB;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,KAAK,CAAC,UAAU,SAAS,aAAa,IAAI;IACpD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,aAAa,EAC9C,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,KAC/C,IAAI,CAAC;IACV;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,aAAa,EAC5C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,KACT,IAAI,CAAC;IACV;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,aAAa,EAC/C,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAChD,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,uBAAuB;;;;;;MAMlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,OAAO,uBAAuB,CAAC,KAAK,CAAC;AAErE;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;UAI9B,CAAC;AAEH,MAAM,MAAM,aAAa,CACvB,EAAE,SAAS,aAAa,EACxB,IAAI,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,IAC9B;IACF,EAAE,EAAE,QAAQ,CAAC;IACb,YAAY,EAAE,IAAI,GAAG,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;UAM5B,CAAC;AAEH,MAAM,MAAM,WAAW,CACrB,EAAE,SAAS,aAAa,EACxB,IAAI,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,IAE9B;IACE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;CACtC,GACD;IACE,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,CAAC;CAC5C,GACD;IACE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC;CACzC,GACD;IACE,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B,GACD;IACE,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B,CAAC;AAEN;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMtB,CAAC;AAEL;;GAEG;AACH,MAAM,MAAM,OAAO,CACjB,EAAE,SAAS,aAAa,EACxB,IAAI,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,IAC9B,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,GAAG,iBAAiB,CAAC;AAE1E;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CACjE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAC5B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KACvD,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG;IACxC;;OAEG;IACH,UAAU,EAAE,CACV,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAC5B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,EAC1D,SAAS,CAAC,EAAE,MAAM,KACf,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACjD;;;;OAIG;IACH,SAAS,EAAE,CACT,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAC5B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI;IAC1D,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,KACX,OAAO,CACV,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CACzE,CAAC;CACH,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,eAAmC,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,WAAW,eAA8B,CAAC;AAEvD,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,YAAY,IACrD;IACF,KAAK,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAAC,CAAC;CACnC,CAAC"}
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 const PayloadSchema = type
35
- .scope({ PayloadCoreSchema, PayloadHeaderSchema, PayloadInitializeSchema })
36
- .type("<Name extends string, I, P, S>", [
37
- ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"],
38
- "|",
39
- "PayloadInitializeSchema",
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.13.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.55.0",
49
- "@vitest/web-worker": "^3.2.4",
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.63.1",
54
- "lint-staged": "^16.1.6",
50
+ "knip": "^5.67.0",
51
+ "lint-staged": "^16.2.6",
55
52
  "nodemon": "^3.1.10",
56
- "oxlint": "^1.14.0",
57
- "pkg-pr-new": "^0.0.59",
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.12",
61
- "typedoc-material-theme": "^1.4.0",
62
- "typedoc-plugin-dt-links": "^2.0.18",
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.9",
66
- "typedoc-plugin-redirect": "^1.2.0",
67
- "typescript": "^5.9.2",
68
- "vite": "^7.1.4",
69
- "vitest": "^3.2.4"
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.7.0",
73
- "npm": "11.6.0"
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
- procedures[functionName].input.assert(input);
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 ?? "(SW)", requestId);
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
- onProgress,
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] ?? "(SW)" }));
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 ?? "(SW)", requestId);
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 ?? "(SW)")) return;
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 arktype schema here, we trust the server to send valid data
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 ?? "(SW)");
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 ?? "(SW)",
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 = PayloadSchema(
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.abort) {
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.input) {
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 { type, type Type } from "arktype";
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 Type, P extends Type, S extends Type> = {
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 Type,
62
- P extends Type,
63
- S extends Type,
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["inferOut"],
72
+ input: Schema.InferInput<I>,
69
73
  /**
70
74
  * Callback to call with progress updates.
71
75
  */
72
- onProgress: (progress: P["inferIn"]) => void,
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["inferIn"]>;
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<Type, Type, Type>>;
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"]["inferOut"],
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"]["inferOut"],
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"]["inferOut"];
186
+ input: Schema.InferOutput<PM[Name]["input"]>;
183
187
  }
184
188
  | {
185
- progress: PM[Name]["progress"]["inferOut"];
189
+ progress: Schema.InferOutput<PM[Name]["progress"]>;
186
190
  }
187
191
  | {
188
- result: PM[Name]["success"]["inferOut"];
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 const PayloadSchema = type
201
- .scope({ PayloadCoreSchema, PayloadHeaderSchema, PayloadInitializeSchema })
202
- .type("<Name extends string, I, P, S>", [
203
- ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"],
204
- "|",
205
- "PayloadInitializeSchema",
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<Type, Type, Type>> = ((
220
- input: P["input"]["inferIn"],
221
- onProgress?: (progress: P["progress"]["inferOut"]) => void,
222
- ) => Promise<P["success"]["inferOut"]>) & {
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"]["inferIn"],
228
- onProgress?: (progress: P["progress"]["inferOut"]) => void,
258
+ input: Schema.InferInput<P["input"]>,
259
+ onProgress?: (progress: Schema.InferOutput<P["progress"]>) => void,
229
260
  requestId?: string,
230
- ) => CancelablePromise<P["success"]["inferOut"]>;
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"]["inferIn"],
238
- onProgress?: (progress: P["progress"]["inferOut"]) => void,
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<PromiseSettledResult<P["success"]["inferOut"]> & { node: string }>
276
+ Array<
277
+ PromiseSettledResult<Schema.InferOutput<P["success"]>> & { node: string }
278
+ >
243
279
  >;
244
280
  };
245
281