@beignet/core 0.0.33 → 0.0.35
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/CHANGELOG.md +24 -0
- package/README.md +207 -30
- package/dist/agent-capabilities/index.d.ts +167 -0
- package/dist/agent-capabilities/index.d.ts.map +1 -0
- package/dist/agent-capabilities/index.js +275 -0
- package/dist/agent-capabilities/index.js.map +1 -0
- package/dist/application/index.d.ts +2 -2
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +13 -3
- package/dist/application/index.js.map +1 -1
- package/dist/client/client.d.ts +2 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +33 -16
- package/dist/client/client.js.map +1 -1
- package/dist/contracts/contract-builder.d.ts +5 -7
- package/dist/contracts/contract-builder.d.ts.map +1 -1
- package/dist/contracts/contract-builder.js +3 -18
- package/dist/contracts/contract-builder.js.map +1 -1
- package/dist/contracts/contract-group.d.ts +2 -2
- package/dist/contracts/contract-group.d.ts.map +1 -1
- package/dist/contracts/contract-group.js +2 -1
- package/dist/contracts/contract-group.js.map +1 -1
- package/dist/contracts/metadata.d.ts +3 -0
- package/dist/contracts/metadata.d.ts.map +1 -0
- package/dist/contracts/metadata.js +14 -0
- package/dist/contracts/metadata.js.map +1 -0
- package/dist/contracts/types.d.ts +20 -0
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js.map +1 -1
- package/dist/events/index.d.ts +4 -4
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +5 -5
- package/dist/events/index.js.map +1 -1
- package/dist/flags/index.d.ts +0 -4
- package/dist/flags/index.d.ts.map +1 -1
- package/dist/flags/index.js +0 -4
- package/dist/flags/index.js.map +1 -1
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js +14 -9
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +10 -4
- package/dist/jobs/index.js.map +1 -1
- package/dist/notifications/index.d.ts +194 -10
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +397 -59
- package/dist/notifications/index.js.map +1 -1
- package/dist/openapi/index.js +15 -8
- package/dist/openapi/index.js.map +1 -1
- package/dist/ports/auth.d.ts +2 -0
- package/dist/ports/auth.d.ts.map +1 -1
- package/dist/ports/auth.js +2 -0
- package/dist/ports/auth.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/index.d.ts +1 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/provider.d.ts +10 -2
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +6 -13
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +24 -44
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/request-executor.d.ts +72 -0
- package/dist/server/request-executor.d.ts.map +1 -0
- package/dist/server/request-executor.js +781 -0
- package/dist/server/request-executor.js.map +1 -0
- package/dist/server/request-preparation.d.ts +39 -0
- package/dist/server/request-preparation.d.ts.map +1 -0
- package/dist/server/request-preparation.js +248 -0
- package/dist/server/request-preparation.js.map +1 -0
- package/dist/server/response-finalization.d.ts +34 -0
- package/dist/server/response-finalization.d.ts.map +1 -0
- package/dist/server/response-finalization.js +279 -0
- package/dist/server/response-finalization.js.map +1 -0
- package/dist/server/route-definitions.d.ts +139 -0
- package/dist/server/route-definitions.d.ts.map +1 -0
- package/dist/server/route-definitions.js +94 -0
- package/dist/server/route-definitions.js.map +1 -0
- package/dist/server/route-matching.d.ts +15 -0
- package/dist/server/route-matching.d.ts.map +1 -0
- package/dist/server/route-matching.js +46 -0
- package/dist/server/route-matching.js.map +1 -0
- package/dist/server/server.d.ts +6 -157
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +29 -1340
- package/dist/server/server.js.map +1 -1
- package/dist/server/use-case-route.d.ts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/index.d.ts +1 -1
- package/dist/tracing/index.d.ts.map +1 -1
- package/dist/tracing/index.js.map +1 -1
- package/dist/uploads/client.js +4 -7
- package/dist/uploads/client.js.map +1 -1
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +59 -12
- package/dist/uploads/index.js.map +1 -1
- package/package.json +5 -5
- package/skills/app-architecture/SKILL.md +31 -1
- package/src/agent-capabilities/index.ts +578 -0
- package/src/application/index.ts +19 -5
- package/src/client/client.ts +41 -17
- package/src/contracts/contract-builder.ts +8 -23
- package/src/contracts/contract-group.ts +4 -6
- package/src/contracts/metadata.ts +20 -0
- package/src/contracts/types.ts +39 -0
- package/src/events/index.ts +16 -20
- package/src/flags/index.ts +0 -5
- package/src/idempotency/index.ts +17 -9
- package/src/jobs/index.ts +8 -3
- package/src/notifications/index.ts +678 -70
- package/src/openapi/index.ts +24 -12
- package/src/ports/auth.ts +2 -0
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +0 -1
- package/src/providers/index.ts +1 -0
- package/src/providers/provider.ts +18 -3
- package/src/schedules/index.ts +57 -63
- package/src/server/index.ts +4 -2
- package/src/server/request-executor.ts +1170 -0
- package/src/server/request-preparation.ts +409 -0
- package/src/server/response-finalization.ts +385 -0
- package/src/server/route-definitions.ts +338 -0
- package/src/server/route-matching.ts +64 -0
- package/src/server/server.ts +63 -2171
- package/src/server/use-case-route.ts +1 -1
- package/src/testing/index.ts +2 -0
- package/src/tracing/index.ts +1 -0
- package/src/uploads/client.ts +4 -7
- package/src/uploads/index.ts +70 -12
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import {
|
|
3
|
+
createJobs,
|
|
4
|
+
type JobDef,
|
|
5
|
+
type JobDispatcher,
|
|
6
|
+
type JobHook,
|
|
7
|
+
type JobRetryOptions,
|
|
8
|
+
type JobTimeoutDuration,
|
|
9
|
+
retry,
|
|
10
|
+
} from "../jobs/index.js";
|
|
2
11
|
import type { SendMailOptions } from "../mail/index.js";
|
|
3
12
|
import type { ProviderInstrumentationTarget } from "../providers/index.js";
|
|
4
13
|
import {
|
|
@@ -60,7 +69,7 @@ export interface NotificationChannelResult {
|
|
|
60
69
|
/**
|
|
61
70
|
* Delivery outcome for this channel.
|
|
62
71
|
*/
|
|
63
|
-
status: "sent" | "skipped" | "failed";
|
|
72
|
+
status: "queued" | "sent" | "skipped" | "failed";
|
|
64
73
|
/**
|
|
65
74
|
* Provider delivery ID when available.
|
|
66
75
|
*/
|
|
@@ -79,6 +88,14 @@ export interface NotificationChannelResult {
|
|
|
79
88
|
details?: Record<string, unknown>;
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Original error captured for one failed notification channel.
|
|
93
|
+
*/
|
|
94
|
+
export interface NotificationChannelError {
|
|
95
|
+
channel: string;
|
|
96
|
+
error: unknown;
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
/**
|
|
83
100
|
* Arguments passed to a notification channel handler.
|
|
84
101
|
*/
|
|
@@ -119,6 +136,45 @@ export type NotificationChannels<Payload extends StandardSchema, Ctx> = Record<
|
|
|
119
136
|
NotificationChannelHandler<Payload, Ctx>
|
|
120
137
|
>;
|
|
121
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Arguments passed to an app-owned notification preference evaluator.
|
|
141
|
+
*/
|
|
142
|
+
export interface NotificationPreferenceArgs<
|
|
143
|
+
Payload extends StandardSchema = StandardSchema,
|
|
144
|
+
Ctx = unknown,
|
|
145
|
+
> extends NotificationChannelHandleArgs<Payload, Ctx> {
|
|
146
|
+
/**
|
|
147
|
+
* Optional metadata supplied by the notification sender.
|
|
148
|
+
*/
|
|
149
|
+
metadata?: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* App-owned decision for one notification channel.
|
|
154
|
+
*/
|
|
155
|
+
export interface NotificationPreferenceDecision {
|
|
156
|
+
/**
|
|
157
|
+
* Whether this channel should deliver.
|
|
158
|
+
*/
|
|
159
|
+
deliver: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Optional reason recorded when delivery is skipped.
|
|
162
|
+
*/
|
|
163
|
+
reason?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Optional app-facing port for notification channel preferences and opt-outs.
|
|
168
|
+
*/
|
|
169
|
+
export interface NotificationPreferencesPort<Ctx = unknown> {
|
|
170
|
+
/**
|
|
171
|
+
* Evaluate the current preference immediately before channel delivery.
|
|
172
|
+
*/
|
|
173
|
+
evaluate(
|
|
174
|
+
args: NotificationPreferenceArgs<StandardSchema, Ctx>,
|
|
175
|
+
): MaybePromise<NotificationPreferenceDecision>;
|
|
176
|
+
}
|
|
177
|
+
|
|
122
178
|
/**
|
|
123
179
|
* Notification definition created by `defineNotification(...)`.
|
|
124
180
|
*/
|
|
@@ -237,13 +293,23 @@ export interface InlineNotificationDispatcherOptions<Ctx> {
|
|
|
237
293
|
*/
|
|
238
294
|
ctx?: Ctx | (() => MaybePromise<Ctx>);
|
|
239
295
|
/**
|
|
240
|
-
* Called when a channel handler
|
|
241
|
-
* the
|
|
296
|
+
* Called when a channel handler or preference check fails. A returned result
|
|
297
|
+
* replaces the default failed result. Observer failures are ignored so the
|
|
298
|
+
* remaining channels still run.
|
|
242
299
|
*/
|
|
243
300
|
onError?: (
|
|
244
301
|
error: unknown,
|
|
245
302
|
args: NotificationChannelHandleArgs<StandardSchema, Ctx>,
|
|
246
303
|
) => MaybePromise<NotificationChannelResult | undefined>;
|
|
304
|
+
/**
|
|
305
|
+
* How completed channel failures are surfaced. Defaults to `"report"`.
|
|
306
|
+
* `"throw"` still runs every selected channel before rejecting.
|
|
307
|
+
*/
|
|
308
|
+
failureMode?: "report" | "throw";
|
|
309
|
+
/**
|
|
310
|
+
* Optional app-owned notification preference evaluator.
|
|
311
|
+
*/
|
|
312
|
+
preferences?: NotificationPreferencesPort<Ctx>;
|
|
247
313
|
/**
|
|
248
314
|
* Optional devtools/provider instrumentation target.
|
|
249
315
|
*/
|
|
@@ -349,6 +415,102 @@ export interface Notifications<Ctx> {
|
|
|
349
415
|
): NotificationDef<Name, Payload, Ctx>;
|
|
350
416
|
}
|
|
351
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Notification definitions available to durable delivery workers.
|
|
420
|
+
*/
|
|
421
|
+
export interface NotificationRegistry<Ctx = unknown> {
|
|
422
|
+
/**
|
|
423
|
+
* Registered definitions in declaration order.
|
|
424
|
+
*/
|
|
425
|
+
readonly definitions: readonly NotificationDef<string, StandardSchema, Ctx>[];
|
|
426
|
+
/**
|
|
427
|
+
* Resolve a notification definition by its stable name.
|
|
428
|
+
*/
|
|
429
|
+
get(name: string): NotificationDef<string, StandardSchema, Ctx> | undefined;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Payload carried by the first-party notification delivery job.
|
|
434
|
+
*/
|
|
435
|
+
export interface NotificationDeliveryJobPayload {
|
|
436
|
+
notificationName: string;
|
|
437
|
+
channel: string;
|
|
438
|
+
payload: unknown;
|
|
439
|
+
options: Omit<SendNotificationOptions, "channels">;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
type NotificationDeliveryPayloadSchema = StandardSchemaV1<
|
|
443
|
+
unknown,
|
|
444
|
+
NotificationDeliveryJobPayload
|
|
445
|
+
>;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Job definition used by queued notification dispatchers and workers.
|
|
449
|
+
*/
|
|
450
|
+
export interface NotificationDeliveryJob<
|
|
451
|
+
Name extends string = string,
|
|
452
|
+
Ctx = unknown,
|
|
453
|
+
> extends JobDef<Name, NotificationDeliveryPayloadSchema, Ctx> {
|
|
454
|
+
/**
|
|
455
|
+
* Registry used by both enqueue-time checks and worker delivery.
|
|
456
|
+
*/
|
|
457
|
+
readonly registry: NotificationRegistry<Ctx>;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Options for the first-party notification delivery job.
|
|
462
|
+
*/
|
|
463
|
+
export interface DefineNotificationDeliveryJobOptions<
|
|
464
|
+
Name extends string,
|
|
465
|
+
Ctx,
|
|
466
|
+
> {
|
|
467
|
+
/**
|
|
468
|
+
* Stable job name. Defaults to `"notifications.deliver"`.
|
|
469
|
+
*/
|
|
470
|
+
name?: Name;
|
|
471
|
+
/**
|
|
472
|
+
* Notification definitions available to the worker.
|
|
473
|
+
*/
|
|
474
|
+
registry: NotificationRegistry<Ctx>;
|
|
475
|
+
/**
|
|
476
|
+
* Optional app-owned preferences evaluated when the job runs.
|
|
477
|
+
*/
|
|
478
|
+
preferences?: NotificationPreferencesPort<Ctx>;
|
|
479
|
+
/**
|
|
480
|
+
* Retry policy. Defaults to exponential backoff with three attempts.
|
|
481
|
+
*/
|
|
482
|
+
retry?: JobRetryOptions;
|
|
483
|
+
/**
|
|
484
|
+
* Optional maximum duration for each channel delivery attempt.
|
|
485
|
+
*/
|
|
486
|
+
timeout?: JobTimeoutDuration;
|
|
487
|
+
/**
|
|
488
|
+
* Optional execution hooks applied to each delivery attempt.
|
|
489
|
+
*/
|
|
490
|
+
hooks?: readonly JobHook<
|
|
491
|
+
JobDef<Name, NotificationDeliveryPayloadSchema, Ctx>,
|
|
492
|
+
Ctx
|
|
493
|
+
>[];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Options for a notification dispatcher backed by Beignet jobs.
|
|
498
|
+
*/
|
|
499
|
+
export interface QueuedNotificationDispatcherOptions<Name extends string, Ctx> {
|
|
500
|
+
/**
|
|
501
|
+
* Job dispatcher used to enqueue one delivery job per channel.
|
|
502
|
+
*/
|
|
503
|
+
jobs: JobDispatcher;
|
|
504
|
+
/**
|
|
505
|
+
* Registered notification delivery job.
|
|
506
|
+
*/
|
|
507
|
+
deliveryJob: NotificationDeliveryJob<Name, Ctx>;
|
|
508
|
+
/**
|
|
509
|
+
* Optional devtools/provider instrumentation target.
|
|
510
|
+
*/
|
|
511
|
+
instrumentation?: ProviderInstrumentationTarget;
|
|
512
|
+
}
|
|
513
|
+
|
|
352
514
|
/**
|
|
353
515
|
* Error thrown when notification payload validation fails.
|
|
354
516
|
*/
|
|
@@ -379,28 +541,57 @@ export class NotificationDeliveryError extends Error {
|
|
|
379
541
|
*/
|
|
380
542
|
readonly notificationName: string;
|
|
381
543
|
/**
|
|
382
|
-
*
|
|
544
|
+
* First channel that failed, retained for concise error handling.
|
|
383
545
|
*/
|
|
384
546
|
readonly channel: string;
|
|
385
547
|
/**
|
|
386
|
-
* Original channel
|
|
548
|
+
* Original error for the first failed channel when available.
|
|
387
549
|
*/
|
|
388
550
|
readonly cause: unknown;
|
|
551
|
+
/**
|
|
552
|
+
* Complete notification result after every selected channel ran.
|
|
553
|
+
*/
|
|
554
|
+
readonly result: SendNotificationResult;
|
|
555
|
+
/**
|
|
556
|
+
* Failed channel results.
|
|
557
|
+
*/
|
|
558
|
+
readonly failures: readonly NotificationChannelResult[];
|
|
559
|
+
/**
|
|
560
|
+
* Original channel errors in delivery order.
|
|
561
|
+
*/
|
|
562
|
+
readonly errors: readonly NotificationChannelError[];
|
|
389
563
|
|
|
390
564
|
constructor(args: {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
cause: unknown;
|
|
565
|
+
result: SendNotificationResult;
|
|
566
|
+
errors?: readonly NotificationChannelError[];
|
|
394
567
|
}) {
|
|
568
|
+
const failures = args.result.results.filter(
|
|
569
|
+
(result) => result.status === "failed",
|
|
570
|
+
);
|
|
571
|
+
const firstFailure = failures[0];
|
|
572
|
+
const errors = args.errors ?? [];
|
|
395
573
|
super(
|
|
396
|
-
`Notification "${args.notificationName}" failed on channel "${
|
|
397
|
-
args.cause instanceof Error ? args.cause.message : String(args.cause)
|
|
398
|
-
}`,
|
|
574
|
+
`Notification "${args.result.notificationName}" failed on ${failures.length} channel${failures.length === 1 ? "" : "s"}: ${failures.map((failure) => failure.channel).join(", ")}.`,
|
|
399
575
|
);
|
|
400
576
|
this.name = "NotificationDeliveryError";
|
|
401
|
-
this.notificationName = args.notificationName;
|
|
402
|
-
this.channel =
|
|
403
|
-
this.cause =
|
|
577
|
+
this.notificationName = args.result.notificationName;
|
|
578
|
+
this.channel = firstFailure?.channel ?? "unknown";
|
|
579
|
+
this.cause = errors.find(
|
|
580
|
+
(entry) => entry.channel === firstFailure?.channel,
|
|
581
|
+
)?.error;
|
|
582
|
+
this.result = args.result;
|
|
583
|
+
this.failures = failures;
|
|
584
|
+
this.errors = errors;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Error thrown when a notification registry cannot safely resolve a delivery.
|
|
590
|
+
*/
|
|
591
|
+
export class NotificationRegistryError extends Error {
|
|
592
|
+
constructor(message: string) {
|
|
593
|
+
super(message);
|
|
594
|
+
this.name = "NotificationRegistryError";
|
|
404
595
|
}
|
|
405
596
|
}
|
|
406
597
|
|
|
@@ -471,7 +662,7 @@ function resolveChannelNames(
|
|
|
471
662
|
const selected = options?.channels ?? available;
|
|
472
663
|
|
|
473
664
|
for (const channel of selected) {
|
|
474
|
-
if (
|
|
665
|
+
if (typeof notification.channels[channel] !== "function") {
|
|
475
666
|
throw new Error(
|
|
476
667
|
`Notification "${notification.name}" does not define channel "${channel}".`,
|
|
477
668
|
);
|
|
@@ -482,13 +673,75 @@ function resolveChannelNames(
|
|
|
482
673
|
}
|
|
483
674
|
|
|
484
675
|
function summarizeResults(results: readonly NotificationChannelResult[]) {
|
|
676
|
+
const queued = results.filter((result) => result.status === "queued").length;
|
|
485
677
|
const sent = results.filter((result) => result.status === "sent").length;
|
|
486
678
|
const skipped = results.filter(
|
|
487
679
|
(result) => result.status === "skipped",
|
|
488
680
|
).length;
|
|
489
681
|
const failed = results.filter((result) => result.status === "failed").length;
|
|
490
682
|
|
|
491
|
-
return { sent, skipped, failed };
|
|
683
|
+
return { queued, sent, skipped, failed };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function resultSummary(results: readonly NotificationChannelResult[]): string {
|
|
687
|
+
const summary = summarizeResults(results);
|
|
688
|
+
const parts = [
|
|
689
|
+
summary.queued ? `${summary.queued} queued` : undefined,
|
|
690
|
+
`${summary.sent} sent`,
|
|
691
|
+
`${summary.skipped} skipped`,
|
|
692
|
+
`${summary.failed} failed`,
|
|
693
|
+
].filter((part): part is string => Boolean(part));
|
|
694
|
+
|
|
695
|
+
return parts.join(", ");
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function recordChannelResult(
|
|
699
|
+
instrumentation: ReturnType<typeof createProviderInstrumentation>,
|
|
700
|
+
notificationName: string,
|
|
701
|
+
result: NotificationChannelResult,
|
|
702
|
+
options: SendNotificationOptions,
|
|
703
|
+
): void {
|
|
704
|
+
instrumentation.custom({
|
|
705
|
+
name: `notification.channel.${result.status}`,
|
|
706
|
+
label: `Notification channel ${result.status}`,
|
|
707
|
+
summary: `${notificationName} (${result.channel})`,
|
|
708
|
+
requestId: options.requestId,
|
|
709
|
+
traceId: options.traceId,
|
|
710
|
+
spanId: options.spanId,
|
|
711
|
+
parentSpanId: options.parentSpanId,
|
|
712
|
+
traceparent: options.traceparent,
|
|
713
|
+
details: {
|
|
714
|
+
notificationName,
|
|
715
|
+
channel: result.channel,
|
|
716
|
+
status: result.status,
|
|
717
|
+
id: result.id,
|
|
718
|
+
provider: result.provider,
|
|
719
|
+
reason: result.reason,
|
|
720
|
+
resultDetails: result.details,
|
|
721
|
+
metadata: options.metadata,
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async function failedChannelResult<Ctx>(
|
|
727
|
+
error: unknown,
|
|
728
|
+
args: NotificationChannelHandleArgs<StandardSchema, Ctx>,
|
|
729
|
+
onError: InlineNotificationDispatcherOptions<Ctx>["onError"],
|
|
730
|
+
): Promise<NotificationChannelResult> {
|
|
731
|
+
if (onError) {
|
|
732
|
+
try {
|
|
733
|
+
const handled = await onError(error, args);
|
|
734
|
+
if (handled) return { ...handled, channel: args.channel };
|
|
735
|
+
} catch {
|
|
736
|
+
// Error observers cannot interrupt delivery of the remaining channels.
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return {
|
|
741
|
+
channel: args.channel,
|
|
742
|
+
status: "failed",
|
|
743
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
744
|
+
};
|
|
492
745
|
}
|
|
493
746
|
|
|
494
747
|
function defineNotificationImpl<
|
|
@@ -508,6 +761,171 @@ function defineNotificationImpl<
|
|
|
508
761
|
};
|
|
509
762
|
}
|
|
510
763
|
|
|
764
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
765
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function contextInstrumentationTarget(
|
|
769
|
+
ctx: unknown,
|
|
770
|
+
): ProviderInstrumentationTarget {
|
|
771
|
+
if (!isRecord(ctx)) return undefined;
|
|
772
|
+
return isRecord(ctx.ports) ? ctx.ports : ctx;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const notificationDeliveryPayloadSchema: NotificationDeliveryPayloadSchema = {
|
|
776
|
+
"~standard": {
|
|
777
|
+
version: 1,
|
|
778
|
+
vendor: "beignet",
|
|
779
|
+
validate(value: unknown) {
|
|
780
|
+
if (!isRecord(value)) {
|
|
781
|
+
return { issues: [{ message: "Expected an object." }] };
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const issues: StandardSchemaV1.Issue[] = [];
|
|
785
|
+
if (
|
|
786
|
+
typeof value.notificationName !== "string" ||
|
|
787
|
+
value.notificationName.length === 0
|
|
788
|
+
) {
|
|
789
|
+
issues.push({
|
|
790
|
+
message: "Expected a non-empty string.",
|
|
791
|
+
path: ["notificationName"],
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (typeof value.channel !== "string" || value.channel.length === 0) {
|
|
795
|
+
issues.push({
|
|
796
|
+
message: "Expected a non-empty string.",
|
|
797
|
+
path: ["channel"],
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (!("payload" in value)) {
|
|
801
|
+
issues.push({ message: "Required.", path: ["payload"] });
|
|
802
|
+
}
|
|
803
|
+
if (!isRecord(value.options)) {
|
|
804
|
+
issues.push({ message: "Expected an object.", path: ["options"] });
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const deliveryOptions = isRecord(value.options) ? value.options : {};
|
|
808
|
+
const correlationFields = [
|
|
809
|
+
"requestId",
|
|
810
|
+
"traceId",
|
|
811
|
+
"spanId",
|
|
812
|
+
"parentSpanId",
|
|
813
|
+
"traceparent",
|
|
814
|
+
] as const;
|
|
815
|
+
for (const field of correlationFields) {
|
|
816
|
+
if (
|
|
817
|
+
deliveryOptions[field] !== undefined &&
|
|
818
|
+
typeof deliveryOptions[field] !== "string"
|
|
819
|
+
) {
|
|
820
|
+
issues.push({
|
|
821
|
+
message: "Expected a string.",
|
|
822
|
+
path: ["options", field],
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (
|
|
827
|
+
deliveryOptions.metadata !== undefined &&
|
|
828
|
+
!isRecord(deliveryOptions.metadata)
|
|
829
|
+
) {
|
|
830
|
+
issues.push({
|
|
831
|
+
message: "Expected an object.",
|
|
832
|
+
path: ["options", "metadata"],
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (issues.length > 0) return { issues };
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
value: {
|
|
840
|
+
notificationName: value.notificationName as string,
|
|
841
|
+
channel: value.channel as string,
|
|
842
|
+
payload: value.payload,
|
|
843
|
+
options: queuedSendOptions(deliveryOptions),
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Define the notification catalog available to durable delivery workers.
|
|
852
|
+
* Duplicate names throw because queued delivery resolves definitions by name.
|
|
853
|
+
*/
|
|
854
|
+
export function defineNotificationRegistry<Ctx>(
|
|
855
|
+
definitions: readonly NotificationDef<string, StandardSchema, Ctx>[],
|
|
856
|
+
): NotificationRegistry<Ctx> {
|
|
857
|
+
const byName = new Map<
|
|
858
|
+
string,
|
|
859
|
+
NotificationDef<string, StandardSchema, Ctx>
|
|
860
|
+
>();
|
|
861
|
+
|
|
862
|
+
for (const notification of definitions) {
|
|
863
|
+
if (byName.has(notification.name)) {
|
|
864
|
+
throw new NotificationRegistryError(
|
|
865
|
+
`Duplicate notification definition "${notification.name}" in notification registry.`,
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
byName.set(notification.name, notification);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return {
|
|
872
|
+
definitions: [...definitions],
|
|
873
|
+
get(name) {
|
|
874
|
+
return byName.get(name);
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Define the generic job that resolves and delivers one notification channel.
|
|
881
|
+
* Register the returned job with every worker or outbox registry that can
|
|
882
|
+
* receive queued notifications.
|
|
883
|
+
*/
|
|
884
|
+
export function defineNotificationDeliveryJob<
|
|
885
|
+
Ctx,
|
|
886
|
+
Name extends string = "notifications.deliver",
|
|
887
|
+
>(
|
|
888
|
+
options: DefineNotificationDeliveryJobOptions<Name, Ctx>,
|
|
889
|
+
): NotificationDeliveryJob<Name, Ctx> {
|
|
890
|
+
const name = (options.name ?? "notifications.deliver") as Name;
|
|
891
|
+
const { defineJob } = createJobs<Ctx>();
|
|
892
|
+
const job = defineJob(name, {
|
|
893
|
+
payload: notificationDeliveryPayloadSchema,
|
|
894
|
+
description: "Deliver one notification channel.",
|
|
895
|
+
retry: options.retry ?? retry.exponential({ attempts: 3 }),
|
|
896
|
+
timeout: options.timeout,
|
|
897
|
+
hooks: options.hooks,
|
|
898
|
+
async handle({ payload, ctx }) {
|
|
899
|
+
const notification = options.registry.get(payload.notificationName);
|
|
900
|
+
if (!notification) {
|
|
901
|
+
throw new NotificationRegistryError(
|
|
902
|
+
`Notification "${payload.notificationName}" is not registered for queued delivery.`,
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
if (!notification.channels[payload.channel]) {
|
|
906
|
+
throw new NotificationRegistryError(
|
|
907
|
+
`Notification "${payload.notificationName}" does not define queued channel "${payload.channel}".`,
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const dispatcher = createInlineNotificationDispatcher<Ctx>({
|
|
912
|
+
ctx,
|
|
913
|
+
preferences: options.preferences,
|
|
914
|
+
failureMode: "throw",
|
|
915
|
+
instrumentation: contextInstrumentationTarget(ctx),
|
|
916
|
+
});
|
|
917
|
+
await dispatcher.send(notification as NotificationDef, payload.payload, {
|
|
918
|
+
...payload.options,
|
|
919
|
+
channels: [payload.channel],
|
|
920
|
+
});
|
|
921
|
+
},
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
return Object.assign(job, {
|
|
925
|
+
registry: options.registry,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
511
929
|
/**
|
|
512
930
|
* Validate and parse a notification payload with the notification's Standard
|
|
513
931
|
* Schema.
|
|
@@ -524,8 +942,9 @@ export async function parseNotificationPayload<
|
|
|
524
942
|
* Create an inline notification dispatcher.
|
|
525
943
|
*
|
|
526
944
|
* The dispatcher validates payloads and runs selected channel handlers
|
|
527
|
-
* immediately.
|
|
528
|
-
*
|
|
945
|
+
* immediately. Channel failures are isolated and reported after every selected
|
|
946
|
+
* channel runs. Use this directly in tests and local apps, or use
|
|
947
|
+
* `createQueuedNotificationDispatcher(...)` for background execution.
|
|
529
948
|
*/
|
|
530
949
|
export function createInlineNotificationDispatcher<Ctx>(
|
|
531
950
|
options: InlineNotificationDispatcherOptions<Ctx> = {},
|
|
@@ -548,6 +967,7 @@ export function createInlineNotificationDispatcher<Ctx>(
|
|
|
548
967
|
const channels = resolveChannelNames(notification, sendOptions);
|
|
549
968
|
const ctx = await resolveCtx(options.ctx);
|
|
550
969
|
const results: NotificationChannelResult[] = [];
|
|
970
|
+
const errors: NotificationChannelError[] = [];
|
|
551
971
|
|
|
552
972
|
instrumentation.custom({
|
|
553
973
|
name: "notification.send.started",
|
|
@@ -567,64 +987,80 @@ export function createInlineNotificationDispatcher<Ctx>(
|
|
|
567
987
|
|
|
568
988
|
for (const channel of channels) {
|
|
569
989
|
const handler = notification.channels[channel];
|
|
990
|
+
const args = {
|
|
991
|
+
notification,
|
|
992
|
+
payload: parsed,
|
|
993
|
+
ctx,
|
|
994
|
+
channel,
|
|
995
|
+
} satisfies NotificationChannelHandleArgs<StandardSchema, Ctx>;
|
|
570
996
|
|
|
571
997
|
try {
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
ctx,
|
|
576
|
-
channel,
|
|
998
|
+
const preference = await options.preferences?.evaluate({
|
|
999
|
+
...args,
|
|
1000
|
+
metadata: sendOptions.metadata,
|
|
577
1001
|
});
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
result ?? {
|
|
1002
|
+
if (preference && !preference.deliver) {
|
|
1003
|
+
const result = {
|
|
581
1004
|
channel,
|
|
582
|
-
status: "
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1005
|
+
status: "skipped",
|
|
1006
|
+
reason:
|
|
1007
|
+
preference.reason ?? "Disabled by notification preferences.",
|
|
1008
|
+
details: {
|
|
1009
|
+
source: "preferences",
|
|
1010
|
+
},
|
|
1011
|
+
} satisfies NotificationChannelResult;
|
|
1012
|
+
results.push(result);
|
|
1013
|
+
recordChannelResult(
|
|
1014
|
+
instrumentation,
|
|
1015
|
+
notification.name,
|
|
1016
|
+
result,
|
|
1017
|
+
sendOptions,
|
|
1018
|
+
);
|
|
595
1019
|
continue;
|
|
596
1020
|
}
|
|
597
1021
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1022
|
+
const handled = await handler(args);
|
|
1023
|
+
const result = handled
|
|
1024
|
+
? { ...handled, channel }
|
|
1025
|
+
: ({
|
|
1026
|
+
channel,
|
|
1027
|
+
status: "sent",
|
|
1028
|
+
} satisfies NotificationChannelResult);
|
|
1029
|
+
|
|
1030
|
+
results.push(result);
|
|
1031
|
+
recordChannelResult(
|
|
1032
|
+
instrumentation,
|
|
1033
|
+
notification.name,
|
|
1034
|
+
result,
|
|
1035
|
+
sendOptions,
|
|
1036
|
+
);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
errors.push({ channel, error });
|
|
1039
|
+
const result = await failedChannelResult(
|
|
1040
|
+
error,
|
|
1041
|
+
args,
|
|
1042
|
+
options.onError,
|
|
1043
|
+
);
|
|
1044
|
+
results.push(result);
|
|
1045
|
+
recordChannelResult(
|
|
1046
|
+
instrumentation,
|
|
1047
|
+
notification.name,
|
|
1048
|
+
result,
|
|
1049
|
+
sendOptions,
|
|
1050
|
+
);
|
|
620
1051
|
}
|
|
621
1052
|
}
|
|
622
1053
|
|
|
623
|
-
const
|
|
1054
|
+
const result = {
|
|
1055
|
+
notificationName: notification.name,
|
|
1056
|
+
payload: parsed,
|
|
1057
|
+
channels,
|
|
1058
|
+
results,
|
|
1059
|
+
} satisfies SendNotificationResult;
|
|
624
1060
|
instrumentation.custom({
|
|
625
1061
|
name: "notification.send.completed",
|
|
626
|
-
label: "Notification
|
|
627
|
-
summary: `${notification.name} (${
|
|
1062
|
+
label: "Notification completed",
|
|
1063
|
+
summary: `${notification.name} (${resultSummary(results)})`,
|
|
628
1064
|
requestId: sendOptions.requestId,
|
|
629
1065
|
traceId: sendOptions.traceId,
|
|
630
1066
|
spanId: sendOptions.spanId,
|
|
@@ -638,12 +1074,14 @@ export function createInlineNotificationDispatcher<Ctx>(
|
|
|
638
1074
|
},
|
|
639
1075
|
});
|
|
640
1076
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
1077
|
+
if (
|
|
1078
|
+
options.failureMode === "throw" &&
|
|
1079
|
+
results.some((channelResult) => channelResult.status === "failed")
|
|
1080
|
+
) {
|
|
1081
|
+
throw new NotificationDeliveryError({ result, errors });
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
return result;
|
|
647
1085
|
},
|
|
648
1086
|
};
|
|
649
1087
|
}
|
|
@@ -705,6 +1143,176 @@ export function createInlineNotificationsProvider(
|
|
|
705
1143
|
});
|
|
706
1144
|
}
|
|
707
1145
|
|
|
1146
|
+
function queuedSendOptions(
|
|
1147
|
+
options: Partial<Record<keyof SendNotificationOptions, unknown>>,
|
|
1148
|
+
): Omit<SendNotificationOptions, "channels"> {
|
|
1149
|
+
const queued: Omit<SendNotificationOptions, "channels"> = {};
|
|
1150
|
+
if (isRecord(options.metadata)) queued.metadata = options.metadata;
|
|
1151
|
+
if (typeof options.requestId === "string")
|
|
1152
|
+
queued.requestId = options.requestId;
|
|
1153
|
+
if (typeof options.traceId === "string") queued.traceId = options.traceId;
|
|
1154
|
+
if (typeof options.spanId === "string") queued.spanId = options.spanId;
|
|
1155
|
+
if (typeof options.parentSpanId === "string") {
|
|
1156
|
+
queued.parentSpanId = options.parentSpanId;
|
|
1157
|
+
}
|
|
1158
|
+
if (typeof options.traceparent === "string")
|
|
1159
|
+
queued.traceparent = options.traceparent;
|
|
1160
|
+
return queued;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Create a notification dispatcher that enqueues one delivery job per channel.
|
|
1165
|
+
* Separate jobs keep provider retries from resending channels that already
|
|
1166
|
+
* completed successfully.
|
|
1167
|
+
*/
|
|
1168
|
+
export function createQueuedNotificationDispatcher<Name extends string, Ctx>(
|
|
1169
|
+
options: QueuedNotificationDispatcherOptions<Name, Ctx>,
|
|
1170
|
+
): NotificationPort {
|
|
1171
|
+
const instrumentation = createProviderInstrumentation(
|
|
1172
|
+
options.instrumentation,
|
|
1173
|
+
{
|
|
1174
|
+
providerName: "queued-notifications",
|
|
1175
|
+
watcher: "notifications",
|
|
1176
|
+
},
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
return {
|
|
1180
|
+
async send<N extends NotificationDef>(
|
|
1181
|
+
notification: N,
|
|
1182
|
+
payload: InferNotificationPayload<N>,
|
|
1183
|
+
sendOptions: SendNotificationOptions = {},
|
|
1184
|
+
) {
|
|
1185
|
+
const registered = options.deliveryJob.registry.get(notification.name);
|
|
1186
|
+
if (!registered) {
|
|
1187
|
+
throw new NotificationRegistryError(
|
|
1188
|
+
`Notification "${notification.name}" is not registered for queued delivery.`,
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const parsed = await parseNotificationPayload(registered, payload);
|
|
1193
|
+
const channels = resolveChannelNames(registered, sendOptions);
|
|
1194
|
+
const results: NotificationChannelResult[] = [];
|
|
1195
|
+
|
|
1196
|
+
instrumentation.custom({
|
|
1197
|
+
name: "notification.enqueue.started",
|
|
1198
|
+
label: "Notification enqueue started",
|
|
1199
|
+
summary: notification.name,
|
|
1200
|
+
requestId: sendOptions.requestId,
|
|
1201
|
+
traceId: sendOptions.traceId,
|
|
1202
|
+
spanId: sendOptions.spanId,
|
|
1203
|
+
parentSpanId: sendOptions.parentSpanId,
|
|
1204
|
+
traceparent: sendOptions.traceparent,
|
|
1205
|
+
details: {
|
|
1206
|
+
notificationName: notification.name,
|
|
1207
|
+
channels,
|
|
1208
|
+
metadata: sendOptions.metadata,
|
|
1209
|
+
jobName: options.deliveryJob.name,
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
for (const channel of channels) {
|
|
1214
|
+
let result: NotificationChannelResult;
|
|
1215
|
+
try {
|
|
1216
|
+
await options.jobs.dispatch(options.deliveryJob, {
|
|
1217
|
+
notificationName: notification.name,
|
|
1218
|
+
channel,
|
|
1219
|
+
payload: parsed,
|
|
1220
|
+
options: queuedSendOptions(sendOptions),
|
|
1221
|
+
});
|
|
1222
|
+
result = {
|
|
1223
|
+
channel,
|
|
1224
|
+
status: "queued",
|
|
1225
|
+
provider: "jobs",
|
|
1226
|
+
details: {
|
|
1227
|
+
jobName: options.deliveryJob.name,
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
result = {
|
|
1232
|
+
channel,
|
|
1233
|
+
status: "failed",
|
|
1234
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1235
|
+
details: {
|
|
1236
|
+
phase: "enqueue",
|
|
1237
|
+
jobName: options.deliveryJob.name,
|
|
1238
|
+
},
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
results.push(result);
|
|
1243
|
+
recordChannelResult(
|
|
1244
|
+
instrumentation,
|
|
1245
|
+
notification.name,
|
|
1246
|
+
result,
|
|
1247
|
+
sendOptions,
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const result = {
|
|
1252
|
+
notificationName: notification.name,
|
|
1253
|
+
payload: parsed,
|
|
1254
|
+
channels,
|
|
1255
|
+
results,
|
|
1256
|
+
} satisfies SendNotificationResult;
|
|
1257
|
+
instrumentation.custom({
|
|
1258
|
+
name: "notification.enqueue.completed",
|
|
1259
|
+
label: "Notification enqueue completed",
|
|
1260
|
+
summary: `${notification.name} (${resultSummary(results)})`,
|
|
1261
|
+
requestId: sendOptions.requestId,
|
|
1262
|
+
traceId: sendOptions.traceId,
|
|
1263
|
+
spanId: sendOptions.spanId,
|
|
1264
|
+
parentSpanId: sendOptions.parentSpanId,
|
|
1265
|
+
traceparent: sendOptions.traceparent,
|
|
1266
|
+
details: {
|
|
1267
|
+
notificationName: notification.name,
|
|
1268
|
+
channels,
|
|
1269
|
+
results,
|
|
1270
|
+
metadata: sendOptions.metadata,
|
|
1271
|
+
jobName: options.deliveryJob.name,
|
|
1272
|
+
},
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
return result;
|
|
1276
|
+
},
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Options for the queued notifications provider.
|
|
1282
|
+
*/
|
|
1283
|
+
export interface QueuedNotificationsProviderOptions<Name extends string, Ctx> {
|
|
1284
|
+
/**
|
|
1285
|
+
* Registered notification delivery job.
|
|
1286
|
+
*/
|
|
1287
|
+
deliveryJob: NotificationDeliveryJob<Name, Ctx>;
|
|
1288
|
+
/**
|
|
1289
|
+
* Provider name. Defaults to `"queued-notifications"`.
|
|
1290
|
+
*/
|
|
1291
|
+
name?: string;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Create a provider that contributes a job-backed notification dispatcher.
|
|
1296
|
+
*/
|
|
1297
|
+
export function createQueuedNotificationsProvider<Name extends string, Ctx>(
|
|
1298
|
+
options: QueuedNotificationsProviderOptions<Name, Ctx>,
|
|
1299
|
+
) {
|
|
1300
|
+
return createProvider<{ jobs: JobDispatcher }>()({
|
|
1301
|
+
name: options.name ?? "queued-notifications",
|
|
1302
|
+
setup({ ports }) {
|
|
1303
|
+
return {
|
|
1304
|
+
ports: {
|
|
1305
|
+
notifications: createQueuedNotificationDispatcher({
|
|
1306
|
+
jobs: ports.jobs,
|
|
1307
|
+
deliveryJob: options.deliveryJob,
|
|
1308
|
+
instrumentation: ports,
|
|
1309
|
+
}),
|
|
1310
|
+
} satisfies InlineNotificationsProviderPorts,
|
|
1311
|
+
};
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
|
|
708
1316
|
/**
|
|
709
1317
|
* Define a mail-backed notification channel.
|
|
710
1318
|
*
|