@socket-mesh/server 19.0.0 → 19.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 SocketMesh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -23,6 +23,9 @@ export interface ServerOptions<TIncoming extends PublicMethodMap = {}, TOutgoing
23
23
  pingIntervalMs?: number;
24
24
  pingTimeoutMs?: number;
25
25
  plugins?: ServerPlugin<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>[];
26
+ serviceHandlers?: {
27
+ [TServiceName in keyof TService & string]?: HandlerMap<TService[TServiceName], TState & ServerSocketState, ServerSocket<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>, ServerTransport<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>>;
28
+ };
26
29
  socketChannelLimit?: number;
27
30
  socketStreamCleanupMode?: StreamCleanupMode;
28
31
  strictHandshake?: boolean;
@@ -1,6 +1,6 @@
1
1
  import { ChannelMap } from '@socket-mesh/channels';
2
2
  import { ClientPrivateMap, ServerPrivateMap } from '@socket-mesh/client';
3
- import { AuthenticateEvent, AuthStateChangeEvent, BadAuthTokenEvent, BaseSocket, BaseSocketOptions, CloseEvent, ConnectEvent, ConnectingEvent, DeauthenticateEvent, DisconnectEvent, ErrorEvent, FunctionReturnType, InvokeMethodOptions, InvokeServiceOptions, LooseHandlerMap, MessageEvent, PingEvent, PongEvent, PrivateMethodMap, PublicMethodMap, RemoveAuthTokenEvent, RequestEvent, ResponseEvent, ServiceMap, ServiceMethodName, ServiceName, Socket, TypedRequestEvent, TypedResponseEvent, TypedSocketEvent } from '@socket-mesh/core';
3
+ import { AuthenticateEvent, AuthStateChangeEvent, BadAuthTokenEvent, BaseSocket, BaseSocketOptions, CloseEvent, ConnectEvent, ConnectingEvent, DeauthenticateEvent, DisconnectEvent, ErrorEvent, FunctionReturnType, InvokeMethodOptions, InvokeServiceOptions, MessageEvent, PingEvent, PongEvent, PrivateMethodMap, PublicMethodMap, RemoveAuthTokenEvent, RequestEvent, ResponseEvent, ServiceMap, ServiceMethodName, ServiceName, Socket, TypedRequestEvent, TypedResponseEvent, TypedSocketEvent } from '@socket-mesh/core';
4
4
  import { DemuxedConsumableStream, StreamEvent } from '@socket-mesh/stream-demux';
5
5
  import { IncomingMessage } from 'http';
6
6
  import { WebSocket } from 'ws';
@@ -9,7 +9,6 @@ import { ServerPlugin } from './plugin/server-plugin.js';
9
9
  import { ServerSocketState } from './server-socket-state.js';
10
10
  import { Server } from './server.js';
