hermes-handler 0.0.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/README.md ADDED
@@ -0,0 +1,232 @@
1
+ # HermesHandler
2
+
3
+ HermesHandler is a lightweight, framework-agnostic message router for browser extensions and event-driven systems. It provides structured request dispatching, a strict `{ ok, result, error }` response envelope, timeout handling, cooperative cancellation, and safe normalization.
4
+
5
+ Designed for reliability and clarity, HermesHandler is especially well-suited for LLM-driven agents, modular browser architectures, automation layers, and distributed runtime systems.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ * 🔁 Deterministic message routing via `type`
12
+ * 📦 Strict response envelope: `{ ok, result?, error? }`
13
+ * ⏱ Built-in timeout handling
14
+ * 🛑 Cooperative cancellation via `AbortSignal`
15
+ * 🧊 Immutable (shallow-frozen) responses
16
+ * 🧠 LLM-friendly deterministic contract
17
+ * 🧩 Framework-agnostic (no runtime dependencies)
18
+ * 📘 Type-safe via generated `.d.ts`
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install hermes-handler
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Quick Start
31
+
32
+ ```js
33
+ import { HermesHandler } from "hermes-handler";
34
+
35
+ const handlers = {
36
+ ping: () => ({ ok: true, result: "pong" }),
37
+
38
+ greet: (msg) => {
39
+ return { ok: true, result: `Hello ${msg.payload.name}` };
40
+ }
41
+ };
42
+
43
+ const hermes = new HermesHandler(handlers);
44
+
45
+ const res = await hermes.dispatch({ type: "ping" });
46
+
47
+ if (res.ok) {
48
+ console.log(res.result); // "pong"
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Browser Extension Usage
55
+
56
+ Attach HermesHandler to a runtime listener:
57
+
58
+ ```js
59
+ browser.runtime.onMessage.addListener(
60
+ hermes.getListener()
61
+ );
62
+ ```
63
+
64
+ HermesHandler supports both:
65
+
66
+ * Promise-returning listeners (MV3 / Firefox / polyfill)
67
+ * Callback-style `sendResponse + return true`
68
+
69
+ ---
70
+
71
+ ## Response Contract
72
+
73
+ All responses follow a strict envelope.
74
+
75
+ ### Success
76
+
77
+ ```js
78
+ { ok: true, result: any }
79
+ ```
80
+
81
+ ### Error
82
+
83
+ ```js
84
+ { ok: false, error: string, details?: any }
85
+ ```
86
+
87
+ Primitive return values are automatically normalized:
88
+
89
+ ```js
90
+ return "hello";
91
+ ```
92
+
93
+ Becomes:
94
+
95
+ ```js
96
+ { ok: true, result: "hello" }
97
+ ```
98
+
99
+ Malformed responses are safely coerced into valid error envelopes.
100
+
101
+ ---
102
+
103
+ ## Timeouts
104
+
105
+ Handlers can be time-limited:
106
+
107
+ ```js
108
+ const hermes = new HermesHandler(handlers, {
109
+ timeoutMs: 7000
110
+ });
111
+ ```
112
+
113
+ If exceeded, HermesHandler returns:
114
+
115
+ ```js
116
+ { ok: false, error: "Handler <type> timed out (7000 ms)" }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Cooperative Cancellation
122
+
123
+ Each handler receives an `AbortSignal`:
124
+
125
+ ```js
126
+ async function longTask(msg, ctx) {
127
+ if (ctx.signal?.aborted) {
128
+ return { ok: false, error: "Cancelled" };
129
+ }
130
+
131
+ ctx.signal?.addEventListener("abort", () => {
132
+ console.log("Cancelled externally");
133
+ });
134
+ }
135
+ ```
136
+
137
+ HermesHandler aborts the signal once a request lifecycle completes.
138
+
139
+ ---
140
+
141
+ ## API
142
+
143
+ ### `new HermesHandler(initialHandlers?, options?)`
144
+
145
+ **initialHandlers**
146
+ `Record<string, HermesHandlerFn>`
147
+
148
+ **options**
149
+
150
+ * `timeoutMs?: number`
151
+ * `onUnknown?: (msg, ctx) => HermesResponse`
152
+ * `onError?: (err, msg, ctx) => HermesResponse`
153
+
154
+ ---
155
+
156
+ ### `.register(type, fn)`
157
+
158
+ Register or overwrite a handler.
159
+
160
+ ### `.registerMany(map)`
161
+
162
+ Register multiple handlers at once.
163
+
164
+ ### `.unregister(type)`
165
+
166
+ Remove a handler.
167
+
168
+ ### `.has(type)`
169
+
170
+ Check if a handler exists.
171
+
172
+ ### `.getListener()`
173
+
174
+ Returns a runtime-compatible message listener.
175
+
176
+ ### `.dispatch(msg, sender?)`
177
+
178
+ Dispatch a message manually (useful for testing or non-extension environments).
179
+
180
+ ---
181
+
182
+ ## Logging
183
+
184
+ HermesHandler emits warnings and errors through a configurable logger.
185
+
186
+ By default, it uses the global `console`. You can disable logging entirely or provide a custom logger implementation.
187
+
188
+ ### Disable Logging
189
+
190
+ ```js
191
+ const hermes = new HermesHandler(handlers, {
192
+ logger: null
193
+ });
194
+ ```
195
+
196
+ ### Custom Logger
197
+
198
+ ```js
199
+ const hermes = new HermesHandler(handlers, {
200
+ logger: {
201
+ warn: (...args) => myLogger.warn(...args),
202
+ error: (...args) => myLogger.error(...args)
203
+ }
204
+ });
205
+ ```
206
+
207
+ **HermesLogger shape**
208
+
209
+ ```ts
210
+ interface HermesLogger {
211
+ debug?(message?: any, ...optionalParams: any[]): void;
212
+ info?(message?: any, ...optionalParams: any[]): void;
213
+ warn?(message?: any, ...optionalParams: any[]): void;
214
+ error?(message?: any, ...optionalParams: any[]): void;
215
+ }
216
+ ```
217
+
218
+ If `logger` is `null`, HermesHandler will not emit any console output.
219
+
220
+ ---
221
+
222
+ ## Design Goals
223
+
224
+ HermesHandler enforces a predictable and deterministic runtime contract. By standardizing request/response handling and isolating message dispatch logic, it simplifies reasoning about complex systems—particularly those involving automation, background scripts, or LLM-driven tool execution.
225
+
226
+ The core remains intentionally minimal, dependency-free, and portable.
227
+
228
+ ---
229
+
230
+ ## License
231
+
232
+ MIT
@@ -0,0 +1,92 @@
1
+ export class HermesHandler {
2
+ /**
3
+ * @param {Record<string, HermesHandlerFn>} initialHandlers
4
+ * @param {Object} [options]
5
+ * @param {number} [options.timeoutMs=5000] max time a handler can take before auto-fail
6
+ * @param {(msg: any, ctx: any) => any} [options.onUnknown] override unknown-type response
7
+ * @param {(err: any, msg: any, ctx: any) => any} [options.onError] override error response
8
+ * @param {HermesLogger|null} [options.logger=console] set to null to silence logs
9
+ */
10
+ constructor(initialHandlers?: Record<string, HermesHandlerFn>, options?: {
11
+ timeoutMs?: number | undefined;
12
+ onUnknown?: ((msg: any, ctx: any) => any) | undefined;
13
+ onError?: ((err: any, msg: any, ctx: any) => any) | undefined;
14
+ logger?: HermesLogger | null | undefined;
15
+ });
16
+ /** @type {Map<string, HermesHandlerFn>} */
17
+ _handlers: Map<string, HermesHandlerFn>;
18
+ _timeoutMs: number;
19
+ _onUnknown: (msg: any, ctx: any) => any;
20
+ _onError: (err: any, msg: any, ctx: any) => any;
21
+ /** @type {HermesLogger|null} */
22
+ _logger: HermesLogger | null;
23
+ /**
24
+ * Register (or overwrite) a handler for a given msg.type
25
+ * @param {string} type
26
+ * @param {HermesHandlerFn} fn
27
+ * @returns {void}
28
+ */
29
+ register(type: string, fn: HermesHandlerFn): void;
30
+ /**
31
+ * Register multiple handlers at once
32
+ * @param {Record<string, HermesHandlerFn>} map
33
+ * @returns {void}
34
+ */
35
+ registerMany(map: Record<string, HermesHandlerFn>): void;
36
+ /** Remove a handler for a given msg.type */
37
+ /** @param {string} type */
38
+ unregister(type: string): void;
39
+ /** Check if a handler exists for a given msg.type */
40
+ /** @param {string} type */
41
+ has(type: string): boolean;
42
+ /**
43
+ * The listener you add using browser.runtime.onMessage.addListener
44
+ *
45
+ * Supports BOTH reply styles:
46
+ * • Promise-returning listener (Firefox / MV3 / polyfill)
47
+ * • sendResponse + return true (callback-style)
48
+ * @returns {(msg: any, sender: any, sendResponse?: (payload: any) => void) => any}
49
+ */
50
+ getListener(): (msg: any, sender: any, sendResponse?: (payload: any) => void) => any;
51
+ /**
52
+ * @param {any} msg
53
+ * @param {any} sender
54
+ * @returns {Promise<HermesResponse<any>>}
55
+ */
56
+ _dispatch(msg: any, sender: any): Promise<HermesResponse<any>>;
57
+ /**
58
+ * Dispatch a message through the router (useful for testing / non-runtime environments).
59
+ * @param {HermesMessage} msg
60
+ * @param {any} [sender]
61
+ * @returns {Promise<HermesResponse<any>>}
62
+ */
63
+ dispatch(msg: HermesMessage, sender?: any): Promise<HermesResponse<any>>;
64
+ }
65
+ export type HermesOk<T> = {
66
+ ok: true;
67
+ result?: T | undefined;
68
+ };
69
+ export type HermesErr = {
70
+ ok: false;
71
+ error: string;
72
+ details?: any;
73
+ };
74
+ export type HermesResponse<T> = HermesOk<T> | HermesErr;
75
+ export type HermesMessage = {
76
+ type: string;
77
+ payload?: any;
78
+ requestId?: string | undefined;
79
+ };
80
+ export type HermesContext = {
81
+ sender: any;
82
+ tabId: number | undefined;
83
+ signal: AbortSignal | undefined;
84
+ send: (payload: any) => void;
85
+ };
86
+ export type HermesHandlerFn = (msg: HermesMessage, ctx: HermesContext) => HermesResponse<any> | Promise<HermesResponse<any>> | any;
87
+ export type HermesLogger = {
88
+ debug?: ((message?: any, ...optionalParams: any[]) => void) | undefined;
89
+ info?: ((message?: any, ...optionalParams: any[]) => void) | undefined;
90
+ warn?: ((message?: any, ...optionalParams: any[]) => void) | undefined;
91
+ error?: ((message?: any, ...optionalParams: any[]) => void) | undefined;
92
+ };
@@ -0,0 +1,350 @@
1
+ // src/HermesHandler.js
2
+
3
+ // ------------------------------------------------------------
4
+ // HermesHandler — universal message router / gatekeeper
5
+ // ------------------------------------------------------------
6
+
7
+
8
+
9
+ /**
10
+ * @template T
11
+ * @typedef {Object} HermesOk
12
+ * @property {true} ok
13
+ * @property {T} [result]
14
+ */
15
+
16
+
17
+ /**
18
+ * @typedef {Object} HermesErr
19
+ * @property {false} ok
20
+ * @property {string} error
21
+ * @property {any} [details]
22
+ */
23
+
24
+
25
+ /**
26
+ * @template T
27
+ * @typedef {HermesOk<T> | HermesErr} HermesResponse
28
+ */
29
+
30
+
31
+ /**
32
+ * @typedef {Object} HermesMessage
33
+ * @property {string} type
34
+ * @property {any} [payload]
35
+ * @property {string} [requestId] // optional correlation id
36
+ */
37
+
38
+
39
+ /**
40
+ * @typedef {Object} HermesContext
41
+ * @property {any} sender
42
+ * @property {number|undefined} tabId
43
+ * @property {AbortSignal|undefined} signal
44
+ * @property {(payload: any) => void} send
45
+ */
46
+
47
+
48
+ /**
49
+ * @callback HermesHandlerFn
50
+ * @param {HermesMessage} msg
51
+ * @param {HermesContext} ctx
52
+ * @returns {HermesResponse<any>|Promise<HermesResponse<any>>|any}
53
+ */
54
+
55
+
56
+ /**
57
+ * @typedef {Object} HermesLogger
58
+ * @property {(message?: any, ...optionalParams: any[]) => void} [debug]
59
+ * @property {(message?: any, ...optionalParams: any[]) => void} [info]
60
+ * @property {(message?: any, ...optionalParams: any[]) => void} [warn]
61
+ * @property {(message?: any, ...optionalParams: any[]) => void} [error]
62
+ */
63
+
64
+
65
+
66
+ /* ------------------------------------------------------------
67
+ * Internal helpers
68
+ * ---------------------------------------------------------- */
69
+
70
+ /**
71
+ * @param {any} x
72
+ * @returns {x is Function}
73
+ */
74
+ function isFn(x) {
75
+ return typeof x === "function";
76
+ }
77
+
78
+ /** @param {any} err */
79
+ function toErrorString(err) {
80
+ if (err instanceof Error) return err.message || String(err);
81
+ return String(err);
82
+ }
83
+
84
+ /** @param {any} payload */
85
+ function normalizePayload(payload) {
86
+ if (payload && typeof payload === "object" && "ok" in payload) {
87
+ // Enforce strict boolean ok
88
+ if (typeof payload.ok !== "boolean") {
89
+ return { ok: false, error: "Invalid response: 'ok' must be boolean" };
90
+ }
91
+
92
+ // If ok:false, ensure error exists
93
+ if (payload.ok === false && typeof payload.error !== "string") {
94
+ return { ok: false, error: "Invalid response: missing 'error' string" };
95
+ }
96
+
97
+ return payload;
98
+ }
99
+
100
+ return { ok: true, result: payload };
101
+ }
102
+
103
+ /** @param {any} payload */
104
+ function freezeNormalized(payload) {
105
+ const normalized = normalizePayload(payload);
106
+ return normalized && typeof normalized === "object"
107
+ ? Object.freeze(normalized)
108
+ : normalized;
109
+ }
110
+
111
+
112
+ /**
113
+ * @template T
114
+ * @param {() => T | Promise<T>} fn
115
+ * @param {number} ms
116
+ * @param {() => any} onTimeout
117
+ * @returns {Promise<T>}
118
+ */
119
+ function withTimeout(fn, ms, onTimeout) {
120
+ if (!Number.isFinite(ms) || ms <= 0) {
121
+ return Promise.resolve().then(fn);
122
+ }
123
+
124
+ const makeTimeoutError = () => {
125
+ try {
126
+ return onTimeout();
127
+ } catch (e) {
128
+ return e;
129
+ }
130
+ };
131
+
132
+ return new Promise((resolve, reject) => {
133
+
134
+ /** @type {ReturnType<typeof setTimeout>} */
135
+ let timerId;
136
+
137
+ const cleanup = () => clearTimeout(timerId);
138
+
139
+ /** @param {T} value */
140
+ const resolveWithCleanup = (value) => {
141
+ cleanup();
142
+ resolve(value);
143
+ };
144
+
145
+ /** @param {any} err */
146
+ const rejectWithCleanup = (err) => {
147
+ cleanup();
148
+ reject(err);
149
+ };
150
+
151
+ timerId = setTimeout(() => rejectWithCleanup(makeTimeoutError()), ms);
152
+
153
+ Promise.resolve()
154
+ .then(fn)
155
+ .then(resolveWithCleanup)
156
+ .catch(rejectWithCleanup);
157
+ });
158
+ }
159
+
160
+
161
+ export class HermesHandler {
162
+ /**
163
+ * @param {Record<string, HermesHandlerFn>} initialHandlers
164
+ * @param {Object} [options]
165
+ * @param {number} [options.timeoutMs=5000] max time a handler can take before auto-fail
166
+ * @param {(msg: any, ctx: any) => any} [options.onUnknown] override unknown-type response
167
+ * @param {(err: any, msg: any, ctx: any) => any} [options.onError] override error response
168
+ * @param {HermesLogger|null} [options.logger=console] set to null to silence logs
169
+ */
170
+ constructor(initialHandlers = {}, options = {}) {
171
+ const {
172
+ timeoutMs = 5000,
173
+ onUnknown = (msg) => ({ ok: false, error: `Unknown msg.type: ${msg?.type}` }),
174
+ onError = (err) => ({ ok: false, error: toErrorString(err) }),
175
+ logger = console
176
+ } = options;
177
+
178
+ /** @type {Map<string, HermesHandlerFn>} */
179
+ this._handlers = new Map(Object.entries(initialHandlers));
180
+ this._timeoutMs = timeoutMs;
181
+ this._onUnknown = onUnknown;
182
+ this._onError = onError;
183
+
184
+ /** @type {HermesLogger|null} */
185
+ this._logger = logger;
186
+ }
187
+
188
+ /**
189
+ * Register (or overwrite) a handler for a given msg.type
190
+ * @param {string} type
191
+ * @param {HermesHandlerFn} fn
192
+ * @returns {void}
193
+ */
194
+ register(type, fn) {
195
+ if (!isFn(fn)) {
196
+ throw new Error(`Handler for ${type} must be a function`);
197
+ }
198
+ this._handlers.set(type, fn);
199
+ }
200
+
201
+ /**
202
+ * Register multiple handlers at once
203
+ * @param {Record<string, HermesHandlerFn>} map
204
+ * @returns {void}
205
+ */
206
+ registerMany(map) {
207
+ for (const [type, fn] of Object.entries(map ?? {})) {
208
+ this.register(type, fn);
209
+ }
210
+ }
211
+
212
+ /** Remove a handler for a given msg.type */
213
+ /** @param {string} type */
214
+ unregister(type) {
215
+ this._handlers.delete(type);
216
+ }
217
+
218
+ /** Check if a handler exists for a given msg.type */
219
+ /** @param {string} type */
220
+ has(type) {
221
+ return this._handlers.has(type);
222
+ }
223
+
224
+
225
+ /**
226
+ * The listener you add using browser.runtime.onMessage.addListener
227
+ *
228
+ * Supports BOTH reply styles:
229
+ * • Promise-returning listener (Firefox / MV3 / polyfill)
230
+ * • sendResponse + return true (callback-style)
231
+ * @returns {(msg: any, sender: any, sendResponse?: (payload: any) => void) => any}
232
+ */
233
+ getListener() {
234
+ return (msg, sender, sendResponse) => {
235
+ const p = this._dispatch(msg, sender);
236
+
237
+
238
+ // Callback-style (works everywhere)
239
+ if (isFn(sendResponse)) {
240
+ p.then(sendResponse).catch((err) =>
241
+ sendResponse(freezeNormalized(this._onError(err, msg, { sender })))
242
+ );
243
+ return true; // keep the port open for async response
244
+ }
245
+
246
+
247
+ // Promise-returning style
248
+ return p;
249
+ };
250
+ }
251
+
252
+ // ---- Core dispatch ------------------------------------------------------
253
+ /**
254
+ * @param {any} msg
255
+ * @param {any} sender
256
+ * @returns {Promise<HermesResponse<any>>}
257
+ */
258
+ async _dispatch(msg, sender) {
259
+
260
+ if (!msg || typeof msg !== "object") {
261
+ return freezeNormalized({ ok: false, error: "Invalid message: msg expected to be an object" });
262
+ }
263
+
264
+ const type = msg.type;
265
+
266
+ if (typeof type !== "string" || !type) {
267
+ return freezeNormalized({ ok: false, error: "Invalid message: msg missing string 'type'" });
268
+ }
269
+
270
+
271
+
272
+ let responded = false;
273
+
274
+ /** @type {HermesResponse<any>} */
275
+ let payloadToReturn = freezeNormalized({ ok: false, error: "No response" });
276
+
277
+ // Cooperative cancellation: handlers MAY honor ctx.signal
278
+ const controller = typeof AbortController !== "undefined"
279
+ ? new AbortController()
280
+ : { signal: undefined, abort: () => { } };
281
+
282
+ const ctx = {
283
+ sender,
284
+ tabId: sender?.tab?.id,
285
+ signal: controller.signal,
286
+ send: (/** @type {any} */payload) => {
287
+ if (responded) {
288
+ this._logger?.warn?.("[Hermes] Multiple send attempts", { type });
289
+ return;
290
+ }
291
+
292
+ responded = true;
293
+
294
+ // Freeze to prevent accidental mutation after responding
295
+ // (shallow freeze is enough and avoids surprising perf hits).
296
+ payloadToReturn = freezeNormalized(payload);
297
+
298
+ }
299
+
300
+ };
301
+
302
+ const fn = this._handlers.get(type);
303
+
304
+ if (!fn) {
305
+ ctx.send(this._onUnknown(msg, ctx));
306
+ return payloadToReturn;
307
+ }
308
+
309
+ try {
310
+ const maybeReturn = await withTimeout(
311
+ () => fn(msg, ctx),
312
+ this._timeoutMs,
313
+ () => new Error(`Handler ${type} timed out (${this._timeoutMs} ms)`)
314
+ );
315
+
316
+ // Handler may either return a payload OR call ctx.send(payload)
317
+ if (!responded && maybeReturn !== undefined) {
318
+ ctx.send(maybeReturn);
319
+ }
320
+
321
+ if (!responded) {
322
+ ctx.send({ ok: false, error: `Handler ${type} returned no response` });
323
+ }
324
+ } catch (err) {
325
+ this._logger?.error?.(`[Hermes] Handler error for ${type}:`, err);
326
+ if (!responded) {
327
+ ctx.send(this._onError(err, msg, ctx));
328
+ }
329
+ } finally {
330
+ // NOTE: Abort does not stop JS execution, but lets handlers cooperate.
331
+ controller.abort();
332
+ }
333
+
334
+ return payloadToReturn;
335
+ }
336
+
337
+
338
+ /**
339
+ * Dispatch a message through the router (useful for testing / non-runtime environments).
340
+ * @param {HermesMessage} msg
341
+ * @param {any} [sender]
342
+ * @returns {Promise<HermesResponse<any>>}
343
+ */
344
+ dispatch(msg, sender) {
345
+ return this._dispatch(msg, sender);
346
+ }
347
+
348
+
349
+
350
+ }
@@ -0,0 +1,3 @@
1
+ export { HermesHandler } from "./HermesHandler.js";
2
+ export type HermesMessage = import("./HermesHandler.js").HermesMessage;
3
+ export type HermesResponse = import("./HermesHandler.js").HermesResponse<any>;
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // src/index.js
2
+
3
+ export { HermesHandler } from "./HermesHandler.js";
4
+
5
+ /**
6
+ * @typedef {import("./HermesHandler.js").HermesMessage} HermesMessage
7
+ * @typedef {import("./HermesHandler.js").HermesResponse<any>} HermesResponse
8
+ */
9
+
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "hermes-handler",
3
+ "version": "0.0.1",
4
+ "description": "HermesHandler is a lightweight, framework-agnostic message router for browser extensions and event-driven systems, with strict {ok,result,error} envelopes, timeouts, cooperative cancellation, and safe normalization—ideal for reliable LLM agent backbones.",
5
+ "license": "MIT",
6
+ "author": "WATT3D",
7
+ "homepage": "https://github.com/iWhatty/HermesHandler-JS#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/iWhatty/HermesHandler-JS.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/iWhatty/HermesHandler-JS/issues"
14
+ },
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "scripts": {
35
+ "clean": "rimraf dist",
36
+ "build": "npm run clean && npm run build:js && npm run build:types",
37
+ "build:js": "node ./scripts/copy-src-to-dist.mjs",
38
+ "build:types": "tsc -p tsconfig.build.json",
39
+ "lint": "eslint .",
40
+ "test": "vitest run",
41
+ "prepublishOnly": "npm run build && npm test"
42
+ },
43
+ "devDependencies": {
44
+ "eslint": "^9.0.0",
45
+ "rimraf": "^6.0.0",
46
+ "typescript": "^5.9.3",
47
+ "vitest": "^2.1.9"
48
+ },
49
+ "keywords": []
50
+ }