swarpc 0.7.1 → 0.9.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 CHANGED
@@ -1,21 +1,47 @@
1
- import { type LogLevel } from "./log.js";
2
- import { Hooks, type ProceduresMap, type SwarpcClient } from "./types.js";
3
- export type { SwarpcClient } from "./types.js";
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { type Logger, type LogLevel } from "./log.js";
6
+ import { ClientMethod, Hooks, zProcedures, type ProceduresMap } from "./types.js";
7
+ /**
8
+ * The sw&rpc client instance, which provides {@link ClientMethod | methods to call procedures}.
9
+ * Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
10
+ * If you want to be able to cancel the request, you can set the request's ID yourself, and call `.abort(requestId, reason)` on the client instance to cancel it.
11
+ */
12
+ export type SwarpcClient<Procedures extends ProceduresMap> = {
13
+ [zProcedures]: Procedures;
14
+ } & {
15
+ [F in keyof Procedures]: ClientMethod<Procedures[F]>;
16
+ };
4
17
  /**
5
18
  *
6
- * @param procedures procedures the client will be able to call
19
+ * @param procedures procedures the client will be able to call, see {@link ProceduresMap}
7
20
  * @param options various options
8
21
  * @param options.worker if provided, the client will use this worker to post messages.
9
22
  * @param options.hooks hooks to run on messages received from the server
10
- * @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.
23
+ * @param options.restartListener if true, will force the listener to restart even if it has already been started
24
+ * @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
+ *
26
+ * An example of defining and using a client:
27
+ * {@includeCode ../example/src/routes/+page.svelte}
11
28
  */
12
- export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker, loglevel, hooks, }?: {
29
+ export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker, loglevel, restartListener, hooks, }?: {
13
30
  worker?: Worker;
14
31
  hooks?: Hooks<Procedures>;
15
32
  loglevel?: LogLevel;
33
+ restartListener?: boolean;
16
34
  }): SwarpcClient<Procedures>;
35
+ /**
36
+ * Starts the client listener, which listens for messages from the sw&rpc server.
37
+ * @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
38
+ * @param force if true, will force the listener to restart even if it has already been started
39
+ * @returns
40
+ */
41
+ export declare function startClientListener<Procedures extends ProceduresMap>(l: Logger, worker?: Worker, hooks?: Hooks<Procedures>): Promise<void>;
17
42
  /**
18
43
  * Generate a random request ID, used to identify requests between client and server.
44
+ * @source
19
45
  * @returns a 6-character hexadecimal string
20
46
  */