11
11
  export interface ServerSocketOptions<TIncoming extends PublicMethodMap, TOutgoing extends PublicMethodMap, TChannel extends ChannelMap, TService extends ServiceMap, TState extends object, TPrivateIncoming extends PrivateMethodMap, TPrivateOutgoing extends PrivateMethodMap, TServerState extends object> extends BaseSocketOptions<TState & ServerSocketState> {
12
- handlers: LooseHandlerMap;
13
12
  id?: string;
14
13
  plugins?: ServerPlugin<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>[];
15
14
  request: IncomingMessage;
package/dist/server.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { AsyncStreamEmitter } from '@socket-mesh/async-stream-emitter';
2
2
  import { AuthEngine } from '@socket-mesh/auth-engine';
3
3
  import { ChannelMap } from '@socket-mesh/channels';
4
- import { PrivateMethodMap, PublicMethodMap, ServiceMap, StreamCleanupMode } from '@socket-mesh/core';
4
+ import { ServerPrivateMap } from '@socket-mesh/client';
5
+ import { HandlerMap, MethodMap, PrivateMethodMap, PublicMethodMap, ServiceMap, StreamCleanupMode } from '@socket-mesh/core';
5
6
  import { CodecEngine } from '@socket-mesh/formatter';
6
7
  import { DemuxedConsumableStream, StreamEvent } from '@socket-mesh/stream-demux';
7
8
  import { Server as HttpServer } from 'http';
@@ -10,10 +11,12 @@ import { Exchange } from './broker/exchange.js';
10
11
  import { CloseEvent, ConnectionEvent, ErrorEvent, HandshakeEvent, HeadersEvent, ListeningEvent, ServerEvent, SocketAuthenticateEvent, SocketAuthStateChangeEvent, SocketBadAuthTokenEvent, SocketCloseEvent, SocketConnectEvent, SocketConnectingEvent, SocketDeauthenticateEvent, SocketDisconnectEvent, SocketErrorEvent, SocketMessageEvent, SocketPingEvent, SocketPongEvent, SocketRemoveAuthTokenEvent, SocketRequestEvent, SocketResponseEvent, SocketSubscribeEvent, SocketSubscribeFailEvent, SocketSubscribeStateChangeEvent, SocketUnsubscribeEvent, WarningEvent } from './events/index.js';
11
12
  import { ServerPlugin } from './plugin/server-plugin.js';
12
13
  import { ServerOptions } from './server-options.js';
14
+ import { ServerSocketState } from './server-socket-state.js';
13
15
  import { ServerSocket } from './server-socket.js';
16
+ import { ServerTransport } from './server-transport.js';
14
17
  export declare class Server<TIncoming extends PublicMethodMap = {}, TOutgoing extends PublicMethodMap = {}, TChannel extends ChannelMap = {}, TService extends ServiceMap = {}, TState extends object = {}, TPrivateIncoming extends PrivateMethodMap = {}, TPrivateOutgoing extends PrivateMethodMap = {}, TServerState extends object = {}> extends AsyncStreamEmitter<ServerEvent<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>> {
15
18
  private readonly _callIdGenerator;
16
- private _handlers;
19
+ private readonly _handlers;
17
20
  private _isListening;
18
21
  private _isReady;
19
22
  private _pingIntervalRef;
@@ -41,6 +44,41 @@ export declare class Server<TIncoming extends PublicMethodMap = {}, TOutgoing ex
41
44
  readonly socketStreamCleanupMode: StreamCleanupMode;
42
45
  strictHandshake: boolean;
43
46
  constructor(options?: ServerOptions<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>);
47
+ /**
48
+ * Register a group of strongly-typed request handlers.
49
+ *
50
+ * Handlers added this way can be added or replaced after the server has
51
+ * started and are immediately visible to all existing and future
52
+ * connections (the underlying handler map is shared by reference).
53
+ *
54
+ * When called with a service name, the handlers are grouped under that
55
+ * service and surfaced via {@link services} so UI/tooling can list which
56
+ * groups are currently installed. When the service name is known to the
57
+ * server's `TService` generic, TypeScript validates the handler shape
58
+ * against the declared method map. For ad-hoc/dynamic services not
59
+ * present in `TService`, pass an explicit generic argument with the
60
+ * method map for the new service.
61
+ *
62
+ * When called without a service name, the handlers are registered as
63
+ * flat (top-level) handlers routed by method name alone — the same
64
+ * surface as `options.handlers` on the server constructor. These do not
65
+ * appear in {@link services}.
66
+ *
67
+ * @example
68
+ * // Flat handlers (no service name):
69
+ * server.addHandlers({ doThing: async (args) => { ... } });
70
+ *
71
+ * @example
72
+ * // Statically declared on the server generic:
73
+ * server.addHandlers('account', { find: async (args) => { ... } });
74
+ *
75
+ * @example
76
+ * // Dynamically added from a module at runtime:
77
+ * server.addHandlers<'inventory', InventoryMethodMap>('inventory', handlers);
78
+ */
79
+ addHandlers(handlers: HandlerMap<TIncoming & TPrivateIncoming & ServerPrivateMap, TState & ServerSocketState, ServerSocket<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>, ServerTransport<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>>): void;
80
+ addHandlers<TServiceName extends keyof TService & string>(service: TServiceName, handlers: HandlerMap<TService[TServiceName], TState & ServerSocketState, ServerSocket<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>, ServerTransport<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>>): void;
81
+ addHandlers<TServiceName extends string, TServiceMethodMap extends MethodMap>(service: TServiceName, handlers: HandlerMap<TServiceMethodMap, TState & ServerSocketState, ServerSocket<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>, ServerTransport<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>>): void;
44
82
  addPlugin(...plugin: ServerPlugin<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>[]): void;
45
83
  private bind;
46
84
  close(keepSocketsOpen?: boolean): Promise<void>;
@@ -74,6 +112,8 @@ export declare class Server<TIncoming extends PublicMethodMap = {}, TOutgoing ex
74
112
  emit(event: 'socketUnsubscribe', data: SocketUnsubscribeEvent<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>): void;
75
113
  emit(event: 'warning', data: WarningEvent): void;
76
114
  get exchange(): Exchange<TChannel>;
115
+ /** Method names registered under a given service, or an empty array if none. */
116
+ getServiceMethods(service: string): string[];
77
117
  get isListening(): boolean;
78
118
  get isReady(): boolean;
79
119
  listen(): DemuxedConsumableStream<StreamEvent<ServerEvent<TIncoming, TOutgoing, TChannel, TService, TState, TPrivateIncoming, TPrivateOutgoing, TServerState>>>;
@@ -111,6 +151,19 @@ export declare class Server<TIncoming extends PublicMethodMap = {}, TOutgoing ex
111
151
  private onError;
112
152
  private onHeaders;
113
153
  private onListening;
154
+ /**
155
+ * Unregister either a whole service (when `methods` is omitted) or a
156
+ * specific set of methods within a service. Removing a service empties
157
+ * the group and drops the key so it no longer appears in {@link services}.
158
+ *
159
+ * Call with an array of method names (no service) to remove flat
160
+ * (top-level) handlers by name. The flat slot itself is never dropped
161
+ * because it holds the built-in protocol handlers.
162
+ */
163
+ removeHandlers(methods: readonly string[]): void;
164
+ removeHandlers(service: string, methods?: readonly string[] | string): void;
165
+ /** Names of all service handler groups currently registered on the server. */
166
+ get services(): string[];
114
167
  private socketDisconnected;
115
168
  private startPinging;
116
169
  private stopPinging;
package/dist/server.js CHANGED
@@ -58,14 +58,23 @@ export class Server extends AsyncStreamEmitter {
58
58
  this.clients = {};
59
59
  this.clientCount = 0;
60
60
  this.codecEngine = options.codecEngine || defaultCodec;
61
- this._handlers = Object.assign({
62
- '#authenticate': authenticateHandler,
63
- '#handshake': handshakeHandler,
64
- '#publish': publishHandler,
65
- '#removeAuthToken': removeAuthTokenHandler,
66
- '#subscribe': subscribeHandler,
67
- '#unsubscribe': unsubscribeHandler
68
- }, options.handlers);
61
+ // Flat handlers live under the empty-string service key so dispatch
62
+ // (and Server.addHandlers/removeHandlers) only has to consult one map.
63
+ this._handlers = {
64
+ '': Object.assign({
65
+ '#authenticate': authenticateHandler,
66
+ '#handshake': handshakeHandler,
67
+ '#publish': publishHandler,
68
+ '#removeAuthToken': removeAuthTokenHandler,
69
+ '#subscribe': subscribeHandler,
70
+ '#unsubscribe': unsubscribeHandler
71
+ }, options.handlers)
72
+ };
73
+ if (options.serviceHandlers) {
74
+ for (const service of Object.keys(options.serviceHandlers)) {
75
+ this._handlers[service] = { ...options.serviceHandlers[service] };
76
+ }
77
+ }
69
78
  this.httpServer = options.server;
70
79
  this.plugins = options.plugins || [];
71
80
  this.origins = options.origins || '*:*';
@@ -104,6 +113,14 @@ export class Server extends AsyncStreamEmitter {
104
113
  })();
105
114
  }
106
115
  }
