swarpc 0.9.0 → 0.10.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/dist/client.d.ts +19 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +51 -9
- package/dist/server.d.ts +7 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +60 -11
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +65 -13
- package/src/server.ts +86 -12
- package/src/types.ts +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
5
|
import { type Logger, type LogLevel } from "./log.js";
|
|
6
|
-
import { ClientMethod, Hooks, zProcedures, type ProceduresMap } from "./types.js";
|
|
6
|
+
import { ClientMethod, Hooks, Payload, zProcedures, type ProceduresMap } from "./types.js";
|
|
7
7
|
/**
|
|
8
8
|
* The sw&rpc client instance, which provides {@link ClientMethod | methods to call procedures}.
|
|
9
9
|
* Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
|
|
@@ -18,27 +18,40 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
|
|
|
18
18
|
*
|
|
19
19
|
* @param procedures procedures the client will be able to call, see {@link ProceduresMap}
|
|
20
20
|
* @param options various options
|
|
21
|
-
* @param options.worker
|
|
22
|
-
*
|
|
23
|
-
* @
|
|
21
|
+
* @param options.worker The instantiated worker object. If not provided, the client will use the service worker.
|
|
22
|
+
* Example: `new Worker("./worker.js")`
|
|
23
|
+
* See {@link Worker} (used by both dedicated workers and service workers), {@link SharedWorker}, and
|
|
24
|
+
* the different [worker types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API#worker_types) that exist
|
|
25
|
+
* @param options.hooks Hooks to run on messages received from the server. See {@link Hooks}
|
|
26
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
27
|
+
* @param options.restartListener If true, will force the listener to restart even if it has already been started. You should probably leave this to false, unless you are testing and want to reset the client state.
|
|
24
28
|
* @returns a sw&rpc client instance. Each property of the procedures map will be a method, that accepts an input and an optional onProgress callback, see {@link ClientMethod}
|
|
25
29
|
*
|
|
26
30
|
* An example of defining and using a client:
|
|
27
31
|
* {@includeCode ../example/src/routes/+page.svelte}
|
|
28
32
|
*/
|
|
29
33
|
export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker, loglevel, restartListener, hooks, }?: {
|
|
30
|
-
worker?: Worker;
|
|
34
|
+
worker?: Worker | SharedWorker;
|
|
31
35
|
hooks?: Hooks<Procedures>;
|
|
32
36
|
loglevel?: LogLevel;
|
|
33
37
|
restartListener?: boolean;
|
|
34
38
|
}): SwarpcClient<Procedures>;
|
|
39
|
+
/**
|
|
40
|
+
* A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
|
|
41
|
+
* esp. useful for abort logic that needs to not be... put behind everything else on the event loop.
|
|
42
|
+
* @param l
|
|
43
|
+
* @param worker
|
|
44
|
+
* @param message
|
|
45
|
+
* @param options
|
|
46
|
+
*/
|
|
47
|
+
export declare function postMessageSync<Procedures extends ProceduresMap>(l: Logger, worker: Worker | SharedWorker | undefined, message: Payload<Procedures>, options?: StructuredSerializeOptions): void;
|
|
35
48
|
/**
|
|
36
49
|
* Starts the client listener, which listens for messages from the sw&rpc server.
|
|
37
50
|
* @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
|
|
38
51
|
* @param force if true, will force the listener to restart even if it has already been started
|
|
39
52
|
* @returns
|
|
40
53
|
*/
|
|
41
|
-
export declare function startClientListener<Procedures extends ProceduresMap>(l: Logger, worker?: Worker, hooks?: Hooks<Procedures>): Promise<void>;
|
|
54
|
+
export declare function startClientListener<Procedures extends ProceduresMap>(l: Logger, worker?: Worker | SharedWorker, hooks?: Hooks<Procedures>): Promise<void>;
|
|
42
55
|
/**
|
|
43
56
|
* Generate a random request ID, used to identify requests between client and server.
|
|
44
57
|
* @source
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnE,OAAO,EACL,YAAY,EACZ,KAAK,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnE,OAAO,EACL,YAAY,EACZ,KAAK,EACL,OAAO,EAEP,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAA;AAGnB;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;CAC1B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAA;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,QAAkB,EAClB,eAAuB,EACvB,KAAU,GACX,GAAE;IACD,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAC9B,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;CACrB,GACL,YAAY,CAAC,UAAU,CAAC,CAsG1B;AAiCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,UAAU,SAAS,aAAa,EAC9D,CAAC,EAAE,MAAM,EACT,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,EACzC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,EAC5B,OAAO,CAAC,EAAE,0BAA0B,GACnC,IAAI,CAiBN;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,SAAS,aAAa,EACxE,CAAC,EAAE,MAAM,EACT,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,EAC9B,KAAK,GAAE,KAAK,CAAC,UAAU,CAAM,iBAmE9B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/client.js
CHANGED
|
@@ -17,9 +17,13 @@ let _clientListenerStarted = false;
|
|
|
17
17
|
*
|
|
18
18
|
* @param procedures procedures the client will be able to call, see {@link ProceduresMap}
|
|
19
19
|
* @param options various options
|
|
20
|
-
* @param options.worker
|
|
21
|
-
*
|
|
22
|
-
* @
|
|
20
|
+
* @param options.worker The instantiated worker object. If not provided, the client will use the service worker.
|
|
21
|
+
* Example: `new Worker("./worker.js")`
|
|
22
|
+
* See {@link Worker} (used by both dedicated workers and service workers), {@link SharedWorker}, and
|
|
23
|
+
* the different [worker types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API#worker_types) that exist
|
|
24
|
+
* @param options.hooks Hooks to run on messages received from the server. See {@link Hooks}
|
|
25
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
26
|
+
* @param options.restartListener If true, will force the listener to restart even if it has already been started. You should probably leave this to false, unless you are testing and want to reset the client state.
|
|
23
27
|
* @returns a sw&rpc client instance. Each property of the procedures map will be a method, that accepts an input and an optional onProgress callback, see {@link ClientMethod}
|
|
24
28
|
*
|
|
25
29
|
* An example of defining and using a client:
|
|
@@ -63,7 +67,7 @@ export function Client(procedures, { worker, loglevel = "debug", restartListener
|
|
|
63
67
|
: [];
|
|
64
68
|
// Post the message to the server
|
|
65
69
|
l.debug(requestId, `Requesting ${functionName} with`, input);
|
|
66
|
-
send(requestId, { input }, { transfer })
|
|
70
|
+
return send(requestId, { input }, { transfer })
|
|
67
71
|
.then(() => { })
|
|
68
72
|
.catch(reject);
|
|
69
73
|
});
|
|
@@ -74,13 +78,18 @@ export function Client(procedures, { worker, loglevel = "debug", restartListener
|
|
|
74
78
|
const requestId = makeRequestId();
|
|
75
79
|
return {
|
|
76
80
|
request: _runProcedure(input, onProgress, requestId),
|
|
77
|
-
|
|
81
|
+
cancel(reason) {
|
|
78
82
|
if (!pendingRequests.has(requestId)) {
|
|
79
83
|
l.warn(requestId, `Cannot cancel ${functionName} request, it has already been resolved or rejected`);
|
|
80
84
|
return;
|
|
81
85
|
}
|
|
82
86
|
l.debug(requestId, `Cancelling ${functionName} with`, reason);
|
|
83
|
-
|
|
87
|
+
postMessageSync(l, worker, {
|
|
88
|
+
by: "sw&rpc",
|
|
89
|
+
requestId,
|
|
90
|
+
functionName,
|
|
91
|
+
abort: { reason },
|
|
92
|
+
});
|
|
84
93
|
pendingRequests.delete(requestId);
|
|
85
94
|
},
|
|
86
95
|
};
|
|
@@ -97,7 +106,33 @@ async function postMessage(l, worker, hooks, message, options) {
|
|
|
97
106
|
if (!worker && !navigator.serviceWorker.controller)
|
|
98
107
|
l.warn("", "Service Worker is not controlling the page");
|
|
99
108
|
// If no worker is provided, we use the service worker
|
|
100
|
-
const w = worker
|
|
109
|
+
const w = worker instanceof SharedWorker
|
|
110
|
+
? worker.port
|
|
111
|
+
: worker === undefined
|
|
112
|
+
? await navigator.serviceWorker.ready.then((r) => r.active)
|
|
113
|
+
: worker;
|
|
114
|
+
if (!w) {
|
|
115
|
+
throw new Error("[SWARPC Client] No active service worker found");
|
|
116
|
+
}
|
|
117
|
+
w.postMessage(message, options);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
|
|
121
|
+
* esp. useful for abort logic that needs to not be... put behind everything else on the event loop.
|
|
122
|
+
* @param l
|
|
123
|
+
* @param worker
|
|
124
|
+
* @param message
|
|
125
|
+
* @param options
|
|
126
|
+
*/
|
|
127
|
+
export function postMessageSync(l, worker, message, options) {
|
|
128
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
129
|
+
l.warn("", "Service Worker is not controlling the page");
|
|
130
|
+
// If no worker is provided, we use the service worker
|
|
131
|
+
const w = worker instanceof SharedWorker
|
|
132
|
+
? worker.port
|
|
133
|
+
: worker === undefined
|
|
134
|
+
? navigator.serviceWorker.controller
|
|
135
|
+
: worker;
|
|
101
136
|
if (!w) {
|
|
102
137
|
throw new Error("[SWARPC Client] No active service worker found");
|
|
103
138
|
}
|
|
@@ -125,7 +160,7 @@ export async function startClientListener(l, worker, hooks = {}) {
|
|
|
125
160
|
const w = worker ?? navigator.serviceWorker;
|
|
126
161
|
// Start listening for messages
|
|
127
162
|
l.debug(null, "Starting client listener", { worker, w, hooks });
|
|
128
|
-
|
|
163
|
+
const listener = (event) => {
|
|
129
164
|
// Get the data from the event
|
|
130
165
|
const eventData = event.data || {};
|
|
131
166
|
// Ignore other messages that aren't for us
|
|
@@ -158,7 +193,14 @@ export async function startClientListener(l, worker, hooks = {}) {
|
|
|
158
193
|
handlers.resolve(data.result);
|
|
159
194
|
pendingRequests.delete(requestId);
|
|
160
195
|
}
|
|
161
|
-
}
|
|
196
|
+
};
|
|
197
|
+
if (w instanceof SharedWorker) {
|
|
198
|
+
w.port.addEventListener("message", listener);
|
|
199
|
+
w.port.start();
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
w.addEventListener("message", listener);
|
|
203
|
+
}
|
|
162
204
|
_clientListenerStarted = true;
|
|
163
205
|
}
|
|
164
206
|
/**
|
package/dist/server.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ImplementationsMap, ProcedureImplementation, zImplementations, zProcedu
|
|
|
11
11
|
export type SwarpcServer<Procedures extends ProceduresMap> = {
|
|
12
12
|
[zProcedures]: Procedures;
|
|
13
13
|
[zImplementations]: ImplementationsMap<Procedures>;
|
|
14
|
-
start(
|
|
14
|
+
start(): Promise<void>;
|
|
15
15
|
} & {
|
|
16
16
|
[F in keyof Procedures]: (impl: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>) => void;
|
|
17
17
|
};
|
|
@@ -19,14 +19,17 @@ export type SwarpcServer<Procedures extends ProceduresMap> = {
|
|
|
19
19
|
* Creates a sw&rpc server instance.
|
|
20
20
|
* @param procedures procedures the server will implement, see {@link ProceduresMap}
|
|
21
21
|
* @param options various options
|
|
22
|
-
* @param options.worker
|
|
22
|
+
* @param options.worker The worker scope to use, defaults to the `self` of the file where Server() is called.
|
|
23
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
24
|
+
* @param options._scopeType @internal Don't touch, this is used in testing environments because the mock is subpar. Manually overrides worker scope type detection.
|
|
23
25
|
* @returns a SwarpcServer instance. Each property of the procedures map will be a method, that accepts a function implementing the procedure (see {@link ProcedureImplementation}). There is also .start(), to be called after implementing all procedures.
|
|
24
26
|
*
|
|
25
27
|
* An example of defining a server:
|
|
26
28
|
* {@includeCode ../example/src/service-worker.ts}
|
|
27
29
|
*/
|
|
28
|
-
export declare function Server<Procedures extends ProceduresMap>(procedures: Procedures, {
|
|
29
|
-
|
|
30
|
+
export declare function Server<Procedures extends ProceduresMap>(procedures: Procedures, { loglevel, scope, _scopeType, }?: {
|
|
31
|
+
scope?: WorkerGlobalScope;
|
|
30
32
|
loglevel?: LogLevel;
|
|
33
|
+
_scopeType?: "dedicated" | "shared" | "service";
|
|
31
34
|
}): SwarpcServer<Procedures>;
|
|
32
35
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,EACL,kBAAkB,EAKlB,uBAAuB,EACvB,gBAAgB,EAChB,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAA;AAgBnB;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;IACzB,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;IAClD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB,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,CAAA;AAKD;;;;;;;;;;;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,CAAA;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAA;CAC3C,GACL,YAAY,CAAC,UAAU,CAAC,CAgN1B"}
|
package/dist/server.js
CHANGED
|
@@ -2,31 +2,52 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
|
+
/// <reference lib="webworker" />
|
|
5
6
|
import { type } from "arktype";
|
|
6
7
|
import { createLogger } from "./log.js";
|
|
7
8
|
import { PayloadHeaderSchema, PayloadSchema, zImplementations, zProcedures, } from "./types.js";
|
|
8
9
|
import { findTransferables } from "./utils.js";
|
|
10
|
+
class MockedWorkerGlobalScope {
|
|
11
|
+
constructor() { }
|
|
12
|
+
}
|
|
13
|
+
const SharedWorkerGlobalScope = globalThis.SharedWorkerGlobalScope ?? MockedWorkerGlobalScope;
|
|
14
|
+
const DedicatedWorkerGlobalScope = globalThis.DedicatedWorkerGlobalScope ?? MockedWorkerGlobalScope;
|
|
15
|
+
const ServiceWorkerGlobalScope = globalThis.ServiceWorkerGlobalScope ?? MockedWorkerGlobalScope;
|
|
9
16
|
const abortControllers = new Map();
|
|
10
17
|
const abortedRequests = new Set();
|
|
11
18
|
/**
|
|
12
19
|
* Creates a sw&rpc server instance.
|
|
13
20
|
* @param procedures procedures the server will implement, see {@link ProceduresMap}
|
|
14
21
|
* @param options various options
|
|
15
|
-
* @param options.worker
|
|
22
|
+
* @param options.worker The worker scope to use, defaults to the `self` of the file where Server() is called.
|
|
23
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
24
|
+
* @param options._scopeType @internal Don't touch, this is used in testing environments because the mock is subpar. Manually overrides worker scope type detection.
|
|
16
25
|
* @returns a SwarpcServer instance. Each property of the procedures map will be a method, that accepts a function implementing the procedure (see {@link ProcedureImplementation}). There is also .start(), to be called after implementing all procedures.
|
|
17
26
|
*
|
|
18
27
|
* An example of defining a server:
|
|
19
28
|
* {@includeCode ../example/src/service-worker.ts}
|
|
20
29
|
*/
|
|
21
|
-
export function Server(procedures, {
|
|
30
|
+
export function Server(procedures, { loglevel = "debug", scope, _scopeType, } = {}) {
|
|
22
31
|
const l = createLogger("server", loglevel);
|
|
32
|
+
// If scope is not provided, use the global scope
|
|
33
|
+
// This function is meant to be used in a worker, so `self` is a WorkerGlobalScope
|
|
34
|
+
scope ??= self;
|
|
35
|
+
function scopeIsShared(scope) {
|
|
36
|
+
return scope instanceof SharedWorkerGlobalScope || _scopeType === "shared";
|
|
37
|
+
}
|
|
38
|
+
function scopeIsDedicated(scope) {
|
|
39
|
+
return (scope instanceof DedicatedWorkerGlobalScope || _scopeType === "dedicated");
|
|
40
|
+
}
|
|
41
|
+
function scopeIsService(scope) {
|
|
42
|
+
return scope instanceof ServiceWorkerGlobalScope || _scopeType === "service";
|
|
43
|
+
}
|
|
23
44
|
// Initialize the instance.
|
|
24
45
|
// Procedures and implementations are stored on properties with symbol keys,
|
|
25
46
|
// to avoid any conflicts with procedure names, and also discourage direct access to them.
|
|
26
47
|
const instance = {
|
|
27
48
|
[zProcedures]: procedures,
|
|
28
49
|
[zImplementations]: {},
|
|
29
|
-
start: (
|
|
50
|
+
start: async () => { },
|
|
30
51
|
};
|
|
31
52
|
// Set all implementation-setter methods
|
|
32
53
|
for (const functionName in procedures) {
|
|
@@ -47,21 +68,32 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
|
|
|
47
68
|
};
|
|
48
69
|
});
|
|
49
70
|
}
|
|
50
|
-
instance.start = (
|
|
71
|
+
instance.start = async () => {
|
|
72
|
+
const port = await new Promise((resolve) => {
|
|
73
|
+
if (!scopeIsShared(scope))
|
|
74
|
+
return resolve(undefined);
|
|
75
|
+
console.log("Awaiting shared worker connection...");
|
|
76
|
+
scope.addEventListener("connect", ({ ports: [port] }) => {
|
|
77
|
+
console.log("Shared worker connected with port", port);
|
|
78
|
+
resolve(port);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
51
81
|
// Used to post messages back to the client
|
|
52
82
|
const postMessage = async (autotransfer, data) => {
|
|
53
83
|
const transfer = autotransfer ? [] : findTransferables(data);
|
|
54
|
-
if (
|
|
55
|
-
|
|
84
|
+
if (port) {
|
|
85
|
+
port.postMessage(data, { transfer });
|
|
56
86
|
}
|
|
57
|
-
else {
|
|
58
|
-
|
|
87
|
+
else if (scopeIsDedicated(scope)) {
|
|
88
|
+
scope.postMessage(data, { transfer });
|
|
89
|
+
}
|
|
90
|
+
else if (scopeIsService(scope)) {
|
|
91
|
+
await scope.clients.matchAll().then((clients) => {
|
|
59
92
|
clients.forEach((client) => client.postMessage(data, { transfer }));
|
|
60
93
|
});
|
|
61
94
|
}
|
|
62
95
|
};
|
|
63
|
-
|
|
64
|
-
self.addEventListener("message", async (event) => {
|
|
96
|
+
const listener = async (event) => {
|
|
65
97
|
// Decode the payload
|
|
66
98
|
const { requestId, functionName } = PayloadHeaderSchema(type.enumerated(...Object.keys(procedures))).assert(event.data);
|
|
67
99
|
l.debug(requestId, `Received request for ${functionName}`, event.data);
|
|
@@ -134,7 +166,24 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
|
|
|
134
166
|
finally {
|
|
135
167
|
abortedRequests.delete(requestId);
|
|
136
168
|
}
|
|
137
|
-
}
|
|
169
|
+
};
|
|
170
|
+
// Listen for messages from the client
|
|
171
|
+
if (scopeIsShared(scope)) {
|
|
172
|
+
if (!port)
|
|
173
|
+
throw new Error("SharedWorker port not initialized");
|
|
174
|
+
console.log("Listening for shared worker messages on port", port);
|
|
175
|
+
port.addEventListener("message", listener);
|
|
176
|
+
port.start();
|
|
177
|
+
}
|
|
178
|
+
else if (scopeIsDedicated(scope)) {
|
|
179
|
+
scope.addEventListener("message", listener);
|
|
180
|
+
}
|
|
181
|
+
else if (scopeIsService(scope)) {
|
|
182
|
+
scope.addEventListener("message", listener);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
throw new Error(`Unsupported worker scope ${scope}`);
|
|
186
|
+
}
|
|
138
187
|
};
|
|
139
188
|
return instance;
|
|
140
189
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type CancelablePromise<T = unknown> = {
|
|
|
48
48
|
* Abort the request.
|
|
49
49
|
* @param reason The reason for cancelling the request.
|
|
50
50
|
*/
|
|
51
|
-
cancel: (reason: string) =>
|
|
51
|
+
cancel: (reason: string) => void;
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
54
|
* An implementation of a procedure
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,KAAK,IAAI,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,EAAU,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAErD;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,IAAI;IACtE;;OAEG;IACH,KAAK,EAAE,CAAC,CAAA;IACR;;;OAGG;IACH,QAAQ,EAAE,CAAC,CAAA;IACX;;OAEG;IACH,OAAO,EAAE,CAAC,CAAA;IACV;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAA;CAClD,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC3C,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACnB;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"sirv-cli": "^3.0.1",
|
|
51
51
|
"typedoc": "^0.28.9",
|
|
52
52
|
"typedoc-material-theme": "^1.4.0",
|
|
53
|
-
"typedoc-plugin-dt-links": "^2.0.
|
|
53
|
+
"typedoc-plugin-dt-links": "^2.0.13",
|
|
54
54
|
"typedoc-plugin-extras": "^4.0.1",
|
|
55
55
|
"typedoc-plugin-inline-sources": "^1.3.0",
|
|
56
|
-
"typedoc-plugin-mdn-links": "^5.0.
|
|
56
|
+
"typedoc-plugin-mdn-links": "^5.0.7",
|
|
57
57
|
"typedoc-plugin-redirect": "^1.2.0",
|
|
58
58
|
"typescript": "^5.9.2",
|
|
59
59
|
"vite": "^7.0.6",
|
package/src/client.ts
CHANGED
|
@@ -45,9 +45,13 @@ let _clientListenerStarted = false
|
|
|
45
45
|
*
|
|
46
46
|
* @param procedures procedures the client will be able to call, see {@link ProceduresMap}
|
|
47
47
|
* @param options various options
|
|
48
|
-
* @param options.worker
|
|
49
|
-
*
|
|
50
|
-
* @
|
|
48
|
+
* @param options.worker The instantiated worker object. If not provided, the client will use the service worker.
|
|
49
|
+
* Example: `new Worker("./worker.js")`
|
|
50
|
+
* See {@link Worker} (used by both dedicated workers and service workers), {@link SharedWorker}, and
|
|
51
|
+
* the different [worker types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API#worker_types) that exist
|
|
52
|
+
* @param options.hooks Hooks to run on messages received from the server. See {@link Hooks}
|
|
53
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
54
|
+
* @param options.restartListener If true, will force the listener to restart even if it has already been started. You should probably leave this to false, unless you are testing and want to reset the client state.
|
|
51
55
|
* @returns a sw&rpc client instance. Each property of the procedures map will be a method, that accepts an input and an optional onProgress callback, see {@link ClientMethod}
|
|
52
56
|
*
|
|
53
57
|
* An example of defining and using a client:
|
|
@@ -61,7 +65,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
61
65
|
restartListener = false,
|
|
62
66
|
hooks = {},
|
|
63
67
|
}: {
|
|
64
|
-
worker?: Worker
|
|
68
|
+
worker?: Worker | SharedWorker
|
|
65
69
|
hooks?: Hooks<Procedures>
|
|
66
70
|
loglevel?: LogLevel
|
|
67
71
|
restartListener?: boolean
|
|
@@ -133,7 +137,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
133
137
|
|
|
134
138
|
// Post the message to the server
|
|
135
139
|
l.debug(requestId, `Requesting ${functionName} with`, input)
|
|
136
|
-
send(requestId, { input }, { transfer })
|
|
140
|
+
return send(requestId, { input }, { transfer })
|
|
137
141
|
.then(() => {})
|
|
138
142
|
.catch(reject)
|
|
139
143
|
})
|
|
@@ -145,7 +149,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
145
149
|
const requestId = makeRequestId()
|
|
146
150
|
return {
|
|
147
151
|
request: _runProcedure(input, onProgress, requestId),
|
|
148
|
-
|
|
152
|
+
cancel(reason: string) {
|
|
149
153
|
if (!pendingRequests.has(requestId)) {
|
|
150
154
|
l.warn(
|
|
151
155
|
requestId,
|
|
@@ -155,7 +159,12 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
l.debug(requestId, `Cancelling ${functionName} with`, reason)
|
|
158
|
-
|
|
162
|
+
postMessageSync(l, worker, {
|
|
163
|
+
by: "sw&rpc",
|
|
164
|
+
requestId,
|
|
165
|
+
functionName,
|
|
166
|
+
abort: { reason },
|
|
167
|
+
})
|
|
159
168
|
pendingRequests.delete(requestId)
|
|
160
169
|
},
|
|
161
170
|
}
|
|
@@ -171,7 +180,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
171
180
|
*/
|
|
172
181
|
async function postMessage<Procedures extends ProceduresMap>(
|
|
173
182
|
l: Logger,
|
|
174
|
-
worker: Worker | undefined,
|
|
183
|
+
worker: Worker | SharedWorker | undefined,
|
|
175
184
|
hooks: Hooks<Procedures>,
|
|
176
185
|
message: Payload<Procedures>,
|
|
177
186
|
options?: StructuredSerializeOptions
|
|
@@ -183,7 +192,43 @@ async function postMessage<Procedures extends ProceduresMap>(
|
|
|
183
192
|
|
|
184
193
|
// If no worker is provided, we use the service worker
|
|
185
194
|
const w =
|
|
186
|
-
worker
|
|
195
|
+
worker instanceof SharedWorker
|
|
196
|
+
? worker.port
|
|
197
|
+
: worker === undefined
|
|
198
|
+
? await navigator.serviceWorker.ready.then((r) => r.active)
|
|
199
|
+
: worker
|
|
200
|
+
|
|
201
|
+
if (!w) {
|
|
202
|
+
throw new Error("[SWARPC Client] No active service worker found")
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
w.postMessage(message, options)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
|
|
210
|
+
* esp. useful for abort logic that needs to not be... put behind everything else on the event loop.
|
|
211
|
+
* @param l
|
|
212
|
+
* @param worker
|
|
213
|
+
* @param message
|
|
214
|
+
* @param options
|
|
215
|
+
*/
|
|
216
|
+
export function postMessageSync<Procedures extends ProceduresMap>(
|
|
217
|
+
l: Logger,
|
|
218
|
+
worker: Worker | SharedWorker | undefined,
|
|
219
|
+
message: Payload<Procedures>,
|
|
220
|
+
options?: StructuredSerializeOptions
|
|
221
|
+
): void {
|
|
222
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
223
|
+
l.warn("", "Service Worker is not controlling the page")
|
|
224
|
+
|
|
225
|
+
// If no worker is provided, we use the service worker
|
|
226
|
+
const w =
|
|
227
|
+
worker instanceof SharedWorker
|
|
228
|
+
? worker.port
|
|
229
|
+
: worker === undefined
|
|
230
|
+
? navigator.serviceWorker.controller
|
|
231
|
+
: worker
|
|
187
232
|
|
|
188
233
|
if (!w) {
|
|
189
234
|
throw new Error("[SWARPC Client] No active service worker found")
|
|
@@ -200,7 +245,7 @@ async function postMessage<Procedures extends ProceduresMap>(
|
|
|
200
245
|
*/
|
|
201
246
|
export async function startClientListener<Procedures extends ProceduresMap>(
|
|
202
247
|
l: Logger,
|
|
203
|
-
worker?: Worker,
|
|
248
|
+
worker?: Worker | SharedWorker,
|
|
204
249
|
hooks: Hooks<Procedures> = {}
|
|
205
250
|
) {
|
|
206
251
|
if (_clientListenerStarted) return
|
|
@@ -221,7 +266,7 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
221
266
|
|
|
222
267
|
// Start listening for messages
|
|
223
268
|
l.debug(null, "Starting client listener", { worker, w, hooks })
|
|
224
|
-
|
|
269
|
+
const listener = (event: Event): void => {
|
|
225
270
|
// Get the data from the event
|
|
226
271
|
const eventData = (event as MessageEvent).data || {}
|
|
227
272
|
|
|
@@ -240,7 +285,7 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
240
285
|
const handlers = pendingRequests.get(requestId)
|
|
241
286
|
if (!handlers) {
|
|
242
287
|
throw new Error(
|
|
243
|
-
`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}
|
|
288
|
+
`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`
|
|
244
289
|
)
|
|
245
290
|
}
|
|
246
291
|
|
|
@@ -258,7 +303,14 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
258
303
|
handlers.resolve(data.result)
|
|
259
304
|
pendingRequests.delete(requestId)
|
|
260
305
|
}
|
|
261
|
-
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (w instanceof SharedWorker) {
|
|
309
|
+
w.port.addEventListener("message", listener)
|
|
310
|
+
w.port.start()
|
|
311
|
+
} else {
|
|
312
|
+
w.addEventListener("message", listener)
|
|
313
|
+
}
|
|
262
314
|
|
|
263
315
|
_clientListenerStarted = true
|
|
264
316
|
}
|
package/src/server.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/// <reference lib="webworker" />
|
|
6
7
|
import { type } from "arktype"
|
|
7
8
|
import { createLogger, type LogLevel } from "./log.js"
|
|
8
9
|
import {
|
|
@@ -18,6 +19,19 @@ import {
|
|
|
18
19
|
} from "./types.js"
|
|
19
20
|
import { findTransferables } from "./utils.js"
|
|
20
21
|
|
|
22
|
+
class MockedWorkerGlobalScope {
|
|
23
|
+
constructor() {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SharedWorkerGlobalScope =
|
|
27
|
+
globalThis.SharedWorkerGlobalScope ?? MockedWorkerGlobalScope
|
|
28
|
+
|
|
29
|
+
const DedicatedWorkerGlobalScope =
|
|
30
|
+
globalThis.DedicatedWorkerGlobalScope ?? MockedWorkerGlobalScope
|
|
31
|
+
|
|
32
|
+
const ServiceWorkerGlobalScope =
|
|
33
|
+
globalThis.ServiceWorkerGlobalScope ?? MockedWorkerGlobalScope
|
|
34
|
+
|
|
21
35
|
/**
|
|
22
36
|
* The sw&rpc server instance, which provides methods to register {@link ProcedureImplementation | procedure implementations},
|
|
23
37
|
* and listens for incoming messages that call those procedures
|
|
@@ -25,7 +39,7 @@ import { findTransferables } from "./utils.js"
|
|
|
25
39
|
export type SwarpcServer<Procedures extends ProceduresMap> = {
|
|
26
40
|
[zProcedures]: Procedures
|
|
27
41
|
[zImplementations]: ImplementationsMap<Procedures>
|
|
28
|
-
start(
|
|
42
|
+
start(): Promise<void>
|
|
29
43
|
} & {
|
|
30
44
|
[F in keyof Procedures]: (
|
|
31
45
|
impl: ProcedureImplementation<
|
|
@@ -43,7 +57,9 @@ const abortedRequests = new Set<string>()
|
|
|
43
57
|
* Creates a sw&rpc server instance.
|
|
44
58
|
* @param procedures procedures the server will implement, see {@link ProceduresMap}
|
|
45
59
|
* @param options various options
|
|
46
|
-
* @param options.worker
|
|
60
|
+
* @param options.worker The worker scope to use, defaults to the `self` of the file where Server() is called.
|
|
61
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
62
|
+
* @param options._scopeType @internal Don't touch, this is used in testing environments because the mock is subpar. Manually overrides worker scope type detection.
|
|
47
63
|
* @returns a SwarpcServer instance. Each property of the procedures map will be a method, that accepts a function implementing the procedure (see {@link ProcedureImplementation}). There is also .start(), to be called after implementing all procedures.
|
|
48
64
|
*
|
|
49
65
|
* An example of defining a server:
|
|
@@ -51,17 +67,49 @@ const abortedRequests = new Set<string>()
|
|
|
51
67
|
*/
|
|
52
68
|
export function Server<Procedures extends ProceduresMap>(
|
|
53
69
|
procedures: Procedures,
|
|
54
|
-
{
|
|
70
|
+
{
|
|
71
|
+
loglevel = "debug",
|
|
72
|
+
scope,
|
|
73
|
+
_scopeType,
|
|
74
|
+
}: {
|
|
75
|
+
scope?: WorkerGlobalScope
|
|
76
|
+
loglevel?: LogLevel
|
|
77
|
+
_scopeType?: "dedicated" | "shared" | "service"
|
|
78
|
+
} = {}
|
|
55
79
|
): SwarpcServer<Procedures> {
|
|
56
80
|
const l = createLogger("server", loglevel)
|
|
57
81
|
|
|
82
|
+
// If scope is not provided, use the global scope
|
|
83
|
+
// This function is meant to be used in a worker, so `self` is a WorkerGlobalScope
|
|
84
|
+
scope ??= self as WorkerGlobalScope
|
|
85
|
+
|
|
86
|
+
function scopeIsShared(
|
|
87
|
+
scope: WorkerGlobalScope
|
|
88
|
+
): scope is SharedWorkerGlobalScope {
|
|
89
|
+
return scope instanceof SharedWorkerGlobalScope || _scopeType === "shared"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function scopeIsDedicated(
|
|
93
|
+
scope: WorkerGlobalScope
|
|
94
|
+
): scope is DedicatedWorkerGlobalScope {
|
|
95
|
+
return (
|
|
96
|
+
scope instanceof DedicatedWorkerGlobalScope || _scopeType === "dedicated"
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scopeIsService(
|
|
101
|
+
scope: WorkerGlobalScope
|
|
102
|
+
): scope is ServiceWorkerGlobalScope {
|
|
103
|
+
return scope instanceof ServiceWorkerGlobalScope || _scopeType === "service"
|
|
104
|
+
}
|
|
105
|
+
|
|
58
106
|
// Initialize the instance.
|
|
59
107
|
// Procedures and implementations are stored on properties with symbol keys,
|
|
60
108
|
// to avoid any conflicts with procedure names, and also discourage direct access to them.
|
|
61
109
|
const instance = {
|
|
62
110
|
[zProcedures]: procedures,
|
|
63
111
|
[zImplementations]: {} as ImplementationsMap<Procedures>,
|
|
64
|
-
start: (
|
|
112
|
+
start: async () => {},
|
|
65
113
|
} as SwarpcServer<Procedures>
|
|
66
114
|
|
|
67
115
|
// Set all implementation-setter methods
|
|
@@ -85,7 +133,16 @@ export function Server<Procedures extends ProceduresMap>(
|
|
|
85
133
|
}) as SwarpcServer<Procedures>[typeof functionName]
|
|
86
134
|
}
|
|
87
135
|
|
|
88
|
-
instance.start = (
|
|
136
|
+
instance.start = async () => {
|
|
137
|
+
const port = await new Promise<MessagePort | undefined>((resolve) => {
|
|
138
|
+
if (!scopeIsShared(scope)) return resolve(undefined)
|
|
139
|
+
console.log("Awaiting shared worker connection...")
|
|
140
|
+
scope.addEventListener("connect", ({ ports: [port] }) => {
|
|
141
|
+
console.log("Shared worker connected with port", port)
|
|
142
|
+
resolve(port)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
89
146
|
// Used to post messages back to the client
|
|
90
147
|
const postMessage = async (
|
|
91
148
|
autotransfer: boolean,
|
|
@@ -93,17 +150,20 @@ export function Server<Procedures extends ProceduresMap>(
|
|
|
93
150
|
) => {
|
|
94
151
|
const transfer = autotransfer ? [] : findTransferables(data)
|
|
95
152
|
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
|
|
153
|
+
if (port) {
|
|
154
|
+
port.postMessage(data, { transfer })
|
|
155
|
+
} else if (scopeIsDedicated(scope)) {
|
|
156
|
+
scope.postMessage(data, { transfer })
|
|
157
|
+
} else if (scopeIsService(scope)) {
|
|
158
|
+
await scope.clients.matchAll().then((clients) => {
|
|
100
159
|
clients.forEach((client) => client.postMessage(data, { transfer }))
|
|
101
160
|
})
|
|
102
161
|
}
|
|
103
162
|
}
|
|
104
163
|
|
|
105
|
-
|
|
106
|
-
|
|
164
|
+
const listener = async (
|
|
165
|
+
event: MessageEvent<any> | ExtendableMessageEvent
|
|
166
|
+
): Promise<void> => {
|
|
107
167
|
// Decode the payload
|
|
108
168
|
const { requestId, functionName } = PayloadHeaderSchema(
|
|
109
169
|
type.enumerated(...Object.keys(procedures))
|
|
@@ -206,7 +266,21 @@ export function Server<Procedures extends ProceduresMap>(
|
|
|
206
266
|
} finally {
|
|
207
267
|
abortedRequests.delete(requestId)
|
|
208
268
|
}
|
|
209
|
-
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Listen for messages from the client
|
|
272
|
+
if (scopeIsShared(scope)) {
|
|
273
|
+
if (!port) throw new Error("SharedWorker port not initialized")
|
|
274
|
+
console.log("Listening for shared worker messages on port", port)
|
|
275
|
+
port.addEventListener("message", listener)
|
|
276
|
+
port.start()
|
|
277
|
+
} else if (scopeIsDedicated(scope)) {
|
|
278
|
+
scope.addEventListener("message", listener)
|
|
279
|
+
} else if (scopeIsService(scope)) {
|
|
280
|
+
scope.addEventListener("message", listener)
|
|
281
|
+
} else {
|
|
282
|
+
throw new Error(`Unsupported worker scope ${scope}`)
|
|
283
|
+
}
|
|
210
284
|
}
|
|
211
285
|
|
|
212
286
|
return instance
|
package/src/types.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type CancelablePromise<T = unknown> = {
|
|
|
51
51
|
* Abort the request.
|
|
52
52
|
* @param reason The reason for cancelling the request.
|
|
53
53
|
*/
|
|
54
|
-
cancel: (reason: string) =>
|
|
54
|
+
cancel: (reason: string) => void
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -87,7 +87,7 @@ export type ProcedureImplementation<
|
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
89
|
* Declarations of procedures by name.
|
|
90
|
-
*
|
|
90
|
+
*
|
|
91
91
|
* An example of declaring procedures:
|
|
92
92
|
* {@includeCode ../example/src/lib/procedures.ts}
|
|
93
93
|
*/
|