swarpc 0.19.0 → 0.20.1
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 +5 -118
- 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 +6 -16
- package/dist/types.js +0 -88
- package/dist/utils.js +5 -0
- package/package.json +25 -22
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 {
|
|
@@ -120,6 +119,7 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
|
|
|
120
119
|
pendingRequests.set(requestId, {
|
|
121
120
|
nodeId,
|
|
122
121
|
functionName,
|
|
122
|
+
startedAt: performance.now(),
|
|
123
123
|
concurrencyKey,
|
|
124
124
|
resolve,
|
|
125
125
|
onProgress: onProgress ?? emptyProgressCallback,
|
|
@@ -260,119 +260,6 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
|
|
|
260
260
|
};
|
|
261
261
|
return instance;
|
|
262
262
|
}
|
|
263
|
-
async function postMessage(ctx, message, options) {
|
|
264
|
-
await startClientListener(ctx);
|
|
265
|
-
const { logger: l, node: worker } = ctx;
|
|
266
|
-
if (!worker && !navigator.serviceWorker.controller)
|
|
267
|
-
l.warn("", "Service Worker is not controlling the page");
|
|
268
|
-
const w = worker instanceof SharedWorker
|
|
269
|
-
? worker.port
|
|
270
|
-
: worker === undefined
|
|
271
|
-
? await navigator.serviceWorker.ready.then((r) => r.active)
|
|
272
|
-
: worker;
|
|
273
|
-
if (!w) {
|
|
274
|
-
throw new Error("[SWARPC Client] No active service worker found");
|
|
275
|
-
}
|
|
276
|
-
w.postMessage(message, options);
|
|
277
|
-
}
|
|
278
|
-
function postMessageSync(l, worker, message, options) {
|
|
279
|
-
if (!worker && !navigator.serviceWorker.controller)
|
|
280
|
-
l.warn("Service Worker is not controlling the page");
|
|
281
|
-
const w = worker instanceof SharedWorker
|
|
282
|
-
? worker.port
|
|
283
|
-
: worker === undefined
|
|
284
|
-
? navigator.serviceWorker.controller
|
|
285
|
-
: worker;
|
|
286
|
-
if (!w) {
|
|
287
|
-
throw new Error("[SWARPC Client] No active service worker found");
|
|
288
|
-
}
|
|
289
|
-
w.postMessage(message, options);
|
|
290
|
-
}
|
|
291
|
-
async function startClientListener(ctx) {
|
|
292
|
-
if (_clientListeners.has(nodeIdOrSW(ctx.nodeId)))
|
|
293
|
-
return;
|
|
294
|
-
const { logger: l, node: worker } = ctx;
|
|
295
|
-
if (!worker) {
|
|
296
|
-
const sw = await navigator.serviceWorker.ready;
|
|
297
|
-
if (!sw?.active) {
|
|
298
|
-
throw new Error("[SWARPC Client] Service Worker is not active");
|
|
299
|
-
}
|
|
300
|
-
if (!navigator.serviceWorker.controller) {
|
|
301
|
-
l.warn("", "Service Worker is not controlling the page");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const w = worker ?? navigator.serviceWorker;
|
|
305
|
-
l.debug(null, "Starting client listener", { w, ...ctx });
|
|
306
|
-
const listener = (event) => {
|
|
307
|
-
const eventData = event.data || {};
|
|
308
|
-
if (eventData?.by !== "sw&rpc")
|
|
309
|
-
return;
|
|
310
|
-
const payload = eventData;
|
|
311
|
-
if ("isInitializeRequest" in payload) {
|
|
312
|
-
l.warn(null, "Ignoring unexpected #initialize from server", payload);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
const { requestId, ...data } = payload;
|
|
316
|
-
if (!requestId) {
|
|
317
|
-
throw new Error("[SWARPC Client] Message received without requestId");
|
|
318
|
-
}
|
|
319
|
-
const handlers = pendingRequests.get(requestId);
|
|
320
|
-
if (!handlers) {
|
|
321
|
-
throw new Error(`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`);
|
|
322
|
-
}
|
|
323
|
-
if ("error" in data) {
|
|
324
|
-
ctx.hooks.error?.({
|
|
325
|
-
procedure: data.functionName,
|
|
326
|
-
error: new Error(data.error.message),
|
|
327
|
-
});
|
|
328
|
-
handlers.reject(new Error(data.error.message));
|
|
329
|
-
pendingRequests.delete(requestId);
|
|
330
|
-
}
|
|
331
|
-
else if ("progress" in data) {
|
|
332
|
-
ctx.hooks.progress?.({
|
|
333
|
-
procedure: data.functionName,
|
|
334
|
-
data: data.progress,
|
|
335
|
-
});
|
|
336
|
-
handlers.onProgress(data.progress);
|
|
337
|
-
}
|
|
338
|
-
else if ("result" in data) {
|
|
339
|
-
ctx.hooks.success?.({
|
|
340
|
-
procedure: data.functionName,
|
|
341
|
-
data: data.result,
|
|
342
|
-
});
|
|
343
|
-
handlers.resolve(data.result);
|
|
344
|
-
pendingRequests.delete(requestId);
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
if (w instanceof SharedWorker) {
|
|
348
|
-
w.port.addEventListener("message", listener);
|
|
349
|
-
w.port.start();
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
w.addEventListener("message", listener);
|
|
353
|
-
}
|
|
354
|
-
_clientListeners.set(nodeIdOrSW(ctx.nodeId), {
|
|
355
|
-
disconnect() {
|
|
356
|
-
if (w instanceof SharedWorker) {
|
|
357
|
-
w.port.removeEventListener("message", listener);
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
w.removeEventListener("message", listener);
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
await postMessage(ctx, {
|
|
365
|
-
by: "sw&rpc",
|
|
366
|
-
functionName: "#initialize",
|
|
367
|
-
isInitializeRequest: true,
|
|
368
|
-
localStorageData: ctx.localStorage,
|
|
369
|
-
nodeId: nodeIdOrSW(ctx.nodeId),
|
|
370
|
-
allNodeIDs: ctx.allNodeIDs,
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
function makeRequestId() {
|
|
374
|
-
return Math.random().toString(16).substring(2, 8).toUpperCase();
|
|
375
|
-
}
|
|
376
263
|
function handleBroadcastOrThrowResults(results) {
|
|
377
264
|
if (results.ok) {
|
|
378
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
|
@@ -90,6 +90,8 @@ type ProcedureNameAndData<Procedures extends ProceduresMap, Key extends "progres
|
|
|
90
90
|
[K in keyof Procedures]: {
|
|
91
91
|
procedure: K;
|
|
92
92
|
data: Schema.InferOutput<Procedures[K][Key]>;
|
|
93
|
+
/** Time in milliseconds the procedure call took */
|
|
94
|
+
duration: number;
|
|
93
95
|
};
|
|
94
96
|
}[keyof Procedures];
|
|
95
97
|
/**
|
|
@@ -102,9 +104,12 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
102
104
|
success?: (arg: ProcedureNameAndData<Procedures, "success">) => void;
|
|
103
105
|
/**
|
|
104
106
|
* Called when a procedure call has failed.
|
|
107
|
+
* @param arg
|
|
108
|
+
* @param arg.duration time in milliseconds the procedure call took
|
|
105
109
|
*/
|
|
106
110
|
error?: (arg: {
|
|
107
111
|
procedure: keyof Procedures;
|
|
112
|
+
duration: number;
|
|
108
113
|
error: Error;
|
|
109
114
|
}) => void;
|
|
110
115
|
/**
|
|
@@ -112,21 +117,6 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
112
117
|
*/
|
|
113
118
|
progress?: (arg: ProcedureNameAndData<Procedures, "progress">) => void;
|
|
114
119
|
};
|
|
115
|
-
export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
|
|
116
|
-
input: Schema.InferOutput<PM[Name]["input"]>;
|
|
117
|
-
} | {
|
|
118
|
-
progress: Schema.InferOutput<PM[Name]["progress"]>;
|
|
119
|
-
} | {
|
|
120
|
-
result: Schema.InferOutput<PM[Name]["success"]>;
|
|
121
|
-
} | {
|
|
122
|
-
abort: {
|
|
123
|
-
reason: string;
|
|
124
|
-
};
|
|
125
|
-
} | {
|
|
126
|
-
error: {
|
|
127
|
-
message: string;
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
120
|
/**
|
|
131
121
|
* The callable function signature for a client method
|
|
132
122
|
*/
|
|
@@ -200,7 +190,7 @@ export type BroadcasterResultExtrasFailure = {
|
|
|
200
190
|
successes: [];
|
|
201
191
|
byNode: Map<string, PromiseRejectedResult>;
|
|
202
192
|
};
|
|
203
|
-
type ClientMethodExtraCallables<P extends Procedure<Schema, Schema, Schema>> = {
|
|
193
|
+
export type ClientMethodExtraCallables<P extends Procedure<Schema, Schema, Schema>> = {
|
|
204
194
|
/**
|
|
205
195
|
* 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.
|
|
206
196
|
*/
|
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,8 @@ 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)
|
|
40
|
+
return false;
|
|
41
|
+
return worker instanceof SharedWorker;
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
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,38 +47,41 @@
|
|
|
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.
|
|
60
|
-
"nodemon": "^3.1.
|
|
61
|
-
"oxlint": "^1.
|
|
62
|
-
"pkg-pr-new": "^0.0.
|
|
63
|
-
"prettier": "^3.8.
|
|
58
|
+
"knip": "^6.5.0",
|
|
59
|
+
"lint-staged": "^16.4.0",
|
|
60
|
+
"nodemon": "^3.1.14",
|
|
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
|
-
"typedoc-plugin-mdn-links": "^5.1.
|
|
72
|
-
"typedoc-plugin-redirect": "^1.
|
|
73
|
-
"typescript": "^
|
|
74
|
-
"vite": "^
|
|
75
|
-
"vitest": "^4.
|
|
71
|
+
"typedoc-plugin-mdn-links": "^5.1.1",
|
|
72
|
+
"typedoc-plugin-redirect": "^1.3.0",
|
|
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
86
|
"oxlint --fix",
|
|
84
87
|
"prettier --write"
|