@dotpress/events 0.0.0-stage → 2.0.0-alpha.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/LICENSE +21 -0
- package/README.md +707 -2
- package/dist/aws-sqs.d.ts +127 -0
- package/dist/aws-sqs.js +440 -0
- package/dist/aws-sqs.js.map +1 -0
- package/dist/azure-service-bus.d.ts +99 -0
- package/dist/azure-service-bus.js +320 -0
- package/dist/azure-service-bus.js.map +1 -0
- package/dist/bullmq.d.ts +92 -0
- package/dist/bullmq.js +169 -0
- package/dist/bullmq.js.map +1 -0
- package/dist/bus-Bei_kN-e.d.ts +137 -0
- package/dist/chunk-E4NMMZMY.js +24 -0
- package/dist/chunk-E4NMMZMY.js.map +1 -0
- package/dist/chunk-NCQ4ZTNB.js +50 -0
- package/dist/chunk-NCQ4ZTNB.js.map +1 -0
- package/dist/cli.js +202 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloudflare-queues.d.ts +120 -0
- package/dist/cloudflare-queues.js +333 -0
- package/dist/cloudflare-queues.js.map +1 -0
- package/dist/google-pubsub.d.ts +111 -0
- package/dist/google-pubsub.js +420 -0
- package/dist/google-pubsub.js.map +1 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +701 -0
- package/dist/index.js.map +1 -0
- package/dist/pg-boss.d.ts +79 -0
- package/dist/pg-boss.js +189 -0
- package/dist/pg-boss.js.map +1 -0
- package/dist/plugin.d.ts +40 -0
- package/dist/plugin.js +27 -0
- package/dist/plugin.js.map +1 -0
- package/dist/transport-BtNR1ucF.d.ts +273 -0
- package/package.json +139 -3
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logger of the bus and the workers. A Dotpress or Pino logger fits;
|
|
5
|
+
* `eventsPlugin` uses the logger of the request.
|
|
6
|
+
*/
|
|
7
|
+
interface EventsLogger {
|
|
8
|
+
debug?(data: object, message: string): void;
|
|
9
|
+
info(data: object, message: string): void;
|
|
10
|
+
warn(data: object, message: string): void;
|
|
11
|
+
error(data: object, message: string): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Names of the transports, besides `default`, declared by module
|
|
16
|
+
* augmentation so that typos are type errors:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* declare module '@dotpress/events' {
|
|
20
|
+
* interface EventTransports {
|
|
21
|
+
* edge: true
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
interface EventTransports {
|
|
27
|
+
}
|
|
28
|
+
/** Name of a transport: `default`, or a name of `EventTransports`. */
|
|
29
|
+
type TransportName = 'default' | Extract<keyof EventTransports, string>;
|
|
30
|
+
/** Event created by `defineEvent()`. */
|
|
31
|
+
interface EventDefinition<S extends z.ZodType = z.ZodType> {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly schema: S;
|
|
34
|
+
/** Transport of its listeners; undefined for `default`. */
|
|
35
|
+
readonly transport: TransportName | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** Payload accepted by `emit()` for an event. */
|
|
38
|
+
type EventInput<E> = E extends EventDefinition<infer S> ? z.input<S> : never;
|
|
39
|
+
/** Payload received by the listeners of an event. */
|
|
40
|
+
type EventPayload<E> = E extends EventDefinition<infer S> ? z.output<S> : never;
|
|
41
|
+
/** Context of a listener call. */
|
|
42
|
+
interface ListenerContext<TPayload = unknown> {
|
|
43
|
+
/** Payload, validated by the schema of the event. */
|
|
44
|
+
readonly payload: TPayload;
|
|
45
|
+
/** Name of the event. */
|
|
46
|
+
readonly event: string;
|
|
47
|
+
/** Name of the listener. */
|
|
48
|
+
readonly listener: string;
|
|
49
|
+
/**
|
|
50
|
+
* Identifier of the message, the same for every attempt and when the
|
|
51
|
+
* emission is repeated: use it to ignore duplicates.
|
|
52
|
+
*/
|
|
53
|
+
readonly id: string;
|
|
54
|
+
/** Identifier of the emission, shared by the messages of its listeners. */
|
|
55
|
+
readonly emissionId: string;
|
|
56
|
+
/** Attempt number, from 1. */
|
|
57
|
+
readonly attempt: number;
|
|
58
|
+
readonly emittedAt: Date;
|
|
59
|
+
/** Metadata of the emission (request ID…). */
|
|
60
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
61
|
+
readonly logger: EventsLogger;
|
|
62
|
+
}
|
|
63
|
+
/** Function of a listener. Throw to have the message retried. */
|
|
64
|
+
type ListenerHandler<TPayload = unknown> = (context: ListenerContext<TPayload>) => unknown;
|
|
65
|
+
/** Module loaded by `load()`: the handler is its default export. */
|
|
66
|
+
interface ListenerModule<TPayload = unknown> {
|
|
67
|
+
default: ListenerHandler<TPayload>;
|
|
68
|
+
}
|
|
69
|
+
/** Options of `defineListener()`. */
|
|
70
|
+
type ListenerOptions<TPayload> = {
|
|
71
|
+
/**
|
|
72
|
+
* Unique name: letters, digits, `.`, `_`, `:` and `-`. It identifies the
|
|
73
|
+
* listener in messages: renaming it orphans messages still in queues.
|
|
74
|
+
*/
|
|
75
|
+
name: string;
|
|
76
|
+
/** Transport of this listener; defaults to the one of the event. */
|
|
77
|
+
transport?: TransportName | undefined;
|
|
78
|
+
} & ({
|
|
79
|
+
handle: ListenerHandler<TPayload>;
|
|
80
|
+
load?: never;
|
|
81
|
+
} | {
|
|
82
|
+
/**
|
|
83
|
+
* Loads the module of the handler on first use, so that the code
|
|
84
|
+
* emitting events does not load the code of the listeners:
|
|
85
|
+
* `load: () => import('./listeners/send-welcome-mail.js')`.
|
|
86
|
+
*/
|
|
87
|
+
load: () => Promise<ListenerModule<TPayload>>;
|
|
88
|
+
handle?: never;
|
|
89
|
+
});
|
|
90
|
+
/** Listener created by `defineListener()`. */
|
|
91
|
+
interface ListenerDefinition<S extends z.ZodType = z.ZodType> {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly event: EventDefinition<S>;
|
|
94
|
+
/** Transport of this listener, if it differs from the one of its event. */
|
|
95
|
+
readonly transport: TransportName | undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Defines an event: a fact that listeners react to, with the schema of its
|
|
99
|
+
* payload.
|
|
100
|
+
*
|
|
101
|
+
* ```ts
|
|
102
|
+
* export const userCreated = defineEvent({
|
|
103
|
+
* name: 'user.created',
|
|
104
|
+
* schema: z.object({ userId: z.string() }),
|
|
105
|
+
* })
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare const defineEvent: <S extends z.ZodType>(options: {
|
|
109
|
+
/** Unique name: letters, digits, `.`, `_`, `:` and `-`. */
|
|
110
|
+
name: string;
|
|
111
|
+
/**
|
|
112
|
+
* Schema of the payload. Payloads travel as JSON: describe JSON values
|
|
113
|
+
* (dates as strings, or coerced on the way in).
|
|
114
|
+
*/
|
|
115
|
+
schema: S;
|
|
116
|
+
/** Transport of the listeners. Defaults to `default`. */
|
|
117
|
+
transport?: TransportName | undefined;
|
|
118
|
+
}) => EventDefinition<S>;
|
|
119
|
+
/**
|
|
120
|
+
* Defines a listener of an event. Its handler runs in a worker, once per
|
|
121
|
+
* emission, and is retried when it throws.
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* export const sendWelcomeMail = defineListener(userCreated, {
|
|
125
|
+
* name: 'send-welcome-mail',
|
|
126
|
+
* load: () => import('./listeners/send-welcome-mail.js'),
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare const defineListener: <S extends z.ZodType>(event: EventDefinition<S>, options: ListenerOptions<z.output<S>>) => ListenerDefinition<S>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Message sent to a transport: one per listener of an emission. It travels
|
|
134
|
+
* as JSON.
|
|
135
|
+
*/
|
|
136
|
+
interface Envelope {
|
|
137
|
+
/** Version of the format. */
|
|
138
|
+
readonly v: 1;
|
|
139
|
+
/** Identifier of the message, stable across retries and emissions. */
|
|
140
|
+
readonly id: string;
|
|
141
|
+
/** Identifier of the emission, shared by its messages. */
|
|
142
|
+
readonly emissionId: string;
|
|
143
|
+
readonly event: string;
|
|
144
|
+
readonly listener: string;
|
|
145
|
+
/** Payload, as JSON. */
|
|
146
|
+
readonly payload: unknown;
|
|
147
|
+
/** ISO date of the emission. */
|
|
148
|
+
readonly emittedAt: string;
|
|
149
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Envelope of a received message (an object, or its JSON), or a reason
|
|
153
|
+
* why it is not one.
|
|
154
|
+
*/
|
|
155
|
+
declare const parseEnvelope: (message: unknown) => {
|
|
156
|
+
envelope: Envelope;
|
|
157
|
+
} | {
|
|
158
|
+
problem: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/** Why a message can never be handled: retrying would not help. */
|
|
162
|
+
type DeadReason =
|
|
163
|
+
/** Not an envelope, or an envelope of another version. */
|
|
164
|
+
'invalid-envelope'
|
|
165
|
+
/** No listener of this name in this worker (not deployed here?). */
|
|
166
|
+
| 'unknown-listener'
|
|
167
|
+
/** The payload does not match the schema of the event. */
|
|
168
|
+
| 'invalid-payload';
|
|
169
|
+
/** Result of `worker.handle()`, translated by each transport. */
|
|
170
|
+
type HandleResult = {
|
|
171
|
+
readonly status: 'success';
|
|
172
|
+
}
|
|
173
|
+
/** The listener failed: retry the message. */
|
|
174
|
+
| {
|
|
175
|
+
readonly status: 'retry';
|
|
176
|
+
readonly error: unknown;
|
|
177
|
+
}
|
|
178
|
+
/** The message can never be handled: do not retry it. */
|
|
179
|
+
| {
|
|
180
|
+
readonly status: 'dead';
|
|
181
|
+
readonly reason: DeadReason;
|
|
182
|
+
readonly error: unknown;
|
|
183
|
+
};
|
|
184
|
+
/** Details of a failure, for `onFailure`. */
|
|
185
|
+
interface FailureInfo {
|
|
186
|
+
/** Envelope of the message; undefined when it is not an envelope. */
|
|
187
|
+
readonly envelope: Envelope | undefined;
|
|
188
|
+
readonly reason: 'listener-error' | DeadReason;
|
|
189
|
+
readonly attempt: number;
|
|
190
|
+
/**
|
|
191
|
+
* Whether the message will not be retried: a dead message, or the last
|
|
192
|
+
* attempt (with `maxAttempts`).
|
|
193
|
+
*/
|
|
194
|
+
readonly final: boolean;
|
|
195
|
+
}
|
|
196
|
+
/** Called for every failure: logs, alerts… */
|
|
197
|
+
type FailureHook = (error: unknown, info: FailureInfo) => void | Promise<void>;
|
|
198
|
+
/** Options of `createWorker()`. */
|
|
199
|
+
interface WorkerOptions {
|
|
200
|
+
/** Listeners run by this worker. */
|
|
201
|
+
listeners: readonly ListenerDefinition[];
|
|
202
|
+
logger?: EventsLogger | undefined;
|
|
203
|
+
/** Called for every failure. Its own errors are logged and ignored. */
|
|
204
|
+
onFailure?: FailureHook | undefined;
|
|
205
|
+
/**
|
|
206
|
+
* Attempts before the transport gives up (its retry limit, like the
|
|
207
|
+
* `maxReceiveCount` of an SQS queue), to report the last one as `final`.
|
|
208
|
+
*/
|
|
209
|
+
maxAttempts?: number | undefined;
|
|
210
|
+
}
|
|
211
|
+
/** Runs the listeners of received messages. */
|
|
212
|
+
interface Worker {
|
|
213
|
+
/** Names of its listeners. */
|
|
214
|
+
readonly listeners: readonly string[];
|
|
215
|
+
/**
|
|
216
|
+
* Handles a received message (an envelope, or its JSON). Never throws:
|
|
217
|
+
* the result tells the transport what to do with the message.
|
|
218
|
+
*/
|
|
219
|
+
handle(message: unknown, options?: {
|
|
220
|
+
attempt?: number | undefined;
|
|
221
|
+
}): Promise<HandleResult>;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Creates a worker: it runs the listeners of the messages it receives from
|
|
225
|
+
* a transport (Lambda handler, queue consumer, `startWorker()`…).
|
|
226
|
+
*
|
|
227
|
+
* ```ts
|
|
228
|
+
* const worker = createWorker({ listeners: [sendWelcomeMail, geocodeUser] })
|
|
229
|
+
* export const handler = createSqsHandler(worker)
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
declare const createWorker: (options: WorkerOptions) => Worker;
|
|
233
|
+
|
|
234
|
+
/** Options of `transport.send()`. */
|
|
235
|
+
interface SendOptions {
|
|
236
|
+
/** Delay before delivery, in seconds. */
|
|
237
|
+
readonly delay: number | undefined;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Sends messages to a queue. Implemented by each transport (SQS, BullMQ,
|
|
241
|
+
* Cloudflare Queues…); throw a `PartialSendError` when only some messages
|
|
242
|
+
* failed.
|
|
243
|
+
*/
|
|
244
|
+
interface Transport {
|
|
245
|
+
/** Kind of transport, for logs: `sqs`, `memory`… */
|
|
246
|
+
readonly kind: string;
|
|
247
|
+
send(envelopes: readonly Envelope[], options: SendOptions): Promise<void>;
|
|
248
|
+
/**
|
|
249
|
+
* In-process transports only (`sync`, `memory`): receives the worker
|
|
250
|
+
* that runs the listeners routed to this transport.
|
|
251
|
+
*/
|
|
252
|
+
bind?(worker: Worker): void;
|
|
253
|
+
/** Attempts of an in-process transport, for `FailureInfo.final`. */
|
|
254
|
+
readonly maxAttempts?: number;
|
|
255
|
+
}
|
|
256
|
+
/** Options of `source.consume()`. */
|
|
257
|
+
interface ConsumeOptions {
|
|
258
|
+
/** Aborted to stop: finish the messages in progress, then resolve. */
|
|
259
|
+
readonly signal: AbortSignal;
|
|
260
|
+
readonly logger: EventsLogger;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Reads messages from a queue and hands them to a worker, until stopped:
|
|
264
|
+
* the long-running side of a transport (SQS polling, BullMQ worker…).
|
|
265
|
+
*/
|
|
266
|
+
interface Source {
|
|
267
|
+
/** Kind of source, for logs. */
|
|
268
|
+
readonly kind: string;
|
|
269
|
+
consume(worker: Worker, options: ConsumeOptions): Promise<void>;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { createWorker as n, defineEvent as o, defineListener as p, parseEnvelope as q };
|
|
273
|
+
export type { ConsumeOptions as C, DeadReason as D, EventsLogger as E, FailureHook as F, HandleResult as H, ListenerContext as L, Source as S, Transport as T, Worker as W, Envelope as a, EventDefinition as b, EventInput as c, EventPayload as d, EventTransports as e, FailureInfo as f, ListenerDefinition as g, ListenerHandler as h, ListenerModule as i, ListenerOptions as j, SendOptions as k, TransportName as l, WorkerOptions as m };
|
package/package.json
CHANGED
|
@@ -1,6 +1,142 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotpress/events",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "2.0.0-alpha.0",
|
|
4
|
+
"description": "Typed events with listeners, delivered by interchangeable transports (SQS, Redis, Cloudflare Queues, Postgres, Pub/Sub, Service Bus)",
|
|
5
|
+
"author": "Renaud Guyon",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://dotpress.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dotpressjs/dotpress.git",
|
|
11
|
+
"directory": "packages/events"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/dotpressjs/dotpress/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"dotpress-events": "./dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./plugin": {
|
|
32
|
+
"types": "./dist/plugin.d.ts",
|
|
33
|
+
"default": "./dist/plugin.js"
|
|
34
|
+
},
|
|
35
|
+
"./aws-sqs": {
|
|
36
|
+
"types": "./dist/aws-sqs.d.ts",
|
|
37
|
+
"default": "./dist/aws-sqs.js"
|
|
38
|
+
},
|
|
39
|
+
"./bullmq": {
|
|
40
|
+
"types": "./dist/bullmq.d.ts",
|
|
41
|
+
"default": "./dist/bullmq.js"
|
|
42
|
+
},
|
|
43
|
+
"./cloudflare-queues": {
|
|
44
|
+
"types": "./dist/cloudflare-queues.d.ts",
|
|
45
|
+
"default": "./dist/cloudflare-queues.js"
|
|
46
|
+
},
|
|
47
|
+
"./pg-boss": {
|
|
48
|
+
"types": "./dist/pg-boss.d.ts",
|
|
49
|
+
"default": "./dist/pg-boss.js"
|
|
50
|
+
},
|
|
51
|
+
"./google-pubsub": {
|
|
52
|
+
"types": "./dist/google-pubsub.d.ts",
|
|
53
|
+
"default": "./dist/google-pubsub.js"
|
|
54
|
+
},
|
|
55
|
+
"./azure-service-bus": {
|
|
56
|
+
"types": "./dist/azure-service-bus.d.ts",
|
|
57
|
+
"default": "./dist/azure-service-bus.js"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=22.12"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@aws-sdk/client-sqs": "^3.572.0",
|
|
65
|
+
"@azure/service-bus": "^7.9.0",
|
|
66
|
+
"@dotpress/core": "^2.0.0-alpha.0",
|
|
67
|
+
"@google-cloud/pubsub": "^6.0.0",
|
|
68
|
+
"bullmq": "^6.0.0",
|
|
69
|
+
"google-auth-library": "^10.0.0 || ^11.0.0",
|
|
70
|
+
"jose": "^6.0.0",
|
|
71
|
+
"pg-boss": "^12.21.0",
|
|
72
|
+
"zod": "^4.6.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"@aws-sdk/client-sqs": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@azure/service-bus": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@dotpress/core": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@google-cloud/pubsub": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"bullmq": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"google-auth-library": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"jose": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"pg-boss": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"devDependencies": {
|
|
101
|
+
"@aws-sdk/client-sqs": "^3.1164.0",
|
|
102
|
+
"@azure/service-bus": "~7.10.0",
|
|
103
|
+
"@dotpress/core": "2.0.0-alpha.0",
|
|
104
|
+
"@dotpress/testing": "2.0.0-alpha.0",
|
|
105
|
+
"@google-cloud/pubsub": "~6.3.2",
|
|
106
|
+
"bullmq": "^6.7.2",
|
|
107
|
+
"google-auth-library": "~11.3.4",
|
|
108
|
+
"ioredis": "^5.11.1",
|
|
109
|
+
"jose": "~6.2.15",
|
|
110
|
+
"pg-boss": "~12.38.1",
|
|
111
|
+
"zod": "^4.6.7"
|
|
112
|
+
},
|
|
113
|
+
"keywords": [
|
|
114
|
+
"dotpress",
|
|
115
|
+
"events",
|
|
116
|
+
"listeners",
|
|
117
|
+
"queue",
|
|
118
|
+
"worker",
|
|
119
|
+
"plugin",
|
|
120
|
+
"typescript",
|
|
121
|
+
"sqs",
|
|
122
|
+
"aws",
|
|
123
|
+
"lambda",
|
|
124
|
+
"bullmq",
|
|
125
|
+
"redis",
|
|
126
|
+
"cloudflare",
|
|
127
|
+
"workers",
|
|
128
|
+
"postgres",
|
|
129
|
+
"pg-boss",
|
|
130
|
+
"pubsub",
|
|
131
|
+
"gcp",
|
|
132
|
+
"azure",
|
|
133
|
+
"service-bus"
|
|
134
|
+
],
|
|
135
|
+
"scripts": {
|
|
136
|
+
"build": "tsup",
|
|
137
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.cli.json && tsc --noEmit -p tsconfig.test.json",
|
|
138
|
+
"lint": "eslint src test",
|
|
139
|
+
"test": "vitest run",
|
|
140
|
+
"test:watch": "vitest watch"
|
|
141
|
+
}
|
|
6
142
|
}
|