swarpc 0.6.1 → 0.7.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/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Type } from "arktype";
1
+ import { type Type } from "arktype";
2
2
  /**
3
3
  * A procedure declaration
4
4
  */
@@ -28,10 +28,27 @@ export type Procedure<I extends Type, P extends Type, S extends Type> = {
28
28
  */
29
29
  autotransfer?: "always" | "never" | "output-only";
30
30
  };
31
+ /**
32
+ * A promise that you can cancel by calling `.cancel(reason)` on it:
33
+ *
34
+ * ```js
35
+ * const { request, cancel } = client.runProcedure.cancelable(input, onProgress)
36
+ * setTimeout(() => cancel("Cancelled by user"), 1000)
37
+ * const result = await request
38
+ * ```
39
+ */
40
+ export type CancelablePromise<T> = {
41
+ request: Promise<T>;
42
+ /**
43
+ * Abort the request.
44
+ * @param reason The reason for cancelling the request.
45
+ */
46
+ cancel: (reason: string) => Promise<void>;
47
+ };
31
48
  /**
32
49
  * An implementation of a procedure
33
50
  */
34
- export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferIn"]) => void) => Promise<S["inferIn"]>;
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"]>;
35
52
  /**
36
53
  * Declarations of procedures by name.
37
54
  */
@@ -59,31 +76,158 @@ export type Hooks<Procedures extends ProceduresMap> = {
59
76
  */
60
77
  progress?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Procedures[Procedure]["progress"]["inferOut"]) => void;
61
78
  };
79
+ export declare const PayloadHeaderSchema: import("arktype").Generic<[["Name", string]], {
80
+ readonly by: "\"sw&rpc\"";
81
+ readonly functionName: "Name";
82
+ readonly requestId: "string >= 1";
83
+ }, {}, {}>;
62
84
  export type PayloadHeader<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
63
85
  by: "sw&rpc";
64
86
  functionName: Name & string;
65
87
  requestId: string;
66
- autotransfer: PM[Name]["autotransfer"];
67
88
  };
89
+ export declare const PayloadCoreSchema: import("arktype").Generic<[["I", unknown], ["P", unknown], ["S", unknown]], {
90
+ readonly "input?": "I";
91
+ readonly "progress?": "P";
92
+ readonly "result?": "S";
93
+ readonly "abort?": {
94
+ readonly reason: "string";
95
+ };
96
+ readonly "error?": {
97
+ readonly message: "string";
98
+ };
99
+ }, {}, {}>;
68
100
  export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
69
101
  input: PM[Name]["input"]["inferOut"];
70
102
  } | {
71
103
  progress: PM[Name]["progress"]["inferOut"];
72
104
  } | {
73
105
  result: PM[Name]["success"]["inferOut"];
106
+ } | {
107
+ abort: {
108
+ reason: string;
109
+ };
74
110
  } | {
75
111
  error: {
76
112
  message: string;
77
113
  };
78
114
  };
