swarpc 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -1,11 +1,10 @@
1
- import { createLogger, } from "./log.js";
1
+ import { createLogger } from "./log.js";
2
2
  import { broadcastNodes, makeNodeId, nodeIdOrSW, whoToSendTo, } from "./nodes.js";
3
+ import { _clientListeners, makeRequestId, pendingRequests, postMessage, postMessageSync, } from "./messaging.js";
3
4
  import { RequestCancelledError, zProcedures, } from "./types.js";
4
- import { findTransferables, extractFulfilleds, extractRejecteds, sizedArray, } from "./utils.js";
5
+ import { findTransferables, extractFulfilleds, extractRejecteds, sizedArray, isSharedWorker, } from "./utils.js";
5
6
  export const RESERVED_PROCEDURE_NAMES = ["onceBy", "destroy"];
6
- const pendingRequests = new Map();
7
7
  const emptyProgressCallback = () => { };
8
- let _clientListeners = new Map();
9
8
  export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug", restartListener = false, hooks = {}, localStorage = {}, nodeIds = [], } = {}) {
10
9
  const l = createLogger("client", loglevel);
11
10
  if (restartListener)
@@ -35,7 +34,7 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
35
34
  }
36
35
  for (const [nodeId, node] of Object.entries(nodes ?? {})) {
37
36
  l.debug(null, `Terminating worker for node ${nodeId}`);
38
- if (node instanceof SharedWorker) {
37
+ if (isSharedWorker(node)) {
39
38
  node.port.close();
40
39
  }
41
40
  else {
@@ -120,6 +119,7 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
120
119
  pendingRequests.set(requestId, {
121
120
  nodeId,
122
121
  functionName,
122
+ startedAt: performance.now(),
123
123
  concurrencyKey,
124
124
  resolve,
125
125
  onProgress: onProgress ?? emptyProgressCallback,
@@ -260,119 +260,6 @@ export function Client(procedures, { worker, nodes: nodeCount, loglevel = "debug
260
260
  };
261
261
  return instance;
262
262
  }
263
- async function postMessage(ctx, message, options) {
264
- await startClientListener(ctx);
265
- const { logger: l, node: worker } = ctx;
266
- if (!worker && !navigator.serviceWorker.controller)
267
- l.warn("", "Service Worker is not controlling the page");
268
- const w = worker instanceof SharedWorker
269
- ? worker.port
270
- : worker === undefined
271
- ? await navigator.serviceWorker.ready.then((r) => r.active)
272
- : worker;
273
- if (!w) {
274
- throw new Error("[SWARPC Client] No active service worker found");
275
- }
276
- w.postMessage(message, options);
277
- }
278
- function postMessageSync(l, worker, message, options) {
279
- if (!worker && !navigator.serviceWorker.controller)
280
- l.warn("Service Worker is not controlling the page");
281
- const w = worker instanceof SharedWorker
282
- ? worker.port
283
- : worker === undefined
284
- ? navigator.serviceWorker.controller
285
- : worker;
286
- if (!w) {
287
- throw new Error("[SWARPC Client] No active service worker found");
288
- }
289
- w.postMessage(message, options);
290
- }
291
- async function startClientListener(ctx) {
292
- if (_clientListeners.has(nodeIdOrSW(ctx.nodeId)))
293
- return;
294
- const { logger: l, node: worker } = ctx;
295
- if (!worker) {
296
- const sw = await navigator.serviceWorker.ready;
297
- if (!sw?.active) {
298
- throw new Error("[SWARPC Client] Service Worker is not active");
299
- }
300
- if (!navigator.serviceWorker.controller) {
301
- l.warn("", "Service Worker is not controlling the page");
302
- }
303
- }
304
- const w = worker ?? navigator.serviceWorker;
305
- l.debug(null, "Starting client listener", { w, ...ctx });
306
- const listener = (event) => {
307
- const eventData = event.data || {};
308
- if (eventData?.by !== "sw&rpc")
309
- return;
310
- const payload = eventData;
311
- if ("isInitializeRequest" in payload) {
312
- l.warn(null, "Ignoring unexpected #initialize from server", payload);
313
- return;
314
- }
315
- const { requestId, ...data } = payload;
316
- if (!requestId) {
317
- throw new Error("[SWARPC Client] Message received without requestId");
318
- }
319
- const handlers = pendingRequests.get(requestId);
320
- if (!handlers) {
321
- throw new Error(`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`);
322
- }
323
- if ("error" in data) {
324
- ctx.hooks.error?.({
325
- procedure: data.functionName,
326
- error: new Error(data.error.message),
327
- });
328
- handlers.reject(new Error(data.error.message));
329
- pendingRequests.delete(requestId);
330
- }
331
- else if ("progress" in data) {
332
- ctx.hooks.progress?.({
333
- procedure: data.functionName,
334
- data: data.progress,
335
- });
336
- handlers.onProgress(data.progress);
337
- }
338
- else if ("result" in data) {
339
- ctx.hooks.success?.({
340
- procedure: data.functionName,
341
- data: data.result,
342
- });
343
- handlers.resolve(data.result);
344
- pendingRequests.delete(requestId);
345
- }
346
- };
347
- if (w instanceof SharedWorker) {
348
- w.port.addEventListener("message", listener);
349
- w.port.start();
350
- }
351
- else {
352
- w.addEventListener("message", listener);
353
- }
354
- _clientListeners.set(nodeIdOrSW(ctx.nodeId), {
355
- disconnect() {
356
- if (w instanceof SharedWorker) {
357
- w.port.removeEventListener("message", listener);
358
- }
359
- else {
360
- w.removeEventListener("message", listener);
361
- }
362
- },
363
- });
364
- await postMessage(ctx, {
365
- by: "sw&rpc",
366
- functionName: "#initialize",
367
- isInitializeRequest: true,
368
- localStorageData: ctx.localStorage,
369
- nodeId: nodeIdOrSW(ctx.nodeId),
370
- allNodeIDs: ctx.allNodeIDs,
371
- });
372
- }
373
- function makeRequestId() {
374
- return Math.random().toString(16).substring(2, 8).toUpperCase();
375
- }
376
263
  function handleBroadcastOrThrowResults(results) {
377
264
  if (results.ok) {
378
265
  return results.successes;
@@ -0,0 +1,44 @@
1
+ import { type Logger, type RequestBoundLogger } from "./log.js";
2
+ import { type Payload } from "./payload.js";
3
+ import { type Hooks, type PendingRequest, type ProceduresMap } from "./types.js";
4
+ /**
5
+ * Context for passing around data useful for requests
6
+ */
7
+ export type Context<Procedures extends ProceduresMap> = {
8
+ /** A logger, bound to the client */
9
+ logger: Logger;
10
+ /** The node to use */
11
+ node: Worker | SharedWorker | undefined;
12
+ /** The ID of the node to use */
13
+ nodeId: string | undefined;
14
+ /** Set of all available nodes' IDs */
15
+ allNodeIDs: Set<string>;
16
+ /** Hooks defined by the client */
17
+ hooks: Hooks<Procedures>;
18
+ /** Local storage data defined by the client for the faux local storage */
19
+ localStorage: Record<string, any>;
20
+ };
21
+ /**
22
+ * Pending requests are stored in a map, where the key is the request ID.
23
+ * Each request has a set of handlers: resolve, reject, and onProgress.
24
+ * This allows having a single listener for the client, and having multiple in-flight calls to the same procedure.
25
+ */
26
+ export declare const pendingRequests: Map<string, PendingRequest>;
27
+ export declare let _clientListeners: Map<string, {
28
+ disconnect: () => void;
29
+ }>;
30
+ /**
31
+ * Warms up the client by starting the listener and getting the worker, then posts a message to the worker.
32
+ */
33
+ export declare function postMessage<Procedures extends ProceduresMap>(ctx: Context<Procedures>, message: Payload<Procedures>, options?: StructuredSerializeOptions): Promise<void>;
34
+ /**
35
+ * A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
36
+ * esp. useful for abort logic that needs to not be... put behind everything else on the event loop.
37
+ */
38
+ export declare function postMessageSync<Procedures extends ProceduresMap>(l: RequestBoundLogger, worker: Worker | SharedWorker | undefined, message: Payload<Procedures>, options?: StructuredSerializeOptions): void;
39
+ /**
40
+ * Generate a random request ID, used to identify requests between client and server.
41
+ * @source
42
+ * @returns a 6-character hexadecimal string
43
+ */
44
+ export declare function makeRequestId(): string;
@@ -0,0 +1,121 @@
1
+ import { nodeIdOrSW } from "./nodes.js";
2
+ import { isSharedWorker } from "./utils.js";
3
+ export const pendingRequests = new Map();
4
+ export let _clientListeners = new Map();
5
+ export async function postMessage(ctx, message, options) {
6
+ await startClientListener(ctx);
7
+ const { logger: l, node: worker } = ctx;
8
+ if (!worker && !navigator.serviceWorker.controller)
9
+ l.warn("", "Service Worker is not controlling the page");
10
+ const w = isSharedWorker(worker)
11
+ ? worker.port
12
+ : worker === undefined
13
+ ? await navigator.serviceWorker.ready.then((r) => r.active)
14
+ : worker;
15
+ if (!w) {
16
+ throw new Error("[SWARPC Client] No active service worker found");
17
+ }
18
+ w.postMessage(message, options);
19
+ }
20
+ export function postMessageSync(l, worker, message, options) {
21
+ if (!worker && !navigator.serviceWorker.controller)
22
+ l.warn("Service Worker is not controlling the page");
23
+ const w = isSharedWorker(worker)
24
+ ? worker.port
25
+ : worker === undefined
26
+ ? navigator.serviceWorker.controller
27
+ : worker;
28
+ if (!w) {
29
+ throw new Error("[SWARPC Client] No active service worker found");
30
+ }
31
+ w.postMessage(message, options);
32
+ }
33
+ async function startClientListener(ctx) {
34
+ if (_clientListeners.has(nodeIdOrSW(ctx.nodeId)))
35
+ return;
36
+ const { logger: l, node: worker } = ctx;
37
+ if (!worker) {
38
+ const sw = await navigator.serviceWorker.ready;
39
+ if (!sw?.active) {
40
+ throw new Error("[SWARPC Client] Service Worker is not active");
41
+ }
42
+ if (!navigator.serviceWorker.controller) {
43
+ l.warn("", "Service Worker is not controlling the page");
44
+ }
45
+ }
46
+ const w = worker ?? navigator.serviceWorker;
47
+ l.debug(null, "Starting client listener", { w, ...ctx });
48
+ const listener = (event) => {
49
+ const eventData = event.data || {};
50
+ if (eventData?.by !== "sw&rpc")
51
+ return;
52
+ const payload = eventData;
53
+ if ("isInitializeRequest" in payload) {
54
+ l.warn(null, "Ignoring unexpected #initialize from server", payload);
55
+ return;
56
+ }
57
+ const { requestId, ...data } = payload;
58
+ if (!requestId) {
59
+ throw new Error("[SWARPC Client] Message received without requestId");
60
+ }
61
+ const handlers = pendingRequests.get(requestId);
62
+ if (!handlers) {
63
+ throw new Error(`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`);
64
+ }
65
+ const duration = performance.now() - handlers.startedAt;
66
+ if ("error" in data) {
67
+ ctx.hooks.error?.({
68
+ procedure: data.functionName,
69
+ error: new Error(data.error.message),
70
+ duration,
71
+ });
72
+ handlers.reject(new Error(data.error.message));
73
+ pendingRequests.delete(requestId);
74
+ }
75
+ else if ("progress" in data) {
76
+ ctx.hooks.progress?.({
77
+ procedure: data.functionName,
78
+ data: data.progress,
79
+ duration,
80
+ });
81
+ handlers.onProgress(data.progress);
82
+ }
83
+ else if ("result" in data) {
84
+ ctx.hooks.success?.({
85
+ procedure: data.functionName,
86
+ data: data.result,
87
+ duration,
88
+ });
89
+ handlers.resolve(data.result);
90
+ pendingRequests.delete(requestId);
91
+ }
92
+ };
93
+ if (isSharedWorker(w)) {
94
+ w.port.addEventListener("message", listener);
95
+ w.port.start();
96
+ }
97
+ else {
98
+ w.addEventListener("message", listener);
99
+ }
100
+ _clientListeners.set(nodeIdOrSW(ctx.nodeId), {
101
+ disconnect() {
102
+ if (isSharedWorker(w)) {
103
+ w.port.removeEventListener("message", listener);
104
+ }
105
+ else {
106
+ w.removeEventListener("message", listener);
107
+ }
108
+ },
109
+ });
110
+ await postMessage(ctx, {
111
+ by: "sw&rpc",
112
+ functionName: "#initialize",
113
+ isInitializeRequest: true,
114
+ localStorageData: ctx.localStorage,
115
+ nodeId: nodeIdOrSW(ctx.nodeId),
116
+ allNodeIDs: ctx.allNodeIDs,
117
+ });
118
+ }
119
+ export function makeRequestId() {
120
+ return Math.random().toString(16).substring(2, 8).toUpperCase();
121
+ }
@@ -0,0 +1,17 @@
1
+ import type { StandardSchemaV1 as Schema } from "./standardschema.js";
2
+ import type { ProceduresMap } from "./types.js";
3
+ export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
4
+ input: Schema.InferOutput<PM[Name]["input"]>;
5
+ } | {
6
+ progress: Schema.InferOutput<PM[Name]["progress"]>;
7
+ } | {
8
+ result: Schema.InferOutput<PM[Name]["success"]>;
9
+ } | {
10
+ abort: {
11
+ reason: string;
12
+ };
13
+ } | {
14
+ error: {
15
+ message: string;
16
+ };
17
+ };
@@ -0,0 +1,88 @@
1
+ export function isPayloadInitialize(payload) {
2
+ if (typeof payload !== "object")
3
+ return false;
4
+ if (payload === null)
5
+ return false;
6
+ if (!("by" in payload))
7
+ return false;
8
+ if (!("nodeId" in payload))
9
+ return false;
10
+ if (!("functionName" in payload))
11
+ return false;
12
+ if (!("localStorageData" in payload))
13
+ return false;
14
+ if (!("isInitializeRequest" in payload))
15
+ return false;
16
+ if (!("allNodeIDs" in payload))
17
+ return false;
18
+ if (payload.by !== "sw&rpc")
19
+ return false;
20
+ if (payload.functionName !== "#initialize")
21
+ return false;
22
+ if (payload.isInitializeRequest !== true)
23
+ return false;
24
+ if (typeof payload.nodeId !== "string")
25
+ return false;
26
+ if (typeof payload.localStorageData !== "object")
27
+ return false;
28
+ if (payload.localStorageData === null)
29
+ return false;
30
+ return true;
31
+ }
32
+ export function isPayloadHeader(procedures, payload) {
33
+ if (typeof payload !== "object")
34
+ return false;
35
+ if (payload === null)
36
+ return false;
37
+ if (!("by" in payload))
38
+ return false;
39
+ if (!("requestId" in payload))
40
+ return false;
41
+ if (!("functionName" in payload))
42
+ return false;
43
+ if (payload.by !== "sw&rpc")
44
+ return false;
45
+ if (typeof payload.requestId !== "string")
46
+ return false;
47
+ if (typeof payload.functionName !== "string")
48
+ return false;
49
+ if (!Object.keys(procedures).includes(payload.functionName))
50
+ return false;
51
+ return true;
52
+ }
53
+ export function validatePayloadCore(procedure, payload) {
54
+ if (typeof payload !== "object")
55
+ throw new Error("payload is not an object");
56
+ if (payload === null)
57
+ throw new Error("payload is null");
58
+ if ("input" in payload) {
59
+ const input = procedure.input["~standard"].validate(payload.input);
60
+ if ("value" in input)
61
+ return { input: input.value };
62
+ }
63
+ if ("progress" in payload) {
64
+ const progress = procedure.progress["~standard"].validate(payload.progress);
65
+ if ("value" in progress)
66
+ return { progress: progress.value };
67
+ }
68
+ if ("result" in payload) {
69
+ const result = procedure.success["~standard"].validate(payload.result);
70
+ if ("value" in result)
71
+ return { result: result.value };
72
+ }
73
+ if ("abort" in payload &&
74
+ typeof payload.abort === "object" &&
75
+ payload.abort !== null &&
76
+ "reason" in payload.abort &&
77
+ typeof payload.abort.reason === "string") {
78
+ return { abort: { reason: payload.abort.reason } };
79
+ }
80
+ if ("error" in payload &&
81
+ typeof payload.error === "object" &&
82
+ payload.error !== null &&
83
+ "message" in payload.error &&
84
+ typeof payload.error.message === "string") {
85
+ return { error: { message: payload.error.message } };
86
+ }
87
+ throw new Error("invalid payload");
88
+ }
package/dist/server.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createLogger, injectIntoConsoleGlobal } from "./log.js";
2
- import { isPayloadHeader, isPayloadInitialize, RequestCancelledError, validatePayloadCore, zImplementations, zProcedures, } from "./types.js";
2
+ import { RequestCancelledError, zImplementations, zProcedures, } from "./types.js";
3
+ import { isPayloadHeader, isPayloadInitialize, validatePayloadCore, } from "./payload.js";
3
4
  import { findTransferables } from "./utils.js";
4
5
  import { FauxLocalStorage } from "./localstorage.js";
5
6
  import { scopeIsDedicated, scopeIsShared, scopeIsService } from "./scopes.js";
package/dist/types.d.ts CHANGED
@@ -90,6 +90,8 @@ type ProcedureNameAndData<Procedures extends ProceduresMap, Key extends "progres
90
90
  [K in keyof Procedures]: {
91
91
  procedure: K;
92
92
  data: Schema.InferOutput<Procedures[K][Key]>;
93
+ /** Time in milliseconds the procedure call took */
94
+ duration: number;
93
95
  };
94
96
  }[keyof Procedures];
95
97
  /**
@@ -102,9 +104,12 @@ export type Hooks<Procedures extends ProceduresMap> = {
102
104
  success?: (arg: ProcedureNameAndData<Procedures, "success">) => void;
103
105
  /**
104
106
  * Called when a procedure call has failed.
107
+ * @param arg
108
+ * @param arg.duration time in milliseconds the procedure call took
105
109
  */
106
110
  error?: (arg: {
107
111
  procedure: keyof Procedures;
112
+ duration: number;
108
113
  error: Error;
109
114
  }) => void;
110
115
  /**
@@ -112,21 +117,6 @@ export type Hooks<Procedures extends ProceduresMap> = {
112
117
  */
113
118
  progress?: (arg: ProcedureNameAndData<Procedures, "progress">) => void;
114
119
  };
115
- export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
116
- input: Schema.InferOutput<PM[Name]["input"]>;
117
- } | {
118
- progress: Schema.InferOutput<PM[Name]["progress"]>;
119
- } | {
120
- result: Schema.InferOutput<PM[Name]["success"]>;
121
- } | {
122
- abort: {
123
- reason: string;
124
- };
125
- } | {
126
- error: {
127
- message: string;
128
- };
129
- };
130
120
  /**
131
121
  * The callable function signature for a client method
132
122
  */
@@ -200,7 +190,7 @@ export type BroadcasterResultExtrasFailure = {
200
190
  successes: [];
201
191
  byNode: Map<string, PromiseRejectedResult>;
202
192
  };
203
- type ClientMethodExtraCallables<P extends Procedure<Schema, Schema, Schema>> = {
193
+ export type ClientMethodExtraCallables<P extends Procedure<Schema, Schema, Schema>> = {
204
194
  /**
205
195
  * A method that returns a `CancelablePromise`. Cancel it by calling `.cancel(reason)` on it, and wait for the request to resolve by awaiting the `request` property on the returned object.
206
196
  */
package/dist/types.js CHANGED
@@ -1,91 +1,3 @@
1
- export function isPayloadInitialize(payload) {
2
- if (typeof payload !== "object")
3
- return false;
4
- if (payload === null)
5
- return false;
6
- if (!("by" in payload))
7
- return false;
8
- if (!("nodeId" in payload))
9
- return false;
10
- if (!("functionName" in payload))
11
- return false;
12
- if (!("localStorageData" in payload))
13
- return false;
14
- if (!("isInitializeRequest" in payload))
15
- return false;
16
- if (!("allNodeIDs" in payload))
17
- return false;
18
- if (payload.by !== "sw&rpc")
19
- return false;
20
- if (payload.functionName !== "#initialize")
21
- return false;
22
- if (payload.isInitializeRequest !== true)
23
- return false;
24
- if (typeof payload.nodeId !== "string")
25
- return false;
26
- if (typeof payload.localStorageData !== "object")
27
- return false;
28
- if (payload.localStorageData === null)
29
- return false;
30
- return true;
31
- }
32
- export function isPayloadHeader(procedures, payload) {
33
- if (typeof payload !== "object")
34
- return false;
35
- if (payload === null)
36
- return false;
37
- if (!("by" in payload))
38
- return false;
39
- if (!("requestId" in payload))
40
- return false;
41
- if (!("functionName" in payload))
42
- return false;
43
- if (payload.by !== "sw&rpc")
44
- return false;
45
- if (typeof payload.requestId !== "string")
46
- return false;
47
- if (typeof payload.functionName !== "string")
48
- return false;
49
- if (!Object.keys(procedures).includes(payload.functionName))
50
- return false;
51
- return true;
52
- }
53
- export function validatePayloadCore(procedure, payload) {
54
- if (typeof payload !== "object")
55
- throw new Error("payload is not an object");
56
- if (payload === null)
57
- throw new Error("payload is null");
58
- if ("input" in payload) {
59
- const input = procedure.input["~standard"].validate(payload.input);
60
- if ("value" in input)
61
- return { input: input.value };
62
- }
63
- if ("progress" in payload) {
64
- const progress = procedure.progress["~standard"].validate(payload.progress);
65
- if ("value" in progress)
66
- return { progress: progress.value };
67
- }
68
- if ("result" in payload) {
69
- const result = procedure.success["~standard"].validate(payload.result);
70
- if ("value" in result)
71
- return { result: result.value };
72
- }
73
- if ("abort" in payload &&
74
- typeof payload.abort === "object" &&
75
- payload.abort !== null &&
76
- "reason" in payload.abort &&
77
- typeof payload.abort.reason === "string") {
78
- return { abort: { reason: payload.abort.reason } };
79
- }
80
- if ("error" in payload &&
81
- typeof payload.error === "object" &&
82
- payload.error !== null &&
83
- "message" in payload.error &&
84
- typeof payload.error.message === "string") {
85
- return { error: { message: payload.error.message } };
86
- }
87
- throw new Error("invalid payload");
88
- }
89
1
  export const zImplementations = Symbol("SWARPC implementations");
90
2
  export const zProcedures = Symbol("SWARPC procedures");
91
3
  export class RequestCancelledError extends Error {
package/dist/utils.js CHANGED
@@ -35,3 +35,8 @@ export function extractFulfilleds(settleds) {
35
35
  export function extractRejecteds(settleds) {
36
36
  return settleds.filter((settled) => settled.status === "rejected");
37
37
  }
38
+ export function isSharedWorker(worker) {
39
+ if (!SharedWorker)
40
+ return false;
41
+ return worker instanceof SharedWorker;
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarpc",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "Full type-safe RPC library for service worker -- move things off of the UI thread with ease!",
5
5
  "keywords": [
6
6
  "service-workers",
@@ -47,38 +47,41 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@8hobbies/typedoc-plugin-plausible": "^2.2.0",
50
- "@playwright/test": "^1.57.0",
51
- "@size-limit/esbuild-why": "^12.0.0",
52
- "@size-limit/preset-small-lib": "^12.0.0",
53
- "@vitest/web-worker": "^4.0.17",
54
- "arktype": "^2.1.29",
50
+ "@playwright/test": "^1.59.1",
51
+ "@size-limit/esbuild-why": "^12.1.0",
52
+ "@size-limit/preset-small-lib": "^12.1.0",
53
+ "@vitest/web-worker": "^4.1.4",
54
+ "arktype": "^2.2.0",
55
55
  "date-fns": "^4.1.0",
56
56
  "husky": "^9.1.7",
57
57
  "kacl": "^1.1.1",
58
- "knip": "^5.82.1",
59
- "lint-staged": "^16.2.7",
60
- "nodemon": "^3.1.11",
61
- "oxlint": "^1.40.0",
62
- "pkg-pr-new": "^0.0.62",
63
- "prettier": "^3.8.0",
58
+ "knip": "^6.5.0",
59
+ "lint-staged": "^16.4.0",
60
+ "nodemon": "^3.1.14",
61
+ "oxlint": "^1.60.0",
62
+ "pkg-pr-new": "^0.0.66",
63
+ "prettier": "^3.8.3",
64
64
  "sirv-cli": "^3.0.1",
65
- "size-limit": "^12.0.0",
66
- "typedoc": "^0.28.16",
65
+ "size-limit": "^12.1.0",
66
+ "typedoc": "^0.28.19",
67
67
  "typedoc-material-theme": "^1.4.1",
68
- "typedoc-plugin-dt-links": "^2.0.38",
68
+ "typedoc-plugin-dt-links": "^2.0.51",
69
69
  "typedoc-plugin-extras": "^4.0.1",
70
70
  "typedoc-plugin-inline-sources": "^1.3.0",
71
- "typedoc-plugin-mdn-links": "^5.1.0",
72
- "typedoc-plugin-redirect": "^1.2.1",
73
- "typescript": "^5.9.3",
74
- "vite": "^7.3.1",
75
- "vitest": "^4.0.17"
71
+ "typedoc-plugin-mdn-links": "^5.1.1",
72
+ "typedoc-plugin-redirect": "^1.3.0",
73
+ "typescript": "^6.0.3",
74
+ "vite": "^8.0.9",
75
+ "vitest": "^4.1.4"
76
76
  },
77
77
  "volta": {
78
- "node": "24.13.0",
79
- "npm": "11.7.0"
78
+ "node": "24.15.0",
79
+ "npm": "11.12.1"
80
80
  },
81
81
  "lint-staged": {
82
+ ".prettierrc": [
83
+ "prettier --write"
84
+ ],
82
85
  "*.{ts,js,md,json,yaml,yml}": [
83
86
  "oxlint --fix",
84
87
  "prettier --write"