21
47
  export declare function makeRequestId(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnE,OAAO,EACL,KAAK,EAIL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAA;AAGnB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAkB9C;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,QAAkB,EAClB,KAAU,GACX,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAO,GAC1E,YAAY,CAAC,UAAU,CAAC,CA+F1B;AAmGD;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
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,EAGL,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;;;;;;;;;;;GAWG;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,CAAA;IACf,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,CAiG1B;AA6BD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,SAAS,aAAa,EACxE,CAAC,EAAE,MAAM,EACT,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,GAAE,KAAK,CAAC,UAAU,CAAM,iBA4D9B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
package/dist/client.js CHANGED
@@ -1,5 +1,9 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  import { createLogger } from "./log.js";
2
- import { zProcedures } from "./types.js";
6
+ import { zProcedures, } from "./types.js";
3
7
  import { findTransferables } from "./utils.js";
4
8
  /**
5
9
  * Pending requests are stored in a map, where the key is the request ID.
@@ -11,14 +15,20 @@ const pendingRequests = new Map();
11
15
  let _clientListenerStarted = false;
12
16
  /**
13
17
  *
14
- * @param procedures procedures the client will be able to call
18
+ * @param procedures procedures the client will be able to call, see {@link ProceduresMap}
15
19
  * @param options various options
16
20
  * @param options.worker if provided, the client will use this worker to post messages.
17
21
  * @param options.hooks hooks to run on messages received from the server
18
- * @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.
22
+ * @param options.restartListener if true, will force the listener to restart even if it has already been started
23
+ * @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
+ *
25
+ * An example of defining and using a client:
26
+ * {@includeCode ../example/src/routes/+page.svelte}
19
27
  */
20
- export function Client(procedures, { worker, loglevel = "debug", hooks = {}, } = {}) {
28
+ export function Client(procedures, { worker, loglevel = "debug", restartListener = false, hooks = {}, } = {}) {
21
29
  const l = createLogger("client", loglevel);
30
+ if (restartListener)
31
+ _clientListenerStarted = false;
22
32
  // Store procedures on a symbol key, to avoid conflicts with procedure names
23
33
  const instance = { [zProcedures]: procedures };
24
34
  for (const functionName of Object.keys(procedures)) {
@@ -96,9 +106,10 @@ async function postMessage(l, worker, hooks, message, options) {
96
106
  /**
97
107
  * Starts the client listener, which listens for messages from the sw&rpc server.
98
108
  * @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
109
+ * @param force if true, will force the listener to restart even if it has already been started
99
110
  * @returns
100
111
  */
101
- async function startClientListener(l, worker, hooks = {}) {
112
+ export async function startClientListener(l, worker, hooks = {}) {
102
113
  if (_clientListenerStarted)
103
114
  return;
104
115
  // Get service worker registration if no worker is provided
@@ -113,7 +124,7 @@ async function startClientListener(l, worker, hooks = {}) {
113
124
  }
114
125
  const w = worker ?? navigator.serviceWorker;
115
126
  // Start listening for messages
116
- l.debug("", "Starting client listener on", w);
127
+ l.debug(null, "Starting client listener", { worker, w, hooks });
117
128
  w.addEventListener("message", (event) => {
118
129
  // Get the data from the event
119
130
  const eventData = event.data || {};
@@ -129,7 +140,7 @@ async function startClientListener(l, worker, hooks = {}) {
129
140
  // Get the associated pending request handlers
130
141
  const handlers = pendingRequests.get(requestId);
131
142
  if (!handlers) {
132
- throw new Error(`[SWARPC Client] ${requestId} has no active request handlers`);
143
+ throw new Error(`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`);
133
144
  }
134
145
  // React to the data received: call hook, call handler,
135
146
  // and remove the request from pendingRequests (unless it's a progress update)
@@ -152,6 +163,7 @@ async function startClientListener(l, worker, hooks = {}) {
152
163
  }
153
164
  /**
154
165
  * Generate a random request ID, used to identify requests between client and server.
166
+ * @source
155
167
  * @returns a 6-character hexadecimal string
156
168
  */
157
169
  export function makeRequestId() {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  export * from "./client.js";
2
6
  export * from "./server.js";
3
7
  export type { ProceduresMap, CancelablePromise } from "./types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,6 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  export * from "./client.js";
2
6
  export * from "./server.js";
package/dist/log.d.ts CHANGED
@@ -1,20 +1,28 @@
1
- export declare function createLogger(side: "server" | "client", level?: LogLevel): {
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ /**
6
+ * @ignore
7
+ */
8
+ export declare function createLogger(side: "server" | "client", level: LogLevel): Logger;
9
+ export declare function createLogger(side: "server" | "client", level: LogLevel, rqid: string): RequestBoundLogger;
10
+ /**
11
+ * @ignore
12
+ */
13
+ export type Logger = {
2
14
  debug: (rqid: string | null, message: string, ...args: any[]) => void;
3
15
  info: (rqid: string | null, message: string, ...args: any[]) => void;
4
16
  warn: (rqid: string | null, message: string, ...args: any[]) => void;
5
17
  error: (rqid: string | null, message: string, ...args: any[]) => void;
6
18
  };
7
- export type Logger = ReturnType<typeof createLogger>;
8
- declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
19
+ export type RequestBoundLogger = {
20
+ debug: (message: string, ...args: any[]) => void;
21
+ info: (message: string, ...args: any[]) => void;
22
+ warn: (message: string, ...args: any[]) => void;
23
+ error: (message: string, ...args: any[]) => void;
24
+ };
25
+ /** @source */
26
+ export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
9
27
  export type LogLevel = (typeof LOG_LEVELS)[number];
10
- /**
11
- * Send log messages to the console, with a helpful prefix.
12
- * @param severity
13
- * @param side
14
- * @param rqid request ID
15
- * @param message
16
- * @param args passed to console methods directly
17
- */
18
- export declare function log(severity: "debug" | "info" | "warn" | "error", side: "server" | "client", rqid: string | null, message: string, ...args: any[]): void;
19
- export {};
20
28
  //# sourceMappingURL=log.d.ts.map
package/dist/log.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,GAAG,QAAQ,EACzB,KAAK,GAAE,QAAkB;kBAwBX,MAAM,GAAG,IAAI,WAAW,MAAM,WAAW,GAAG,EAAE;iBAA9C,MAAM,GAAG,IAAI,WAAW,MAAM,WAAW,GAAG,EAAE;iBAA9C,MAAM,GAAG,IAAI,WAAW,MAAM,WAAW,GAAG,EAAE;kBAA9C,MAAM,GAAG,IAAI,WAAW,MAAM,WAAW,GAAG,EAAE;EAd7D;AAED,MAAM,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAA;AAEpD,QAAA,MAAM,UAAU,6CAA8C,CAAA;AAC9D,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAalD;;;;;;;GAOG;AACH,wBAAgB,GAAG,CACjB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC7C,IAAI,EAAE,QAAQ,GAAG,QAAQ,EACzB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,QAkBf"}
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAA;AAChF,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,GAAG,QAAQ,EACzB,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,MAAM,GACX,kBAAkB,CAAA;AAyBrB;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACrE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACpE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACpE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;CACtE,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/C,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/C,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;CACjD,CAAA;AAED,cAAc;AACd,eAAO,MAAM,UAAU,6CAA8C,CAAA;AAErE,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA"}
package/dist/log.js CHANGED
@@ -1,21 +1,31 @@
1
- export function createLogger(side, level = "debug") {
2
- const enabledLevels = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level));
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ export function createLogger(side, level = "debug", rqid) {
6
+ const lvls = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level));
7
+ if (rqid) {
8
+ return {
9
+ debug: lvls.includes("debug") ? logger("debug", side, rqid) : () => { },
10
+ info: lvls.includes("info") ? logger("info", side, rqid) : () => { },
11
+ warn: lvls.includes("warn") ? logger("warn", side, rqid) : () => { },
12
+ error: lvls.includes("error") ? logger("error", side, rqid) : () => { },
13
+ };
14
+ }
3
15
  return {
4
- debug: enabledLevels.includes("debug") ? logger("debug", side) : () => { },
5
- info: enabledLevels.includes("info") ? logger("info", side) : () => { },
6
- warn: enabledLevels.includes("warn") ? logger("warn", side) : () => { },
7
- error: enabledLevels.includes("error") ? logger("error", side) : () => { },
16
+ debug: lvls.includes("debug") ? logger("debug", side) : () => { },
17
+ info: lvls.includes("info") ? logger("info", side) : () => { },
18
+ warn: lvls.includes("warn") ? logger("warn", side) : () => { },
19
+ error: lvls.includes("error") ? logger("error", side) : () => { },
8
20
  };
9
21
  }
10
- const LOG_LEVELS = ["debug", "info", "warn", "error"];
11
- /**
12
- * Creates partially-applied logging functions given the first 2 args
13
- * @param severity
14
- * @param side
15
- * @returns
16
- */
17
- function logger(severity, side) {
18
- return (rqid, message, ...args) => log(severity, side, rqid, message, ...args);
22
+ /** @source */
23
+ export const LOG_LEVELS = ["debug", "info", "warn", "error"];
24
+ function logger(severity, side, rqid) {
25
+ if (rqid === undefined) {
26
+ return (rqid, message, ...args) => log(severity, side, rqid, message, ...args);
27
+ }
28
+ return (message, ...args) => log(severity, side, rqid, message, ...args);
19
29
  }
20
30
  /**
21
31
  * Send log messages to the console, with a helpful prefix.
@@ -25,7 +35,7 @@ function logger(severity, side) {
25
35
  * @param message
26
36
  * @param args passed to console methods directly
27
37
  */
28
- export function log(severity, side, rqid, message, ...args) {
38
+ function log(severity, side, rqid, message, ...args) {
29
39
  const prefix = "[" +
30
40
  ["SWARPC", side, rqid ? `%c${rqid}%c` : ""].filter(Boolean).join(" ") +
31
41
  "]";
package/dist/server.d.ts CHANGED
@@ -1,12 +1,29 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  import { type LogLevel } from "./log.js";
2
- import { type ProceduresMap, type SwarpcServer } from "./types.js";
3
- export type { SwarpcServer } from "./types.js";
6
+ import { ImplementationsMap, ProcedureImplementation, zImplementations, zProcedures, type ProceduresMap } from "./types.js";
7
+ /**
8
+ * The sw&rpc server instance, which provides methods to register {@link ProcedureImplementation | procedure implementations},
9
+ * and listens for incoming messages that call those procedures
10
+ */
11
+ export type SwarpcServer<Procedures extends ProceduresMap> = {
12
+ [zProcedures]: Procedures;
13
+ [zImplementations]: ImplementationsMap<Procedures>;
14
+ start(self: Window | Worker): void;
15
+ } & {
16
+ [F in keyof Procedures]: (impl: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>) => void;
17
+ };
4
18
  /**
5
19
  * Creates a sw&rpc server instance.
6
- * @param procedures procedures the server will implement
20
+ * @param procedures procedures the server will implement, see {@link ProceduresMap}
7
21
  * @param options various options
8
22
  * @param options.worker if provided, the server will use this worker to post messages, instead of sending it to all clients
9
- * @returns a SwarpcServer instance. Each property of the procedures map will be a method, that accepts a function implementing the procedure. There is also .start(), to be called after implementing all procedures.
23
+ * @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
+ *
25
+ * An example of defining a server:
26
+ * {@includeCode ../example/src/service-worker.ts}
10
27
  */
11
28
  export declare function Server<Procedures extends ProceduresMap>(procedures: Procedures, { worker, loglevel }?: {
12
29
  worker?: Worker;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAA;AAGnB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK9C;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EAAE,MAAM,EAAE,QAAkB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAO,GAC5E,YAAY,CAAC,UAAU,CAAC,CAkK1B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,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;AAGnB;;;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,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CACnC,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;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EAAE,MAAM,EAAE,QAAkB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAO,GAC5E,YAAY,CAAC,UAAU,CAAC,CA8J1B"}
package/dist/server.js CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  import { type } from "arktype";
2
6
  import { createLogger } from "./log.js";
3
7
  import { PayloadHeaderSchema, PayloadSchema, zImplementations, zProcedures, } from "./types.js";
@@ -6,10 +10,13 @@ const abortControllers = new Map();
6
10
  const abortedRequests = new Set();
7
11
  /**
8
12
  * Creates a sw&rpc server instance.
9
- * @param procedures procedures the server will implement
13
+ * @param procedures procedures the server will implement, see {@link ProceduresMap}
10
14
  * @param options various options
11
15
  * @param options.worker if provided, the server will use this worker to post messages, instead of sending it to all clients
12
- * @returns a SwarpcServer instance. Each property of the procedures map will be a method, that accepts a function implementing the procedure. There is also .start(), to be called after implementing all procedures.
16
+ * @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
+ *
18
+ * An example of defining a server:
19
+ * {@includeCode ../example/src/service-worker.ts}
13
20
  */
14
21
  export function Server(procedures, { worker, loglevel = "debug" } = {}) {
15
22
  const l = createLogger("server", loglevel);
@@ -27,17 +34,15 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
27
34
  if (!instance[zProcedures][functionName]) {
28
35
  throw new Error(`No procedure found for function name: ${functionName}`);
29
36
  }
30
- instance[zImplementations][functionName] = (input, onProgress, abortSignal) => {
31
- abortSignal?.throwIfAborted();
37
+ instance[zImplementations][functionName] = (input, onProgress, tools) => {
38
+ tools.abortSignal?.throwIfAborted();
32
39
  return new Promise((resolve, reject) => {
33
- abortSignal?.addEventListener("abort", () => {
34
- let { requestId, reason } = abortSignal?.reason;
40
+ tools.abortSignal?.addEventListener("abort", () => {
41
+ let { requestId, reason } = tools.abortSignal?.reason;
35
42
  l.debug(requestId, `Aborted ${functionName} request: ${reason}`);
36
43
  reject({ aborted: reason });
37
44
  });
38
- implementation(input, onProgress, abortSignal)
39
- .then(resolve)
40
- .catch(reject);
45
+ implementation(input, onProgress, tools).then(resolve).catch(reject);
41
46
  });
42
47
  };
43
48
  });
@@ -101,13 +106,21 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
101
106
  await postError("No input provided");
102
107
  return;
103
108
  }
104
- // Call the implementation with the input and a progress callback
105
- await implementation(payload.input, async (progress) => {
106
- l.debug(requestId, `Progress for ${functionName}`, progress);
107
- await postMsg({ progress });
108
- }, abortControllers.get(requestId)?.signal)
109
+ try {
110
+ // Call the implementation with the input and a progress callback
111
+ const result = await implementation(payload.input, async (progress) => {
112
+ l.debug(requestId, `Progress for ${functionName}`, progress);
113
+ await postMsg({ progress });
114
+ }, {
115
+ abortSignal: abortControllers.get(requestId)?.signal,
116
+ logger: createLogger("server", loglevel, requestId),
117
+ });
118
+ // Send results
119
+ l.debug(requestId, `Result for ${functionName}`, result);
120
+ await postMsg({ result });
121
+ }
122
+ catch (error) {
109
123
  // Send errors
110
- .catch(async (error) => {
111
124
  // Handle errors caused by abortions
112
125
  if ("aborted" in error) {
113
126
  l.debug(requestId, `Received abort error for ${functionName}`, error.aborted);
@@ -115,17 +128,12 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
115
128
  abortControllers.delete(requestId);
116
129
  return;
117
130
  }
118
- l.error(requestId, `Error in ${functionName}`, error);
131
+ l.info(requestId, `Error in ${functionName}`, error);
119
132
  await postError(error);
120
- })
121
- // Send results
122
- .then(async (result) => {
123
- l.debug(requestId, `Result for ${functionName}`, result);
124
- await postMsg({ result });
125
- })
126
- .finally(() => {
133
+ }
134
+ finally {
127
135
  abortedRequests.delete(requestId);
128
- });
136
+ }
129
137
  });
130
138
  };
131
139
  return instance;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,9 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  import { type Type } from "arktype";
6
+ import { RequestBoundLogger } from "./log.js";
2
7
  /**
3
8
  * A procedure declaration
4
9
  */
@@ -37,7 +42,7 @@ export type Procedure<I extends Type, P extends Type, S extends Type> = {
37
42
  * const result = await request
38
43
  * ```
39
44
  */
40
- export type CancelablePromise<T> = {
45
+ export type CancelablePromise<T = unknown> = {
41
46
  request: Promise<T>;
42
47
  /**
43
48
  * Abort the request.
@@ -48,9 +53,33 @@ export type CancelablePromise<T> = {
48
53
  /**
49
54
  * An implementation of a procedure
50
55
  */
51
- export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferIn"]) => void, abortSignal?: AbortSignal) => Promise<S["inferIn"]>;
56
+ export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (
57
+ /**
58
+ * Input data for the procedure
59
+ */
60
+ input: I["inferOut"],
61
+ /**
62
+ * Callback to call with progress updates.
63
+ */
64
+ onProgress: (progress: P["inferIn"]) => void,
65
+ /**
66
+ * Additional tools useful when implementing the procedure.
67
+ */
68
+ tools: {
69
+ /**
70
+ * AbortSignal that can be used to handle request cancellation -- see [Make cancellable requests](https://gwennlbh.github.io/swarpc/docs/#make-cancelable-requests)
71
+ */
72
+ abortSignal?: AbortSignal;
73
+ /**
74
+ * Logger instance to use for logging messages related to this procedure call, using the same format as SWARPC's built-in logging.
75
+ */
76
+ logger: RequestBoundLogger;
77
+ }) => Promise<S["inferIn"]>;
52
78
  /**
53
79
  * Declarations of procedures by name.
80
+ *
81
+ * An example of declaring procedures:
82
+ * {@includeCode ../example/src/lib/procedures.ts}
54
83
  */
55
84
  export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>;
56
85
  /**
@@ -76,6 +105,9 @@ export type Hooks<Procedures extends ProceduresMap> = {
76
105
  */
77
106
  progress?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Procedures[Procedure]["progress"]["inferOut"]) => void;
78
107
  };
108
+ /**
109
+ * @source
110
+ */
79
111
  export declare const PayloadHeaderSchema: import("arktype").Generic<[["Name", string]], {
80
112
  readonly by: "\"sw&rpc\"";
81
113
  readonly functionName: "Name";
@@ -86,6 +118,9 @@ export type PayloadHeader<PM extends ProceduresMap, Name extends keyof PM = keyo
86
118
  functionName: Name & string;
87
119
  requestId: string;
88
120
  };
121
+ /**
122
+ * @source
123
+ */
89
124
  export declare const PayloadCoreSchema: import("arktype").Generic<[["I", unknown], ["P", unknown], ["S", unknown]], {
90
125
  readonly "input?": "I";
91
126
  readonly "progress?": "P";
@@ -112,6 +147,9 @@ export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof
112
147
  message: string;
113
148
  };
114
149
  };
150
+ /**
151
+ * @source
152
+ */
115
153
  export declare const PayloadSchema: import("arktype").Generic<[["Name", string], ["I", unknown], ["P", unknown], ["S", unknown]], readonly ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"], {
116
154
  PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
117
155
  readonly "input?": "I";
@@ -230,31 +268,14 @@ export type ClientMethod<P extends Procedure<Type, Type, Type>> = ((input: P["in
230
268
  };
231
269
  /**
232
270
  * Symbol used as the key for the procedures map on the server instance
271
+ * @internal
272
+ * @source
233
273
  */
234
274
  export declare const zImplementations: unique symbol;
235
275
  /**
236
276
  * Symbol used as the key for the procedures map on instances
277
+ * @internal
278
+ * @source
237
279
  */
238
280
  export declare const zProcedures: unique symbol;
239
- /**
240
- * The sw&rpc client instance, which provides methods to call procedures.
241
- * Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
242
- * If you want to be able to cancel the request, you can set the request's ID yourself, and call `.abort(requestId, reason)` on the client instance to cancel it.
243
- */
244
- export type SwarpcClient<Procedures extends ProceduresMap> = {
245
- [zProcedures]: Procedures;
246
- } & {
247
- [F in keyof Procedures]: ClientMethod<Procedures[F]>;
248
- };
249
- /**
250
- * The sw&rpc server instance, which provides methods to register procedure implementations,
251
- * and listens for incoming messages that call those procedures
252
- */
253
- export type SwarpcServer<Procedures extends ProceduresMap> = {
254
- [zProcedures]: Procedures;
255
- [zImplementations]: ImplementationsMap<Procedures>;
256
- start(self: Window | Worker): void;
257
- } & {
258
- [F in keyof Procedures]: (impl: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>) => void;
259
- };
260
281
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -1,9 +1,19 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
1
5
  import { type } from "arktype";
6
+ /**
7
+ * @source
8
+ */
2
9
  export const PayloadHeaderSchema = type("<Name extends string>", {
3
10
  by: '"sw&rpc"',
4
11
  functionName: "Name",
5
12
  requestId: "string >= 1",
6
13
  });
14
+ /**
15
+ * @source
16
+ */
7
17
  export const PayloadCoreSchema = type("<I, P, S>", {
8
18
  "input?": "I",
9
19
  "progress?": "P",
@@ -11,6 +21,9 @@ export const PayloadCoreSchema = type("<I, P, S>", {
11
21
  "abort?": { reason: "string" },
12
22
  "error?": { message: "string" },
13
23
  });
24
+ /**
25
+ * @source
26
+ */
14
27
  export const PayloadSchema = type
15
28
  .scope({ PayloadCoreSchema, PayloadHeaderSchema })
16
29
  .type("<Name extends string, I, P, S>", [
@@ -20,9 +33,13 @@ export const PayloadSchema = type
20
33
  ]);
21
34
  /**
22
35
  * Symbol used as the key for the procedures map on the server instance
36
+ * @internal
37
+ * @source
23
38
  */
24
39
  export const zImplementations = Symbol("SWARPC implementations");
25
40
  /**
26
41
  * Symbol used as the key for the procedures map on instances
42
+ * @internal
43
+ * @source
27
44
  */
28
45
  export const zProcedures = Symbol("SWARPC procedures");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarpc",
3
- "version": "0.7.1",
3
+ "version": "0.9.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",
@@ -31,19 +31,36 @@
31
31
  "dev": "tsc --watch",
32
32
  "typecheck": "tsc --noEmit",
33
33
  "test": "vitest",
34
- "typedoc": "typedoc src/index.ts src/types.ts --readme README.md",
35
- "version": "kacl release && prettier -w CHANGELOG.md && git add CHANGELOG.md"
34
+ "typedoc": "typedoc",
35
+ "version": "kacl release && prettier -w CHANGELOG.md && git add CHANGELOG.md",
36
+ "typedoc:dev": "nodemon --watch src --watch README.md --watch typedoc.json --watch typedoc.css --exec npm run typedoc:withplugins",
37
+ "typedoc:serve": "npx sirv-cli --dev docs",
38
+ "typedoc:plugins": "node -e \"const fs=require('fs');const p=JSON.parse(fs.readFileSync('typedoc.json')).plugin||[];if(p.length)require('child_process').execSync('npm add --legacy-peer-deps -D '+p.join(' '),{stdio:'inherit'});else console.log('No plugins found');\"",
39
+ "typedoc:withplugins": "npm run typedoc:plugins && npm run typedoc"
36
40
  },
37
41
  "dependencies": {
38
42
  "arktype": "^2.1.20"
39
43
  },
40
44
  "devDependencies": {
45
+ "@8hobbies/typedoc-plugin-plausible": "^2.2.0",
41
46
  "@vitest/web-worker": "^3.2.4",
42
47
  "kacl": "^1.1.1",
48
+ "nodemon": "^3.1.10",
43
49
  "prettier": "^3.6.2",
50
+ "sirv-cli": "^3.0.1",
44
51
  "typedoc": "^0.28.9",
52
+ "typedoc-material-theme": "^1.4.0",
53
+ "typedoc-plugin-dt-links": "^2.0.12",
54
+ "typedoc-plugin-extras": "^4.0.1",
55
+ "typedoc-plugin-inline-sources": "^1.3.0",
56
+ "typedoc-plugin-mdn-links": "^5.0.6",
57
+ "typedoc-plugin-redirect": "^1.2.0",
45
58
  "typescript": "^5.9.2",
46
59
  "vite": "^7.0.6",
47
60
  "vitest": "^3.2.4"
61
+ },
62
+ "volta": {
63
+ "node": "22.18.0",
64
+ "npm": "11.5.2"
48
65
  }
49
66
  }
package/src/client.ts CHANGED
@@ -1,15 +1,29 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+
1
6
  import { createLogger, type Logger, type LogLevel } from "./log.js"
2
7
  import {
8
+ ClientMethod,
3
9
  Hooks,
4
10
  Payload,
5
11
  PayloadCore,
6
12
  zProcedures,
7
13
  type ProceduresMap,
8
- type SwarpcClient
9
14
  } from "./types.js"
10
15
  import { findTransferables } from "./utils.js"
11
16
 
12
- export type { SwarpcClient } from "./types.js"
17
+ /**
18
+ * The sw&rpc client instance, which provides {@link ClientMethod | methods to call procedures}.
19
+ * Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
20
+ * If you want to be able to cancel the request, you can set the request's ID yourself, and call `.abort(requestId, reason)` on the client instance to cancel it.
21
+ */
22
+ export type SwarpcClient<Procedures extends ProceduresMap> = {
23
+ [zProcedures]: Procedures
24
+ } & {
25
+ [F in keyof Procedures]: ClientMethod<Procedures[F]>
26
+ }
13
27
 
14
28
  /**
15
29
  * Pending requests are stored in a map, where the key is the request ID.
@@ -29,22 +43,34 @@ let _clientListenerStarted = false
29
43
 
30
44
  /**
31
45
  *
32
- * @param procedures procedures the client will be able to call
46
+ * @param procedures procedures the client will be able to call, see {@link ProceduresMap}
33
47
  * @param options various options
34
48
  * @param options.worker if provided, the client will use this worker to post messages.
35
49
  * @param options.hooks hooks to run on messages received from the server
36
- * @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.
50
+ * @param options.restartListener if true, will force the listener to restart even if it has already been started
51
+ * @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
+ *
53
+ * An example of defining and using a client:
54
+ * {@includeCode ../example/src/routes/+page.svelte}
37
55
  */
38
56
  export function Client<Procedures extends ProceduresMap>(
39
57
  procedures: Procedures,
40
58
  {
41
59
  worker,
42
60
  loglevel = "debug",
61
+ restartListener = false,
43
62
  hooks = {},
44
- }: { worker?: Worker; hooks?: Hooks<Procedures>; loglevel?: LogLevel } = {}
63
+ }: {
64
+ worker?: Worker
65
+ hooks?: Hooks<Procedures>
66
+ loglevel?: LogLevel
67
+ restartListener?: boolean
68
+ } = {}
45
69
  ): SwarpcClient<Procedures> {
46
70
  const l = createLogger("client", loglevel)
47
71
 
72
+ if (restartListener) _clientListenerStarted = false
73
+
48
74
  // Store procedures on a symbol key, to avoid conflicts with procedure names
49
75
  const instance = { [zProcedures]: procedures } as Partial<
50
76
  SwarpcClient<Procedures>
@@ -169,9 +195,10 @@ async function postMessage<Procedures extends ProceduresMap>(
169
195
  /**
170
196
  * Starts the client listener, which listens for messages from the sw&rpc server.
171
197
  * @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
198
+ * @param force if true, will force the listener to restart even if it has already been started
172
199
  * @returns
173
200
  */
174
- async function startClientListener<Procedures extends ProceduresMap>(
201
+ export async function startClientListener<Procedures extends ProceduresMap>(
175
202
  l: Logger,
176
203
  worker?: Worker,
177
204
  hooks: Hooks<Procedures> = {}
@@ -193,7 +220,7 @@ async function startClientListener<Procedures extends ProceduresMap>(
193
220
  const w = worker ?? navigator.serviceWorker
194
221
 
195
222
  // Start listening for messages
196
- l.debug("", "Starting client listener on", w)
223
+ l.debug(null, "Starting client listener", { worker, w, hooks })
197
224
  w.addEventListener("message", (event) => {
198
225
  // Get the data from the event
199
226
  const eventData = (event as MessageEvent).data || {}
@@ -213,7 +240,7 @@ async function startClientListener<Procedures extends ProceduresMap>(
213
240
  const handlers = pendingRequests.get(requestId)
214
241
  if (!handlers) {
215
242
  throw new Error(
216
- `[SWARPC Client] ${requestId} has no active request handlers`
243
+ `[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`,
217
244
  )
218
245
  }
219
246
 
@@ -238,6 +265,7 @@ async function startClientListener<Procedures extends ProceduresMap>(
238
265
 
239
266
  /**
240
267
  * Generate a random request ID, used to identify requests between client and server.
268
+ * @source
241
269
  * @returns a 6-character hexadecimal string
242
270
  */
243
271
  export function makeRequestId(): string {
package/src/index.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+
1
6
  export * from "./client.js"
2
7
  export * from "./server.js"
3
8
  export type { ProceduresMap, CancelablePromise } from "./types.js"
package/src/log.ts CHANGED
@@ -1,30 +1,86 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+
6
+ /**
7
+ * @ignore
8
+ */
9
+ export function createLogger(side: "server" | "client", level: LogLevel): Logger
1
10
  export function createLogger(
2
11
  side: "server" | "client",
3
- level: LogLevel = "debug"
12
+ level: LogLevel,
13
+ rqid: string
14
+ ): RequestBoundLogger
15
+ export function createLogger(
16
+ side: "server" | "client",
17
+ level: LogLevel = "debug",
18
+ rqid?: string
4
19
  ) {
5
- const enabledLevels = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level))
20
+ const lvls = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level))
21
+
22
+ if (rqid) {
23
+ return {
24
+ debug: lvls.includes("debug") ? logger("debug", side, rqid) : () => {},
25
+ info: lvls.includes("info") ? logger("info", side, rqid) : () => {},
26
+ warn: lvls.includes("warn") ? logger("warn", side, rqid) : () => {},
27
+ error: lvls.includes("error") ? logger("error", side, rqid) : () => {},
28
+ } as RequestBoundLogger
29
+ }
6
30
 
7
31
  return {
8
- debug: enabledLevels.includes("debug") ? logger("debug", side) : () => {},
9
- info: enabledLevels.includes("info") ? logger("info", side) : () => {},
10
- warn: enabledLevels.includes("warn") ? logger("warn", side) : () => {},
11
- error: enabledLevels.includes("error") ? logger("error", side) : () => {},
32
+ debug: lvls.includes("debug") ? logger("debug", side) : () => {},
33
+ info: lvls.includes("info") ? logger("info", side) : () => {},
34
+ warn: lvls.includes("warn") ? logger("warn", side) : () => {},
35
+ error: lvls.includes("error") ? logger("error", side) : () => {},
12
36
  }
13
37
  }
14
38
 
15
- export type Logger = ReturnType<typeof createLogger>
39
+ /**
40
+ * @ignore
41
+ */
42
+ export type Logger = {
43
+ debug: (rqid: string | null, message: string, ...args: any[]) => void
44
+ info: (rqid: string | null, message: string, ...args: any[]) => void
45
+ warn: (rqid: string | null, message: string, ...args: any[]) => void
46
+ error: (rqid: string | null, message: string, ...args: any[]) => void
47
+ }
48
+
49
+ export type RequestBoundLogger = {
50
+ debug: (message: string, ...args: any[]) => void
51
+ info: (message: string, ...args: any[]) => void
52
+ warn: (message: string, ...args: any[]) => void
53
+ error: (message: string, ...args: any[]) => void
54
+ }
55
+
56
+ /** @source */
57
+ export const LOG_LEVELS = ["debug", "info", "warn", "error"] as const
16
58
 
17
- const LOG_LEVELS = ["debug", "info", "warn", "error"] as const
18
59
  export type LogLevel = (typeof LOG_LEVELS)[number]
19
60
 
20
61
  /**
21
- * Creates partially-applied logging functions given the first 2 args
62
+ * Creates partially-applied logging functions given the first 2 or 3 args
22
63
  * @param severity
23
64
  * @param side
65
+ * @param rqid request ID, or null to not bind it
24
66
  * @returns
25
67
  */
26
- function logger(severity: LogLevel, side: "server" | "client") {
27
- return (rqid: string | null, message: string, ...args: any[]) =>
68
+ function logger(
69
+ severity: LogLevel,
70
+ side: "server" | "client",
71
+ rqid: string
72
+ ): (message: string, ...args: any[]) => void
73
+ function logger(
74
+ severity: LogLevel,
75
+ side: "server" | "client"
76
+ ): (rqid: string | null, message: string, ...args: any[]) => void
77
+ function logger(severity: LogLevel, side: "server" | "client", rqid?: string) {
78
+ if (rqid === undefined) {
79
+ return (rqid: string | null, message: string, ...args: any[]) =>
80
+ log(severity, side, rqid, message, ...args)
81
+ }
82
+
83
+ return (message: string, ...args: any[]) =>
28
84
  log(severity, side, rqid, message, ...args)
29
85
  }
30
86
 
@@ -36,7 +92,7 @@ function logger(severity: LogLevel, side: "server" | "client") {
36
92
  * @param message
37
93
  * @param args passed to console methods directly
38
94
  */
39
- export function log(
95
+ function log(
40
96
  severity: "debug" | "info" | "warn" | "error",
41
97
  side: "server" | "client",
42
98
  rqid: string | null,
package/src/server.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+
1
6
  import { type } from "arktype"
2
7
  import { createLogger, type LogLevel } from "./log.js"
3
8
  import {
@@ -6,24 +11,43 @@ import {
6
11
  PayloadCore,
7
12
  PayloadHeaderSchema,
8
13
  PayloadSchema,
14
+ ProcedureImplementation,
9
15
  zImplementations,
10
16
  zProcedures,
11
17
  type ProceduresMap,
12
- type SwarpcServer,
13
18
  } from "./types.js"
14
19
  import { findTransferables } from "./utils.js"
15
20
 
16
- export type { SwarpcServer } from "./types.js"
21
+ /**
22
+ * The sw&rpc server instance, which provides methods to register {@link ProcedureImplementation | procedure implementations},
23
+ * and listens for incoming messages that call those procedures
24
+ */
25
+ export type SwarpcServer<Procedures extends ProceduresMap> = {
26
+ [zProcedures]: Procedures
27
+ [zImplementations]: ImplementationsMap<Procedures>
28
+ start(self: Window | Worker): void
29
+ } & {
30
+ [F in keyof Procedures]: (
31
+ impl: ProcedureImplementation<
32
+ Procedures[F]["input"],
33
+ Procedures[F]["progress"],
34
+ Procedures[F]["success"]
35
+ >
36
+ ) => void
37
+ }
17
38
 
18
39
  const abortControllers = new Map<string, AbortController>()
19
40
  const abortedRequests = new Set<string>()
20
41
 
21
42
  /**
22
43
  * Creates a sw&rpc server instance.
23
- * @param procedures procedures the server will implement
44
+ * @param procedures procedures the server will implement, see {@link ProceduresMap}
24
45
  * @param options various options
25
46
  * @param options.worker if provided, the server will use this worker to post messages, instead of sending it to all clients
26
- * @returns a SwarpcServer instance. Each property of the procedures map will be a method, that accepts a function implementing the procedure. There is also .start(), to be called after implementing all procedures.
47
+ * @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
+ *
49
+ * An example of defining a server:
50
+ * {@includeCode ../example/src/service-worker.ts}
27
51
  */
28
52
  export function Server<Procedures extends ProceduresMap>(
29
53
  procedures: Procedures,
@@ -46,22 +70,16 @@ export function Server<Procedures extends ProceduresMap>(
46
70
  if (!instance[zProcedures][functionName]) {
47
71
  throw new Error(`No procedure found for function name: ${functionName}`)
48
72
  }
49
- instance[zImplementations][functionName] = (
50
- input,
51
- onProgress,
52
- abortSignal
53
- ) => {
54
- abortSignal?.throwIfAborted()
73
+ instance[zImplementations][functionName] = (input, onProgress, tools) => {
74
+ tools.abortSignal?.throwIfAborted()
55
75
  return new Promise((resolve, reject) => {
56
- abortSignal?.addEventListener("abort", () => {
57
- let { requestId, reason } = abortSignal?.reason
76
+ tools.abortSignal?.addEventListener("abort", () => {
77
+ let { requestId, reason } = tools.abortSignal?.reason
58
78
  l.debug(requestId, `Aborted ${functionName} request: ${reason}`)
59
79
  reject({ aborted: reason })
60
80
  })
61
81
 
62
- implementation(input, onProgress, abortSignal)
63
- .then(resolve)
64
- .catch(reject)
82
+ implementation(input, onProgress, tools).then(resolve).catch(reject)
65
83
  })
66
84
  }
67
85
  }) as SwarpcServer<Procedures>[typeof functionName]
@@ -152,40 +170,42 @@ export function Server<Procedures extends ProceduresMap>(
152
170
  return
153
171
  }
154
172
 
155
- // Call the implementation with the input and a progress callback
156
- await implementation(
157
- payload.input,
158
- async (progress: any) => {
159
- l.debug(requestId, `Progress for ${functionName}`, progress)
160
- await postMsg({ progress })
161
- },
162
- abortControllers.get(requestId)?.signal
163
- )
164
- // Send errors
165
- .catch(async (error: any) => {
166
- // Handle errors caused by abortions
167
- if ("aborted" in error) {
168
- l.debug(
169
- requestId,
170
- `Received abort error for ${functionName}`,
171
- error.aborted
172
- )
173
- abortedRequests.add(requestId)
174
- abortControllers.delete(requestId)
175
- return
173
+ try {
174
+ // Call the implementation with the input and a progress callback
175
+ const result = await implementation(
176
+ payload.input,
177
+ async (progress: any) => {
178
+ l.debug(requestId, `Progress for ${functionName}`, progress)
179
+ await postMsg({ progress })
180
+ },
181
+ {
182
+ abortSignal: abortControllers.get(requestId)?.signal,
183
+ logger: createLogger("server", loglevel, requestId),
176
184
  }
185
+ )
177
186
 
178
- l.error(requestId, `Error in ${functionName}`, error)
179
- await postError(error)
180
- })
181
187
  // Send results
182
- .then(async (result: any) => {
183
- l.debug(requestId, `Result for ${functionName}`, result)
184
- await postMsg({ result })
185
- })
186
- .finally(() => {
187
- abortedRequests.delete(requestId)
188
- })
188
+ l.debug(requestId, `Result for ${functionName}`, result)
189
+ await postMsg({ result })
190
+ } catch (error: any) {
191
+ // Send errors
192
+ // Handle errors caused by abortions
193
+ if ("aborted" in error) {
194
+ l.debug(
195
+ requestId,
196
+ `Received abort error for ${functionName}`,
197
+ error.aborted
198
+ )
199
+ abortedRequests.add(requestId)
200
+ abortControllers.delete(requestId)
201
+ return
202
+ }
203
+
204
+ l.info(requestId, `Error in ${functionName}`, error)
205
+ await postError(error)
206
+ } finally {
207
+ abortedRequests.delete(requestId)
208
+ }
189
209
  })
190
210
  }
191
211
 
package/src/types.ts CHANGED
@@ -1,4 +1,10 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+
1
6
  import { type, type Type } from "arktype"
7
+ import { Logger, RequestBoundLogger } from "./log.js"
2
8
 
3
9
  /**
4
10
  * A procedure declaration
@@ -39,7 +45,7 @@ export type Procedure<I extends Type, P extends Type, S extends Type> = {
39
45
  * const result = await request
40
46
  * ```
41
47
  */
42
- export type CancelablePromise<T> = {
48
+ export type CancelablePromise<T = unknown> = {
43
49
  request: Promise<T>
44
50
  /**
45
51
  * Abort the request.
@@ -56,13 +62,34 @@ export type ProcedureImplementation<
56
62
  P extends Type,
57
63
  S extends Type,
58
64
  > = (
65
+ /**
66
+ * Input data for the procedure
67
+ */
59
68
  input: I["inferOut"],
69
+ /**
70
+ * Callback to call with progress updates.
71
+ */
60
72
  onProgress: (progress: P["inferIn"]) => void,
61
- abortSignal?: AbortSignal
73
+ /**
74
+ * Additional tools useful when implementing the procedure.
75
+ */
76
+ tools: {
77
+ /**
78
+ * AbortSignal that can be used to handle request cancellation -- see [Make cancellable requests](https://gwennlbh.github.io/swarpc/docs/#make-cancelable-requests)
79
+ */
80
+ abortSignal?: AbortSignal
81
+ /**
82
+ * Logger instance to use for logging messages related to this procedure call, using the same format as SWARPC's built-in logging.
83
+ */
84
+ logger: RequestBoundLogger
85
+ }
62
86
  ) => Promise<S["inferIn"]>
63
87
 
64
88
  /**
65
89
  * Declarations of procedures by name.
90
+ *
91
+ * An example of declaring procedures:
92
+ * {@includeCode ../example/src/lib/procedures.ts}
66
93
  */
67
94
  export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>
68
95
 
@@ -104,6 +131,9 @@ export type Hooks<Procedures extends ProceduresMap> = {
104
131
  ) => void
105
132
  }
106
133
 
134
+ /**
135
+ * @source
136
+ */
107
137
  export const PayloadHeaderSchema = type("<Name extends string>", {
108
138
  by: '"sw&rpc"',
109
139
  functionName: "Name",
@@ -119,6 +149,9 @@ export type PayloadHeader<
119
149
  requestId: string
120
150
  }
121
151
 
152
+ /**
153
+ * @source
154
+ */
122
155
  export const PayloadCoreSchema = type("<I, P, S>", {
123
156
  "input?": "I",
124
157
  "progress?": "P",
@@ -147,6 +180,9 @@ export type PayloadCore<
147
180
  error: { message: string }
148
181
  }
149
182
 
183
+ /**
184
+ * @source
185
+ */
150
186
  export const PayloadSchema = type
151
187
  .scope({ PayloadCoreSchema, PayloadHeaderSchema })
152
188
  .type("<Name extends string, I, P, S>", [
@@ -182,38 +218,14 @@ export type ClientMethod<P extends Procedure<Type, Type, Type>> = ((
182
218
 
183
219
  /**
184
220
  * Symbol used as the key for the procedures map on the server instance
221
+ * @internal
222
+ * @source
185
223
  */
186
224
  export const zImplementations = Symbol("SWARPC implementations")
225
+
187
226
  /**
188
227
  * Symbol used as the key for the procedures map on instances
228
+ * @internal
229
+ * @source
189
230
  */
190
231
  export const zProcedures = Symbol("SWARPC procedures")
191
-
192
- /**
193
- * The sw&rpc client instance, which provides methods to call procedures.
194
- * Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
195
- * If you want to be able to cancel the request, you can set the request's ID yourself, and call `.abort(requestId, reason)` on the client instance to cancel it.
196
- */
197
- export type SwarpcClient<Procedures extends ProceduresMap> = {
198
- [zProcedures]: Procedures
199
- } & {
200
- [F in keyof Procedures]: ClientMethod<Procedures[F]>
201
- }
202
-
203
- /**
204
- * The sw&rpc server instance, which provides methods to register procedure implementations,
205
- * and listens for incoming messages that call those procedures
206
- */
207
- export type SwarpcServer<Procedures extends ProceduresMap> = {
208
- [zProcedures]: Procedures
209
- [zImplementations]: ImplementationsMap<Procedures>
210
- start(self: Window | Worker): void
211
- } & {
212
- [F in keyof Procedures]: (
213
- impl: ProcedureImplementation<
214
- Procedures[F]["input"],
215
- Procedures[F]["progress"],
216
- Procedures[F]["success"]
217
- >
218
- ) => void
219
- }