@classytic/arc-notifications 0.9.0 → 0.10.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/README.md +15 -0
- package/dist/index.d.mts +321 -62
- package/dist/index.mjs +594 -247
- package/llms.txt +29 -0
- package/package.json +8 -6
- package/skills/arc-notifications/SKILL.md +129 -0
- package/skills/arc-notifications/references/date-rules.md +55 -0
- package/skills/arc-notifications/references/delivery-log.md +33 -0
- package/skills/arc-notifications/references/feed.md +55 -0
- package/skills/arc-notifications/references/triggers-templates.md +36 -0
package/README.md
CHANGED
|
@@ -129,6 +129,21 @@ sseHub.onConnect((userId, orgId) => backfill.onConnect(userId, orgId)); // debou
|
|
|
129
129
|
badge's axis). Both stay host-owned in the repository.
|
|
130
130
|
- All realtime is **best-effort on top of the durable feed** — a dead socket never
|
|
131
131
|
fails a request or a send; clients converge via `GET /notifications/`.
|
|
132
|
+
- **Cursor replay.** Pass the client's last-seen time on connect —
|
|
133
|
+
`backfill.onConnect(userId, orgId, { since: req.headers['last-event-id'] })` — and
|
|
134
|
+
a repository that also implements `listSince(org, user, since, limit)` replays the
|
|
135
|
+
gap, including rows an earlier connection received but dropped; they merge with the
|
|
136
|
+
claimed rows into the same single bulk event.
|
|
137
|
+
- **List envelope and verbs.** `inAppFeedResource({ listEnvelope: 'offset' })` answers
|
|
138
|
+
repo-core's `OffsetPaginationResult` (`feedListResult` is the same conversion);
|
|
139
|
+
`{ actions: true }` also mounts `markRead` / `markAllRead` as arc action-router verbs.
|
|
140
|
+
- **One push per recipient.** The package calls `port.push` once per recipient and event
|
|
141
|
+
and never publishes on a bus itself. If your port forwards frames onto an event bus,
|
|
142
|
+
give each frame its own correlation id (it is a transport hop, not a second publish of
|
|
143
|
+
one logical event), or arc's dev duplicate-publish detector will warn on every
|
|
144
|
+
multi-recipient notification.
|
|
145
|
+
- **Clock.** `createUnreadCountCache({ clock })` and
|
|
146
|
+
`createRepositoryDeliveryLog(repo, { clock })` take a `{ now(): Date }`.
|
|
132
147
|
|
|
133
148
|
### 5. Durable dispatch — crash-safe bulk sends (10k+)
|
|
134
149
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { BaseChannel, ChannelConfig, DeliveryLog, DeliveryLogEntry, NotificationPayload, NotificationService, NotificationServiceConfig, QueueAdapter, Recipient, SendResult, TemplateResolver } from "@classytic/notifications";
|
|
2
|
-
import { JobDefinition, JobDispatcher } from "@classytic/arc/integrations/jobs";
|
|
3
1
|
import { ArcModule, EventHandlerDefinition } from "@classytic/arc/factory";
|
|
2
|
+
import { BaseChannel, ChannelConfig, DeliveryLog, DeliveryLogEntry, DispatchResult, NotificationPayload, NotificationService, NotificationServiceConfig, QueueAdapter, Recipient, SendResult, TemplateResolver } from "@classytic/notifications";
|
|
3
|
+
import { JobDefinition, JobDispatcher } from "@classytic/arc/integrations/jobs";
|
|
4
4
|
import { PermissionCheck } from "@classytic/arc/permissions";
|
|
5
|
+
import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
5
6
|
import { FastifyInstance, FastifyRequest } from "fastify";
|
|
6
7
|
import { DomainEvent } from "@classytic/primitives/events";
|
|
7
8
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
8
|
-
//#region src/inApp.d.ts
|
|
9
9
|
/**
|
|
10
10
|
* In-app notification content — the shared seam between a `notifyOn` trigger's
|
|
11
11
|
* declarative `inApp` template and the channels that deliver it (the DB feed
|
|
@@ -23,7 +23,7 @@ import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
|
23
23
|
* in-app convention; the email template layer keeps its own `${var}` engine.
|
|
24
24
|
*/
|
|
25
25
|
/** Reserved `payload.data` key carrying the rendered in-app content. */
|
|
26
|
-
declare const INAPP_DATA_KEY = "__inApp";
|
|
26
|
+
export declare const INAPP_DATA_KEY = "__inApp";
|
|
27
27
|
/**
|
|
28
28
|
* Host realtime transport — push an event to one user within a tenant.
|
|
29
29
|
* The transport itself (connection registry, heartbeats, the `/stream`
|
|
@@ -59,11 +59,21 @@ interface InAppContent {
|
|
|
59
59
|
readonly priority: InAppPriority;
|
|
60
60
|
}
|
|
61
61
|
/** Render a trigger's in-app template against event data + priority. */
|
|
62
|
-
declare function renderInApp(template: InAppTemplate, vars: Record<string, unknown>, priority?: InAppPriority): InAppContent;
|
|
62
|
+
export declare function renderInApp(template: InAppTemplate, vars: Record<string, unknown>, priority?: InAppPriority): InAppContent;
|
|
63
63
|
/** Read the rendered in-app content a trigger parked on the send payload's data. */
|
|
64
|
-
declare function readInAppContent(data: Record<string, unknown> | undefined): InAppContent | null;
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
export declare function readInAppContent(data: Record<string, unknown> | undefined): InAppContent | null;
|
|
65
|
+
/**
|
|
66
|
+
* The notification clock — one seam so every "now" the in-app feed, the reconnect backfill
|
|
67
|
+
* and the delivery log stamp goes through a single injectable source.
|
|
68
|
+
*
|
|
69
|
+
* Structural on purpose: a host's own `Clock` (`{ now(): Date }`) satisfies it with no
|
|
70
|
+
* adapter, and a test passes a frozen or stepped one to make unread-cache expiry, ordering
|
|
71
|
+
* and `timestamp` stamps deterministic. The default is real time.
|
|
72
|
+
*/
|
|
73
|
+
interface NotificationClock {
|
|
74
|
+
now(): Date;
|
|
75
|
+
}
|
|
76
|
+
export declare const systemNotificationClock: NotificationClock;
|
|
67
77
|
/** A feed row the {@link InAppChannel} writes and this resource reads. */
|
|
68
78
|
interface InAppFeedRow {
|
|
69
79
|
readonly organizationId: string;
|
|
@@ -124,6 +134,18 @@ interface InAppFeedRepository<TRow = unknown> {
|
|
|
124
134
|
* the debounce window) from double-toasting the same rows.
|
|
125
135
|
*/
|
|
126
136
|
claimUndelivered?(organizationId: string, userId: string, limit: number): Promise<TRow[]>;
|
|
137
|
+
/**
|
|
138
|
+
* OPTIONAL — rows created AFTER `since`, oldest first, WITHOUT claiming them. Powers the
|
|
139
|
+
* cursor flavour of reconnect backfill (`onConnect(user, org, { since })`): a client that
|
|
140
|
+
* remembers the time of its last event (SSE `Last-Event-ID`) replays exactly the gap,
|
|
141
|
+
* including rows an earlier connection already received but dropped before rendering.
|
|
142
|
+
*/
|
|
143
|
+
listSince?(organizationId: string, userId: string, since: Date, limit: number): Promise<TRow[]>;
|
|
144
|
+
/**
|
|
145
|
+
* OPTIONAL — stamp one row delivered (a live push reached a client), so the next
|
|
146
|
+
* reconnect does not re-send it. `false` when not found / not owned.
|
|
147
|
+
*/
|
|
148
|
+
markDelivered?(organizationId: string, userId: string, id: string): Promise<boolean>;
|
|
127
149
|
/**
|
|
128
150
|
* OPTIONAL — delete one row (user dismissed it for good); `false` when not
|
|
129
151
|
* found / not owned. When implemented, the feed resource registers
|
|
@@ -168,13 +190,15 @@ interface CreateUnreadCountCacheOptions {
|
|
|
168
190
|
readonly ttlMs?: number;
|
|
169
191
|
/** Entry cap — oldest entries evicted past it (bounds memory). Default 10_000. */
|
|
170
192
|
readonly maxEntries?: number;
|
|
193
|
+
/** Source of "now" for entry freshness. Default real time. */
|
|
194
|
+
readonly clock?: NotificationClock;
|
|
171
195
|
}
|
|
172
196
|
/**
|
|
173
197
|
* In-process TTL cache for unread counts. Per-process: invalidations don't
|
|
174
198
|
* cross workers, so a multi-process host either accepts up-to-TTL staleness on
|
|
175
199
|
* the other workers or brings a Redis-backed {@link UnreadCountCache}.
|
|
176
200
|
*/
|
|
177
|
-
declare function createUnreadCountCache(options?: CreateUnreadCountCacheOptions): UnreadCountCache;
|
|
201
|
+
export declare function createUnreadCountCache(options?: CreateUnreadCountCacheOptions): UnreadCountCache;
|
|
178
202
|
/** The current user + tenant, resolved off a request by the host. */
|
|
179
203
|
interface InAppFeedScope {
|
|
180
204
|
readonly userId: string;
|
|
@@ -206,10 +230,31 @@ interface InAppFeedResourceOptions {
|
|
|
206
230
|
* instance with `InAppChannel` so new-row writes invalidate too.
|
|
207
231
|
*/
|
|
208
232
|
readonly unreadCache?: UnreadCountCache;
|
|
233
|
+
/**
|
|
234
|
+
* Shape of `GET <prefix>/`. `"native"` (default) answers the port's `{ data, pagination }`
|
|
235
|
+
* page untouched; `"offset"` answers repo-core's canonical `OffsetPaginationResult` (see
|
|
236
|
+
* {@link feedListResult}) for a host whose every other list is that shape.
|
|
237
|
+
*/
|
|
238
|
+
readonly listEnvelope?: "native" | "offset";
|
|
239
|
+
/**
|
|
240
|
+
* Also mount `markRead` (`POST /:id/action`) and `markAllRead` (`POST /action`) as arc
|
|
241
|
+
* action-router verbs ({@link FEED_ACTION_VERBS}, camelCase), beside the `PATCH` routes.
|
|
242
|
+
* Same handlers, same cache invalidation and cross-tab read-sync. Default false.
|
|
243
|
+
*/
|
|
244
|
+
readonly actions?: boolean;
|
|
209
245
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
246
|
+
/** The action-router verbs {@link InAppFeedResourceOptions.actions} mounts. */
|
|
247
|
+
export declare const FEED_ACTION_VERBS: {
|
|
248
|
+
readonly markRead: "markRead";
|
|
249
|
+
readonly markAllRead: "markAllRead";
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* A feed page in repo-core's canonical offset envelope. Pure and exported so a host that
|
|
253
|
+
* builds its own list route (or asserts the envelope in a test) converts the same way the
|
|
254
|
+
* resource does. `hasNext`/`hasPrev` follow from the page arithmetic the port already did.
|
|
255
|
+
*/
|
|
256
|
+
export declare function feedListResult<TRow>(page: InAppFeedPage<TRow>): OffsetPaginationResult<TRow>;
|
|
257
|
+
export declare function inAppFeedResource(options: InAppFeedResourceOptions): import("@classytic/arc").ResourceDefinition<import("@classytic/arc").AnyRecord>;
|
|
213
258
|
interface InAppBackfillOptions {
|
|
214
259
|
/** Feed repository — MUST implement the optional `claimUndelivered`. */
|
|
215
260
|
readonly repository: InAppFeedRepository;
|
|
@@ -224,15 +269,18 @@ interface InAppBackfillOptions {
|
|
|
224
269
|
/** Claim/push failure sink (backfill is best-effort). Default: swallow. */
|
|
225
270
|
readonly onError?: (error: unknown) => void;
|
|
226
271
|
}
|
|
272
|
+
/** Per-connection hints for {@link InAppBackfill.onConnect}. */
|
|
273
|
+
interface InAppBackfillConnect {
|
|
274
|
+
/** Replay rows created after this instant (a `Last-Event-ID` / `since` cursor). */
|
|
275
|
+
readonly since?: Date | string | number | undefined;
|
|
276
|
+
}
|
|
227
277
|
interface InAppBackfill {
|
|
228
278
|
/** Call from the host's SSE/WS connection handler. Sync, never throws. */
|
|
229
|
-
onConnect(userId: string, organizationId: string): void;
|
|
279
|
+
onConnect(userId: string, organizationId: string, connect?: InAppBackfillConnect): void;
|
|
230
280
|
/** Cancel pending debounces (graceful shutdown / tests). */
|
|
231
281
|
close(): void;
|
|
232
282
|
}
|
|
233
|
-
declare function createInAppBackfill(options: InAppBackfillOptions): InAppBackfill;
|
|
234
|
-
//#endregion
|
|
235
|
-
//#region src/triggerOverride.d.ts
|
|
283
|
+
export declare function createInAppBackfill(options: InAppBackfillOptions): InAppBackfill;
|
|
236
284
|
/**
|
|
237
285
|
* Notification trigger OVERRIDES — the admin-editable half of the registry.
|
|
238
286
|
*
|
|
@@ -299,7 +347,7 @@ declare function createInAppBackfill(options: InAppBackfillOptions): InAppBackfi
|
|
|
299
347
|
* which a host fills from its integrations registry. A channel nobody registered
|
|
300
348
|
* is simply not offered, instead of being unrepresentable.
|
|
301
349
|
*/
|
|
302
|
-
declare const IN_APP_CHANNEL = "in-app";
|
|
350
|
+
export declare const IN_APP_CHANNEL = "in-app";
|
|
303
351
|
/**
|
|
304
352
|
* Open by design — see above. Kept as a named alias rather than inlining
|
|
305
353
|
* `string` so the intent reads at every call site and a future narrowing has one
|
|
@@ -390,7 +438,7 @@ interface CreateCachedTriggerOverrideStoreOptions {
|
|
|
390
438
|
* any mutation so an operator's change applies within one save rather than one
|
|
391
439
|
* TTL window.
|
|
392
440
|
*/
|
|
393
|
-
declare function createCachedTriggerOverrideStore(load: TriggerOverrideLoader, options?: CreateCachedTriggerOverrideStoreOptions): TriggerOverrideStore & {
|
|
441
|
+
export declare function createCachedTriggerOverrideStore(load: TriggerOverrideLoader, options?: CreateCachedTriggerOverrideStoreOptions): TriggerOverrideStore & {
|
|
394
442
|
invalidate: () => void;
|
|
395
443
|
};
|
|
396
444
|
/**
|
|
@@ -435,7 +483,7 @@ interface CodeTriggerDefaults {
|
|
|
435
483
|
*/
|
|
436
484
|
sourceChannels?: readonly string[] | undefined;
|
|
437
485
|
}
|
|
438
|
-
declare function resolveEffectiveSettings(triggerType: string, organizationId: string | null, rows: readonly NotificationTriggerOverrideDoc[], defaults: CodeTriggerDefaults): EffectiveTriggerSettings;
|
|
486
|
+
export declare function resolveEffectiveSettings(triggerType: string, organizationId: string | null, rows: readonly NotificationTriggerOverrideDoc[], defaults: CodeTriggerDefaults): EffectiveTriggerSettings;
|
|
439
487
|
/**
|
|
440
488
|
* Why a notification was not delivered.
|
|
441
489
|
*
|
|
@@ -476,9 +524,7 @@ type NotificationDecision = {
|
|
|
476
524
|
* is ever consulted, so an event with no `sourceChannel` under no restriction
|
|
477
525
|
* still delivers.
|
|
478
526
|
*/
|
|
479
|
-
declare function evaluateSourceChannel(sourceChannels: string[] | null | undefined, sourceChannel: string | undefined): NotificationDecision;
|
|
480
|
-
//#endregion
|
|
481
|
-
//#region src/channelCatalog.d.ts
|
|
527
|
+
export declare function evaluateSourceChannel(sourceChannels: string[] | null | undefined, sourceChannel: string | undefined): NotificationDecision;
|
|
482
528
|
/**
|
|
483
529
|
* Which channels a deployment can deliver on.
|
|
484
530
|
*
|
|
@@ -509,9 +555,206 @@ interface NotificationChannelCatalog {
|
|
|
509
555
|
* than stored, so a misconfigured provider cannot introduce an unusable ""
|
|
510
556
|
* channel that validation would then accept.
|
|
511
557
|
*/
|
|
512
|
-
declare function createNotificationChannelCatalog(connectorChannels?: readonly string[]): NotificationChannelCatalog;
|
|
513
|
-
|
|
514
|
-
|
|
558
|
+
export declare function createNotificationChannelCatalog(connectorChannels?: readonly string[]): NotificationChannelCatalog;
|
|
559
|
+
/** `YYYY-MM-DD`. */
|
|
560
|
+
type CivilDateString = string;
|
|
561
|
+
/** True for a real calendar date in `YYYY-MM-DD` form. */
|
|
562
|
+
export declare function isCivilDateString(value: unknown): value is CivilDateString;
|
|
563
|
+
/** `date` moved by `days` (negative = earlier). */
|
|
564
|
+
export declare function addCivilDays(date: CivilDateString, days: number): CivilDateString;
|
|
565
|
+
/** Whole days from `a` to `b` (`b - a`). */
|
|
566
|
+
export declare function civilDaysBetween(a: CivilDateString, b: CivilDateString): number;
|
|
567
|
+
/** When a rule fires, relative to the item's date. */
|
|
568
|
+
interface DateRuleSchedule {
|
|
569
|
+
/**
|
|
570
|
+
* Days from the date to the FIRST occurrence: negative = before (`-30` = 30 days
|
|
571
|
+
* before), `0` = on the day, positive = after (`1` = the day after: "overdue").
|
|
572
|
+
*/
|
|
573
|
+
readonly offsetDays: number;
|
|
574
|
+
/** Fire again every N days after the first occurrence while the item stays open. Omit = once. */
|
|
575
|
+
readonly repeatEveryDays?: number;
|
|
576
|
+
/** Bounds on repetition. Without it a repeating rule runs until the item is no longer open. */
|
|
577
|
+
readonly until?: {
|
|
578
|
+
/** Total occurrences, the first included. */
|
|
579
|
+
readonly occurrences?: number;
|
|
580
|
+
/** No occurrence after `date + offsetDays` (relative to the item's date, like `offsetDays`). */
|
|
581
|
+
readonly offsetDays?: number;
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* One admin-defined date rule. `TAudience` is the host's audience selector — this
|
|
586
|
+
* package never interprets it; it is handed to the host's `resolveAudience`.
|
|
587
|
+
*/
|
|
588
|
+
interface DateRule<TAudience = unknown> {
|
|
589
|
+
/** Stable id: part of every ledger key, so it must not change for the life of the rule. */
|
|
590
|
+
readonly id: string;
|
|
591
|
+
/** Default true. */
|
|
592
|
+
readonly enabled?: boolean;
|
|
593
|
+
/** A registered {@link DateRuleSource} key. */
|
|
594
|
+
readonly source: string;
|
|
595
|
+
/** One of that source's `dateFields`. */
|
|
596
|
+
readonly dateField: string;
|
|
597
|
+
readonly schedule: DateRuleSchedule;
|
|
598
|
+
readonly audience: TAudience;
|
|
599
|
+
/** Restrict to these channel names. Omit = every channel that applies. */
|
|
600
|
+
readonly channels?: readonly string[];
|
|
601
|
+
/**
|
|
602
|
+
* Template id / notification kind (the feed row's type). Default
|
|
603
|
+
* `<source>.<dateField>`. With a template resolver configured on the service it
|
|
604
|
+
* also names the email template.
|
|
605
|
+
*/
|
|
606
|
+
readonly template?: string;
|
|
607
|
+
/** In-app copy with `{var}` placeholders resolved against the item's `data`. */
|
|
608
|
+
readonly inApp?: InAppTemplate;
|
|
609
|
+
readonly priority?: InAppPriority;
|
|
610
|
+
/** Scope passed to the source; `null`/absent = every organization the source serves. */
|
|
611
|
+
readonly organizationId?: string | null;
|
|
612
|
+
}
|
|
613
|
+
/** One record a source reports, with the date the rule's `dateField` names. */
|
|
614
|
+
interface DateRuleItem {
|
|
615
|
+
/** Stable record id (part of the ledger key). */
|
|
616
|
+
readonly id: string;
|
|
617
|
+
/** The `dateField` value, `YYYY-MM-DD`. */
|
|
618
|
+
readonly date: CivilDateString;
|
|
619
|
+
/** Template variables (and anything the host's audience resolver needs). */
|
|
620
|
+
readonly data: Record<string, unknown>;
|
|
621
|
+
/** The business document, for the delivery log's per-document history. */
|
|
622
|
+
readonly subject?: {
|
|
623
|
+
readonly sourceModel: string;
|
|
624
|
+
readonly sourceId: string;
|
|
625
|
+
};
|
|
626
|
+
readonly organizationId?: string;
|
|
627
|
+
}
|
|
628
|
+
interface DateRuleSourceQuery {
|
|
629
|
+
readonly dateField: string;
|
|
630
|
+
/** Inclusive lower bound; absent = unbounded (an open-ended repeating rule). */
|
|
631
|
+
readonly from?: CivilDateString;
|
|
632
|
+
/** Inclusive upper bound. */
|
|
633
|
+
readonly to: CivilDateString;
|
|
634
|
+
readonly organizationId?: string | null;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* A host record type that carries dates. Registered by the host (one per record
|
|
638
|
+
* type); the admin screen lists them through {@link DateRuleEngine.catalog}.
|
|
639
|
+
*/
|
|
640
|
+
interface DateRuleSource {
|
|
641
|
+
readonly key: string;
|
|
642
|
+
readonly label?: string;
|
|
643
|
+
readonly dateFields: ReadonlyArray<{
|
|
644
|
+
readonly key: string;
|
|
645
|
+
readonly label?: string;
|
|
646
|
+
}>;
|
|
647
|
+
/** Template variables items carry, as a hint for rule authors (`{employeeName}`). */
|
|
648
|
+
readonly variables?: readonly string[];
|
|
649
|
+
/**
|
|
650
|
+
* OPEN items whose `dateField` lies in `[from, to]`. "Open" is the source's call:
|
|
651
|
+
* an item that no longer needs reminding must not be returned.
|
|
652
|
+
*/
|
|
653
|
+
find(query: DateRuleSourceQuery): Promise<readonly DateRuleItem[]> | AsyncIterable<DateRuleItem>;
|
|
654
|
+
}
|
|
655
|
+
type DateRuleAudienceResolver<TAudience = unknown> = (audience: TAudience, context: {
|
|
656
|
+
readonly rule: DateRule<TAudience>;
|
|
657
|
+
readonly item: DateRuleItem;
|
|
658
|
+
readonly occurrence: DateRuleOccurrence;
|
|
659
|
+
}) => readonly Recipient[] | null | undefined | Promise<readonly Recipient[] | null | undefined>;
|
|
660
|
+
/**
|
|
661
|
+
* "Has this occurrence fired?" — claimed BEFORE dispatch, released on failure so the
|
|
662
|
+
* next sweep retries. Back it with a unique index in production; the memory ledger
|
|
663
|
+
* is for tests and single-process dev.
|
|
664
|
+
*/
|
|
665
|
+
interface DateRuleLedger {
|
|
666
|
+
/** `true` when this call claimed the key (first time), `false` when already claimed. */
|
|
667
|
+
claim(key: string, info: DateRuleLedgerEntry): Promise<boolean>;
|
|
668
|
+
/** Undo a claim whose dispatch failed. */
|
|
669
|
+
release(key: string): Promise<void>;
|
|
670
|
+
}
|
|
671
|
+
interface DateRuleLedgerEntry {
|
|
672
|
+
readonly ruleId: string;
|
|
673
|
+
readonly itemId: string;
|
|
674
|
+
readonly occurrenceOn: CivilDateString;
|
|
675
|
+
readonly index: number;
|
|
676
|
+
readonly sweptOn: CivilDateString;
|
|
677
|
+
}
|
|
678
|
+
export declare function createMemoryDateRuleLedger(): DateRuleLedger & {
|
|
679
|
+
keys(): string[];
|
|
680
|
+
};
|
|
681
|
+
interface DateRuleOccurrence {
|
|
682
|
+
/** 0-based occurrence index. */
|
|
683
|
+
readonly index: number;
|
|
684
|
+
/** The civil date this occurrence is scheduled for. */
|
|
685
|
+
readonly on: CivilDateString;
|
|
686
|
+
}
|
|
687
|
+
/** Problems with a schedule, as messages; empty = valid. */
|
|
688
|
+
export declare function validateDateRuleSchedule(schedule: DateRuleSchedule): string[];
|
|
689
|
+
/**
|
|
690
|
+
* The occurrence of `schedule` for an item dated `date` that a sweep on `today`
|
|
691
|
+
* should send, or `null`. The latest occurrence on or before `today` is due when it
|
|
692
|
+
* is at most `graceDays` old — so a missed sweep catches up, but a rule created
|
|
693
|
+
* today does not replay years of history.
|
|
694
|
+
*/
|
|
695
|
+
export declare function dueOccurrence(schedule: DateRuleSchedule, date: CivilDateString, today: CivilDateString, graceDays?: number): DateRuleOccurrence | null;
|
|
696
|
+
/** The `[from, to]` range of item dates a sweep on `today` must ask a source for. */
|
|
697
|
+
export declare function dateRuleWindow(schedule: DateRuleSchedule, today: CivilDateString, graceDays?: number): {
|
|
698
|
+
from?: CivilDateString;
|
|
699
|
+
to: CivilDateString;
|
|
700
|
+
};
|
|
701
|
+
interface DateRuleEngineOptions<TAudience = unknown> {
|
|
702
|
+
readonly sources: Iterable<DateRuleSource>;
|
|
703
|
+
/** The rules to evaluate (admin-created rows); disabled rows are skipped. */
|
|
704
|
+
readonly rules: () => readonly DateRule<TAudience>[] | Promise<readonly DateRule<TAudience>[]>;
|
|
705
|
+
readonly resolveAudience: DateRuleAudienceResolver<TAudience>;
|
|
706
|
+
/** The notification service (read per sweep, so a module export can back it). */
|
|
707
|
+
readonly service: () => NotificationService;
|
|
708
|
+
/** Default: an in-memory ledger (single process, lost on restart). */
|
|
709
|
+
readonly ledger?: DateRuleLedger;
|
|
710
|
+
/** How many days late an occurrence may still be sent (missed sweeps). Default 0. */
|
|
711
|
+
readonly graceDays?: number;
|
|
712
|
+
/** Extra send data per item (e.g. email `subject`/`text` rendered from the rule's copy). */
|
|
713
|
+
readonly data?: (rule: DateRule<TAudience>, item: DateRuleItem) => Record<string, unknown>;
|
|
714
|
+
/** A rule or item failed; the sweep continues. */
|
|
715
|
+
readonly onError?: (err: unknown, context: {
|
|
716
|
+
readonly ruleId: string;
|
|
717
|
+
readonly itemId?: string;
|
|
718
|
+
}) => void;
|
|
719
|
+
}
|
|
720
|
+
interface DateRuleSweepResult {
|
|
721
|
+
readonly date: CivilDateString;
|
|
722
|
+
/** Enabled rules evaluated. */
|
|
723
|
+
readonly rules: number;
|
|
724
|
+
/** Items whose occurrence was due. */
|
|
725
|
+
readonly due: number;
|
|
726
|
+
/** Occurrences dispatched by this sweep. */
|
|
727
|
+
readonly fired: number;
|
|
728
|
+
/** Occurrences already claimed (an earlier sweep, another replica). */
|
|
729
|
+
readonly duplicates: number;
|
|
730
|
+
/** Occurrences with nobody to tell. */
|
|
731
|
+
readonly noRecipients: number;
|
|
732
|
+
readonly failed: number;
|
|
733
|
+
}
|
|
734
|
+
interface DateRuleCatalogEntry {
|
|
735
|
+
readonly key: string;
|
|
736
|
+
readonly label: string;
|
|
737
|
+
readonly dateFields: ReadonlyArray<{
|
|
738
|
+
readonly key: string;
|
|
739
|
+
readonly label: string;
|
|
740
|
+
}>;
|
|
741
|
+
readonly variables: readonly string[];
|
|
742
|
+
}
|
|
743
|
+
interface DateRuleEngine<TAudience = unknown> {
|
|
744
|
+
/** Evaluate every enabled rule for the civil date `today`. */
|
|
745
|
+
sweep(today: CivilDateString): Promise<DateRuleSweepResult>;
|
|
746
|
+
/** Items a rule would notify on `today` (no claims, no sends): the admin "preview". */
|
|
747
|
+
preview(rule: DateRule<TAudience>, today: CivilDateString): Promise<Array<DateRuleItem & {
|
|
748
|
+
occurrence: DateRuleOccurrence;
|
|
749
|
+
}>>;
|
|
750
|
+
/** Registered sources for the rule editor. */
|
|
751
|
+
catalog(): DateRuleCatalogEntry[];
|
|
752
|
+
/** Problems with a rule against the registered sources, as messages; empty = valid. */
|
|
753
|
+
validate(rule: Pick<DateRule<TAudience>, "source" | "dateField" | "schedule">): string[];
|
|
754
|
+
}
|
|
755
|
+
/** Ledger key of one occurrence. */
|
|
756
|
+
export declare const dateRuleOccurrenceKey: (ruleId: string, itemId: string, on: CivilDateString) => string;
|
|
757
|
+
export declare function createDateRuleEngine<TAudience = unknown>(options: DateRuleEngineOptions<TAudience>): DateRuleEngine<TAudience>;
|
|
515
758
|
/** One draftable template row — the shape be-prod's EmailTemplate model proved. */
|
|
516
759
|
interface StoredTemplate {
|
|
517
760
|
readonly key: string;
|
|
@@ -547,9 +790,7 @@ interface DbFirstTemplateResolver {
|
|
|
547
790
|
/** Drop one key (after a draft is saved) or the whole cache. */
|
|
548
791
|
invalidate(key?: string): void;
|
|
549
792
|
}
|
|
550
|
-
declare function createDbFirstResolver(options: DbFirstResolverOptions): DbFirstTemplateResolver;
|
|
551
|
-
//#endregion
|
|
552
|
-
//#region src/inAppChannel.d.ts
|
|
793
|
+
export declare function createDbFirstResolver(options: DbFirstResolverOptions): DbFirstTemplateResolver;
|
|
553
794
|
interface InAppChannelConfig extends ChannelConfig {
|
|
554
795
|
/** Feed store the channel writes rows to (host-owned persistence). */
|
|
555
796
|
readonly repository: InAppFeedRepository;
|
|
@@ -571,7 +812,7 @@ interface InAppChannelConfig extends ChannelConfig {
|
|
|
571
812
|
*/
|
|
572
813
|
readonly unreadCache?: UnreadCountCache;
|
|
573
814
|
}
|
|
574
|
-
declare class InAppChannel extends BaseChannel<InAppChannelConfig> {
|
|
815
|
+
export declare class InAppChannel extends BaseChannel<InAppChannelConfig> {
|
|
575
816
|
private readonly repository;
|
|
576
817
|
private readonly port;
|
|
577
818
|
private readonly eventName;
|
|
@@ -582,8 +823,6 @@ declare class InAppChannel extends BaseChannel<InAppChannelConfig> {
|
|
|
582
823
|
send(payload: NotificationPayload): Promise<SendResult>;
|
|
583
824
|
private orgOf;
|
|
584
825
|
}
|
|
585
|
-
//#endregion
|
|
586
|
-
//#region src/jobsQueue.d.ts
|
|
587
826
|
interface NotificationsJobOptions {
|
|
588
827
|
/** Job/queue name registered with `jobsPlugin`. Default "notifications". */
|
|
589
828
|
readonly name?: string;
|
|
@@ -619,9 +858,7 @@ interface NotificationsJob {
|
|
|
619
858
|
* `enqueue`/`getJob`/`size` through `fastify.jobs`, keying `enqueue` on
|
|
620
859
|
* `idempotencyKey` so crash-recovery / at-least-once redelivery dedupes.
|
|
621
860
|
*/
|
|
622
|
-
declare function notificationsJob(options?: NotificationsJobOptions): NotificationsJob;
|
|
623
|
-
//#endregion
|
|
624
|
-
//#region src/module.d.ts
|
|
861
|
+
export declare function notificationsJob(options?: NotificationsJobOptions): NotificationsJob;
|
|
625
862
|
/**
|
|
626
863
|
* Resolve a trigger's host-typed `audience` selector to concrete recipients —
|
|
627
864
|
* the shared seam so branch-role → members (or team → users, tenant → admins)
|
|
@@ -728,13 +965,13 @@ declare module "fastify" {
|
|
|
728
965
|
notifications?: NotificationService;
|
|
729
966
|
}
|
|
730
967
|
}
|
|
731
|
-
declare function notificationsModule(config: NotificationsModuleConfig): ArcModule<NotificationService>;
|
|
968
|
+
export declare function notificationsModule(config: NotificationsModuleConfig): ArcModule<NotificationService>;
|
|
732
969
|
/**
|
|
733
970
|
* Triggers as arc `eventHandlers` — what `notifyOn` subscribes, exported so a BRIDGE module can own
|
|
734
971
|
* the triggers for one producer (`dependsOn: [producer, "notifications"]`) and vanish with it.
|
|
735
972
|
* `service` is read per dispatch, so a bridge can resolve it from the notifications module's export.
|
|
736
973
|
*/
|
|
737
|
-
declare function notifyTriggerHandlers(options: {
|
|
974
|
+
export declare function notifyTriggerHandlers(options: {
|
|
738
975
|
/** Handler-name prefix; arc rejects duplicate handler names across the module graph. */
|
|
739
976
|
readonly name: string;
|
|
740
977
|
readonly service: () => NotificationService;
|
|
@@ -742,11 +979,9 @@ declare function notifyTriggerHandlers(options: {
|
|
|
742
979
|
readonly recipientResolver?: RecipientResolver;
|
|
743
980
|
}): EventHandlerDefinition[];
|
|
744
981
|
/** One event → N sends. Exported for direct unit testing. */
|
|
745
|
-
declare function dispatchTrigger(service: NotificationService, trigger: NotifyTrigger, event: DomainEvent, opts?: {
|
|
982
|
+
export declare function dispatchTrigger(service: NotificationService, trigger: NotifyTrigger, event: DomainEvent, opts?: {
|
|
746
983
|
recipientResolver?: RecipientResolver;
|
|
747
984
|
}): Promise<void>;
|
|
748
|
-
//#endregion
|
|
749
|
-
//#region src/repositoryDeliveryLog.d.ts
|
|
750
985
|
type EntryStatus = DeliveryLogEntry["status"];
|
|
751
986
|
/** The persisted document shape (also what the resource/UI reads). */
|
|
752
987
|
interface DeliveryLogDoc {
|
|
@@ -774,21 +1009,54 @@ interface DeliveryLogDoc {
|
|
|
774
1009
|
readonly sourceModel: string;
|
|
775
1010
|
readonly sourceId: string;
|
|
776
1011
|
};
|
|
1012
|
+
/**
|
|
1013
|
+
* The authored rule (an admin-defined trigger) that produced this delivery, when there
|
|
1014
|
+
* is one. A top-level column — not a `metadata` key — so a store can index it and
|
|
1015
|
+
* {@link RepositoryDeliveryLog.listByRule} is a keyed read.
|
|
1016
|
+
*/
|
|
1017
|
+
readonly ruleId?: string;
|
|
1018
|
+
/** Host-typed columns stored verbatim beside the package's own. Never read by the package. */
|
|
1019
|
+
readonly extraFields?: Record<string, unknown>;
|
|
777
1020
|
}
|
|
778
|
-
interface RepositoryDeliveryLogOptions {
|
|
1021
|
+
interface RepositoryDeliveryLogOptions<TExtra extends Record<string, unknown> = Record<string, unknown>> {
|
|
779
1022
|
/** Cap for `query()` when the caller gives no limit. Default 100. */
|
|
780
1023
|
readonly defaultLimit?: number;
|
|
1024
|
+
/** Source of the entry `timestamp`. Default real time. */
|
|
1025
|
+
readonly clock?: NotificationClock;
|
|
1026
|
+
/**
|
|
1027
|
+
* Which authored rule a send belongs to. Default: `payload.metadata.ruleId` when it is a
|
|
1028
|
+
* non-empty string. A host whose rule id rides elsewhere (an event name such as
|
|
1029
|
+
* `notify.rule.<id>`) supplies its own resolver.
|
|
1030
|
+
*/
|
|
1031
|
+
readonly ruleId?: (payload: NotificationPayload) => string | undefined;
|
|
1032
|
+
/** Host-typed columns to persist on the entry (`DeliveryLogDoc.extraFields`). */
|
|
1033
|
+
readonly extraFields?: (payload: NotificationPayload, dispatch: DispatchResult) => TExtra | undefined;
|
|
1034
|
+
}
|
|
1035
|
+
/** A delivery entry with the rule and host columns the plain `DeliveryLogEntry` has no slot for. */
|
|
1036
|
+
type RuleDeliveryLogEntry<TExtra extends Record<string, unknown> = Record<string, unknown>> = DeliveryLogEntry & {
|
|
1037
|
+
readonly ruleId?: string;
|
|
1038
|
+
readonly extraFields?: TExtra;
|
|
1039
|
+
};
|
|
1040
|
+
interface DeliveryLogByRuleQuery {
|
|
1041
|
+
/** Cap on rows returned. Falls back to the log's `defaultLimit`. */
|
|
1042
|
+
readonly limit?: number;
|
|
1043
|
+
readonly status?: EntryStatus;
|
|
1044
|
+
readonly after?: Date;
|
|
1045
|
+
readonly before?: Date;
|
|
1046
|
+
}
|
|
1047
|
+
/** The kernel `DeliveryLog` plus the per-rule read. Assignable wherever a `DeliveryLog` is. */
|
|
1048
|
+
interface RepositoryDeliveryLog<TExtra extends Record<string, unknown> = Record<string, unknown>> extends DeliveryLog {
|
|
1049
|
+
/** Newest-first deliveries of ONE authored rule. */
|
|
1050
|
+
listByRule(ruleId: string, query?: DeliveryLogByRuleQuery): Promise<RuleDeliveryLogEntry<TExtra>[]>;
|
|
781
1051
|
}
|
|
782
|
-
declare function createRepositoryDeliveryLog(repository: RepositoryLike, options?: RepositoryDeliveryLogOptions):
|
|
783
|
-
//#endregion
|
|
784
|
-
//#region src/sseChannel.d.ts
|
|
1052
|
+
export declare function createRepositoryDeliveryLog<TExtra extends Record<string, unknown> = Record<string, unknown>>(repository: RepositoryLike, options?: RepositoryDeliveryLogOptions<TExtra>): RepositoryDeliveryLog<TExtra>;
|
|
785
1053
|
interface SseChannelConfig extends ChannelConfig {
|
|
786
1054
|
/** Host realtime transport (be-prod's SSE manager, a WS hub, ...). */
|
|
787
1055
|
readonly port: RealtimePushPort;
|
|
788
1056
|
/** SSE event name pushed to the client. Default "notification". */
|
|
789
1057
|
readonly eventName?: string;
|
|
790
1058
|
}
|
|
791
|
-
declare class SseChannel extends BaseChannel<SseChannelConfig> {
|
|
1059
|
+
export declare class SseChannel extends BaseChannel<SseChannelConfig> {
|
|
792
1060
|
private readonly port;
|
|
793
1061
|
private readonly eventName;
|
|
794
1062
|
constructor(config: SseChannelConfig);
|
|
@@ -796,8 +1064,6 @@ declare class SseChannel extends BaseChannel<SseChannelConfig> {
|
|
|
796
1064
|
send(payload: NotificationPayload): Promise<SendResult>;
|
|
797
1065
|
private orgOf;
|
|
798
1066
|
}
|
|
799
|
-
//#endregion
|
|
800
|
-
//#region src/templatesResource.d.ts
|
|
801
1067
|
/** Code-defined template map (same shape `createSimpleResolver` accepts). */
|
|
802
1068
|
type CodeTemplates = Record<string, {
|
|
803
1069
|
readonly subject?: string;
|
|
@@ -824,9 +1090,7 @@ interface TemplatesResourceOptions {
|
|
|
824
1090
|
/** Tenant field. Default `false` — templates are company-wide (be-prod convention). */
|
|
825
1091
|
readonly tenantField?: string | false;
|
|
826
1092
|
}
|
|
827
|
-
declare function templatesResource(options: TemplatesResourceOptions): import("@classytic/arc").ResourceDefinition<unknown>;
|
|
828
|
-
//#endregion
|
|
829
|
-
//#region src/triggerAuthoring.d.ts
|
|
1093
|
+
export declare function templatesResource(options: TemplatesResourceOptions): import("@classytic/arc").ResourceDefinition<unknown>;
|
|
830
1094
|
/** Who a trigger targets. */
|
|
831
1095
|
type NotificationAudienceType = "staff" | "customer";
|
|
832
1096
|
/** Direct contact for a customer-facing notification. */
|
|
@@ -946,8 +1210,6 @@ interface NotificationTrigger {
|
|
|
946
1210
|
*/
|
|
947
1211
|
extract: (payload: Record<string, any>) => NotificationExtractResult | null;
|
|
948
1212
|
}
|
|
949
|
-
//#endregion
|
|
950
|
-
//#region src/triggerAdapter.d.ts
|
|
951
1213
|
/**
|
|
952
1214
|
* `entityType` → the `extract` variable holding that entity's id.
|
|
953
1215
|
*
|
|
@@ -955,7 +1217,7 @@ interface NotificationTrigger {
|
|
|
955
1217
|
* the variable it lives under is named per entity. This is the default
|
|
956
1218
|
* commerce mapping; a host with its own entity kinds passes `entityIdVars`.
|
|
957
1219
|
*/
|
|
958
|
-
declare const DEFAULT_ENTITY_ID_VARS: Record<string, string>;
|
|
1220
|
+
export declare const DEFAULT_ENTITY_ID_VARS: Record<string, string>;
|
|
959
1221
|
interface TriggerAdapterOptions {
|
|
960
1222
|
/**
|
|
961
1223
|
* Extra `entityType` → id-variable mappings, merged over the defaults.
|
|
@@ -1051,14 +1313,14 @@ interface TriggerOverrideResolution {
|
|
|
1051
1313
|
* calls both per dispatch, so an `extract` with side effects would run them
|
|
1052
1314
|
* twice — keep extractors pure (every one in practice is a payload reshape).
|
|
1053
1315
|
*/
|
|
1054
|
-
declare function toNotifyTrigger(t: NotificationTrigger, options?: TriggerAdapterOptions): NotifyTrigger;
|
|
1316
|
+
export declare function toNotifyTrigger(t: NotificationTrigger, options?: TriggerAdapterOptions): NotifyTrigger;
|
|
1055
1317
|
/**
|
|
1056
1318
|
* Adapt a whole registry — what a host assigns to the module's `notifyOn`.
|
|
1057
1319
|
*
|
|
1058
1320
|
* With `options.overrides`, each trigger becomes one entry per channel, gated on
|
|
1059
1321
|
* the admin's stored settings. Without it, the mapping is 1:1 and unchanged.
|
|
1060
1322
|
*/
|
|
1061
|
-
declare function toNotifyTriggers(triggers: readonly NotificationTrigger[], options?: TriggerAdapterOptions): NotifyTrigger[];
|
|
1323
|
+
export declare function toNotifyTriggers(triggers: readonly NotificationTrigger[], options?: TriggerAdapterOptions): NotifyTrigger[];
|
|
1062
1324
|
/**
|
|
1063
1325
|
* First trigger registered for an event.
|
|
1064
1326
|
*
|
|
@@ -1067,20 +1329,18 @@ declare function toNotifyTriggers(triggers: readonly NotificationTrigger[], opti
|
|
|
1067
1329
|
* returns only the first. For anything that must see all of them — a docs page,
|
|
1068
1330
|
* an admin matrix, a dispatch count — use `getTriggersByEvent`.
|
|
1069
1331
|
*/
|
|
1070
|
-
declare function getTriggerByEvent(triggers: readonly NotificationTrigger[], event: string): NotificationTrigger | undefined;
|
|
1332
|
+
export declare function getTriggerByEvent(triggers: readonly NotificationTrigger[], event: string): NotificationTrigger | undefined;
|
|
1071
1333
|
/** Every trigger registered for an event, in registration order. */
|
|
1072
|
-
declare function getTriggersByEvent(triggers: readonly NotificationTrigger[], event: string): NotificationTrigger[];
|
|
1334
|
+
export declare function getTriggersByEvent(triggers: readonly NotificationTrigger[], event: string): NotificationTrigger[];
|
|
1073
1335
|
/** Distinct event names the registry subscribes to (for docs / admin UI). */
|
|
1074
|
-
declare function getRegisteredEvents(triggers: readonly NotificationTrigger[]): string[];
|
|
1336
|
+
export declare function getRegisteredEvents(triggers: readonly NotificationTrigger[]): string[];
|
|
1075
1337
|
/**
|
|
1076
1338
|
* Notification kind → recipient roles (for the admin UI).
|
|
1077
1339
|
*
|
|
1078
1340
|
* Keyed by `type ?? event`, so two triggers sharing an event stay distinct
|
|
1079
1341
|
* rows — which is exactly why they carry different `type`s.
|
|
1080
1342
|
*/
|
|
1081
|
-
declare function getRecipientMatrix(triggers: readonly NotificationTrigger[]): Record<string, string[]>;
|
|
1082
|
-
//#endregion
|
|
1083
|
-
//#region src/triggerCatalog.d.ts
|
|
1343
|
+
export declare function getRecipientMatrix(triggers: readonly NotificationTrigger[]): Record<string, string[]>;
|
|
1084
1344
|
/**
|
|
1085
1345
|
* One trigger as an operator sees it, with the CODE defaults an override replaces.
|
|
1086
1346
|
*
|
|
@@ -1121,5 +1381,4 @@ interface TriggerCatalogEntry {
|
|
|
1121
1381
|
* exists", which is the silent-empty failure this file exists to avoid.
|
|
1122
1382
|
*/
|
|
1123
1383
|
type TriggerCatalogPort = () => ReadonlyArray<TriggerCatalogEntry> | Promise<ReadonlyArray<TriggerCatalogEntry>>;
|
|
1124
|
-
|
|
1125
|
-
export { type CodeTemplates, type CodeTriggerDefaults, type CreateCachedTriggerOverrideStoreOptions, type CreateUnreadCountCacheOptions, DEFAULT_ENTITY_ID_VARS, type DbFirstResolverOptions, type DbFirstTemplateResolver, type DeliveryLogDoc, type EffectiveTriggerSettings, INAPP_DATA_KEY, IN_APP_CHANNEL, type InAppBackfill, type InAppBackfillOptions, InAppChannel, type InAppChannelConfig, type InAppContent, type InAppFeedListOptions, type InAppFeedPage, type InAppFeedRepository, type InAppFeedResourceOptions, type InAppFeedRow, type InAppFeedScope, type InAppPriority, type InAppTemplate, type NotificationAudienceType, type NotificationChannel, type NotificationChannelCatalog, type NotificationDecision, type NotificationExtractResult, type NotificationRecipientContact, type NotificationSkipReason, type NotificationTrigger, type NotificationTriggerOverrideDoc, type NotificationsJob, type NotificationsJobOptions, type NotificationsModuleConfig, type NotifyTrigger, type RealtimePushPort, type RecipientResolver, type RepositoryDeliveryLogOptions, SseChannel, type SseChannelConfig, type StoredTemplate, type TemplateStore, type TemplatesResourceOptions, type TriggerAdapterOptions, type TriggerCatalogEntry, type TriggerCatalogPort, type TriggerOverrideLoader, type TriggerOverrideResolution, type TriggerOverrideStore, type UnreadCountCache, createCachedTriggerOverrideStore, createDbFirstResolver, createInAppBackfill, createNotificationChannelCatalog, createRepositoryDeliveryLog, createUnreadCountCache, dispatchTrigger, evaluateSourceChannel, getRecipientMatrix, getRegisteredEvents, getTriggerByEvent, getTriggersByEvent, inAppFeedResource, notificationsJob, notificationsModule, notifyTriggerHandlers, readInAppContent, renderInApp, resolveEffectiveSettings, templatesResource, toNotifyTrigger, toNotifyTriggers };
|
|
1384
|
+
export type { CivilDateString, CodeTemplates, CodeTriggerDefaults, CreateCachedTriggerOverrideStoreOptions, CreateUnreadCountCacheOptions, DateRule, DateRuleAudienceResolver, DateRuleCatalogEntry, DateRuleEngine, DateRuleEngineOptions, DateRuleItem, DateRuleLedger, DateRuleLedgerEntry, DateRuleOccurrence, DateRuleSchedule, DateRuleSource, DateRuleSourceQuery, DateRuleSweepResult, DbFirstResolverOptions, DbFirstTemplateResolver, DeliveryLogByRuleQuery, DeliveryLogDoc, EffectiveTriggerSettings, InAppBackfill, InAppBackfillConnect, InAppBackfillOptions, InAppChannelConfig, InAppContent, InAppFeedListOptions, InAppFeedPage, InAppFeedRepository, InAppFeedResourceOptions, InAppFeedRow, InAppFeedScope, InAppPriority, InAppTemplate, NotificationAudienceType, NotificationChannel, NotificationChannelCatalog, NotificationClock, NotificationDecision, NotificationExtractResult, NotificationRecipientContact, NotificationSkipReason, NotificationTrigger, NotificationTriggerOverrideDoc, NotificationsJob, NotificationsJobOptions, NotificationsModuleConfig, NotifyTrigger, RealtimePushPort, RecipientResolver, RepositoryDeliveryLog, RepositoryDeliveryLogOptions, RuleDeliveryLogEntry, SseChannelConfig, StoredTemplate, TemplateStore, TemplatesResourceOptions, TriggerAdapterOptions, TriggerCatalogEntry, TriggerCatalogPort, TriggerOverrideLoader, TriggerOverrideResolution, TriggerOverrideStore, UnreadCountCache };
|