116
+ addHandlers(serviceOrHandlers, handlers) {
117
+ const service = typeof serviceOrHandlers === 'string' ? serviceOrHandlers : '';
118
+ const map = typeof serviceOrHandlers === 'string' ? handlers : serviceOrHandlers;
119
+ if (!this._handlers[service]) {
120
+ this._handlers[service] = {};
121
+ }
122
+ Object.assign(this._handlers[service], map);
123
+ }
107
124
  addPlugin(...plugin) {
108
125
  this.plugins.push(...plugin);
109
126
  }
@@ -180,6 +197,16 @@ export class Server extends AsyncStreamEmitter {
180
197
  get exchange() {
181
198
  return this.brokerEngine.exchange;
182
199
  }
200
+ /** Method names registered under a given service, or an empty array if none. */
201
+ getServiceMethods(service) {
202
+ if (service === '') {
203
+ // The empty-string slot stores flat (non-service) handlers internally
204
+ // and is not part of the public service surface.
205
+ return [];
206
+ }
207
+ const group = this._handlers[service];
208
+ return group ? Object.keys(group) : [];
209
+ }
183
210
  get isListening() {
184
211
  return this._isListening;
185
212
  }
@@ -204,10 +231,10 @@ export class Server extends AsyncStreamEmitter {
204
231
  ackTimeoutMs: this.ackTimeoutMs,
205
232
  callIdGenerator: this._callIdGenerator,
206
233
  codecEngine: this.codecEngine,
207
- handlers: this._handlers,
208
234
  plugins: this.plugins,
209
235
  request: upgradeReq,
210
236
  server: this,
237
+ serviceHandlers: this._handlers,
211
238
  socket: wsSocket,
212
239
  state: {},
213
240
  streamCleanupMode: this.socketStreamCleanupMode
@@ -232,6 +259,41 @@ export class Server extends AsyncStreamEmitter {
232
259
  this._isListening = true;
233
260
  this.emit('listening', {});
234
261
  }
262
+ removeHandlers(serviceOrMethods, methods) {
263
+ let service;
264
+ let list;
265
+ if (Array.isArray(serviceOrMethods)) {
266
+ service = '';
267
+ list = serviceOrMethods;
268
+ }
269
+ else {
270
+ service = serviceOrMethods;
271
+ list = typeof methods === 'string' ? [methods] : methods;
272
+ }
273
+ const group = this._handlers[service];
274
+ if (!group) {
275
+ return;
276
+ }
277
+ if (list === undefined) {
278
+ // Never drop the flat slot — it holds built-in protocol handlers.
279
+ if (service !== '') {
280
+ delete this._handlers[service];
281
+ }
282
+ return;
283
+ }
284
+ for (const method of list) {
285
+ delete group[method];
286
+ }
287
+ if (service !== '' && Object.keys(group).length === 0) {
288
+ delete this._handlers[service];
289
+ }
290
+ }
291
+ /** Names of all service handler groups currently registered on the server. */
292
+ get services() {
293
+ // The empty-string slot holds flat (non-service) handlers internally
294
+ // and is not part of the public service surface.
295
+ return Object.keys(this._handlers).filter(service => service !== '');
296
+ }
235
297
  socketDisconnected(socket) {
236
298
  if (this.pendingClients[socket.id]) {
237
299
  delete this.pendingClients[socket.id];
package/package.json CHANGED
@@ -1,81 +1,83 @@
1
- {
2
- "name": "@socket-mesh/server",
3
- "description": "A TCP socket pair for easily transmitting full messages without worrying about request size limits.",
4
- "version": "19.0.0",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "files": ["./dist"],
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "default": "./dist/index.js"
14
- },
15
- "./broker": {
16
- "types": "./dist/broker/index.d.ts",
17
- "import": "./dist/broker/index.js",
18
- "default": "./dist/broker/index.js"
19
- },
20
- "./events": {
21
- "types": "./dist/events/index.d.ts",
22
- "import": "./dist/events/index.js",
23
- "default": "./dist/events/index.js"
24
- },
25
- "./handlers": {
26
- "types": "./dist/handlers/index.d.ts",
27
- "import": "./dist/handlers/index.js",
28
- "default": "./dist/handlers/index.js"
29
- }
30
- },
31
- "author": {
32
- "name": "Greg Kimmy"
33
- },
34
- "scripts": {
35
- "build": "node ../../scripts/build.mjs && tsc --project tsconfig.build.json",
36
- "deploy": "node ../../scripts/publish.mjs",
37
- "lint": "eslint . --c ../../eslint.config.mjs",
38
- "lint:fix": "eslint . --c ../../eslint.config.mjs --fix",
39
- "test": "tsx --tsconfig ./tsconfig.build.json --test ./src/**/*.{spec,test}.ts ./test/**/*.{spec,test}.ts"
40
- },
41
- "repository": {
42
- "type": "git",
43
- "url": "git+https://github.com/socket-mesh/client-server.git"
44
- },
45
- "bugs": {
46
- "url": "https://github.com/socket-mesh/client-server/labels/server"
47
- },
48
- "devDependencies": {
49
- "@socket-mesh/local-storage": "workspace:^",
50
- "@socket-mesh/typescript-config": "workspace:^",
51
- "@types/base64id": "catalog:base64id",
52
- "@types/jsonwebtoken": "catalog:json-web-token",
53
- "@types/ws": "catalog:ws"
54
- },
55
- "dependencies": {
56
- "@socket-mesh/async-stream-emitter": "workspace:^",
57
- "@socket-mesh/auth": "workspace:^",
58
- "@socket-mesh/auth-engine": "workspace:^",
59
- "@socket-mesh/channels": "workspace:^",
60
- "@socket-mesh/client": "workspace:^",
61
- "@socket-mesh/core": "workspace:^",
62
- "@socket-mesh/errors": "workspace:^",
63
- "@socket-mesh/formatter": "workspace:^",
64
- "@socket-mesh/stream-demux": "workspace:^",
65
- "@socket-mesh/writable-consumable-stream": "workspace:^",
66
- "base64id": "catalog:base64id",
67
- "events": "catalog:",
68
- "jsonwebtoken": "catalog:json-web-token",
69
- "ws": "catalog:ws"
70
- },
71
- "keywords": [
72
- "ncom",
73
- "tcp",
74
- "simple",
75
- "socket",
76
- "messages"
77
- ],
78
- "engines": {
79
- "node": ">= 0.8.0"
80
- }
81
- }
1
+ {
2
+ "name": "@socket-mesh/server",
3
+ "description": "A TCP socket pair for easily transmitting full messages without worrying about request size limits.",
4
+ "version": "19.1.1",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "./dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./broker": {
18
+ "types": "./dist/broker/index.d.ts",
19
+ "import": "./dist/broker/index.js",
20
+ "default": "./dist/broker/index.js"
21
+ },
22
+ "./events": {
23
+ "types": "./dist/events/index.d.ts",
24
+ "import": "./dist/events/index.js",
25
+ "default": "./dist/events/index.js"
26
+ },
27
+ "./handlers": {
28
+ "types": "./dist/handlers/index.d.ts",
29
+ "import": "./dist/handlers/index.js",
30
+ "default": "./dist/handlers/index.js"
31
+ }
32
+ },
33
+ "author": {
34
+ "name": "Greg Kimmy"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/socket-mesh/client-server.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/socket-mesh/client-server/labels/server"
42
+ },
43
+ "devDependencies": {
44
+ "@types/base64id": "^2.0.2",
45
+ "@types/jsonwebtoken": "^9.0.10",
46
+ "@types/ws": "^8.18.1",
47
+ "@socket-mesh/local-storage": "^1.0.5",
48
+ "@socket-mesh/typescript-config": "^0.0.0"
49
+ },
50
+ "dependencies": {
51
+ "base64id": "^2.0.0",
52
+ "events": "^3.3.0",
53
+ "jsonwebtoken": "^9.0.3",
54
+ "ws": "^8.20.0",
55
+ "@socket-mesh/auth": "^2.2.2",
56
+ "@socket-mesh/async-stream-emitter": "^7.2.3",
57
+ "@socket-mesh/channels": "^6.1.6",
58
+ "@socket-mesh/auth-engine": "^1.0.3",
59
+ "@socket-mesh/client": "^19.1.1",
60
+ "@socket-mesh/errors": "^3.2.2",
61
+ "@socket-mesh/core": "^2.1.1",
62
+ "@socket-mesh/stream-demux": "^10.2.5",
63
+ "@socket-mesh/writable-consumable-stream": "^4.3.3",
64
+ "@socket-mesh/formatter": "^4.1.2"
65
+ },
66
+ "keywords": [
67
+ "ncom",
68
+ "tcp",
69
+ "simple",
70
+ "socket",
71
+ "messages"
72
+ ],
73
+ "engines": {
74
+ "node": ">= 0.8.0"
75
+ },
76
+ "scripts": {
77
+ "build": "node ../../scripts/build.mjs && tsc --project tsconfig.build.json",
78
+ "deploy": "node ../../scripts/publish.mjs",
79
+ "lint": "eslint . --c ../../eslint.config.mjs",
80
+ "lint:fix": "eslint . --c ../../eslint.config.mjs --fix",
81
+ "test": "tsx --tsconfig ./tsconfig.build.json --test ./src/**/*.{spec,test}.ts ./test/**/*.{spec,test}.ts"
82
+ }
83
+ }