115
+ export declare const PayloadSchema: import("arktype").Generic<[["Name", string], ["I", unknown], ["P", unknown], ["S", unknown]], readonly ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"], {
116
+ PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
117
+ readonly "input?": "I";
118
+ readonly "progress?": "P";
119
+ readonly "result?": "S";
120
+ readonly "abort?": {
121
+ readonly reason: "string";
122
+ };
123
+ readonly "error?": {
124
+ readonly message: "string";
125
+ };
126
+ }, {}, {}>, {
127
+ PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
128
+ readonly "input?": "I";
129
+ readonly "progress?": "P";
130
+ readonly "result?": "S";
131
+ readonly "abort?": {
132
+ readonly reason: "string";
133
+ };
134
+ readonly "error?": {
135
+ readonly message: "string";
136
+ };
137
+ }, {}, {}>;
138
+ PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
139
+ readonly by: "\"sw&rpc\"";
140
+ readonly functionName: "Name";
141
+ readonly requestId: "string >= 1";
142
+ }, {}, {}>;
143
+ } & {}>;
144
+ PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
145
+ readonly by: "\"sw&rpc\"";
146
+ readonly functionName: "Name";
147
+ readonly requestId: "string >= 1";
148
+ }, {}, {}>, {
149
+ PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
150
+ readonly "input?": "I";
151
+ readonly "progress?": "P";
152
+ readonly "result?": "S";
153
+ readonly "abort?": {
154
+ readonly reason: "string";
155
+ };
156
+ readonly "error?": {
157
+ readonly message: "string";
158
+ };
159
+ }, {}, {}>;
160
+ PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
161
+ readonly by: "\"sw&rpc\"";
162
+ readonly functionName: "Name";
163
+ readonly requestId: "string >= 1";
164
+ }, {}, {}>;
165
+ } & {}>;
166
+ }, {
167
+ PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
168
+ readonly "input?": "I";
169
+ readonly "progress?": "P";
170
+ readonly "result?": "S";
171
+ readonly "abort?": {
172
+ readonly reason: "string";
173
+ };
174
+ readonly "error?": {
175
+ readonly message: "string";
176
+ };
177
+ }, {}, {}>, {
178
+ PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
179
+ readonly "input?": "I";
180
+ readonly "progress?": "P";
181
+ readonly "result?": "S";
182
+ readonly "abort?": {
183
+ readonly reason: "string";
184
+ };
185
+ readonly "error?": {
186
+ readonly message: "string";
187
+ };
188
+ }, {}, {}>;
189
+ PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
190
+ readonly by: "\"sw&rpc\"";
191
+ readonly functionName: "Name";
192
+ readonly requestId: "string >= 1";
193
+ }, {}, {}>;
194
+ } & {}>;
195
+ PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
196
+ readonly by: "\"sw&rpc\"";
197
+ readonly functionName: "Name";
198
+ readonly requestId: "string >= 1";
199
+ }, {}, {}>, {
200
+ PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
201
+ readonly "input?": "I";
202
+ readonly "progress?": "P";
203
+ readonly "result?": "S";
204
+ readonly "abort?": {
205
+ readonly reason: "string";
206
+ };
207
+ readonly "error?": {
208
+ readonly message: "string";
209
+ };
210
+ }, {}, {}>;
211
+ PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
212
+ readonly by: "\"sw&rpc\"";
213
+ readonly functionName: "Name";
214
+ readonly requestId: "string >= 1";
215
+ }, {}, {}>;
216
+ } & {}>;
217
+ }>;
79
218
  /**
80
219
  * The effective payload as sent by the server to the client
81
220
  */
82
221
  export type Payload<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = PayloadHeader<PM, Name> & PayloadCore<PM, Name>;
83
222
  /**
84
- * A procedure's corresponding method on the client instance -- used to call the procedure
223
+ * A procedure's corresponding method on the client instance -- used to call the procedure. If you want to be able to cancel the request, you can use the `cancelable` method instead of running the procedure directly.
85
224
  */
