swarpc 0.20.0 → 0.20.2
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/dist/client.js +4 -122
- package/dist/messaging.d.ts +44 -0
- package/dist/messaging.js +121 -0
- package/dist/payload.d.ts +17 -0
- package/dist/payload.js +88 -0
- package/dist/server.js +2 -1
- package/dist/types.d.ts +0 -15
- package/dist/types.js +0 -88
- package/dist/utils.js +6 -0
- package/package.json +23 -20
package/dist/client.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createLogger
|
|
1
|
+
import { createLogger } from "./log.js";
|
|
2
2
|
import { broadcastNodes, makeNodeId, nodeIdOrSW, whoToSendTo, } from "./nodes.js";
|
|
3
|
+
import { _clientListeners, makeRequestId, pendingRequests, postMessage, postMessageSync, } from "./messaging.js";
|
|
3
4
|
import { RequestCancelledError, zProcedures, } from "./types.js";
|
|
4
|
-
import { findTransferables, extractFulfilleds, extractRejecteds, sizedArray, } from "./utils.js";
|
|
5
|
+
import { findTransferables, extractFulfilleds, extractRejecteds, sizedArray, isSharedWorker, } from "./utils.js";
|
|
5
6
|
export const RESERVED_PROCEDURE_NAMES = ["onceBy", "destroy"];
|
|
6
|
-
const pendingRequests = new Map();
|
|
7
7
|
const emptyProgressCallback = () => { };
|
|
8
|
-
let _clientListeners = new Map();
|
|
9
8
|
export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug", restartListener = false, hooks = {}, localStorage = {}, nodeIds = [], } = {}) {
|
|
10
9
|
const l = createLogger("client", loglevel);
|
|
11
10
|
if (restartListener)
|
|
@@ -35,7 +34,7 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
|
|
|
35
34
|
}
|
|
36
35
|
for (const [nodeId, node] of Object.entries(nodes ?? {})) {
|
|
37
36
|
l.debug(null, `Terminating worker for node ${nodeId}`);
|
|
38
|
-
if (node
|
|
37
|
+
if (isSharedWorker(node)) {
|
|
39
38
|
node.port.close();
|
|
40
39
|
}
|
|
41
40
|
else {
|
|
@@ -261,123 +260,6 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
|
|
|
261
260
|
};
|
|
262
261
|
return instance;
|
|
263
262
|
}
|
|
264
|
-
async function postMessage(ctx, message, options) {
|
|
265
|
-
await startClientListener(ctx);
|
|
266
|
-
const { logger: l, node: worker } = ctx;
|
|
267
|
-
if (!worker && !navigator.serviceWorker.controller)
|
|
268
|
-
l.warn("", "Service Worker is not controlling the page");
|
|
269
|
-
const w = worker instanceof SharedWorker
|
|
270
|
-
? worker.port
|
|
271
|
-
: worker === undefined
|
|
272
|
-
? await navigator.serviceWorker.ready.then((r) => r.active)
|
|
273
|
-
: worker;
|
|
274
|
-
if (!w) {
|
|
275
|
-
throw new Error("[SWARPC Client] No active service worker found");
|
|
276
|
-
}
|
|
277
|
-
w.postMessage(message, options);
|
|
278
|
-
}
|
|
279
|
-
function postMessageSync(l, worker, message, options) {
|
|
280
|
-
if (!worker && !navigator.serviceWorker.controller)
|
|
281
|
-
l.warn("Service Worker is not controlling the page");
|
|
282
|
-
const w = worker instanceof SharedWorker
|
|
283
|
-
? worker.port
|
|
284
|
-
: worker === undefined
|
|
285
|
-
? navigator.serviceWorker.controller
|
|
286
|
-
: worker;
|
|
287
|
-
if (!w) {
|
|
288
|
-
throw new Error("[SWARPC Client] No active service worker found");
|
|
289
|
-
}
|
|
290
|
-
w.postMessage(message, options);
|
|
291
|
-
}
|
|
292
|
-
async function startClientListener(ctx) {
|
|
293
|
-
if (_clientListeners.has(nodeIdOrSW(ctx.nodeId)))
|
|
294
|
-
return;
|
|
295
|
-
const { logger: l, node: worker } = ctx;
|
|
296
|
-
if (!worker) {
|
|
297
|
-
const sw = await navigator.serviceWorker.ready;
|
|
298
|
-
if (!sw?.active) {
|
|
299
|
-
throw new Error("[SWARPC Client] Service Worker is not active");
|
|
300
|
-
}
|
|
301
|
-
if (!navigator.serviceWorker.controller) {
|
|
302
|
-
l.warn("", "Service Worker is not controlling the page");
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const w = worker ?? navigator.serviceWorker;
|
|
306
|
-
l.debug(null, "Starting client listener", { w, ...ctx });
|
|
307
|
-
const listener = (event) => {
|
|
308
|
-
const eventData = event.data || {};
|
|
309
|
-
if (eventData?.by !== "sw&rpc")
|
|
310
|
-
return;
|
|
311
|
-
const payload = eventData;
|
|
312
|
-
if ("isInitializeRequest" in payload) {
|
|
313
|
-
l.warn(null, "Ignoring unexpected #initialize from server", payload);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const { requestId, ...data } = payload;
|
|
317
|
-
if (!requestId) {
|
|
318
|
-
throw new Error("[SWARPC Client] Message received without requestId");
|
|
319
|
-
}
|
|
320
|
-
const handlers = pendingRequests.get(requestId);
|
|
321
|
-
if (!handlers) {
|
|
322
|
-
throw new Error(`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`);
|
|
323
|
-
}
|
|
324
|
-
const duration = performance.now() - handlers.startedAt;
|
|
325
|
-
if ("error" in data) {
|
|
326
|
-
ctx.hooks.error?.({
|
|
327
|
-
procedure: data.functionName,
|
|
328
|
-
error: new Error(data.error.message),
|
|
329
|
-
duration,
|
|
330
|
-
});
|
|
331
|
-
handlers.reject(new Error(data.error.message));
|
|
332
|
-
pendingRequests.delete(requestId);
|
|
333
|
-
}
|
|
334
|
-
else if ("progress" in data) {
|
|
335
|
-
ctx.hooks.progress?.({
|
|
336
|
-
procedure: data.functionName,
|
|
337
|
-
data: data.progress,
|
|
338
|
-
duration,
|
|
339
|
-
});
|
|
340
|
-
handlers.onProgress(data.progress);
|
|
341
|
-
}
|
|
342
|
-
else if ("result" in data) {
|
|
343
|
-
ctx.hooks.success?.({
|
|
344
|
-
procedure: data.functionName,
|
|
345
|
-
data: data.result,
|
|
346
|
-
duration,
|
|
347
|
-
});
|
|
348
|
-
handlers.resolve(data.result);
|
|
349
|
-
pendingRequests.delete(requestId);
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
if (w instanceof SharedWorker) {
|
|
353
|
-
w.port.addEventListener("message", listener);
|
|
354
|
-
w.port.start();
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
w.addEventListener("message", listener);
|
|
358
|
-
}
|
|
359
|
-
_clientListeners.set(nodeIdOrSW(ctx.nodeId), {
|
|
360
|
-
disconnect() {
|
|
361
|
-
if (w instanceof SharedWorker) {
|
|
362
|
-
w.port.removeEventListener("message", listener);
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
w.removeEventListener("message", listener);
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
});
|
|
369
|
-
await postMessage(ctx, {
|
|
370
|
-
by: "sw&rpc",
|
|
371
|
-
functionName: "#initialize",
|
|
372
|
-
isInitializeRequest: true,
|
|
373
|
-
localStorageData: ctx.localStorage,
|
|
374
|
-
nodeId: nodeIdOrSW(ctx.nodeId),
|
|
375
|
-
allNodeIDs: ctx.allNodeIDs,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
function makeRequestId() {
|
|
379
|
-
return Math.random().toString(16).substring(2, 8).toUpperCase();
|
|
380
|
-
}
|
|
381
263
|
function handleBroadcastOrThrowResults(results) {
|
|
382
264
|
if (results.ok) {
|
|
383
265
|
return results.successes;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Logger, type RequestBoundLogger } from "./log.js";
|
|
2
|
+
import { type Payload } from "./payload.js";
|
|
3
|
+
import { type Hooks, type PendingRequest, type ProceduresMap } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Context for passing around data useful for requests
|
|
6
|
+
*/
|
|
7
|
+
export type Context<Procedures extends ProceduresMap> = {
|
|
8
|
+
/** A logger, bound to the client */
|
|
9
|
+
logger: Logger;
|
|
10
|
+
/** The node to use */
|
|
11
|
+
node: Worker | SharedWorker | undefined;
|
|
12
|
+
/** The ID of the node to use */
|
|
13
|
+
nodeId: string | undefined;
|
|
14
|
+
/** Set of all available nodes' IDs */
|
|
15
|
+
allNodeIDs: Set<string>;
|
|
16
|
+
/** Hooks defined by the client */
|
|
17
|
+
hooks: Hooks<Procedures>;
|
|
18
|
+
/** Local storage data defined by the client for the faux local storage */
|
|
19
|
+
localStorage: Record<string, any>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Pending requests are stored in a map, where the key is the request ID.
|
|
23
|
+
* Each request has a set of handlers: resolve, reject, and onProgress.
|
|
24
|
+
* This allows having a single listener for the client, and having multiple in-flight calls to the same procedure.
|
|
25
|
+
*/
|
|
26
|
+
export declare const pendingRequests: Map<string, PendingRequest>;
|
|
27
|
+
export declare let _clientListeners: Map<string, {
|
|
28
|
+
disconnect: () => void;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Warms up the client by starting the listener and getting the worker, then posts a message to the worker.
|
|
32
|
+
*/
|
|
33
|
+
export declare function postMessage<Procedures extends ProceduresMap>(ctx: Context<Procedures>, message: Payload<Procedures>, options?: StructuredSerializeOptions): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
|
|
36
|
+
* esp. useful for abort logic that needs to not be... put behind everything else on the event loop.
|
|
37
|
+
*/
|
|
38
|
+
export declare function postMessageSync<Procedures extends ProceduresMap>(l: RequestBoundLogger, worker: Worker | SharedWorker | undefined, message: Payload<Procedures>, options?: StructuredSerializeOptions): void;
|
|
39
|
+
/**
|
|
40
|
+
* Generate a random request ID, used to identify requests between client and server.
|
|
41
|
+
* @source
|
|
42
|
+
* @returns a 6-character hexadecimal string
|
|
43
|
+
*/
|
|
44
|
+
export declare function makeRequestId(): string;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { nodeIdOrSW } from "./nodes.js";
|
|
2
|
+
import { isSharedWorker } from "./utils.js";
|
|
3
|
+
export const pendingRequests = new Map();
|
|
4
|
+
export let _clientListeners = new Map();
|
|
5
|
+
export async function postMessage(ctx, message, options) {
|
|
6
|
+
await startClientListener(ctx);
|
|
7
|
+
const { logger: l, node: worker } = ctx;
|
|
8
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
9
|
+
l.warn("", "Service Worker is not controlling the page");
|
|
10
|
+
const w = isSharedWorker(worker)
|
|
11
|
+
? worker.port
|
|
12
|
+
: worker === undefined
|
|
13
|
+
? await navigator.serviceWorker.ready.then((r) => r.active)
|
|
14
|
+
: worker;
|
|
15
|
+
if (!w) {
|
|
16
|
+
throw new Error("[SWARPC Client] No active service worker found");
|
|
17
|
+
}
|
|
18
|
+
w.postMessage(message, options);
|
|
19
|
+
}
|
|
20
|
+
export function postMessageSync(l, worker, message, options) {
|
|
21
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
22
|
+
l.warn("Service Worker is not controlling the page");
|
|
23
|
+
const w = isSharedWorker(worker)
|
|
24
|
+
? worker.port
|
|
25
|
+
: worker === undefined
|
|
26
|
+
? navigator.serviceWorker.controller
|
|
27
|
+
: worker;
|
|
28
|
+
if (!w) {
|
|
29
|
+
throw new Error("[SWARPC Client] No active service worker found");
|
|
30
|
+
}
|
|
31
|
+
w.postMessage(message, options);
|
|
32
|
+
}
|
|
33
|
+
async function startClientListener(ctx) {
|
|
34
|
+
if (_clientListeners.has(nodeIdOrSW(ctx.nodeId)))
|
|
35
|
+
return;
|
|
36
|
+
const { logger: l, node: worker } = ctx;
|
|
37
|
+
if (!worker) {
|
|
38
|
+
const sw = await navigator.serviceWorker.ready;
|
|
39
|
+
if (!sw?.active) {
|
|
40
|
+
throw new Error("[SWARPC Client] Service Worker is not active");
|
|
41
|
+
}
|
|
42
|
+
if (!navigator.serviceWorker.controller) {
|
|
43
|
+
l.warn("", "Service Worker is not controlling the page");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const w = worker ?? navigator.serviceWorker;
|
|
47
|
+
l.debug(null, "Starting client listener", { w, ...ctx });
|
|
48
|
+
const listener = (event) => {
|
|
49
|
+
const eventData = event.data || {};
|
|
50
|
+
if (eventData?.by !== "sw&rpc")
|
|
51
|
+
return;
|
|
52
|
+
const payload = eventData;
|
|
53
|
+
if ("isInitializeRequest" in payload) {
|
|
54
|
+
l.warn(null, "Ignoring unexpected #initialize from server", payload);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const { requestId, ...data } = payload;
|
|
58
|
+
if (!requestId) {
|
|
59
|
+
throw new Error("[SWARPC Client] Message received without requestId");
|
|
60
|
+
}
|
|
61
|
+
const handlers = pendingRequests.get(requestId);
|
|
62
|
+
if (!handlers) {
|
|
63
|
+
throw new Error(`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`);
|
|
64
|
+
}
|
|
65
|
+
const duration = performance.now() - handlers.startedAt;
|
|
66
|
+
if ("error" in data) {
|
|
67
|
+
ctx.hooks.error?.({
|
|
68
|
+
procedure: data.functionName,
|
|
69
|
+
error: new Error(data.error.message),
|
|
70
|
+
duration,
|
|
71
|
+
});
|
|
72
|
+
handlers.reject(new Error(data.error.message));
|
|
73
|
+
pendingRequests.delete(requestId);
|
|
74
|
+
}
|
|
75
|
+
else if ("progress" in data) {
|
|
76
|
+
ctx.hooks.progress?.({
|
|
77
|
+
procedure: data.functionName,
|
|
78
|
+
data: data.progress,
|
|
79
|
+
duration,
|
|
80
|
+
});
|
|
81
|
+
handlers.onProgress(data.progress);
|
|
82
|
+
}
|
|
83
|
+
else if ("result" in data) {
|
|
84
|
+
ctx.hooks.success?.({
|
|
85
|
+
procedure: data.functionName,
|
|
86
|
+
data: data.result,
|
|
87
|
+
duration,
|
|
88
|
+
});
|
|
89
|
+
handlers.resolve(data.result);
|
|
90
|
+
pendingRequests.delete(requestId);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
if (isSharedWorker(w)) {
|
|
94
|
+
w.port.addEventListener("message", listener);
|
|
95
|
+
w.port.start();
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
w.addEventListener("message", listener);
|
|
99
|
+
}
|
|
100
|
+
_clientListeners.set(nodeIdOrSW(ctx.nodeId), {
|
|
101
|
+
disconnect() {
|
|
102
|
+
if (isSharedWorker(w)) {
|
|
103
|
+
w.port.removeEventListener("message", listener);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
w.removeEventListener("message", listener);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
await postMessage(ctx, {
|
|
111
|
+
by: "sw&rpc",
|
|
112
|
+
functionName: "#initialize",
|
|
113
|
+
isInitializeRequest: true,
|
|
114
|
+
localStorageData: ctx.localStorage,
|
|
115
|
+
nodeId: nodeIdOrSW(ctx.nodeId),
|
|
116
|
+
allNodeIDs: ctx.allNodeIDs,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function makeRequestId() {
|
|
120
|
+
return Math.random().toString(16).substring(2, 8).toUpperCase();
|
|
121
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { StandardSchemaV1 as Schema } from "./standardschema.js";
|
|
2
|
+
import type { ProceduresMap } from "./types.js";
|
|
3
|
+
export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
|
|
4
|
+
input: Schema.InferOutput<PM[Name]["input"]>;
|
|
5
|
+
} | {
|
|
6
|
+
progress: Schema.InferOutput<PM[Name]["progress"]>;
|
|
7
|
+
} | {
|
|
8
|
+
result: Schema.InferOutput<PM[Name]["success"]>;
|
|
9
|
+
} | {
|
|
10
|
+
abort: {
|
|
11
|
+
reason: string;
|
|
12
|
+
};
|
|
13
|
+
} | {
|
|
14
|
+
error: {
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
package/dist/payload.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function isPayloadInitialize(payload) {
|
|
2
|
+
if (typeof payload !== "object")
|
|
3
|
+
return false;
|
|
4
|
+
if (payload === null)
|
|
5
|
+
return false;
|
|
6
|
+
if (!("by" in payload))
|
|
7
|
+
return false;
|
|
8
|
+
if (!("nodeId" in payload))
|
|
9
|
+
return false;
|
|
10
|
+
if (!("functionName" in payload))
|
|
11
|
+
return false;
|
|
12
|
+
if (!("localStorageData" in payload))
|
|
13
|
+
return false;
|
|
14
|
+
if (!("isInitializeRequest" in payload))
|
|
15
|
+
return false;
|
|
16
|
+
if (!("allNodeIDs" in payload))
|
|
17
|
+
return false;
|
|
18
|
+
if (payload.by !== "sw&rpc")
|
|
19
|
+
return false;
|
|
20
|
+
if (payload.functionName !== "#initialize")
|
|
21
|
+
return false;
|
|
22
|
+
if (payload.isInitializeRequest !== true)
|
|
23
|
+
return false;
|
|
24
|
+
if (typeof payload.nodeId !== "string")
|
|
25
|
+
return false;
|
|
26
|
+
if (typeof payload.localStorageData !== "object")
|
|
27
|
+
return false;
|
|
28
|
+
if (payload.localStorageData === null)
|
|
29
|
+
return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
export function isPayloadHeader(procedures, payload) {
|
|
33
|
+
if (typeof payload !== "object")
|
|
34
|
+
return false;
|
|
35
|
+
if (payload === null)
|
|
36
|
+
return false;
|
|
37
|
+
if (!("by" in payload))
|
|
38
|
+
return false;
|
|
39
|
+
if (!("requestId" in payload))
|
|
40
|
+
return false;
|
|
41
|
+
if (!("functionName" in payload))
|
|
42
|
+
return false;
|
|
43
|
+
if (payload.by !== "sw&rpc")
|
|
44
|
+
return false;
|
|
45
|
+
if (typeof payload.requestId !== "string")
|
|
46
|
+
return false;
|
|
47
|
+
if (typeof payload.functionName !== "string")
|
|
48
|
+
return false;
|
|
49
|
+
if (!Object.keys(procedures).includes(payload.functionName))
|
|
50
|
+
return false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
export function validatePayloadCore(procedure, payload) {
|
|
54
|
+
if (typeof payload !== "object")
|
|
55
|
+
throw new Error("payload is not an object");
|
|
56
|
+
if (payload === null)
|
|
57
|
+
throw new Error("payload is null");
|
|
58
|
+
if ("input" in payload) {
|
|
59
|
+
const input = procedure.input["~standard"].validate(payload.input);
|
|
60
|
+
if ("value" in input)
|
|
61
|
+
return { input: input.value };
|
|
62
|
+
}
|
|
63
|
+
if ("progress" in payload) {
|
|
64
|
+
const progress = procedure.progress["~standard"].validate(payload.progress);
|
|
65
|
+
if ("value" in progress)
|
|
66
|
+
return { progress: progress.value };
|
|
67
|
+
}
|
|
68
|
+
if ("result" in payload) {
|
|
69
|
+
const result = procedure.success["~standard"].validate(payload.result);
|
|
70
|
+
if ("value" in result)
|
|
71
|
+
return { result: result.value };
|
|
72
|
+
}
|
|
73
|
+
if ("abort" in payload &&
|
|
74
|
+
typeof payload.abort === "object" &&
|
|
75
|
+
payload.abort !== null &&
|
|
76
|
+
"reason" in payload.abort &&
|
|
77
|
+
typeof payload.abort.reason === "string") {
|
|
78
|
+
return { abort: { reason: payload.abort.reason } };
|
|
79
|
+
}
|
|
80
|
+
if ("error" in payload &&
|
|
81
|
+
typeof payload.error === "object" &&
|
|
82
|
+
payload.error !== null &&
|
|
83
|
+
"message" in payload.error &&
|
|
84
|
+
typeof payload.error.message === "string") {
|
|
85
|
+
return { error: { message: payload.error.message } };
|
|
86
|
+
}
|
|
87
|
+
throw new Error("invalid payload");
|
|
88
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLogger, injectIntoConsoleGlobal } from "./log.js";
|
|
2
|
-
import {
|
|
2
|
+
import { RequestCancelledError, zImplementations, zProcedures, } from "./types.js";
|
|
3
|
+
import { isPayloadHeader, isPayloadInitialize, validatePayloadCore, } from "./payload.js";
|
|
3
4
|
import { findTransferables } from "./utils.js";
|
|
4
5
|
import { FauxLocalStorage } from "./localstorage.js";
|
|
5
6
|
import { scopeIsDedicated, scopeIsShared, scopeIsService } from "./scopes.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -117,21 +117,6 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
117
117
|
*/
|
|
118
118
|
progress?: (arg: ProcedureNameAndData<Procedures, "progress">) => void;
|
|
119
119
|
};
|
|
120
|
-
export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
|
|
121
|
-
input: Schema.InferOutput<PM[Name]["input"]>;
|
|
122
|
-
} | {
|
|
123
|
-
progress: Schema.InferOutput<PM[Name]["progress"]>;
|
|
124
|
-
} | {
|
|
125
|
-
result: Schema.InferOutput<PM[Name]["success"]>;
|
|
126
|
-
} | {
|
|
127
|
-
abort: {
|
|
128
|
-
reason: string;
|
|
129
|
-
};
|
|
130
|
-
} | {
|
|
131
|
-
error: {
|
|
132
|
-
message: string;
|
|
133
|
-
};
|
|
134
|
-
};
|
|
135
120
|
/**
|
|
136
121
|
* The callable function signature for a client method
|
|
137
122
|
*/
|
package/dist/types.js
CHANGED
|
@@ -1,91 +1,3 @@
|
|
|
1
|
-
export function isPayloadInitialize(payload) {
|
|
2
|
-
if (typeof payload !== "object")
|
|
3
|
-
return false;
|
|
4
|
-
if (payload === null)
|
|
5
|
-
return false;
|
|
6
|
-
if (!("by" in payload))
|
|
7
|
-
return false;
|
|
8
|
-
if (!("nodeId" in payload))
|
|
9
|
-
return false;
|
|
10
|
-
if (!("functionName" in payload))
|
|
11
|
-
return false;
|
|
12
|
-
if (!("localStorageData" in payload))
|
|
13
|
-
return false;
|
|
14
|
-
if (!("isInitializeRequest" in payload))
|
|
15
|
-
return false;
|
|
16
|
-
if (!("allNodeIDs" in payload))
|
|
17
|
-
return false;
|
|
18
|
-
if (payload.by !== "sw&rpc")
|
|
19
|
-
return false;
|
|
20
|
-
if (payload.functionName !== "#initialize")
|
|
21
|
-
return false;
|
|
22
|
-
if (payload.isInitializeRequest !== true)
|
|
23
|
-
return false;
|
|
24
|
-
if (typeof payload.nodeId !== "string")
|
|
25
|
-
return false;
|
|
26
|
-
if (typeof payload.localStorageData !== "object")
|
|
27
|
-
return false;
|
|
28
|
-
if (payload.localStorageData === null)
|
|
29
|
-
return false;
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
export function isPayloadHeader(procedures, payload) {
|
|
33
|
-
if (typeof payload !== "object")
|
|
34
|
-
return false;
|
|
35
|
-
if (payload === null)
|
|
36
|
-
return false;
|
|
37
|
-
if (!("by" in payload))
|
|
38
|
-
return false;
|
|
39
|
-
if (!("requestId" in payload))
|
|
40
|
-
return false;
|
|
41
|
-
if (!("functionName" in payload))
|
|
42
|
-
return false;
|
|
43
|
-
if (payload.by !== "sw&rpc")
|
|
44
|
-
return false;
|
|
45
|
-
if (typeof payload.requestId !== "string")
|
|
46
|
-
return false;
|
|
47
|
-
if (typeof payload.functionName !== "string")
|
|
48
|
-
return false;
|
|
49
|
-
if (!Object.keys(procedures).includes(payload.functionName))
|
|
50
|
-
return false;
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
export function validatePayloadCore(procedure, payload) {
|
|
54
|
-
if (typeof payload !== "object")
|
|
55
|
-
throw new Error("payload is not an object");
|
|
56
|
-
if (payload === null)
|
|
57
|
-
throw new Error("payload is null");
|
|
58
|
-
if ("input" in payload) {
|
|
59
|
-
const input = procedure.input["~standard"].validate(payload.input);
|
|
60
|
-
if ("value" in input)
|
|
61
|
-
return { input: input.value };
|
|
62
|
-
}
|
|
63
|
-
if ("progress" in payload) {
|
|
64
|
-
const progress = procedure.progress["~standard"].validate(payload.progress);
|
|
65
|
-
if ("value" in progress)
|
|
66
|
-
return { progress: progress.value };
|
|
67
|
-
}
|
|
68
|
-
if ("result" in payload) {
|
|
69
|
-
const result = procedure.success["~standard"].validate(payload.result);
|
|
70
|
-
if ("value" in result)
|
|
71
|
-
return { result: result.value };
|
|
72
|
-
}
|
|
73
|
-
if ("abort" in payload &&
|
|
74
|
-
typeof payload.abort === "object" &&
|
|
75
|
-
payload.abort !== null &&
|
|
76
|
-
"reason" in payload.abort &&
|
|
77
|
-
typeof payload.abort.reason === "string") {
|
|
78
|
-
return { abort: { reason: payload.abort.reason } };
|
|
79
|
-
}
|
|
80
|
-
if ("error" in payload &&
|
|
81
|
-
typeof payload.error === "object" &&
|
|
82
|
-
payload.error !== null &&
|
|
83
|
-
"message" in payload.error &&
|
|
84
|
-
typeof payload.error.message === "string") {
|
|
85
|
-
return { error: { message: payload.error.message } };
|
|
86
|
-
}
|
|
87
|
-
throw new Error("invalid payload");
|
|
88
|
-
}
|
|
89
1
|
export const zImplementations = Symbol("SWARPC implementations");
|
|
90
2
|
export const zProcedures = Symbol("SWARPC procedures");
|
|
91
3
|
export class RequestCancelledError extends Error {
|
package/dist/utils.js
CHANGED
|
@@ -35,3 +35,9 @@ export function extractFulfilleds(settleds) {
|
|
|
35
35
|
export function extractRejecteds(settleds) {
|
|
36
36
|
return settleds.filter((settled) => settled.status === "rejected");
|
|
37
37
|
}
|
|
38
|
+
export function isSharedWorker(worker) {
|
|
39
|
+
if (!("SharedWorker" in globalThis)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return worker instanceof SharedWorker;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
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",
|
|
@@ -47,40 +47,43 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@8hobbies/typedoc-plugin-plausible": "^2.2.0",
|
|
50
|
-
"@playwright/test": "^1.
|
|
51
|
-
"@size-limit/esbuild-why": "^12.
|
|
52
|
-
"@size-limit/preset-small-lib": "^12.
|
|
53
|
-
"@vitest/web-worker": "^4.
|
|
54
|
-
"arktype": "^2.
|
|
50
|
+
"@playwright/test": "^1.59.1",
|
|
51
|
+
"@size-limit/esbuild-why": "^12.1.0",
|
|
52
|
+
"@size-limit/preset-small-lib": "^12.1.0",
|
|
53
|
+
"@vitest/web-worker": "^4.1.4",
|
|
54
|
+
"arktype": "^2.2.0",
|
|
55
55
|
"date-fns": "^4.1.0",
|
|
56
56
|
"husky": "^9.1.7",
|
|
57
57
|
"kacl": "^1.1.1",
|
|
58
|
-
"knip": "^5.
|
|
59
|
-
"lint-staged": "^16.
|
|
58
|
+
"knip": "^6.5.0",
|
|
59
|
+
"lint-staged": "^16.4.0",
|
|
60
60
|
"nodemon": "^3.1.14",
|
|
61
|
-
"oxlint": "^1.
|
|
62
|
-
"pkg-pr-new": "^0.0.
|
|
63
|
-
"prettier": "^3.8.
|
|
61
|
+
"oxlint": "^1.60.0",
|
|
62
|
+
"pkg-pr-new": "^0.0.66",
|
|
63
|
+
"prettier": "^3.8.3",
|
|
64
64
|
"sirv-cli": "^3.0.1",
|
|
65
|
-
"size-limit": "^12.
|
|
66
|
-
"typedoc": "^0.28.
|
|
65
|
+
"size-limit": "^12.1.0",
|
|
66
|
+
"typedoc": "^0.28.19",
|
|
67
67
|
"typedoc-material-theme": "^1.4.1",
|
|
68
|
-
"typedoc-plugin-dt-links": "^2.0.
|
|
68
|
+
"typedoc-plugin-dt-links": "^2.0.51",
|
|
69
69
|
"typedoc-plugin-extras": "^4.0.1",
|
|
70
70
|
"typedoc-plugin-inline-sources": "^1.3.0",
|
|
71
71
|
"typedoc-plugin-mdn-links": "^5.1.1",
|
|
72
72
|
"typedoc-plugin-redirect": "^1.3.0",
|
|
73
|
-
"typescript": "^
|
|
74
|
-
"vite": "^
|
|
75
|
-
"vitest": "^4.
|
|
73
|
+
"typescript": "^6.0.3",
|
|
74
|
+
"vite": "^8.0.9",
|
|
75
|
+
"vitest": "^4.1.4"
|
|
76
76
|
},
|
|
77
77
|
"volta": {
|
|
78
|
-
"node": "24.
|
|
79
|
-
"npm": "11.
|
|
78
|
+
"node": "24.15.0",
|
|
79
|
+
"npm": "11.12.1"
|
|
80
80
|
},
|
|
81
81
|
"lint-staged": {
|
|
82
|
+
".prettierrc": [
|
|
83
|
+
"prettier --write"
|
|
84
|
+
],
|
|
82
85
|
"*.{ts,js,md,json,yaml,yml}": [
|
|
83
|
-
"oxlint --fix",
|
|
86
|
+
"oxlint --fix --no-error-on-unmatched-pattern",
|
|
84
87
|
"prettier --write"
|
|
85
88
|
]
|
|
86
89
|
}
|