swarpc 0.8.0 → 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
@@ -2,7 +2,7 @@
2
2
  * @module
3
3
  * @mergeModuleWith <project>
4
4
  */
5
- import { type LogLevel } from "./log.js";
5
+ import { type Logger, type LogLevel } from "./log.js";
6
6
  import { ClientMethod, Hooks, zProcedures, type ProceduresMap } from "./types.js";
7
7
  /**
8
8
  * The sw&rpc client instance, which provides {@link ClientMethod | methods to call procedures}.
@@ -20,16 +20,25 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
20
20
  * @param options various options
21
21
  * @param options.worker if provided, the client will use this worker to post messages.
22
22
  * @param options.hooks hooks to run on messages received from the server
23
+ * @param options.restartListener if true, will force the listener to restart even if it has already been started
23
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}
24
25
  *
25
26
  * An example of defining and using a client:
26
27
  * {@includeCode ../example/src/routes/+page.svelte}
27
28
  */
28
- 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, }?: {
29
30
  worker?: Worker;
30
31
  hooks?: Hooks<Procedures>;
31
32
  loglevel?: LogLevel;
33
+ restartListener?: boolean;
32
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>;
33
42
  /**
34
43
  * Generate a random request ID, used to identify requests between client and server.
35
44
  * @source
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAA6B,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;;;;;;;;;;GAUG;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;;;;GAIG;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
@@ -19,13 +19,16 @@ let _clientListenerStarted = false;
19
19
  * @param options various options
20
20
  * @param options.worker if provided, the client will use this worker to post messages.
21
21
  * @param options.hooks hooks to run on messages received from the server
22
+ * @param options.restartListener if true, will force the listener to restart even if it has already been started
22
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}
23
24
  *
24
25
  * An example of defining and using a client:
25
26
  * {@includeCode ../example/src/routes/+page.svelte}
26
27
  */
27
- export function Client(procedures, { worker, loglevel = "debug", hooks = {}, } = {}) {
28
+ export function Client(procedures, { worker, loglevel = "debug", restartListener = false, hooks = {}, } = {}) {
28
29
  const l = createLogger("client", loglevel);
30
+ if (restartListener)
31
+ _clientListenerStarted = false;
29
32
  // Store procedures on a symbol key, to avoid conflicts with procedure names
30
33
  const instance = { [zProcedures]: procedures };
31
34
  for (const functionName of Object.keys(procedures)) {
@@ -103,9 +106,10 @@ async function postMessage(l, worker, hooks, message, options) {
103
106
  /**
104
107
  * Starts the client listener, which listens for messages from the sw&rpc server.
105
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
106
110
  * @returns
107
111
  */
108
- async function startClientListener(l, worker, hooks = {}) {
112
+ export async function startClientListener(l, worker, hooks = {}) {
109
113
  if (_clientListenerStarted)
110
114
  return;
111
115
  // Get service worker registration if no worker is provided
@@ -120,7 +124,7 @@ async function startClientListener(l, worker, hooks = {}) {
120
124
  }
121
125
  const w = worker ?? navigator.serviceWorker;
122
126
  // Start listening for messages
123
- l.debug("", "Starting client listener on", w);
127
+ l.debug(null, "Starting client listener", { worker, w, hooks });
124
128
  w.addEventListener("message", (event) => {
125
129
  // Get the data from the event
126
130
  const eventData = event.data || {};
@@ -136,7 +140,7 @@ async function startClientListener(l, worker, hooks = {}) {
136
140
  // Get the associated pending request handlers
137
141
  const handlers = pendingRequests.get(requestId);
138
142
  if (!handlers) {
139
- 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)}`);
140
144
  }
141
145
  // React to the data received: call hook, call handler,
142
146
  // and remove the request from pendingRequests (unless it's a progress update)
@@ -1 +1 @@
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,CA+J1B"}
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
@@ -106,16 +106,21 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
106
106
  await postError("No input provided");
107
107
  return;
108
108
  }
109
- // Call the implementation with the input and a progress callback
110
- await implementation(payload.input, async (progress) => {
111
- l.debug(requestId, `Progress for ${functionName}`, progress);
112
- await postMsg({ progress });
113
- }, {
114
- abortSignal: abortControllers.get(requestId)?.signal,
115
- logger: createLogger("server", loglevel, requestId),
116
- })
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) {
117
123
  // Send errors
118
- .catch(async (error) => {
119
124
  // Handle errors caused by abortions
120
125
  if ("aborted" in error) {
121
126
  l.debug(requestId, `Received abort error for ${functionName}`, error.aborted);
@@ -123,17 +128,12 @@ export function Server(procedures, { worker, loglevel = "debug" } = {}) {
123
128
  abortControllers.delete(requestId);
124
129
  return;
125
130
  }
126
- l.error(requestId, `Error in ${functionName}`, error);
131
+ l.info(requestId, `Error in ${functionName}`, error);
127
132
  await postError(error);
128
- })
129
- // Send results
130
- .then(async (result) => {
131
- l.debug(requestId, `Result for ${functionName}`, result);
132
- await postMsg({ result });
133
- })
134
- .finally(() => {
133
+ }
134
+ finally {
135
135
  abortedRequests.delete(requestId);
136
- });
136
+ }
137
137
  });
138
138
  };
139
139
  return instance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarpc",
3
- "version": "0.8.0",
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",
@@ -58,5 +58,9 @@
58
58
  "typescript": "^5.9.2",
59
59
  "vite": "^7.0.6",
60
60
  "vitest": "^3.2.4"
61
+ },
62
+ "volta": {
63
+ "node": "22.18.0",
64
+ "npm": "11.5.2"
61
65
  }
62
66
  }
package/src/client.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @module
2
+ * @module
3
3
  * @mergeModuleWith <project>
4
4
  */
5
5
 
@@ -47,8 +47,9 @@ let _clientListenerStarted = false
47
47
  * @param options various options
48
48
  * @param options.worker if provided, the client will use this worker to post messages.
49
49
  * @param options.hooks hooks to run on messages received from the server
50
+ * @param options.restartListener if true, will force the listener to restart even if it has already been started
50
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}
51
- *
52
+ *
52
53
  * An example of defining and using a client:
53
54
  * {@includeCode ../example/src/routes/+page.svelte}
54
55
  */
@@ -57,11 +58,19 @@ export function Client<Procedures extends ProceduresMap>(
57
58
  {
58
59
  worker,
59
60
  loglevel = "debug",
61
+ restartListener = false,
60
62
  hooks = {},
61
- }: { worker?: Worker; hooks?: Hooks<Procedures>; loglevel?: LogLevel } = {}
63
+ }: {
64
+ worker?: Worker
65
+ hooks?: Hooks<Procedures>
66
+ loglevel?: LogLevel
67
+ restartListener?: boolean
68
+ } = {}
62
69
  ): SwarpcClient<Procedures> {
63
70
  const l = createLogger("client", loglevel)
64
71
 
72
+ if (restartListener) _clientListenerStarted = false
73
+
65
74
  // Store procedures on a symbol key, to avoid conflicts with procedure names
66
75
  const instance = { [zProcedures]: procedures } as Partial<
67
76
  SwarpcClient<Procedures>
@@ -186,9 +195,10 @@ async function postMessage<Procedures extends ProceduresMap>(
186
195
  /**
187
196
  * Starts the client listener, which listens for messages from the sw&rpc server.
188
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
189
199
  * @returns
190
200
  */
191
- async function startClientListener<Procedures extends ProceduresMap>(
201
+ export async function startClientListener<Procedures extends ProceduresMap>(
192
202
  l: Logger,
193
203
  worker?: Worker,
194
204
  hooks: Hooks<Procedures> = {}
@@ -210,7 +220,7 @@ async function startClientListener<Procedures extends ProceduresMap>(
210
220
  const w = worker ?? navigator.serviceWorker
211
221
 
212
222
  // Start listening for messages
213
- l.debug("", "Starting client listener on", w)
223
+ l.debug(null, "Starting client listener", { worker, w, hooks })
214
224
  w.addEventListener("message", (event) => {
215
225
  // Get the data from the event
216
226
  const eventData = (event as MessageEvent).data || {}
@@ -230,7 +240,7 @@ async function startClientListener<Procedures extends ProceduresMap>(
230
240
  const handlers = pendingRequests.get(requestId)
231
241
  if (!handlers) {
232
242
  throw new Error(
233
- `[SWARPC Client] ${requestId} has no active request handlers`
243
+ `[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`,
234
244
  )
235
245
  }
236
246
 
@@ -255,7 +265,7 @@ async function startClientListener<Procedures extends ProceduresMap>(
255
265
 
256
266
  /**
257
267
  * Generate a random request ID, used to identify requests between client and server.
258
- * @source
268
+ * @source
259
269
  * @returns a 6-character hexadecimal string
260
270
  */
261
271
  export function makeRequestId(): string {
package/src/server.ts CHANGED
@@ -45,7 +45,7 @@ const abortedRequests = new Set<string>()
45
45
  * @param options various options
46
46
  * @param options.worker if provided, the server will use this worker to post messages, instead of sending it to all clients
47
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
- *
48
+ *
49
49
  * An example of defining a server:
50
50
  * {@includeCode ../example/src/service-worker.ts}
51
51
  */
@@ -170,43 +170,42 @@ export function Server<Procedures extends ProceduresMap>(
170
170
  return
171
171
  }
172
172
 
173
- // Call the implementation with the input and a progress callback
174
- await implementation(
175
- payload.input,
176
- async (progress: any) => {
177
- l.debug(requestId, `Progress for ${functionName}`, progress)
178
- await postMsg({ progress })
179
- },
180
- {
181
- abortSignal: abortControllers.get(requestId)?.signal,
182
- logger: createLogger("server", loglevel, requestId),
183
- }
184
- )
185
- // Send errors
186
- .catch(async (error: any) => {
187
- // Handle errors caused by abortions
188
- if ("aborted" in error) {
189
- l.debug(
190
- requestId,
191
- `Received abort error for ${functionName}`,
192
- error.aborted
193
- )
194
- abortedRequests.add(requestId)
195
- abortControllers.delete(requestId)
196
- 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),
197
184
  }
185
+ )
198
186
 
199
- l.error(requestId, `Error in ${functionName}`, error)
200
- await postError(error)
201
- })
202
187
  // Send results
203
- .then(async (result: any) => {
204
- l.debug(requestId, `Result for ${functionName}`, result)
205
- await postMsg({ result })
206
- })
207
- .finally(() => {
208
- abortedRequests.delete(requestId)
209
- })
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
+ }
210
209
  })
211
210
  }
212
211