@dotpress/events 0.0.0-stage → 2.0.0-alpha.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 +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,420 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PartialSendError
|
|
3
|
+
} from "./chunk-NCQ4ZTNB.js";
|
|
4
|
+
|
|
5
|
+
// src/transports/google-pubsub.ts
|
|
6
|
+
var MAX_MESSAGES = 1e3;
|
|
7
|
+
var MAX_REQUEST_BYTES = 1e7;
|
|
8
|
+
var DEFAULT_BASE_URL = "https://pubsub.googleapis.com";
|
|
9
|
+
var GOOGLE_CERTS = "https://www.googleapis.com/oauth2/v3/certs";
|
|
10
|
+
var SCOPE = "https://www.googleapis.com/auth/pubsub";
|
|
11
|
+
var TOPIC = /^projects\/[^/]+\/topics\/[^/]+$/;
|
|
12
|
+
var SUBSCRIPTION = /^projects\/[^/]+\/subscriptions\/[^/]+$/;
|
|
13
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
var checkOptions = (label, options, allowed) => {
|
|
15
|
+
if (!isRecord(options)) {
|
|
16
|
+
throw new TypeError(`${label}: expected an object of options.`);
|
|
17
|
+
}
|
|
18
|
+
for (const key of Object.keys(options)) {
|
|
19
|
+
if (!allowed.includes(key)) {
|
|
20
|
+
throw new TypeError(`${label}: unknown option "${key}".`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return options;
|
|
24
|
+
};
|
|
25
|
+
var checkName = (label, key, value, kind, example) => {
|
|
26
|
+
if (typeof value !== "string" || !kind.test(value)) {
|
|
27
|
+
throw new TypeError(
|
|
28
|
+
`${label}: ${key} must be a full name, like ${example}.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
34
|
+
var encoder = new TextEncoder();
|
|
35
|
+
var base64 = (text) => {
|
|
36
|
+
let binary = "";
|
|
37
|
+
for (const byte of encoder.encode(text)) binary += String.fromCharCode(byte);
|
|
38
|
+
return btoa(binary);
|
|
39
|
+
};
|
|
40
|
+
var fromBase64 = (data) => {
|
|
41
|
+
try {
|
|
42
|
+
return new TextDecoder().decode(
|
|
43
|
+
Uint8Array.from(atob(data), (char) => char.charCodeAt(0))
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var googleAuth;
|
|
50
|
+
var defaultAccessToken = async () => {
|
|
51
|
+
googleAuth ??= import("google-auth-library").then(
|
|
52
|
+
({ GoogleAuth }) => new GoogleAuth({ scopes: SCOPE }),
|
|
53
|
+
(error) => {
|
|
54
|
+
googleAuth = void 0;
|
|
55
|
+
throw new Error(
|
|
56
|
+
"Install google-auth-library, or pass getAccessToken, to publish to Pub/Sub.",
|
|
57
|
+
{ cause: error }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
const token = await (await googleAuth).getAccessToken();
|
|
62
|
+
if (!token) throw new Error("No Google access token: check the credentials.");
|
|
63
|
+
return token;
|
|
64
|
+
};
|
|
65
|
+
var publisherOf = (label, options) => {
|
|
66
|
+
const topic = checkName(
|
|
67
|
+
label,
|
|
68
|
+
"topic",
|
|
69
|
+
options.topic,
|
|
70
|
+
TOPIC,
|
|
71
|
+
"projects/my-project/topics/events"
|
|
72
|
+
);
|
|
73
|
+
const {
|
|
74
|
+
getAccessToken = defaultAccessToken,
|
|
75
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
76
|
+
fetch: fetchFn = globalThis.fetch
|
|
77
|
+
} = options;
|
|
78
|
+
if (getAccessToken !== false && typeof getAccessToken !== "function") {
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
`${label}: getAccessToken must be a function, or false.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (typeof baseUrl !== "string" || !/^https?:\/\/[^/]/.test(baseUrl)) {
|
|
84
|
+
throw new TypeError(`${label}: baseUrl must be an http(s) URL.`);
|
|
85
|
+
}
|
|
86
|
+
if (typeof fetchFn !== "function") {
|
|
87
|
+
throw new TypeError(`${label}: fetch must be a function.`);
|
|
88
|
+
}
|
|
89
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/v1/${topic}:publish`;
|
|
90
|
+
return async (messages) => {
|
|
91
|
+
const token = getAccessToken === false ? void 0 : await getAccessToken();
|
|
92
|
+
const response = await fetchFn(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: {
|
|
95
|
+
"content-type": "application/json",
|
|
96
|
+
...token === void 0 ? {} : { authorization: `Bearer ${token}` }
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({ messages })
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
let detail = "";
|
|
102
|
+
try {
|
|
103
|
+
const body = await response.json();
|
|
104
|
+
detail = [body.error?.status, body.error?.message].filter(Boolean).join(": ");
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Pub/Sub API: ${response.status} ${detail || response.statusText}`.trim()
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
var messageOf = (envelope) => ({
|
|
114
|
+
data: base64(JSON.stringify(envelope)),
|
|
115
|
+
attributes: { event: envelope.event, listener: envelope.listener }
|
|
116
|
+
});
|
|
117
|
+
var createPubSubTransport = (options) => {
|
|
118
|
+
const label = "createPubSubTransport()";
|
|
119
|
+
const values = checkOptions(label, options, [
|
|
120
|
+
"topic",
|
|
121
|
+
"getAccessToken",
|
|
122
|
+
"baseUrl",
|
|
123
|
+
"fetch"
|
|
124
|
+
]);
|
|
125
|
+
const publish = publisherOf(label, values);
|
|
126
|
+
return {
|
|
127
|
+
kind: "google-pubsub",
|
|
128
|
+
send: async (envelopes, { delay }) => {
|
|
129
|
+
if (delay) {
|
|
130
|
+
throw new RangeError(
|
|
131
|
+
"Pub/Sub has no delayed delivery: emit without delay, or use another transport."
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const failed = [];
|
|
135
|
+
const batches = [];
|
|
136
|
+
for (const envelope of envelopes) {
|
|
137
|
+
const message = messageOf(envelope);
|
|
138
|
+
const size = JSON.stringify(message).length + 1;
|
|
139
|
+
if (size + 20 > MAX_REQUEST_BYTES) {
|
|
140
|
+
failed.push({
|
|
141
|
+
id: envelope.id,
|
|
142
|
+
error: new RangeError(
|
|
143
|
+
`Pub/Sub accepts publish requests of at most ${MAX_REQUEST_BYTES} bytes (data base64-encoded), got ${size} for one message.`
|
|
144
|
+
)
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const last = batches.at(-1);
|
|
149
|
+
if (last && last.messages.length < MAX_MESSAGES && last.size + size + 20 <= MAX_REQUEST_BYTES) {
|
|
150
|
+
last.envelopes.push(envelope);
|
|
151
|
+
last.messages.push(message);
|
|
152
|
+
last.size += size;
|
|
153
|
+
} else {
|
|
154
|
+
batches.push({ envelopes: [envelope], messages: [message], size });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const batch of batches) {
|
|
158
|
+
try {
|
|
159
|
+
await publish(batch.messages);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
for (const { id } of batch.envelopes) failed.push({ id, error });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (failed.length > 0) throw new PartialSendError(failed);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
var verifierOf = (audience, certsUrl) => {
|
|
169
|
+
let loaded;
|
|
170
|
+
return async (token) => {
|
|
171
|
+
loaded ??= import("jose").then(
|
|
172
|
+
({ createRemoteJWKSet, jwtVerify: jwtVerify2 }) => ({
|
|
173
|
+
jwks: createRemoteJWKSet(new URL(certsUrl)),
|
|
174
|
+
jwtVerify: jwtVerify2
|
|
175
|
+
}),
|
|
176
|
+
(error) => {
|
|
177
|
+
loaded = void 0;
|
|
178
|
+
throw new Error("Install jose to verify Pub/Sub push tokens.", {
|
|
179
|
+
cause: error
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
const { jwks, jwtVerify } = await loaded;
|
|
184
|
+
const { payload } = await jwtVerify(
|
|
185
|
+
token,
|
|
186
|
+
jwks,
|
|
187
|
+
{
|
|
188
|
+
issuer: ["https://accounts.google.com", "accounts.google.com"],
|
|
189
|
+
audience
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
return payload;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
var moveToDeadLetter = async (publish, data, result, logger) => {
|
|
196
|
+
if (result.status !== "dead" || !publish) return false;
|
|
197
|
+
try {
|
|
198
|
+
await publish([
|
|
199
|
+
{
|
|
200
|
+
data,
|
|
201
|
+
attributes: {
|
|
202
|
+
reason: result.reason,
|
|
203
|
+
error: describe(result.error).slice(0, 1024)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
]);
|
|
207
|
+
return true;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
logger?.error(
|
|
210
|
+
{ error: describe(error) },
|
|
211
|
+
"Cannot publish the message to the dead-letter topic"
|
|
212
|
+
);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var createPubSubPushHandler = (worker, options = {}) => {
|
|
217
|
+
const label = "createPubSubPushHandler()";
|
|
218
|
+
if (typeof worker?.handle !== "function") {
|
|
219
|
+
throw new TypeError(
|
|
220
|
+
`${label}: expected a worker created by createWorker().`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const values = checkOptions(label, options, [
|
|
224
|
+
"verifyToken",
|
|
225
|
+
"audience",
|
|
226
|
+
"serviceAccountEmail",
|
|
227
|
+
"certsUrl",
|
|
228
|
+
"deadLetter",
|
|
229
|
+
"fetch",
|
|
230
|
+
"logger"
|
|
231
|
+
]);
|
|
232
|
+
const {
|
|
233
|
+
verifyToken = true,
|
|
234
|
+
audience,
|
|
235
|
+
serviceAccountEmail,
|
|
236
|
+
certsUrl = GOOGLE_CERTS
|
|
237
|
+
} = values;
|
|
238
|
+
if (typeof verifyToken !== "boolean") {
|
|
239
|
+
throw new TypeError(`${label}: verifyToken must be a boolean.`);
|
|
240
|
+
}
|
|
241
|
+
if (verifyToken && (typeof audience !== "string" || audience === "")) {
|
|
242
|
+
throw new TypeError(
|
|
243
|
+
`${label}: audience is required to verify push tokens (or set verifyToken: false behind a proxy that checks them).`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (serviceAccountEmail !== void 0 && (typeof serviceAccountEmail !== "string" || serviceAccountEmail === "")) {
|
|
247
|
+
throw new TypeError(
|
|
248
|
+
`${label}: serviceAccountEmail must be a non-empty string.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (typeof certsUrl !== "string" || !/^https?:\/\/[^/]/.test(certsUrl)) {
|
|
252
|
+
throw new TypeError(`${label}: certsUrl must be an http(s) URL.`);
|
|
253
|
+
}
|
|
254
|
+
if (values.deadLetter !== void 0) {
|
|
255
|
+
checkOptions(`${label}: deadLetter`, values.deadLetter, [
|
|
256
|
+
"topic",
|
|
257
|
+
"getAccessToken",
|
|
258
|
+
"baseUrl"
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
const logger = values.logger;
|
|
262
|
+
const verify = verifyToken ? verifierOf(audience, certsUrl) : void 0;
|
|
263
|
+
const publishDead = values.deadLetter === void 0 ? void 0 : publisherOf(label, { ...values.deadLetter, fetch: values.fetch });
|
|
264
|
+
const handle = async (body, authorization) => {
|
|
265
|
+
if (verify) {
|
|
266
|
+
const token = /^Bearer (.+)$/.exec(authorization ?? "")?.[1];
|
|
267
|
+
if (!token) return 401;
|
|
268
|
+
try {
|
|
269
|
+
const claims = await verify(token);
|
|
270
|
+
if (claims.email_verified !== true || serviceAccountEmail !== void 0 && claims.email !== serviceAccountEmail) {
|
|
271
|
+
return 403;
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
logger?.warn({ error: describe(error) }, "Invalid Pub/Sub push token");
|
|
275
|
+
return 401;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const message = isRecord(body) && isRecord(body.message) ? body.message : void 0;
|
|
279
|
+
if (!message) return 400;
|
|
280
|
+
const deliveryAttempt = body.deliveryAttempt;
|
|
281
|
+
const attempt = typeof deliveryAttempt === "number" ? deliveryAttempt : 1;
|
|
282
|
+
const raw = typeof message.data === "string" ? message.data : "";
|
|
283
|
+
const result = await worker.handle(fromBase64(raw), { attempt });
|
|
284
|
+
if (result.status === "success") return 204;
|
|
285
|
+
if (await moveToDeadLetter(publishDead, raw, result, logger)) return 204;
|
|
286
|
+
return 503;
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
handle,
|
|
290
|
+
fetch: async (request) => {
|
|
291
|
+
const body = await request.json().catch(() => void 0);
|
|
292
|
+
const status = await handle(
|
|
293
|
+
body,
|
|
294
|
+
request.headers.get("authorization") ?? void 0
|
|
295
|
+
);
|
|
296
|
+
return new Response(null, { status });
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
var createPubSubPullSource = (options) => {
|
|
301
|
+
const label = "createPubSubPullSource()";
|
|
302
|
+
const values = checkOptions(label, options, [
|
|
303
|
+
"subscription",
|
|
304
|
+
"client",
|
|
305
|
+
"clientConfig",
|
|
306
|
+
"maxMessages",
|
|
307
|
+
"deadLetterTopic",
|
|
308
|
+
"closeTimeout"
|
|
309
|
+
]);
|
|
310
|
+
const subscriptionName = checkName(
|
|
311
|
+
label,
|
|
312
|
+
"subscription",
|
|
313
|
+
values.subscription,
|
|
314
|
+
SUBSCRIPTION,
|
|
315
|
+
"projects/my-project/subscriptions/events"
|
|
316
|
+
);
|
|
317
|
+
const {
|
|
318
|
+
client,
|
|
319
|
+
clientConfig,
|
|
320
|
+
maxMessages = 10,
|
|
321
|
+
deadLetterTopic,
|
|
322
|
+
closeTimeout = 3e4
|
|
323
|
+
} = values;
|
|
324
|
+
if (client !== void 0 && clientConfig !== void 0) {
|
|
325
|
+
throw new TypeError(`${label}: set client or clientConfig, not both.`);
|
|
326
|
+
}
|
|
327
|
+
if (client !== void 0 && typeof client?.subscription !== "function") {
|
|
328
|
+
throw new TypeError(`${label}: client must be a PubSub client.`);
|
|
329
|
+
}
|
|
330
|
+
if (!Number.isInteger(maxMessages) || maxMessages < 1) {
|
|
331
|
+
throw new TypeError(`${label}: maxMessages must be a positive integer.`);
|
|
332
|
+
}
|
|
333
|
+
if (deadLetterTopic !== void 0) {
|
|
334
|
+
checkName(
|
|
335
|
+
label,
|
|
336
|
+
"deadLetterTopic",
|
|
337
|
+
deadLetterTopic,
|
|
338
|
+
TOPIC,
|
|
339
|
+
"projects/my-project/topics/events-dlq"
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
if (!Number.isInteger(closeTimeout) || closeTimeout < 0) {
|
|
343
|
+
throw new TypeError(
|
|
344
|
+
`${label}: closeTimeout must be a non-negative integer (milliseconds).`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
kind: "google-pubsub",
|
|
349
|
+
consume: async (worker, { signal, logger }) => {
|
|
350
|
+
const pubsub = client ?? new (await import("@google-cloud/pubsub")).PubSub(
|
|
351
|
+
clientConfig
|
|
352
|
+
);
|
|
353
|
+
const subscription = pubsub.subscription(subscriptionName, {
|
|
354
|
+
flowControl: {
|
|
355
|
+
maxMessages,
|
|
356
|
+
allowExcessMessages: false
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
const deadTopic = deadLetterTopic === void 0 ? void 0 : pubsub.topic(deadLetterTopic);
|
|
360
|
+
subscription.on("message", (message) => {
|
|
361
|
+
void (async () => {
|
|
362
|
+
const data = message.data.toString("utf8");
|
|
363
|
+
const result = await worker.handle(data, {
|
|
364
|
+
attempt: message.deliveryAttempt || 1
|
|
365
|
+
});
|
|
366
|
+
if (result.status === "success") return message.ack();
|
|
367
|
+
if (result.status === "dead" && deadTopic) {
|
|
368
|
+
try {
|
|
369
|
+
await deadTopic.publishMessage({
|
|
370
|
+
data: message.data,
|
|
371
|
+
attributes: {
|
|
372
|
+
reason: result.reason,
|
|
373
|
+
error: describe(result.error).slice(0, 1024)
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
return message.ack();
|
|
377
|
+
} catch (error) {
|
|
378
|
+
logger.error(
|
|
379
|
+
{ error: describe(error) },
|
|
380
|
+
"Cannot publish the message to the dead-letter topic"
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
message.nack();
|
|
385
|
+
})();
|
|
386
|
+
});
|
|
387
|
+
subscription.on("error", (error) => {
|
|
388
|
+
logger.error(
|
|
389
|
+
{ subscription: subscriptionName, error: error.message },
|
|
390
|
+
"Pub/Sub subscription error"
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
if (signal.aborted) resolve();
|
|
395
|
+
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
396
|
+
});
|
|
397
|
+
let timer;
|
|
398
|
+
const closed = await Promise.race([
|
|
399
|
+
subscription.close().then(() => true),
|
|
400
|
+
new Promise((resolve) => {
|
|
401
|
+
timer = setTimeout(() => resolve(false), closeTimeout);
|
|
402
|
+
})
|
|
403
|
+
]);
|
|
404
|
+
clearTimeout(timer);
|
|
405
|
+
if (!closed) {
|
|
406
|
+
logger.warn(
|
|
407
|
+
{ subscription: subscriptionName, closeTimeout },
|
|
408
|
+
"Pub/Sub subscription not closed in time: stopped without waiting"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (!client) await pubsub.close().catch(() => void 0);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
export {
|
|
416
|
+
createPubSubPullSource,
|
|
417
|
+
createPubSubPushHandler,
|
|
418
|
+
createPubSubTransport
|
|
419
|
+
};
|
|
420
|
+
//# sourceMappingURL=google-pubsub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transports/google-pubsub.ts"],"sourcesContent":["import type { PubSub } from '@google-cloud/pubsub'\nimport type { Envelope } from '../envelope.js'\nimport { PartialSendError } from '../errors.js'\nimport type { EventsLogger } from '../logger.js'\nimport type { Source, Transport } from '../transport.js'\nimport type { HandleResult, Worker } from '../worker.js'\n\n/**\n * OAuth access token of the publisher, or `false` without authentication\n * (the Pub/Sub emulator). Defaults to Application Default Credentials,\n * through `google-auth-library`.\n */\nexport type AccessTokenOption = (() => Promise<string>) | false\n\n/** Options to publish to a topic over the REST API. */\nexport interface PubSubPublishOptions {\n /** Full topic name: `projects/{project}/topics/{topic}`. */\n topic: string\n getAccessToken?: AccessTokenOption | undefined\n /** Base URL of the API. Defaults to `https://pubsub.googleapis.com`. */\n baseUrl?: string | undefined\n /** `fetch` implementation. Defaults to the global `fetch`. */\n fetch?: typeof fetch | undefined\n}\n\n/** Options of `createPubSubPushHandler()`. */\nexport interface PubSubPushHandlerOptions {\n /**\n * Checks the OIDC token of push requests (`Authorization: Bearer`).\n * Defaults to true; set `false` only behind a proxy that checks it.\n */\n verifyToken?: boolean | undefined\n /** Audience of the token, as configured on the push subscription. */\n audience?: string | undefined\n /** Expected service account of the push subscription (`email` claim). */\n serviceAccountEmail?: string | undefined\n /** Keys of the tokens. Defaults to the Google certificates. */\n certsUrl?: string | undefined\n /**\n * Topic where messages that can never be handled are published at once,\n * instead of failing until `maxDeliveryAttempts`.\n */\n deadLetter?: Omit<PubSubPublishOptions, 'fetch'> | undefined\n /** `fetch` implementation, for the dead-letter topic. */\n fetch?: typeof fetch | undefined\n logger?: EventsLogger | undefined\n}\n\n/** Push endpoint of a subscription. */\nexport interface PubSubPushHandler {\n /** Handles a push request (Web `Request`), and returns the response. */\n fetch(request: Request): Promise<Response>\n /**\n * Handles a push body (parsed JSON) with its `Authorization` header, for\n * frameworks without Web requests. Returns the HTTP status to answer.\n */\n handle(body: unknown, authorization: string | undefined): Promise<number>\n}\n\n/** Options of `createPubSubPullSource()`. */\nexport interface PubSubPullSourceOptions {\n /** Full subscription name: `projects/{project}/subscriptions/{name}`. */\n subscription: string\n /** Existing client. Defaults to a client created with `clientConfig`. */\n client?: PubSub | undefined\n /** Options of the created client (`projectId`, `credentials`…). */\n clientConfig?: ConstructorParameters<typeof PubSub>[0] | undefined\n /** Messages handled at the same time. Defaults to 10. */\n maxMessages?: number | undefined\n /** Full name of the topic for messages that can never be handled. */\n deadLetterTopic?: string | undefined\n /**\n * On stop, how long to wait for the messages in progress, in\n * milliseconds. Defaults to 30 seconds.\n */\n closeTimeout?: number | undefined\n}\n\n// Limits of the publish API: 1,000 messages and 10 MB per request.\nconst MAX_MESSAGES = 1000\nconst MAX_REQUEST_BYTES = 10_000_000\nconst DEFAULT_BASE_URL = 'https://pubsub.googleapis.com'\nconst GOOGLE_CERTS = 'https://www.googleapis.com/oauth2/v3/certs'\nconst SCOPE = 'https://www.googleapis.com/auth/pubsub'\nconst TOPIC = /^projects\\/[^/]+\\/topics\\/[^/]+$/\nconst SUBSCRIPTION = /^projects\\/[^/]+\\/subscriptions\\/[^/]+$/\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 checkName = (\n label: string,\n key: string,\n value: unknown,\n kind: RegExp,\n example: string\n) => {\n if (typeof value !== 'string' || !kind.test(value)) {\n throw new TypeError(\n `${label}: ${key} must be a full name, like ${example}.`\n )\n }\n return value\n}\n\nconst describe = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\nconst encoder = new TextEncoder()\n\nconst base64 = (text: string) => {\n let binary = ''\n for (const byte of encoder.encode(text)) binary += String.fromCharCode(byte)\n return btoa(binary)\n}\n\n/** Decodes base64 data; data that is not base64 gives an empty string. */\nconst fromBase64 = (data: string) => {\n try {\n return new TextDecoder().decode(\n Uint8Array.from(atob(data), (char) => char.charCodeAt(0))\n )\n } catch {\n return ''\n }\n}\n\nlet googleAuth:\n Promise<{ getAccessToken(): Promise<string | null | undefined> }> | undefined\n\n/** Access token from Application Default Credentials. */\nconst defaultAccessToken = async () => {\n googleAuth ??= import('google-auth-library').then(\n ({ GoogleAuth }) => new GoogleAuth({ scopes: SCOPE }),\n (error: unknown) => {\n googleAuth = undefined\n throw new Error(\n 'Install google-auth-library, or pass getAccessToken, to publish to Pub/Sub.',\n { cause: error }\n )\n }\n )\n const token = await (await googleAuth).getAccessToken()\n if (!token) throw new Error('No Google access token: check the credentials.')\n return token\n}\n\n/** Publishes JSON messages to a topic over the REST API. */\nconst publisherOf = (label: string, options: Record<string, unknown>) => {\n const topic = checkName(\n label,\n 'topic',\n options.topic,\n TOPIC,\n 'projects/my-project/topics/events'\n )\n const {\n getAccessToken = defaultAccessToken,\n baseUrl = DEFAULT_BASE_URL,\n fetch: fetchFn = globalThis.fetch,\n } = options\n if (getAccessToken !== false && typeof getAccessToken !== 'function') {\n throw new TypeError(\n `${label}: getAccessToken must be a function, or false.`\n )\n }\n if (typeof baseUrl !== 'string' || !/^https?:\\/\\/[^/]/.test(baseUrl)) {\n throw new TypeError(`${label}: baseUrl must be an http(s) URL.`)\n }\n if (typeof fetchFn !== 'function') {\n throw new TypeError(`${label}: fetch must be a function.`)\n }\n const url = `${baseUrl.replace(/\\/+$/, '')}/v1/${topic}:publish`\n\n return async (\n messages: { data: string; attributes: Record<string, string> }[]\n ) => {\n const token =\n getAccessToken === false\n ? undefined\n : await (getAccessToken as () => Promise<string>)()\n const response = await (fetchFn as typeof fetch)(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(token === undefined ? {} : { authorization: `Bearer ${token}` }),\n },\n body: JSON.stringify({ messages }),\n })\n if (!response.ok) {\n let detail = ''\n try {\n const body = (await response.json()) as {\n error?: { status?: unknown; message?: unknown }\n }\n detail = [body.error?.status, body.error?.message]\n .filter(Boolean)\n .join(': ')\n } catch {\n // Not JSON.\n }\n throw new Error(\n `Pub/Sub API: ${response.status} ${detail || response.statusText}`.trim()\n )\n }\n }\n}\n\nconst messageOf = (envelope: Envelope) => ({\n data: base64(JSON.stringify(envelope)),\n attributes: { event: envelope.event, listener: envelope.listener },\n})\n\n/**\n * Creates a transport that publishes messages to a Google Cloud Pub/Sub\n * topic, over the REST API with `fetch`: it also runs outside Node.js.\n * Pub/Sub has no delayed delivery: `delay` is refused.\n *\n * ```ts\n * const transport = createPubSubTransport({ topic: 'projects/my-project/topics/events' })\n * ```\n */\nexport const createPubSubTransport = (\n options: PubSubPublishOptions\n): Transport => {\n const label = 'createPubSubTransport()'\n const values = checkOptions(label, options, [\n 'topic',\n 'getAccessToken',\n 'baseUrl',\n 'fetch',\n ])\n const publish = publisherOf(label, values)\n\n return {\n kind: 'google-pubsub',\n send: async (envelopes, { delay }) => {\n if (delay) {\n throw new RangeError(\n 'Pub/Sub has no delayed delivery: emit without delay, or use another transport.'\n )\n }\n const failed: { id: string; error: unknown }[] = []\n const batches: {\n envelopes: Envelope[]\n messages: ReturnType<typeof messageOf>[]\n size: number\n }[] = []\n for (const envelope of envelopes) {\n const message = messageOf(envelope)\n // Base64 data, attributes and JSON overhead.\n const size = JSON.stringify(message).length + 1\n if (size + 20 > MAX_REQUEST_BYTES) {\n failed.push({\n id: envelope.id,\n error: new RangeError(\n `Pub/Sub accepts publish requests of at most ${MAX_REQUEST_BYTES} bytes (data base64-encoded), got ${size} for one message.`\n ),\n })\n continue\n }\n const last = batches.at(-1)\n if (\n last &&\n last.messages.length < MAX_MESSAGES &&\n last.size + size + 20 <= MAX_REQUEST_BYTES\n ) {\n last.envelopes.push(envelope)\n last.messages.push(message)\n last.size += size\n } else {\n batches.push({ envelopes: [envelope], messages: [message], size })\n }\n }\n for (const batch of batches) {\n try {\n await publish(batch.messages)\n } catch (error) {\n for (const { id } of batch.envelopes) failed.push({ id, error })\n }\n }\n if (failed.length > 0) throw new PartialSendError(failed)\n },\n }\n}\n\ntype Verifier = (token: string) => Promise<Record<string, unknown>>\n\n/** Verifier of Google OIDC tokens, with `jose` (peer dependency). */\nconst verifierOf = (audience: string, certsUrl: string): Verifier => {\n let loaded:\n | Promise<{\n jwks: unknown\n jwtVerify: typeof import('jose').jwtVerify\n }>\n | undefined\n return async (token) => {\n loaded ??= import('jose').then(\n ({ createRemoteJWKSet, jwtVerify }) => ({\n jwks: createRemoteJWKSet(new URL(certsUrl)),\n jwtVerify,\n }),\n (error: unknown) => {\n loaded = undefined\n throw new Error('Install jose to verify Pub/Sub push tokens.', {\n cause: error,\n })\n }\n )\n const { jwks, jwtVerify } = await loaded\n const { payload } = await jwtVerify(\n token,\n jwks as Parameters<typeof jwtVerify>[1],\n {\n issuer: ['https://accounts.google.com', 'accounts.google.com'],\n audience,\n }\n )\n return payload\n }\n}\n\n/** Applies a dead-letter publication: true when the message was moved. */\nconst moveToDeadLetter = async (\n publish:\n | ((\n messages: { data: string; attributes: Record<string, string> }[]\n ) => Promise<void>)\n | undefined,\n data: string,\n result: HandleResult,\n logger: EventsLogger | undefined\n) => {\n if (result.status !== 'dead' || !publish) return false\n try {\n await publish([\n {\n data,\n attributes: {\n reason: result.reason,\n error: describe(result.error).slice(0, 1024),\n },\n },\n ])\n return true\n } catch (error) {\n logger?.error(\n { error: describe(error) },\n 'Cannot publish the message to the dead-letter topic'\n )\n return false\n }\n}\n\n/**\n * Creates the endpoint of a push subscription: Pub/Sub POSTs each message\n * to it, and an answer other than 2xx makes it deliver the message again.\n * Mount `fetch` on any Web-standard runtime (Cloudflare Workers, Hono,\n * Bun, Deno, Cloud Run), or call `handle` from another framework.\n *\n * ```ts\n * const push = createPubSubPushHandler(worker, {\n * audience: 'https://api.example.com/pubsub',\n * serviceAccountEmail: 'pubsub-push@my-project.iam.gserviceaccount.com',\n * })\n * app.post('/pubsub', (c) => push.fetch(c.req.raw))\n * ```\n */\nexport const createPubSubPushHandler = (\n worker: Worker,\n options: PubSubPushHandlerOptions = {}\n): PubSubPushHandler => {\n const label = 'createPubSubPushHandler()'\n if (typeof worker?.handle !== 'function') {\n throw new TypeError(\n `${label}: expected a worker created by createWorker().`\n )\n }\n const values = checkOptions(label, options, [\n 'verifyToken',\n 'audience',\n 'serviceAccountEmail',\n 'certsUrl',\n 'deadLetter',\n 'fetch',\n 'logger',\n ])\n const {\n verifyToken = true,\n audience,\n serviceAccountEmail,\n certsUrl = GOOGLE_CERTS,\n } = values\n if (typeof verifyToken !== 'boolean') {\n throw new TypeError(`${label}: verifyToken must be a boolean.`)\n }\n if (verifyToken && (typeof audience !== 'string' || audience === '')) {\n throw new TypeError(\n `${label}: audience is required to verify push tokens (or set verifyToken: false behind a proxy that checks them).`\n )\n }\n if (\n serviceAccountEmail !== undefined &&\n (typeof serviceAccountEmail !== 'string' || serviceAccountEmail === '')\n ) {\n throw new TypeError(\n `${label}: serviceAccountEmail must be a non-empty string.`\n )\n }\n if (typeof certsUrl !== 'string' || !/^https?:\\/\\/[^/]/.test(certsUrl)) {\n throw new TypeError(`${label}: certsUrl must be an http(s) URL.`)\n }\n if (values.deadLetter !== undefined) {\n checkOptions(`${label}: deadLetter`, values.deadLetter, [\n 'topic',\n 'getAccessToken',\n 'baseUrl',\n ])\n }\n const logger = values.logger as EventsLogger | undefined\n const verify = verifyToken\n ? verifierOf(audience as string, certsUrl)\n : undefined\n const publishDead =\n values.deadLetter === undefined\n ? undefined\n : publisherOf(label, { ...values.deadLetter, fetch: values.fetch })\n\n const handle: PubSubPushHandler['handle'] = async (body, authorization) => {\n if (verify) {\n const token = /^Bearer (.+)$/.exec(authorization ?? '')?.[1]\n if (!token) return 401\n try {\n const claims = await verify(token)\n if (\n claims.email_verified !== true ||\n (serviceAccountEmail !== undefined &&\n claims.email !== serviceAccountEmail)\n ) {\n return 403\n }\n } catch (error) {\n logger?.warn({ error: describe(error) }, 'Invalid Pub/Sub push token')\n return 401\n }\n }\n const message =\n isRecord(body) && isRecord(body.message) ? body.message : undefined\n if (!message) return 400\n const deliveryAttempt = (body as { deliveryAttempt?: unknown })\n .deliveryAttempt\n const attempt = typeof deliveryAttempt === 'number' ? deliveryAttempt : 1\n // Messages without data (attributes only) are not envelopes: dead.\n const raw = typeof message.data === 'string' ? message.data : ''\n\n const result = await worker.handle(fromBase64(raw), { attempt })\n if (result.status === 'success') return 204\n if (await moveToDeadLetter(publishDead, raw, result, logger)) return 204\n // Any status but 102 and 2xx makes Pub/Sub deliver the message again.\n return 503\n }\n\n return {\n handle,\n fetch: async (request) => {\n const body: unknown = await request.json().catch(() => undefined)\n const status = await handle(\n body,\n request.headers.get('authorization') ?? undefined\n )\n return new Response(null, { status })\n },\n }\n}\n\n/**\n * Creates a source that pulls a subscription with the Pub/Sub client\n * (`@google-cloud/pubsub`, peer dependency; Node.js), for `startWorker()`\n * and `dotpress-events work`.\n *\n * ```ts\n * export default {\n * worker: createWorker({ listeners }),\n * source: createPubSubPullSource({ subscription: 'projects/my-project/subscriptions/events' }),\n * }\n * ```\n */\nexport const createPubSubPullSource = (\n options: PubSubPullSourceOptions\n): Source => {\n const label = 'createPubSubPullSource()'\n const values = checkOptions(label, options, [\n 'subscription',\n 'client',\n 'clientConfig',\n 'maxMessages',\n 'deadLetterTopic',\n 'closeTimeout',\n ])\n const subscriptionName = checkName(\n label,\n 'subscription',\n values.subscription,\n SUBSCRIPTION,\n 'projects/my-project/subscriptions/events'\n )\n const {\n client,\n clientConfig,\n maxMessages = 10,\n deadLetterTopic,\n closeTimeout = 30_000,\n } = values\n if (client !== undefined && clientConfig !== undefined) {\n throw new TypeError(`${label}: set client or clientConfig, not both.`)\n }\n if (\n client !== undefined &&\n typeof (client as PubSub | null)?.subscription !== 'function'\n ) {\n throw new TypeError(`${label}: client must be a PubSub client.`)\n }\n if (!Number.isInteger(maxMessages) || (maxMessages as number) < 1) {\n throw new TypeError(`${label}: maxMessages must be a positive integer.`)\n }\n if (deadLetterTopic !== undefined) {\n checkName(\n label,\n 'deadLetterTopic',\n deadLetterTopic,\n TOPIC,\n 'projects/my-project/topics/events-dlq'\n )\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: 'google-pubsub',\n consume: async (worker, { signal, logger }) => {\n const pubsub =\n (client as PubSub | undefined) ??\n new (await import('@google-cloud/pubsub')).PubSub(\n clientConfig as ConstructorParameters<typeof PubSub>[0]\n )\n const subscription = pubsub.subscription(subscriptionName, {\n flowControl: {\n maxMessages: maxMessages as number,\n allowExcessMessages: false,\n },\n })\n const deadTopic =\n deadLetterTopic === undefined\n ? undefined\n : pubsub.topic(deadLetterTopic as string)\n\n subscription.on('message', (message) => {\n void (async () => {\n const data = message.data.toString('utf8')\n const result = await worker.handle(data, {\n attempt: message.deliveryAttempt || 1,\n })\n if (result.status === 'success') return message.ack()\n if (result.status === 'dead' && deadTopic) {\n try {\n await deadTopic.publishMessage({\n data: message.data,\n attributes: {\n reason: result.reason,\n error: describe(result.error).slice(0, 1024),\n },\n })\n return message.ack()\n } catch (error) {\n logger.error(\n { error: describe(error) },\n 'Cannot publish the message to the dead-letter topic'\n )\n }\n }\n message.nack()\n })()\n })\n subscription.on('error', (error: Error) => {\n logger.error(\n { subscription: subscriptionName, error: error.message },\n 'Pub/Sub subscription error'\n )\n })\n\n await new Promise<void>((resolve) => {\n if (signal.aborted) resolve()\n signal.addEventListener('abort', () => resolve(), { once: true })\n })\n // close() waits for the messages in progress, within closeTimeout.\n let timer: ReturnType<typeof setTimeout> | undefined\n const closed = await Promise.race([\n subscription.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 logger.warn(\n { subscription: subscriptionName, closeTimeout },\n 'Pub/Sub subscription not closed in time: stopped without waiting'\n )\n }\n if (!client) await pubsub.close().catch(() => undefined)\n },\n }\n}\n"],"mappings":"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","names":["jwtVerify"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { E as EventBus } from './bus-Bei_kN-e.js';
|
|
2
|
+
export { a as EmitOptions, b as EmitResult, c as EventBusContext, d as EventBusOptions, e as EventEmitError, f as EventPayloadError, g as EventPayloadIssue, h as EventsError, F as FailedMessage, M as MessageRef, P as PartialSendError, i as createEventBus } from './bus-Bei_kN-e.js';
|
|
3
|
+
import { E as EventsLogger, W as Worker, S as Source, T as Transport, a as Envelope } from './transport-BtNR1ucF.js';
|
|
4
|
+
export { C as ConsumeOptions, D as DeadReason, b as EventDefinition, c as EventInput, d as EventPayload, e as EventTransports, F as FailureHook, f as FailureInfo, H as HandleResult, L as ListenerContext, g as ListenerDefinition, h as ListenerHandler, i as ListenerModule, j as ListenerOptions, k as SendOptions, l as TransportName, m as WorkerOptions, n as createWorker, o as defineEvent, p as defineListener, q as parseEnvelope } from './transport-BtNR1ucF.js';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
/** An emission recorded by a fake event bus. */
|
|
8
|
+
interface RecordedEmission {
|
|
9
|
+
readonly emissionId: string;
|
|
10
|
+
/** Name of the event. */
|
|
11
|
+
readonly event: string;
|
|
12
|
+
/** Payload, as the listeners would receive it (after JSON). */
|
|
13
|
+
readonly payload: unknown;
|
|
14
|
+
readonly delay: number | undefined;
|
|
15
|
+
/** Metadata of the emission, with the metadata of the context. */
|
|
16
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
17
|
+
readonly emittedAt: Date;
|
|
18
|
+
}
|
|
19
|
+
/** Event bus that records emissions instead of sending them, for tests. */
|
|
20
|
+
interface FakeEventBus extends EventBus {
|
|
21
|
+
/** Recorded emissions, oldest first, from this bus and its contexts. */
|
|
22
|
+
readonly emitted: readonly RecordedEmission[];
|
|
23
|
+
/** Forgets the recorded emissions. */
|
|
24
|
+
clear(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates an event bus that checks emissions like `createEventBus()` (event,
|
|
28
|
+
* payload, delay, metadata) and records them, without running listeners:
|
|
29
|
+
* to test the code that emits, with `expectEventEmitted()` of
|
|
30
|
+
* `@dotpress/testing`.
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* const events = createFakeEventBus()
|
|
34
|
+
* await signUp(events, 'ada@example.com')
|
|
35
|
+
* expect(events.emitted).toMatchObject([{ event: 'user.created' }])
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const createFakeEventBus: () => FakeEventBus;
|
|
39
|
+
|
|
40
|
+
/** A started worker. */
|
|
41
|
+
interface RunningWorker {
|
|
42
|
+
/** Stops reading messages, waits for those in progress, then resolves. */
|
|
43
|
+
stop(): Promise<void>;
|
|
44
|
+
/** Resolves when the worker has stopped; rejects if the source failed. */
|
|
45
|
+
readonly done: Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Options of `startWorker()`. */
|
|
48
|
+
interface StartWorkerOptions {
|
|
49
|
+
logger?: EventsLogger | undefined;
|
|
50
|
+
/** Stops the worker when aborted. */
|
|
51
|
+
signal?: AbortSignal | undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Starts a long-running worker: the source reads messages from its queue
|
|
55
|
+
* (SQS polling, BullMQ, pg-boss…) and the worker runs their listeners,
|
|
56
|
+
* until `stop()`. Used by containers and by `dotpress-events work`.
|
|
57
|
+
*/
|
|
58
|
+
declare const startWorker: (worker: Worker, source: Source, options?: StartWorkerOptions) => RunningWorker;
|
|
59
|
+
|
|
60
|
+
/** Options of `createMemoryTransport()`. */
|
|
61
|
+
interface MemoryTransportOptions {
|
|
62
|
+
/** Attempts per message, the first included. Defaults to 3. */
|
|
63
|
+
maxAttempts?: number | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Delay before an attempt, in milliseconds, given the number of the
|
|
66
|
+
* failed attempt. Defaults to 100 ms, doubled at each attempt.
|
|
67
|
+
*/
|
|
68
|
+
retryDelay?: ((attempt: number) => number) | undefined;
|
|
69
|
+
}
|
|
70
|
+
/** In-process queue, for development and tests. */
|
|
71
|
+
interface MemoryTransport extends Transport {
|
|
72
|
+
/** Every message sent, oldest first. */
|
|
73
|
+
readonly sent: readonly Envelope[];
|
|
74
|
+
/** Resolves when no message is waiting or running. */
|
|
75
|
+
drain(): Promise<void>;
|
|
76
|
+
/** Forgets the sent messages (not the pending ones). */
|
|
77
|
+
clear(): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Creates a transport that queues messages in memory and runs their
|
|
81
|
+
* listeners in the same process, with retries. Messages are lost when the
|
|
82
|
+
* process stops: use a real queue in production.
|
|
83
|
+
*/
|
|
84
|
+
declare const createMemoryTransport: (options?: MemoryTransportOptions) => MemoryTransport;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Transport that runs the listeners inside `emit()`, which waits for them
|
|
88
|
+
* and throws an `EventEmitError` when one fails. For development, tests,
|
|
89
|
+
* and apps without a queue. Delays are ignored: listeners run at once.
|
|
90
|
+
*/
|
|
91
|
+
declare const createSyncTransport: () => Transport;
|
|
92
|
+
|
|
93
|
+
export { Envelope, EventBus, EventsLogger, Source, Transport, Worker, createFakeEventBus, createMemoryTransport, createSyncTransport, startWorker };
|
|
94
|
+
export type { FakeEventBus, MemoryTransport, MemoryTransportOptions, RecordedEmission, RunningWorker, StartWorkerOptions };
|