@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
package/dist/bullmq.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkLogger
|
|
3
|
+
} from "./chunk-E4NMMZMY.js";
|
|
4
|
+
|
|
5
|
+
// src/transports/bullmq.ts
|
|
6
|
+
import {
|
|
7
|
+
Queue,
|
|
8
|
+
UnrecoverableError,
|
|
9
|
+
Worker as BullWorker
|
|
10
|
+
} from "bullmq";
|
|
11
|
+
var DEFAULT_JOB_OPTIONS = {
|
|
12
|
+
attempts: 3,
|
|
13
|
+
backoff: { type: "exponential", delay: 1e3 },
|
|
14
|
+
removeOnComplete: true,
|
|
15
|
+
removeOnFail: false
|
|
16
|
+
};
|
|
17
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
var checkOptions = (label, options, allowed) => {
|
|
19
|
+
if (!isRecord(options)) {
|
|
20
|
+
throw new TypeError(`${label}: expected an object of options.`);
|
|
21
|
+
}
|
|
22
|
+
for (const key of Object.keys(options)) {
|
|
23
|
+
if (!allowed.includes(key)) {
|
|
24
|
+
throw new TypeError(`${label}: unknown option "${key}".`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return options;
|
|
28
|
+
};
|
|
29
|
+
var checkQueue = (label, values) => {
|
|
30
|
+
if (typeof values.name !== "string" || values.name === "") {
|
|
31
|
+
throw new TypeError(`${label}: name must be a non-empty string.`);
|
|
32
|
+
}
|
|
33
|
+
if (typeof values.connection !== "object" || values.connection === null) {
|
|
34
|
+
throw new TypeError(
|
|
35
|
+
`${label}: connection must be Redis connection options or an ioredis instance.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (values.prefix !== void 0 && (typeof values.prefix !== "string" || values.prefix === "")) {
|
|
39
|
+
throw new TypeError(`${label}: prefix must be a non-empty string.`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var createBullmqTransport = (options) => {
|
|
43
|
+
const label = "createBullmqTransport()";
|
|
44
|
+
const values = checkOptions(label, options, [
|
|
45
|
+
"name",
|
|
46
|
+
"connection",
|
|
47
|
+
"prefix",
|
|
48
|
+
"queue",
|
|
49
|
+
"jobOptions"
|
|
50
|
+
]);
|
|
51
|
+
let queue;
|
|
52
|
+
if (values.queue !== void 0) {
|
|
53
|
+
if (values.name !== void 0 || values.connection !== void 0 || values.prefix !== void 0) {
|
|
54
|
+
throw new TypeError(
|
|
55
|
+
`${label}: set queue, or name and connection, not both.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (typeof values.queue?.addBulk !== "function") {
|
|
59
|
+
throw new TypeError(`${label}: queue must be a BullMQ queue.`);
|
|
60
|
+
}
|
|
61
|
+
queue = values.queue;
|
|
62
|
+
} else {
|
|
63
|
+
checkQueue(label, values);
|
|
64
|
+
queue = new Queue(values.name, {
|
|
65
|
+
connection: values.connection,
|
|
66
|
+
...values.prefix === void 0 ? {} : { prefix: values.prefix }
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (values.jobOptions !== void 0 && !isRecord(values.jobOptions)) {
|
|
70
|
+
throw new TypeError(`${label}: jobOptions must be an object.`);
|
|
71
|
+
}
|
|
72
|
+
const jobOptions = { ...DEFAULT_JOB_OPTIONS, ...values.jobOptions ?? {} };
|
|
73
|
+
return {
|
|
74
|
+
kind: "bullmq",
|
|
75
|
+
send: async (envelopes, { delay }) => {
|
|
76
|
+
await queue.addBulk(
|
|
77
|
+
envelopes.map((envelope) => ({
|
|
78
|
+
name: envelope.listener,
|
|
79
|
+
data: envelope,
|
|
80
|
+
opts: {
|
|
81
|
+
...jobOptions,
|
|
82
|
+
// Duplicates of an existing job are ignored.
|
|
83
|
+
jobId: envelope.id,
|
|
84
|
+
...delay ? { delay: delay * 1e3 } : {}
|
|
85
|
+
}
|
|
86
|
+
}))
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
close: () => queue.close()
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
var toError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
93
|
+
var processorOf = (worker) => async (job) => {
|
|
94
|
+
const result = await worker.handle(job.data, {
|
|
95
|
+
attempt: job.attemptsMade + 1
|
|
96
|
+
});
|
|
97
|
+
if (result.status === "retry") throw toError(result.error);
|
|
98
|
+
if (result.status === "dead") {
|
|
99
|
+
throw new UnrecoverableError(
|
|
100
|
+
`${result.reason}: ${toError(result.error).message}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var createBullmqSource = (options) => {
|
|
105
|
+
const label = "createBullmqSource()";
|
|
106
|
+
const values = checkOptions(label, options, [
|
|
107
|
+
"name",
|
|
108
|
+
"connection",
|
|
109
|
+
"prefix",
|
|
110
|
+
"concurrency",
|
|
111
|
+
"closeTimeout"
|
|
112
|
+
]);
|
|
113
|
+
checkQueue(label, values);
|
|
114
|
+
const { concurrency = 1, closeTimeout = 3e4 } = values;
|
|
115
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
116
|
+
throw new TypeError(`${label}: concurrency must be a positive integer.`);
|
|
117
|
+
}
|
|
118
|
+
if (!Number.isInteger(closeTimeout) || closeTimeout < 0) {
|
|
119
|
+
throw new TypeError(
|
|
120
|
+
`${label}: closeTimeout must be a non-negative integer (milliseconds).`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
kind: "bullmq",
|
|
125
|
+
consume: async (worker, { signal, logger }) => {
|
|
126
|
+
checkLogger(label, logger);
|
|
127
|
+
const bullWorker = new BullWorker(
|
|
128
|
+
values.name,
|
|
129
|
+
processorOf(worker),
|
|
130
|
+
{
|
|
131
|
+
connection: values.connection,
|
|
132
|
+
concurrency,
|
|
133
|
+
...values.prefix === void 0 ? {} : { prefix: values.prefix }
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
bullWorker.on("error", (error) => {
|
|
137
|
+
logger.error(
|
|
138
|
+
{ queue: values.name, error: error.message },
|
|
139
|
+
"BullMQ worker error"
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
await new Promise((resolve) => {
|
|
143
|
+
if (signal.aborted) resolve();
|
|
144
|
+
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
145
|
+
});
|
|
146
|
+
let timer;
|
|
147
|
+
const closed = await Promise.race([
|
|
148
|
+
bullWorker.close().then(() => true),
|
|
149
|
+
new Promise((resolve) => {
|
|
150
|
+
timer = setTimeout(() => resolve(false), closeTimeout);
|
|
151
|
+
})
|
|
152
|
+
]);
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
if (!closed) {
|
|
155
|
+
logger.warn(
|
|
156
|
+
{ queue: values.name, closeTimeout },
|
|
157
|
+
"BullMQ worker not closed in time: stopped without waiting"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
export {
|
|
164
|
+
DEFAULT_JOB_OPTIONS,
|
|
165
|
+
createBullmqSource,
|
|
166
|
+
createBullmqTransport,
|
|
167
|
+
processorOf
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=bullmq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transports/bullmq.ts"],"sourcesContent":["import {\n Queue,\n UnrecoverableError,\n Worker as BullWorker,\n type ConnectionOptions,\n type Job,\n type JobsOptions,\n} from 'bullmq'\nimport type { Envelope } from '../envelope.js'\nimport { checkLogger } from '../logger.js'\nimport type { Source, Transport } from '../transport.js'\nimport type { Worker } from '../worker.js'\n\n/** A BullMQ queue, or any object with the same methods. */\nexport interface BullmqQueue {\n addBulk(\n jobs: { name: string; data: unknown; opts?: JobsOptions }[]\n ): Promise<unknown>\n close(): Promise<void>\n}\n\n/** Options of `createBullmqTransport()`. */\nexport type BullmqTransportOptions = {\n /**\n * Options of the jobs, merged with the defaults: 3 attempts, exponential\n * backoff from 1 second, completed jobs removed, failed jobs kept.\n */\n jobOptions?: Omit<JobsOptions, 'jobId' | 'delay'> | undefined\n} & (\n | {\n /** Name of the queue. */\n name: string\n /** Redis connection: ioredis options or instance. */\n connection: ConnectionOptions\n /** Prefix of the Redis keys (instead of the ioredis `keyPrefix`). */\n prefix?: string | undefined\n queue?: never\n }\n | {\n /** Existing queue (another backend, shared connection…). */\n queue: BullmqQueue\n name?: never\n connection?: never\n prefix?: never\n }\n)\n\n/** BullMQ transport: close it to release its Redis connection. */\nexport interface BullmqTransport extends Transport {\n close(): Promise<void>\n}\n\n/** Options of `createBullmqSource()`. */\nexport interface BullmqSourceOptions {\n /** Name of the queue. */\n name: string\n /** Redis connection: ioredis options or instance. */\n connection: ConnectionOptions\n /** Prefix of the Redis keys, as in the transport. */\n prefix?: string | undefined\n /** Messages handled at the same time. Defaults to 1. */\n concurrency?: number | undefined\n /**\n * On stop, how long to wait for the jobs in progress, in milliseconds,\n * before stopping anyway (their jobs become stalled and are retried by\n * another worker). Defaults to 30 seconds. Also bounds the stop when\n * Redis is unreachable.\n */\n closeTimeout?: number | undefined\n}\n\nexport const DEFAULT_JOB_OPTIONS: JobsOptions = {\n attempts: 3,\n backoff: { type: 'exponential', delay: 1000 },\n removeOnComplete: true,\n removeOnFail: false,\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst checkOptions = (\n label: string,\n options: unknown,\n allowed: readonly string[]\n): Record<string, unknown> => {\n if (!isRecord(options)) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.includes(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n return options\n}\n\nconst checkQueue = (label: string, values: Record<string, unknown>) => {\n if (typeof values.name !== 'string' || values.name === '') {\n throw new TypeError(`${label}: name must be a non-empty string.`)\n }\n if (typeof values.connection !== 'object' || values.connection === null) {\n throw new TypeError(\n `${label}: connection must be Redis connection options or an ioredis instance.`\n )\n }\n if (\n values.prefix !== undefined &&\n (typeof values.prefix !== 'string' || values.prefix === '')\n ) {\n throw new TypeError(`${label}: prefix must be a non-empty string.`)\n }\n}\n\n/**\n * Creates a transport that adds messages as jobs to a BullMQ queue\n * (`bullmq`, peer dependency; Redis by default). The job name is the\n * listener, its id the message id: emitting again adds no duplicate.\n *\n * ```ts\n * const transport = createBullmqTransport({\n * name: 'events',\n * connection: { host: 'localhost', port: 6379 },\n * })\n * ```\n */\nexport const createBullmqTransport = (\n options: BullmqTransportOptions\n): BullmqTransport => {\n const label = 'createBullmqTransport()'\n const values = checkOptions(label, options, [\n 'name',\n 'connection',\n 'prefix',\n 'queue',\n 'jobOptions',\n ])\n let queue: BullmqQueue\n if (values.queue !== undefined) {\n if (\n values.name !== undefined ||\n values.connection !== undefined ||\n values.prefix !== undefined\n ) {\n throw new TypeError(\n `${label}: set queue, or name and connection, not both.`\n )\n }\n if (typeof (values.queue as BullmqQueue | null)?.addBulk !== 'function') {\n throw new TypeError(`${label}: queue must be a BullMQ queue.`)\n }\n queue = values.queue as BullmqQueue\n } else {\n checkQueue(label, values)\n queue = new Queue(values.name as string, {\n connection: values.connection as ConnectionOptions,\n ...(values.prefix === undefined\n ? {}\n : { prefix: values.prefix as string }),\n })\n }\n if (values.jobOptions !== undefined && !isRecord(values.jobOptions)) {\n throw new TypeError(`${label}: jobOptions must be an object.`)\n }\n const jobOptions = { ...DEFAULT_JOB_OPTIONS, ...(values.jobOptions ?? {}) }\n\n return {\n kind: 'bullmq',\n send: async (envelopes, { delay }) => {\n await queue.addBulk(\n envelopes.map((envelope) => ({\n name: envelope.listener,\n data: envelope,\n opts: {\n ...jobOptions,\n // Duplicates of an existing job are ignored.\n jobId: envelope.id,\n ...(delay ? { delay: delay * 1000 } : {}),\n },\n }))\n )\n },\n close: () => queue.close(),\n }\n}\n\nconst toError = (error: unknown) =>\n error instanceof Error ? error : new Error(String(error))\n\n/**\n * Processor of BullMQ jobs: a failed listener throws (retried within the\n * `attempts` of the job), a message that can never be handled throws an\n * `UnrecoverableError` (failed at once).\n */\nexport const processorOf =\n (worker: Worker) =>\n async (job: Pick<Job<Envelope>, 'data' | 'attemptsMade'>) => {\n const result = await worker.handle(job.data, {\n attempt: job.attemptsMade + 1,\n })\n if (result.status === 'retry') throw toError(result.error)\n if (result.status === 'dead') {\n throw new UnrecoverableError(\n `${result.reason}: ${toError(result.error).message}`\n )\n }\n }\n\n/**\n * Creates a source that runs a BullMQ worker on the queue, for\n * `startWorker()` and `dotpress-events work` (Node.js and Bun).\n *\n * ```ts\n * export default {\n * worker: createWorker({ listeners, maxAttempts: 3 }),\n * source: createBullmqSource({ name: 'events', connection, concurrency: 5 }),\n * }\n * ```\n */\nexport const createBullmqSource = (options: BullmqSourceOptions): Source => {\n const label = 'createBullmqSource()'\n const values = checkOptions(label, options, [\n 'name',\n 'connection',\n 'prefix',\n 'concurrency',\n 'closeTimeout',\n ])\n checkQueue(label, values)\n const { concurrency = 1, closeTimeout = 30_000 } = values\n if (!Number.isInteger(concurrency) || (concurrency as number) < 1) {\n throw new TypeError(`${label}: concurrency must be a positive integer.`)\n }\n if (!Number.isInteger(closeTimeout) || (closeTimeout as number) < 0) {\n throw new TypeError(\n `${label}: closeTimeout must be a non-negative integer (milliseconds).`\n )\n }\n\n return {\n kind: 'bullmq',\n consume: async (worker, { signal, logger }) => {\n checkLogger(label, logger)\n const bullWorker = new BullWorker(\n values.name as string,\n processorOf(worker),\n {\n connection: values.connection as ConnectionOptions,\n concurrency: concurrency as number,\n ...(values.prefix === undefined\n ? {}\n : { prefix: values.prefix as string }),\n }\n )\n // Without a listener, an error may stop the worker.\n bullWorker.on('error', (error) => {\n logger.error(\n { queue: values.name, error: error.message },\n 'BullMQ worker error'\n )\n })\n await new Promise<void>((resolve) => {\n if (signal.aborted) resolve()\n signal.addEventListener('abort', () => resolve(), { once: true })\n })\n // Waits for the jobs in progress, within closeTimeout: BullMQ never\n // closes gracefully while Redis is unreachable.\n let timer: ReturnType<typeof setTimeout> | undefined\n const closed = await Promise.race([\n bullWorker.close().then(() => true),\n new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), closeTimeout as number)\n }),\n ])\n clearTimeout(timer)\n if (!closed) {\n // close() never resolves while Redis is unreachable, and a forced\n // close() returns the same pending promise: give up waiting.\n // BullMQ may keep reconnecting until the process exits.\n logger.warn(\n { queue: values.name, closeTimeout },\n 'BullMQ worker not closed in time: stopped without waiting'\n )\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { b as EventDefinition, c as EventInput, E as EventsLogger, g as ListenerDefinition, F as FailureHook, T as Transport, l as TransportName } from './transport-BtNR1ucF.js';
|
|
2
|
+
|
|
3
|
+
/** Base class of the errors thrown by `@dotpress/events`. */
|
|
4
|
+
declare class EventsError extends Error {
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
/** Problem found in the payload of an event. */
|
|
8
|
+
interface EventPayloadIssue {
|
|
9
|
+
/** Path of the invalid value, empty for the payload itself. */
|
|
10
|
+
readonly path: readonly (string | number)[];
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}
|
|
13
|
+
/** The payload passed to `emit()` does not match the schema of the event. */
|
|
14
|
+
declare class EventPayloadError extends EventsError {
|
|
15
|
+
/** Name of the event. */
|
|
16
|
+
readonly event: string;
|
|
17
|
+
readonly issues: readonly EventPayloadIssue[];
|
|
18
|
+
name: string;
|
|
19
|
+
constructor(
|
|
20
|
+
/** Name of the event. */
|
|
21
|
+
event: string, issues: readonly EventPayloadIssue[]);
|
|
22
|
+
}
|
|
23
|
+
/** A message sent by `emit()`: one per listener. */
|
|
24
|
+
interface MessageRef {
|
|
25
|
+
/** Identifier of the message, stable across retries. */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly listener: string;
|
|
28
|
+
/** Name of the transport (`default`, `edge`…). */
|
|
29
|
+
readonly transport: string;
|
|
30
|
+
}
|
|
31
|
+
/** A message that `emit()` could not send. */
|
|
32
|
+
interface FailedMessage extends MessageRef {
|
|
33
|
+
readonly error: unknown;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Some messages of an emission could not be sent. `sent` lists the
|
|
37
|
+
* others: emitting again gives the same message ids, so listeners can
|
|
38
|
+
* ignore duplicates.
|
|
39
|
+
*/
|
|
40
|
+
declare class EventEmitError extends EventsError {
|
|
41
|
+
/** Name of the event. */
|
|
42
|
+
readonly event: string;
|
|
43
|
+
readonly sent: readonly MessageRef[];
|
|
44
|
+
readonly failed: readonly FailedMessage[];
|
|
45
|
+
name: string;
|
|
46
|
+
constructor(
|
|
47
|
+
/** Name of the event. */
|
|
48
|
+
event: string, sent: readonly MessageRef[], failed: readonly FailedMessage[]);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Thrown by a transport when some messages of a `send()` call failed;
|
|
52
|
+
* other errors mean that every message failed.
|
|
53
|
+
*/
|
|
54
|
+
declare class PartialSendError extends EventsError {
|
|
55
|
+
/** Failed messages, by id. */
|
|
56
|
+
readonly failed: readonly {
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly error: unknown;
|
|
59
|
+
}[];
|
|
60
|
+
name: string;
|
|
61
|
+
constructor(
|
|
62
|
+
/** Failed messages, by id. */
|
|
63
|
+
failed: readonly {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly error: unknown;
|
|
66
|
+
}[]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Options of `createEventBus()`. */
|
|
70
|
+
type EventBusOptions = {
|
|
71
|
+
/**
|
|
72
|
+
* Every listener of the app, with their routes: the bus sends one
|
|
73
|
+
* message per listener of an emitted event. Workers only need the
|
|
74
|
+
* listeners they run.
|
|
75
|
+
*/
|
|
76
|
+
listeners: readonly ListenerDefinition[];
|
|
77
|
+
logger?: EventsLogger | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Failures of the listeners run by in-process transports (`sync`,
|
|
80
|
+
* `memory`). Workers have their own.
|
|
81
|
+
*/
|
|
82
|
+
onFailure?: FailureHook | undefined;
|
|
83
|
+
} & ({
|
|
84
|
+
/** The transport of every event. */
|
|
85
|
+
transport: Transport;
|
|
86
|
+
transports?: never;
|
|
87
|
+
} | {
|
|
88
|
+
/** Transports by name; events without a `transport` use `default`. */
|
|
89
|
+
transports: {
|
|
90
|
+
readonly [K in TransportName]?: Transport;
|
|
91
|
+
};
|
|
92
|
+
transport?: never;
|
|
93
|
+
});
|
|
94
|
+
/** Options of `emit()`. */
|
|
95
|
+
interface EmitOptions {
|
|
96
|
+
/** Delay before delivery, in seconds, within the limit of the transport. */
|
|
97
|
+
delay?: number | undefined;
|
|
98
|
+
/** Metadata of the emission, passed to the listeners. */
|
|
99
|
+
metadata?: Readonly<Record<string, string>> | undefined;
|
|
100
|
+
}
|
|
101
|
+
/** Result of `emit()`. */
|
|
102
|
+
interface EmitResult {
|
|
103
|
+
readonly emissionId: string;
|
|
104
|
+
/** One message per listener of the event. */
|
|
105
|
+
readonly messages: readonly MessageRef[];
|
|
106
|
+
}
|
|
107
|
+
/** Context of a bus: the logger and metadata of a request, for instance. */
|
|
108
|
+
interface EventBusContext {
|
|
109
|
+
logger?: EventsLogger | undefined;
|
|
110
|
+
/** Added to the metadata of every emission. */
|
|
111
|
+
metadata?: Readonly<Record<string, string>> | undefined;
|
|
112
|
+
}
|
|
113
|
+
interface EventBus {
|
|
114
|
+
/**
|
|
115
|
+
* Emits an event: validates its payload, then sends one message per
|
|
116
|
+
* listener to the transport of the listener.
|
|
117
|
+
*/
|
|
118
|
+
emit<E extends EventDefinition>(event: E, payload: EventInput<E>, options?: EmitOptions): Promise<EmitResult>;
|
|
119
|
+
/** The same bus, with another logger and additional metadata. */
|
|
120
|
+
withContext(context: EventBusContext): EventBus;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Creates an event bus: it validates the emitted events and sends one
|
|
124
|
+
* message per listener to its transport.
|
|
125
|
+
*
|
|
126
|
+
* ```ts
|
|
127
|
+
* const events = createEventBus({
|
|
128
|
+
* transport: createMemoryTransport(),
|
|
129
|
+
* listeners: [sendWelcomeMail, geocodeUser],
|
|
130
|
+
* })
|
|
131
|
+
* await events.emit(userCreated, { userId: '42' })
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
declare const createEventBus: (options: EventBusOptions) => EventBus;
|
|
135
|
+
|
|
136
|
+
export { PartialSendError as P, EventEmitError as e, EventPayloadError as f, EventsError as h, createEventBus as i };
|
|
137
|
+
export type { EventBus as E, FailedMessage as F, MessageRef as M, EmitOptions as a, EmitResult as b, EventBusContext as c, EventBusOptions as d, EventPayloadIssue as g };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
var noop = () => void 0;
|
|
3
|
+
var silentLogger = {
|
|
4
|
+
debug: noop,
|
|
5
|
+
info: noop,
|
|
6
|
+
warn: noop,
|
|
7
|
+
error: noop
|
|
8
|
+
};
|
|
9
|
+
var checkLogger = (label, logger) => {
|
|
10
|
+
for (const level of ["info", "warn", "error"]) {
|
|
11
|
+
if (typeof logger?.[level] !== "function") {
|
|
12
|
+
throw new TypeError(
|
|
13
|
+
`${label}: logger must have info(), warn() and error() methods.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return logger;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
silentLogger,
|
|
22
|
+
checkLogger
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=chunk-E4NMMZMY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts"],"sourcesContent":["/**\n * Logger of the bus and the workers. A Dotpress or Pino logger fits;\n * `eventsPlugin` uses the logger of the request.\n */\nexport interface EventsLogger {\n debug?(data: object, message: string): void\n info(data: object, message: string): void\n warn(data: object, message: string): void\n error(data: object, message: string): void\n}\n\nconst noop = () => undefined\n\n/** Logger that discards everything. */\nexport const silentLogger: EventsLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n}\n\n/** Checks a logger option. */\nexport const checkLogger = (label: string, logger: unknown) => {\n for (const level of ['info', 'warn', 'error'] as const) {\n if (\n typeof (logger as Record<string, unknown> | null)?.[level] !== 'function'\n ) {\n throw new TypeError(\n `${label}: logger must have info(), warn() and error() methods.`\n )\n }\n }\n return logger as EventsLogger\n}\n"],"mappings":";AAWA,IAAM,OAAO,MAAM;AAGZ,IAAM,eAA6B;AAAA,EACxC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAGO,IAAM,cAAc,CAAC,OAAe,WAAoB;AAC7D,aAAW,SAAS,CAAC,QAAQ,QAAQ,OAAO,GAAY;AACtD,QACE,OAAQ,SAA4C,KAAK,MAAM,YAC/D;AACA,YAAM,IAAI;AAAA,QACR,GAAG,KAAK;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var EventsError = class extends Error {
|
|
3
|
+
name = "EventsError";
|
|
4
|
+
};
|
|
5
|
+
var describeIssues = (issues) => issues.map(
|
|
6
|
+
({ path, message }) => path.length > 0 ? `${path.join(".")}: ${message}` : message
|
|
7
|
+
).join("; ");
|
|
8
|
+
var EventPayloadError = class extends EventsError {
|
|
9
|
+
constructor(event, issues) {
|
|
10
|
+
super(`Invalid payload for event "${event}": ${describeIssues(issues)}`);
|
|
11
|
+
this.event = event;
|
|
12
|
+
this.issues = issues;
|
|
13
|
+
}
|
|
14
|
+
event;
|
|
15
|
+
issues;
|
|
16
|
+
name = "EventPayloadError";
|
|
17
|
+
};
|
|
18
|
+
var EventEmitError = class extends EventsError {
|
|
19
|
+
constructor(event, sent, failed) {
|
|
20
|
+
const [first] = failed;
|
|
21
|
+
const reason = first?.error instanceof Error ? first.error.message : String(first?.error);
|
|
22
|
+
super(
|
|
23
|
+
`Event "${event}" not fully emitted: ${failed.length} of ${sent.length + failed.length} messages failed (${failed.map(({ listener }) => listener).join(", ")}): ${reason}`,
|
|
24
|
+
{ cause: first?.error }
|
|
25
|
+
);
|
|
26
|
+
this.event = event;
|
|
27
|
+
this.sent = sent;
|
|
28
|
+
this.failed = failed;
|
|
29
|
+
}
|
|
30
|
+
event;
|
|
31
|
+
sent;
|
|
32
|
+
failed;
|
|
33
|
+
name = "EventEmitError";
|
|
34
|
+
};
|
|
35
|
+
var PartialSendError = class extends EventsError {
|
|
36
|
+
constructor(failed) {
|
|
37
|
+
super(`${failed.length} messages not sent.`);
|
|
38
|
+
this.failed = failed;
|
|
39
|
+
}
|
|
40
|
+
failed;
|
|
41
|
+
name = "PartialSendError";
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
EventsError,
|
|
46
|
+
EventPayloadError,
|
|
47
|
+
EventEmitError,
|
|
48
|
+
PartialSendError
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=chunk-NCQ4ZTNB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["/** Base class of the errors thrown by `@dotpress/events`. */\nexport class EventsError extends Error {\n override name = 'EventsError'\n}\n\n/** Problem found in the payload of an event. */\nexport interface EventPayloadIssue {\n /** Path of the invalid value, empty for the payload itself. */\n readonly path: readonly (string | number)[]\n readonly message: string\n}\n\nconst describeIssues = (issues: readonly EventPayloadIssue[]) =>\n issues\n .map(({ path, message }) =>\n path.length > 0 ? `${path.join('.')}: ${message}` : message\n )\n .join('; ')\n\n/** The payload passed to `emit()` does not match the schema of the event. */\nexport class EventPayloadError extends EventsError {\n override name = 'EventPayloadError'\n\n constructor(\n /** Name of the event. */\n readonly event: string,\n readonly issues: readonly EventPayloadIssue[]\n ) {\n super(`Invalid payload for event \"${event}\": ${describeIssues(issues)}`)\n }\n}\n\n/** A message sent by `emit()`: one per listener. */\nexport interface MessageRef {\n /** Identifier of the message, stable across retries. */\n readonly id: string\n readonly listener: string\n /** Name of the transport (`default`, `edge`…). */\n readonly transport: string\n}\n\n/** A message that `emit()` could not send. */\nexport interface FailedMessage extends MessageRef {\n readonly error: unknown\n}\n\n/**\n * Some messages of an emission could not be sent. `sent` lists the\n * others: emitting again gives the same message ids, so listeners can\n * ignore duplicates.\n */\nexport class EventEmitError extends EventsError {\n override name = 'EventEmitError'\n\n constructor(\n /** Name of the event. */\n readonly event: string,\n readonly sent: readonly MessageRef[],\n readonly failed: readonly FailedMessage[]\n ) {\n const [first] = failed\n const reason =\n first?.error instanceof Error ? first.error.message : String(first?.error)\n super(\n `Event \"${event}\" not fully emitted: ${failed.length} of ${sent.length + failed.length} messages failed (${failed.map(({ listener }) => listener).join(', ')}): ${reason}`,\n { cause: first?.error }\n )\n }\n}\n\n/**\n * Thrown by a transport when some messages of a `send()` call failed;\n * other errors mean that every message failed.\n */\nexport class PartialSendError extends EventsError {\n override name = 'PartialSendError'\n\n constructor(\n /** Failed messages, by id. */\n readonly failed: readonly { readonly id: string; readonly error: unknown }[]\n ) {\n super(`${failed.length} messages not sent.`)\n }\n}\n"],"mappings":";AACO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B,OAAO;AAClB;AASA,IAAM,iBAAiB,CAAC,WACtB,OACG;AAAA,EAAI,CAAC,EAAE,MAAM,QAAQ,MACpB,KAAK,SAAS,IAAI,GAAG,KAAK,KAAK,GAAG,CAAC,KAAK,OAAO,KAAK;AACtD,EACC,KAAK,IAAI;AAGP,IAAM,oBAAN,cAAgC,YAAY;AAAA,EAGjD,YAEW,OACA,QACT;AACA,UAAM,8BAA8B,KAAK,MAAM,eAAe,MAAM,CAAC,EAAE;AAH9D;AACA;AAAA,EAGX;AAAA,EAJW;AAAA,EACA;AAAA,EALF,OAAO;AASlB;AAqBO,IAAM,iBAAN,cAA6B,YAAY;AAAA,EAG9C,YAEW,OACA,MACA,QACT;AACA,UAAM,CAAC,KAAK,IAAI;AAChB,UAAM,SACJ,OAAO,iBAAiB,QAAQ,MAAM,MAAM,UAAU,OAAO,OAAO,KAAK;AAC3E;AAAA,MACE,UAAU,KAAK,wBAAwB,OAAO,MAAM,OAAO,KAAK,SAAS,OAAO,MAAM,qBAAqB,OAAO,IAAI,CAAC,EAAE,SAAS,MAAM,QAAQ,EAAE,KAAK,IAAI,CAAC,MAAM,MAAM;AAAA,MACxK,EAAE,OAAO,OAAO,MAAM;AAAA,IACxB;AAVS;AACA;AACA;AAAA,EASX;AAAA,EAXW;AAAA,EACA;AAAA,EACA;AAAA,EANF,OAAO;AAgBlB;AAMO,IAAM,mBAAN,cAA+B,YAAY;AAAA,EAGhD,YAEW,QACT;AACA,UAAM,GAAG,OAAO,MAAM,qBAAqB;AAFlC;AAAA,EAGX;AAAA,EAHW;AAAA,EAJF,OAAO;AAQlB;","names":[]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
// src/cli.ts
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
|
|
13
|
+
// src/logger.ts
|
|
14
|
+
var noop = () => void 0;
|
|
15
|
+
var silentLogger = {
|
|
16
|
+
debug: noop,
|
|
17
|
+
info: noop,
|
|
18
|
+
warn: noop,
|
|
19
|
+
error: noop
|
|
20
|
+
};
|
|
21
|
+
var checkLogger = (label, logger) => {
|
|
22
|
+
for (const level of ["info", "warn", "error"]) {
|
|
23
|
+
if (typeof logger?.[level] !== "function") {
|
|
24
|
+
throw new TypeError(
|
|
25
|
+
`${label}: logger must have info(), warn() and error() methods.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return logger;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/start-worker.ts
|
|
33
|
+
var startWorker = (worker, source, options = {}) => {
|
|
34
|
+
const label = "startWorker()";
|
|
35
|
+
if (typeof worker?.handle !== "function") {
|
|
36
|
+
throw new TypeError(
|
|
37
|
+
`${label}: expected a worker created by createWorker().`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (typeof source?.consume !== "function") {
|
|
41
|
+
throw new TypeError(`${label}: expected a source with a consume() method.`);
|
|
42
|
+
}
|
|
43
|
+
const logger = options.logger === void 0 ? silentLogger : checkLogger(label, options.logger);
|
|
44
|
+
const controller2 = new AbortController();
|
|
45
|
+
const stop = () => controller2.abort();
|
|
46
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
47
|
+
if (options.signal?.aborted) stop();
|
|
48
|
+
logger.info(
|
|
49
|
+
{ source: source.kind, listeners: worker.listeners },
|
|
50
|
+
"Event worker started"
|
|
51
|
+
);
|
|
52
|
+
const done = source.consume(worker, { signal: controller2.signal, logger }).finally(() => {
|
|
53
|
+
options.signal?.removeEventListener("abort", stop);
|
|
54
|
+
logger.info({ source: source.kind }, "Event worker stopped");
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
stop: () => {
|
|
58
|
+
stop();
|
|
59
|
+
return done;
|
|
60
|
+
},
|
|
61
|
+
done
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/cli.ts
|
|
66
|
+
var TYPESCRIPT_HINT = 'TypeScript modules require Node.js 22.18 or later, with erasable syntax and relative imports ending in .ts. Otherwise, point to the compiled module, or run the command with NODE_OPTIONS="--import tsx".';
|
|
67
|
+
var USAGE = `Usage: dotpress-events work <module> [options]
|
|
68
|
+
|
|
69
|
+
Runs a worker until stopped (Ctrl+C): reads the messages of a queue and
|
|
70
|
+
runs their listeners. <module> exports { worker, source } (and optionally
|
|
71
|
+
a logger), or a function returning it:
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
worker: createWorker({ listeners: [sendWelcomeMail] }),
|
|
75
|
+
source: createSqsSource({ queueUrl }),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Options:
|
|
79
|
+
--export <name> Export of the module (default: "default")
|
|
80
|
+
--watch Restarts the worker when a loaded file changes
|
|
81
|
+
-h, --help Shows this help
|
|
82
|
+
|
|
83
|
+
${TYPESCRIPT_HINT}
|
|
84
|
+
`;
|
|
85
|
+
var CliError = class extends Error {
|
|
86
|
+
};
|
|
87
|
+
var isEntry = (value) => typeof value?.worker?.handle === "function" && typeof value.source?.consume === "function";
|
|
88
|
+
var loadEntry = async (modulePath, exportName, cwd) => {
|
|
89
|
+
const file = resolve(cwd, modulePath);
|
|
90
|
+
if (!existsSync(file)) throw new CliError(`Module not found: ${file}`);
|
|
91
|
+
let module;
|
|
92
|
+
try {
|
|
93
|
+
module = await import(pathToFileURL(file).href);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const hint = /\.[cm]?ts$/.test(file) ? `
|
|
96
|
+
${TYPESCRIPT_HINT}` : "";
|
|
97
|
+
throw new CliError(
|
|
98
|
+
`Cannot load ${file}: ${error instanceof Error ? error.message : String(error)}${hint}`,
|
|
99
|
+
{ cause: error }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (!(exportName in module)) {
|
|
103
|
+
throw new CliError(
|
|
104
|
+
`${modulePath} has no "${exportName}" export (exports: ${Object.keys(module).sort().join(", ") || "none"}). Choose one with --export.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const exported = module[exportName];
|
|
108
|
+
const entry = typeof exported === "function" ? await exported() : exported;
|
|
109
|
+
if (!isEntry(entry)) {
|
|
110
|
+
throw new CliError(
|
|
111
|
+
`The "${exportName}" export of ${modulePath} must be { worker, source }: a worker created by createWorker() and the source of a transport, or a function returning them.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return entry;
|
|
115
|
+
};
|
|
116
|
+
var stderrLogger = (io) => {
|
|
117
|
+
const write2 = (level) => (data, message) => void io.stderr(
|
|
118
|
+
`${JSON.stringify({ level, time: (/* @__PURE__ */ new Date()).toISOString(), message, ...data })}
|
|
119
|
+
`
|
|
120
|
+
);
|
|
121
|
+
return {
|
|
122
|
+
debug: write2("debug"),
|
|
123
|
+
info: write2("info"),
|
|
124
|
+
warn: write2("warn"),
|
|
125
|
+
error: write2("error")
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
var main = async (args, io) => {
|
|
129
|
+
let parsed;
|
|
130
|
+
try {
|
|
131
|
+
parsed = parseArgs({
|
|
132
|
+
args: [...args],
|
|
133
|
+
allowPositionals: true,
|
|
134
|
+
options: {
|
|
135
|
+
export: { type: "string", default: "default" },
|
|
136
|
+
watch: { type: "boolean", default: false },
|
|
137
|
+
help: { type: "boolean", short: "h" }
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
await io.stderr(`${error.message}
|
|
142
|
+
|
|
143
|
+
${USAGE}`);
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
const { values, positionals } = parsed;
|
|
147
|
+
if (values.help) {
|
|
148
|
+
await io.stdout(USAGE);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
const [command, modulePath, ...rest] = positionals;
|
|
152
|
+
if (command !== "work" || modulePath === void 0 || rest.length > 0) {
|
|
153
|
+
await io.stderr(USAGE);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
if (values.watch) {
|
|
157
|
+
return io.watch(args.filter((arg) => arg !== "--watch"));
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const entry = await loadEntry(modulePath, values.export, io.cwd);
|
|
161
|
+
const running = startWorker(entry.worker, entry.source, {
|
|
162
|
+
logger: entry.logger ?? stderrLogger(io),
|
|
163
|
+
signal: io.signal
|
|
164
|
+
});
|
|
165
|
+
await running.done;
|
|
166
|
+
return 0;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
await io.stderr(
|
|
169
|
+
`${error instanceof CliError ? error.message : `Worker failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`}
|
|
170
|
+
`
|
|
171
|
+
);
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/bin.ts
|
|
177
|
+
var write = (stream) => (text) => new Promise((resolve2) => {
|
|
178
|
+
stream.write(text, () => resolve2());
|
|
179
|
+
});
|
|
180
|
+
var controller = new AbortController();
|
|
181
|
+
var onSignal = () => {
|
|
182
|
+
if (controller.signal.aborted) process.exit(130);
|
|
183
|
+
controller.abort();
|
|
184
|
+
};
|
|
185
|
+
process.on("SIGINT", onSignal);
|
|
186
|
+
process.on("SIGTERM", onSignal);
|
|
187
|
+
var code = await main(process.argv.slice(2), {
|
|
188
|
+
cwd: process.cwd(),
|
|
189
|
+
stdout: write(process.stdout),
|
|
190
|
+
stderr: write(process.stderr),
|
|
191
|
+
signal: controller.signal,
|
|
192
|
+
watch: (args) => new Promise((resolve2) => {
|
|
193
|
+
const child = spawn(
|
|
194
|
+
process.execPath,
|
|
195
|
+
["--watch", fileURLToPath(import.meta.url), ...args],
|
|
196
|
+
{ stdio: "inherit" }
|
|
197
|
+
);
|
|
198
|
+
child.on("exit", (exitCode) => resolve2(exitCode ?? 0));
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
process.exit(code);
|
|
202
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts","../src/cli.ts","../src/logger.ts","../src/start-worker.ts"],"sourcesContent":["import { spawn } from 'node:child_process'\nimport { fileURLToPath } from 'node:url'\nimport { main } from './cli.js'\n\nconst write = (stream: NodeJS.WriteStream) => (text: string) =>\n new Promise<void>((resolve) => {\n stream.write(text, () => resolve())\n })\n\n// First SIGINT / SIGTERM: graceful stop. Second one: immediate exit.\nconst controller = new AbortController()\nconst onSignal = () => {\n if (controller.signal.aborted) process.exit(130)\n controller.abort()\n}\nprocess.on('SIGINT', onSignal)\nprocess.on('SIGTERM', onSignal)\n\nconst code = await main(process.argv.slice(2), {\n cwd: process.cwd(),\n stdout: write(process.stdout),\n stderr: write(process.stderr),\n signal: controller.signal,\n watch: (args) =>\n new Promise((resolve) => {\n // node --watch restarts the command when a loaded file changes.\n const child = spawn(\n process.execPath,\n ['--watch', fileURLToPath(import.meta.url), ...args],\n { stdio: 'inherit' }\n )\n child.on('exit', (exitCode) => resolve(exitCode ?? 0))\n }),\n})\n\nprocess.exit(code)\n","import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { parseArgs } from 'node:util'\nimport type { EventsLogger } from './logger.js'\nimport { startWorker } from './start-worker.js'\nimport type { Source } from './transport.js'\nimport type { Worker } from './worker.js'\n\n/** What the module of `dotpress-events work` exports. */\nexport interface WorkerEntry {\n worker: Worker\n source: Source\n logger?: EventsLogger | undefined\n}\n\n/** Inputs and outputs of the command, injected for tests. */\nexport interface CliIo {\n readonly cwd: string\n stdout(text: string): void | Promise<void>\n stderr(text: string): void | Promise<void>\n /** Aborted on SIGINT / SIGTERM: stops the worker. */\n readonly signal: AbortSignal\n /** Runs the command again under `node --watch`; returns its exit code. */\n watch(args: readonly string[]): Promise<number>\n}\n\nconst TYPESCRIPT_HINT =\n 'TypeScript modules require Node.js 22.18 or later, with erasable syntax and relative imports ending in .ts. Otherwise, point to the compiled module, or run the command with NODE_OPTIONS=\"--import tsx\".'\n\nexport const USAGE = `Usage: dotpress-events work <module> [options]\n\nRuns a worker until stopped (Ctrl+C): reads the messages of a queue and\nruns their listeners. <module> exports { worker, source } (and optionally\na logger), or a function returning it:\n\n export default {\n worker: createWorker({ listeners: [sendWelcomeMail] }),\n source: createSqsSource({ queueUrl }),\n }\n\nOptions:\n --export <name> Export of the module (default: \"default\")\n --watch Restarts the worker when a loaded file changes\n -h, --help Shows this help\n\n${TYPESCRIPT_HINT}\n`\n\nclass CliError extends Error {}\n\nconst isEntry = (value: unknown): value is WorkerEntry =>\n typeof (value as WorkerEntry | null)?.worker?.handle === 'function' &&\n typeof (value as WorkerEntry).source?.consume === 'function'\n\nconst loadEntry = async (\n modulePath: string,\n exportName: string,\n cwd: string\n): Promise<WorkerEntry> => {\n const file = resolve(cwd, modulePath)\n if (!existsSync(file)) throw new CliError(`Module not found: ${file}`)\n\n let module: Record<string, unknown>\n try {\n module = (await import(pathToFileURL(file).href)) as Record<string, unknown>\n } catch (error) {\n const hint = /\\.[cm]?ts$/.test(file) ? `\\n${TYPESCRIPT_HINT}` : ''\n throw new CliError(\n `Cannot load ${file}: ${error instanceof Error ? error.message : String(error)}${hint}`,\n { cause: error }\n )\n }\n if (!(exportName in module)) {\n throw new CliError(\n `${modulePath} has no \"${exportName}\" export (exports: ${Object.keys(module).sort().join(', ') || 'none'}). Choose one with --export.`\n )\n }\n const exported = module[exportName]\n const entry: unknown =\n typeof exported === 'function'\n ? await (exported as () => unknown)()\n : exported\n if (!isEntry(entry)) {\n throw new CliError(\n `The \"${exportName}\" export of ${modulePath} must be { worker, source }: a worker created by createWorker() and the source of a transport, or a function returning them.`\n )\n }\n return entry\n}\n\n/** Logger writing JSON lines to the standard error. */\nconst stderrLogger = (io: CliIo): EventsLogger => {\n const write =\n (level: string) =>\n (data: object, message: string): void =>\n void io.stderr(\n `${JSON.stringify({ level, time: new Date().toISOString(), message, ...data })}\\n`\n )\n return {\n debug: write('debug'),\n info: write('info'),\n warn: write('warn'),\n error: write('error'),\n }\n}\n\n/** Runs the `dotpress-events` command, and returns its exit code. */\nexport const main = async (\n args: readonly string[],\n io: CliIo\n): Promise<number> => {\n let parsed\n try {\n parsed = parseArgs({\n args: [...args],\n allowPositionals: true,\n options: {\n export: { type: 'string', default: 'default' },\n watch: { type: 'boolean', default: false },\n help: { type: 'boolean', short: 'h' },\n },\n })\n } catch (error) {\n await io.stderr(`${(error as Error).message}\\n\\n${USAGE}`)\n return 1\n }\n\n const { values, positionals } = parsed\n if (values.help) {\n await io.stdout(USAGE)\n return 0\n }\n const [command, modulePath, ...rest] = positionals\n if (command !== 'work' || modulePath === undefined || rest.length > 0) {\n await io.stderr(USAGE)\n return 1\n }\n if (values.watch) {\n return io.watch(args.filter((arg) => arg !== '--watch'))\n }\n\n try {\n const entry = await loadEntry(modulePath, values.export, io.cwd)\n const running = startWorker(entry.worker, entry.source, {\n logger: entry.logger ?? stderrLogger(io),\n signal: io.signal,\n })\n await running.done\n return 0\n } catch (error) {\n await io.stderr(\n `${error instanceof CliError ? error.message : `Worker failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`}\\n`\n )\n return 1\n }\n}\n","/**\n * Logger of the bus and the workers. A Dotpress or Pino logger fits;\n * `eventsPlugin` uses the logger of the request.\n */\nexport interface EventsLogger {\n debug?(data: object, message: string): void\n info(data: object, message: string): void\n warn(data: object, message: string): void\n error(data: object, message: string): void\n}\n\nconst noop = () => undefined\n\n/** Logger that discards everything. */\nexport const silentLogger: EventsLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n}\n\n/** Checks a logger option. */\nexport const checkLogger = (label: string, logger: unknown) => {\n for (const level of ['info', 'warn', 'error'] as const) {\n if (\n typeof (logger as Record<string, unknown> | null)?.[level] !== 'function'\n ) {\n throw new TypeError(\n `${label}: logger must have info(), warn() and error() methods.`\n )\n }\n }\n return logger as EventsLogger\n}\n","import { checkLogger, silentLogger, type EventsLogger } from './logger.js'\nimport type { Source } from './transport.js'\nimport type { Worker } from './worker.js'\n\n/** A started worker. */\nexport interface RunningWorker {\n /** Stops reading messages, waits for those in progress, then resolves. */\n stop(): Promise<void>\n /** Resolves when the worker has stopped; rejects if the source failed. */\n readonly done: Promise<void>\n}\n\n/** Options of `startWorker()`. */\nexport interface StartWorkerOptions {\n logger?: EventsLogger | undefined\n /** Stops the worker when aborted. */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Starts a long-running worker: the source reads messages from its queue\n * (SQS polling, BullMQ, pg-boss…) and the worker runs their listeners,\n * until `stop()`. Used by containers and by `dotpress-events work`.\n */\nexport const startWorker = (\n worker: Worker,\n source: Source,\n options: StartWorkerOptions = {}\n): RunningWorker => {\n const label = 'startWorker()'\n if (typeof worker?.handle !== 'function') {\n throw new TypeError(\n `${label}: expected a worker created by createWorker().`\n )\n }\n if (typeof source?.consume !== 'function') {\n throw new TypeError(`${label}: expected a source with a consume() method.`)\n }\n const logger =\n options.logger === undefined\n ? silentLogger\n : checkLogger(label, options.logger)\n\n const controller = new AbortController()\n const stop = () => controller.abort()\n options.signal?.addEventListener('abort', stop, { once: true })\n if (options.signal?.aborted) stop()\n\n logger.info(\n { source: source.kind, listeners: worker.listeners },\n 'Event worker started'\n )\n const done = source\n .consume(worker, { signal: controller.signal, logger })\n .finally(() => {\n options.signal?.removeEventListener('abort', stop)\n logger.info({ source: source.kind }, 'Event worker stopped')\n })\n\n return {\n stop: () => {\n stop()\n return done\n },\n done,\n }\n}\n"],"mappings":"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","names":["controller","write","resolve"]}
|