86
- export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>;
225
+ export type ClientMethod<P extends Procedure<Type, Type, Type>> = ((input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>) & {
226
+ /**
227
+ * 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.
228
+ */
229
+ cancelable: (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void, requestId?: string) => CancelablePromise<P["success"]["inferOut"]>;
230
+ };
87
231
  /**
88
232
  * Symbol used as the key for the procedures map on the server instance
89
233
  */
@@ -93,7 +237,9 @@ export declare const zImplementations: unique symbol;
93
237
  */
94
238
  export declare const zProcedures: unique symbol;
95
239
  /**
96
- * The sw&rpc client instance, which provides methods to call procedures
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.
97
243
  */
98
244
  export type SwarpcClient<Procedures extends ProceduresMap> = {
99
245
  [zProcedures]: Procedures;
@@ -107,7 +253,7 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
107
253
  export type SwarpcServer<Procedures extends ProceduresMap> = {
108
254
  [zProcedures]: Procedures;
109
255
  [zImplementations]: ImplementationsMap<Procedures>;
110
- start(self: Window): void;
256
+ start(self: Window | Worker): void;
111
257
  } & {
112
258
  [F in keyof Procedures]: (impl: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>) => void;
113
259
  };
@@ -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,3 +1,23 @@
1
+ import { type } from "arktype";
2
+ export const PayloadHeaderSchema = type("<Name extends string>", {
3
+ by: '"sw&rpc"',
4
+ functionName: "Name",
5
+ requestId: "string >= 1",
6
+ });
7
+ export const PayloadCoreSchema = type("<I, P, S>", {
8
+ "input?": "I",
9
+ "progress?": "P",
10
+ "result?": "S",
11
+ "abort?": { reason: "string" },
12
+ "error?": { message: "string" },
13
+ });
14
+ export const PayloadSchema = type
15
+ .scope({ PayloadCoreSchema, PayloadHeaderSchema })
16
+ .type("<Name extends string, I, P, S>", [
17
+ "PayloadHeaderSchema<Name>",
18
+ "&",
19
+ "PayloadCoreSchema<I, P, S>",
20
+ ]);
1
21
  /**
2
22
  * Symbol used as the key for the procedures map on the server instance
3
23
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarpc",
3
- "version": "0.6.1",
3
+ "version": "0.7.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",
@@ -24,22 +24,26 @@
24
24
  "dist",
25
25
  "src"
26
26
  ],
27
- "main": "dist/swarpc.js",
28
- "types": "dist/swarpc.d.ts",
27
+ "main": "dist/index.js",
28
+ "types": "dist/index.d.ts",
29
29
  "scripts": {
30
30
  "build": "tsc",
31
31
  "dev": "tsc --watch",
32
- "test": "echo \"Error: no test specified\" && exit 1",
33
- "typedoc": "typedoc src/swarpc.ts src/types.ts --readme README.md",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest",
34
+ "typedoc": "typedoc src/index.ts src/types.ts --readme README.md",
34
35
  "version": "kacl release && prettier -w CHANGELOG.md && git add CHANGELOG.md"
35
36
  },
36
37
  "dependencies": {
37
38
  "arktype": "^2.1.20"
38
39
  },
39
40
  "devDependencies": {
41
+ "@vitest/web-worker": "^3.2.4",
40
42
  "kacl": "^1.1.1",
41
43
  "prettier": "^3.6.2",
42
- "typedoc": "^0.28.7",
43
- "typescript": "^5.8.3"
44
+ "typedoc": "^0.28.9",
45
+ "typescript": "^5.9.2",
46
+ "vite": "^7.0.6",
47
+ "vitest": "^3.2.4"
44
48
  }
45
49
  }
package/src/client.ts ADDED
@@ -0,0 +1,245 @@
1
+ import { createLogger, type Logger, type LogLevel } from "./log.js"
2
+ import {
3
+ Hooks,
4
+ Payload,
5
+ PayloadCore,
6
+ zProcedures,
7
+ type ProceduresMap,
8
+ type SwarpcClient
9
+ } from "./types.js"
10
+ import { findTransferables } from "./utils.js"
11
+
12
+ export type { SwarpcClient } from "./types.js"
13
+
14
+ /**
15
+ * Pending requests are stored in a map, where the key is the request ID.
16
+ * Each request has a set of handlers: resolve, reject, and onProgress.
17
+ * This allows having a single listener for the client, and having multiple in-flight calls to the same procedure.
18
+ */
19
+ const pendingRequests = new Map<string, PendingRequest>()
20
+ type PendingRequest = {
21
+ functionName: string
22
+ reject: (err: Error) => void
23
+ onProgress: (progress: any) => void
24
+ resolve: (result: any) => void
25
+ }
26
+
27
+ // Have we started the client listener?
28
+ let _clientListenerStarted = false
29
+
30
+ /**
31
+ *
32
+ * @param procedures procedures the client will be able to call
33
+ * @param options various options
34
+ * @param options.worker if provided, the client will use this worker to post messages.
35
+ * @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.
37
+ */
38
+ export function Client<Procedures extends ProceduresMap>(
39
+ procedures: Procedures,
40
+ {
41
+ worker,
42
+ loglevel = "debug",
43
+ hooks = {},
44
+ }: { worker?: Worker; hooks?: Hooks<Procedures>; loglevel?: LogLevel } = {}
45
+ ): SwarpcClient<Procedures> {
46
+ const l = createLogger("client", loglevel)
47
+
48
+ // Store procedures on a symbol key, to avoid conflicts with procedure names
49
+ const instance = { [zProcedures]: procedures } as Partial<
50
+ SwarpcClient<Procedures>
51
+ >
52
+
53
+ for (const functionName of Object.keys(procedures) as Array<
54
+ keyof Procedures
55
+ >) {
56
+ if (typeof functionName !== "string") {
57
+ throw new Error(
58
+ `[SWARPC Client] Invalid function name, don't use symbols`
59
+ )
60
+ }
61
+
62
+ const send = async (
63
+ requestId: string,
64
+ msg: PayloadCore<Procedures, typeof functionName>,
65
+ options?: StructuredSerializeOptions
66
+ ) => {
67
+ return postMessage(
68
+ l,
69
+ worker,
70
+ hooks,
71
+ {
72
+ ...msg,
73
+ by: "sw&rpc",
74
+ requestId,
75
+ functionName,
76
+ },
77
+ options
78
+ )
79
+ }
80
+
81
+ // Set the method on the instance
82
+ const _runProcedure = async (
83
+ input: unknown,
84
+ onProgress: (progress: unknown) => void | Promise<void> = () => {},
85
+ reqid?: string
86
+ ) => {
87
+ // Validate the input against the procedure's input schema
88
+ procedures[functionName].input.assert(input)
89
+
90
+ const requestId = reqid ?? makeRequestId()
91
+
92
+ return new Promise((resolve, reject) => {
93
+ // Store promise handlers (as well as progress updates handler)
94
+ // so the client listener can resolve/reject the promise (and react to progress updates)
95
+ // when the server sends messages back
96
+ pendingRequests.set(requestId, {
97
+ functionName,
98
+ resolve,
99
+ onProgress,
100
+ reject,
101
+ })
102
+
103
+ const transfer =
104
+ procedures[functionName].autotransfer === "always"
105
+ ? findTransferables(input)
106
+ : []
107
+
108
+ // Post the message to the server
109
+ l.debug(requestId, `Requesting ${functionName} with`, input)
110
+ send(requestId, { input }, { transfer })
111
+ .then(() => {})
112
+ .catch(reject)
113
+ })
114
+ }
115
+
116
+ // @ts-expect-error
117
+ instance[functionName] = _runProcedure
118
+ instance[functionName]!.cancelable = (input, onProgress) => {
119
+ const requestId = makeRequestId()
120
+ return {
121
+ request: _runProcedure(input, onProgress, requestId),
122
+ async cancel(reason: string) {
123
+ if (!pendingRequests.has(requestId)) {
124
+ l.warn(
125
+ requestId,
126
+ `Cannot cancel ${functionName} request, it has already been resolved or rejected`
127
+ )
128
+ return
129
+ }
130
+
131
+ l.debug(requestId, `Cancelling ${functionName} with`, reason)
132
+ await send(requestId, { abort: { reason } })
133
+ pendingRequests.delete(requestId)
134
+ },
135
+ }
136
+ }
137
+ }
138
+
139
+ return instance as SwarpcClient<Procedures>
140
+ }
141
+
142
+ /**
143
+ * Warms up the client by starting the listener and getting the worker, then posts a message to the worker.
144
+ * @returns the worker to use
145
+ */
146
+ async function postMessage<Procedures extends ProceduresMap>(
147
+ l: Logger,
148
+ worker: Worker | undefined,
149
+ hooks: Hooks<Procedures>,
150
+ message: Payload<Procedures>,
151
+ options?: StructuredSerializeOptions
152
+ ) {
153
+ await startClientListener(l, worker, hooks)
154
+
155
+ if (!worker && !navigator.serviceWorker.controller)
156
+ l.warn("", "Service Worker is not controlling the page")
157
+
158
+ // If no worker is provided, we use the service worker
159
+ const w =
160
+ worker ?? (await navigator.serviceWorker.ready.then((r) => r.active))
161
+
162
+ if (!w) {
163
+ throw new Error("[SWARPC Client] No active service worker found")
164
+ }
165
+
166
+ w.postMessage(message, options)
167
+ }
168
+
169
+ /**
170
+ * Starts the client listener, which listens for messages from the sw&rpc server.
171
+ * @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
172
+ * @returns
173
+ */
174
+ async function startClientListener<Procedures extends ProceduresMap>(
175
+ l: Logger,
176
+ worker?: Worker,
177
+ hooks: Hooks<Procedures> = {}
178
+ ) {
179
+ if (_clientListenerStarted) return
180
+
181
+ // Get service worker registration if no worker is provided
182
+ if (!worker) {
183
+ const sw = await navigator.serviceWorker.ready
184
+ if (!sw?.active) {
185
+ throw new Error("[SWARPC Client] Service Worker is not active")
186
+ }
187
+
188
+ if (!navigator.serviceWorker.controller) {
189
+ l.warn("", "Service Worker is not controlling the page")
190
+ }
191
+ }
192
+
193
+ const w = worker ?? navigator.serviceWorker
194
+
195
+ // Start listening for messages
196
+ l.debug("", "Starting client listener on", w)
197
+ w.addEventListener("message", (event) => {
198
+ // Get the data from the event
199
+ const eventData = (event as MessageEvent).data || {}
200
+
201
+ // Ignore other messages that aren't for us
202
+ if (eventData?.by !== "sw&rpc") return
203
+
204
+ // We don't use a arktype schema here, we trust the server to send valid data
205
+ const { requestId, ...data } = eventData as Payload<Procedures>
206
+
207
+ // Sanity check in case we somehow receive a message without requestId
208
+ if (!requestId) {
209
+ throw new Error("[SWARPC Client] Message received without requestId")
210
+ }
211
+
212
+ // Get the associated pending request handlers
213
+ const handlers = pendingRequests.get(requestId)
214
+ if (!handlers) {
215
+ throw new Error(
216
+ `[SWARPC Client] ${requestId} has no active request handlers`
217
+ )
218
+ }
219
+
220
+ // React to the data received: call hook, call handler,
221
+ // and remove the request from pendingRequests (unless it's a progress update)
222
+ if ("error" in data) {
223
+ hooks.error?.(data.functionName, new Error(data.error.message))
224
+ handlers.reject(new Error(data.error.message))
225
+ pendingRequests.delete(requestId)
226
+ } else if ("progress" in data) {
227
+ hooks.progress?.(data.functionName, data.progress)
228
+ handlers.onProgress(data.progress)
229
+ } else if ("result" in data) {
230
+ hooks.success?.(data.functionName, data.result)
231
+ handlers.resolve(data.result)
232
+ pendingRequests.delete(requestId)
233
+ }
234
+ })
235
+
236
+ _clientListenerStarted = true
237
+ }
238
+
239
+ /**
240
+ * Generate a random request ID, used to identify requests between client and server.
241
+ * @returns a 6-character hexadecimal string
242
+ */
243
+ export function makeRequestId(): string {
244
+ return Math.random().toString(16).substring(2, 8).toUpperCase()
245
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./client.js"
2
+ export * from "./server.js"
3
+ export type { ProceduresMap, CancelablePromise } from "./types.js"
package/src/log.ts ADDED
@@ -0,0 +1,62 @@
1
+ export function createLogger(
2
+ side: "server" | "client",
3
+ level: LogLevel = "debug"
4
+ ) {
5
+ const enabledLevels = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level))
6
+
7
+ 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) : () => {},
12
+ }
13
+ }
14
+
15
+ export type Logger = ReturnType<typeof createLogger>
16
+
17
+ const LOG_LEVELS = ["debug", "info", "warn", "error"] as const
18
+ export type LogLevel = (typeof LOG_LEVELS)[number]
19
+
20
+ /**
21
+ * Creates partially-applied logging functions given the first 2 args
22
+ * @param severity
23
+ * @param side
24
+ * @returns
25
+ */
26
+ function logger(severity: LogLevel, side: "server" | "client") {
27
+ return (rqid: string | null, message: string, ...args: any[]) =>
28
+ log(severity, side, rqid, message, ...args)
29
+ }
30
+
31
+ /**
32
+ * Send log messages to the console, with a helpful prefix.
33
+ * @param severity
34
+ * @param side
35
+ * @param rqid request ID
36
+ * @param message
37
+ * @param args passed to console methods directly
38
+ */
39
+ export function log(
40
+ severity: "debug" | "info" | "warn" | "error",
41
+ side: "server" | "client",
42
+ rqid: string | null,
43
+ message: string,
44
+ ...args: any[]
45
+ ) {
46
+ const prefix =
47
+ "[" +
48
+ ["SWARPC", side, rqid ? `%c${rqid}%c` : ""].filter(Boolean).join(" ") +
49
+ "]"
50
+
51
+ const prefixStyles = rqid ? ["color: cyan;", "color: inherit;"] : []
52
+
53
+ if (severity === "debug") {
54
+ console.debug(prefix, ...prefixStyles, message, ...args)
55
+ } else if (severity === "info") {
56
+ console.info(prefix, ...prefixStyles, message, ...args)
57
+ } else if (severity === "warn") {
58
+ console.warn(prefix, ...prefixStyles, message, ...args)
59
+ } else if (severity === "error") {
60
+ console.error(prefix, ...prefixStyles, message, ...args)
61
+ }
62
+ }