@voyant-travel/notifications 0.111.7

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.
Files changed (96) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +179 -0
  3. package/dist/index.d.ts +61 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +196 -0
  6. package/dist/liquid.d.ts +5 -0
  7. package/dist/liquid.d.ts.map +1 -0
  8. package/dist/liquid.js +156 -0
  9. package/dist/providers/local.d.ts +22 -0
  10. package/dist/providers/local.d.ts.map +1 -0
  11. package/dist/providers/local.js +23 -0
  12. package/dist/providers/voyant-cloud-email.d.ts +27 -0
  13. package/dist/providers/voyant-cloud-email.d.ts.map +1 -0
  14. package/dist/providers/voyant-cloud-email.js +73 -0
  15. package/dist/providers/voyant-cloud-sms.d.ts +26 -0
  16. package/dist/providers/voyant-cloud-sms.d.ts.map +1 -0
  17. package/dist/providers/voyant-cloud-sms.js +24 -0
  18. package/dist/routes.d.ts +1546 -0
  19. package/dist/routes.d.ts.map +1 -0
  20. package/dist/routes.js +337 -0
  21. package/dist/schema.d.ts +2119 -0
  22. package/dist/schema.d.ts.map +1 -0
  23. package/dist/schema.js +356 -0
  24. package/dist/service-booking-document-lifecycle.d.ts +99 -0
  25. package/dist/service-booking-document-lifecycle.d.ts.map +1 -0
  26. package/dist/service-booking-document-lifecycle.js +259 -0
  27. package/dist/service-booking-documents.d.ts +256 -0
  28. package/dist/service-booking-documents.d.ts.map +1 -0
  29. package/dist/service-booking-documents.js +323 -0
  30. package/dist/service-deliveries.d.ts +183 -0
  31. package/dist/service-deliveries.d.ts.map +1 -0
  32. package/dist/service-deliveries.js +413 -0
  33. package/dist/service-delivery-metadata.d.ts +42 -0
  34. package/dist/service-delivery-metadata.d.ts.map +1 -0
  35. package/dist/service-delivery-metadata.js +114 -0
  36. package/dist/service-reminder-authoring.d.ts +33 -0
  37. package/dist/service-reminder-authoring.d.ts.map +1 -0
  38. package/dist/service-reminder-authoring.js +247 -0
  39. package/dist/service-reminder-booking-context.d.ts +94 -0
  40. package/dist/service-reminder-booking-context.d.ts.map +1 -0
  41. package/dist/service-reminder-booking-context.js +164 -0
  42. package/dist/service-reminder-events.d.ts +33 -0
  43. package/dist/service-reminder-events.d.ts.map +1 -0
  44. package/dist/service-reminder-events.js +178 -0
  45. package/dist/service-reminder-run-state.d.ts +114 -0
  46. package/dist/service-reminder-run-state.d.ts.map +1 -0
  47. package/dist/service-reminder-run-state.js +100 -0
  48. package/dist/service-reminder-stage-runs.d.ts +6 -0
  49. package/dist/service-reminder-stage-runs.d.ts.map +1 -0
  50. package/dist/service-reminder-stage-runs.js +310 -0
  51. package/dist/service-reminders.d.ts +30 -0
  52. package/dist/service-reminders.d.ts.map +1 -0
  53. package/dist/service-reminders.js +189 -0
  54. package/dist/service-sequence-targets.d.ts +50 -0
  55. package/dist/service-sequence-targets.d.ts.map +1 -0
  56. package/dist/service-sequence-targets.js +136 -0
  57. package/dist/service-sequence.d.ts +68 -0
  58. package/dist/service-sequence.d.ts.map +1 -0
  59. package/dist/service-sequence.js +316 -0
  60. package/dist/service-shared.d.ts +107 -0
  61. package/dist/service-shared.d.ts.map +1 -0
  62. package/dist/service-shared.js +159 -0
  63. package/dist/service-stages.d.ts +23 -0
  64. package/dist/service-stages.d.ts.map +1 -0
  65. package/dist/service-stages.js +203 -0
  66. package/dist/service-template-data.d.ts +19 -0
  67. package/dist/service-template-data.d.ts.map +1 -0
  68. package/dist/service-template-data.js +278 -0
  69. package/dist/service-templates.d.ts +260 -0
  70. package/dist/service-templates.d.ts.map +1 -0
  71. package/dist/service-templates.js +293 -0
  72. package/dist/service.d.ts +273 -0
  73. package/dist/service.d.ts.map +1 -0
  74. package/dist/service.js +51 -0
  75. package/dist/task-runtime.d.ts +19 -0
  76. package/dist/task-runtime.d.ts.map +1 -0
  77. package/dist/task-runtime.js +11 -0
  78. package/dist/tasks/deliver-reminder.d.ts +9 -0
  79. package/dist/tasks/deliver-reminder.d.ts.map +1 -0
  80. package/dist/tasks/deliver-reminder.js +12 -0
  81. package/dist/tasks/index.d.ts +3 -0
  82. package/dist/tasks/index.d.ts.map +1 -0
  83. package/dist/tasks/index.js +2 -0
  84. package/dist/tasks/send-due-reminders.d.ts +7 -0
  85. package/dist/tasks/send-due-reminders.d.ts.map +1 -0
  86. package/dist/tasks/send-due-reminders.js +31 -0
  87. package/dist/template-authoring.d.ts +23 -0
  88. package/dist/template-authoring.d.ts.map +1 -0
  89. package/dist/template-authoring.js +386 -0
  90. package/dist/types.d.ts +82 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +1 -0
  93. package/dist/validation.d.ts +1093 -0
  94. package/dist/validation.d.ts.map +1 -0
  95. package/dist/validation.js +451 -0
  96. package/package.json +102 -0
@@ -0,0 +1,136 @@
1
+ import { bookings } from "@voyant-travel/bookings/schema";
2
+ import { bookingPaymentSchedules, invoices } from "@voyant-travel/finance";
3
+ import { and, eq, gt, gte, inArray, lte, or } from "drizzle-orm";
4
+ import { addUtcDays, startOfUtcDay } from "./service-shared.js";
5
+ /**
6
+ * Computes the date range a target's `due_date` (or `issue_date`) needs to
7
+ * fall in for any of the rule's stages to be inside their eligibility window
8
+ * today.
9
+ *
10
+ * From `inWindow`: today must satisfy
11
+ * anchor + windowStartDays ≤ today ≤ anchor + windowEndDays
12
+ * Solving for anchor:
13
+ * today − windowEndDays ≤ anchor ≤ today − windowStartDays
14
+ *
15
+ * Across all stages with anchor=`due_date`, we union the [start, end] ranges
16
+ * and use the resulting envelope as a SQL `BETWEEN` filter. Returns null when
17
+ * no stage is anchored on the requested column (e.g. all stages anchor on
18
+ * `departure_date`) — caller should skip the pushdown in that case.
19
+ */
20
+ export function computeAnchorDateEnvelope(stages, today, anchor) {
21
+ const matching = stages.filter((s) => s.anchor === anchor);
22
+ if (matching.length === 0)
23
+ return null;
24
+ const todayStart = startOfUtcDay(today);
25
+ let from = Number.POSITIVE_INFINITY;
26
+ let to = Number.NEGATIVE_INFINITY;
27
+ for (const stage of matching) {
28
+ const fromDays = -stage.windowEndDays;
29
+ const toDays = -stage.windowStartDays;
30
+ if (fromDays < from)
31
+ from = fromDays;
32
+ if (toDays > to)
33
+ to = toDays;
34
+ }
35
+ return {
36
+ from: addUtcDays(todayStart, from).toISOString().slice(0, 10),
37
+ to: addUtcDays(todayStart, to).toISOString().slice(0, 10),
38
+ };
39
+ }
40
+ const PAYABLE_BOOKING_STATUSES = [
41
+ "on_hold",
42
+ "awaiting_payment",
43
+ "confirmed",
44
+ "in_progress",
45
+ ];
46
+ export async function fetchOpenPaymentScheduleTargets(db, envelopes = {}) {
47
+ const conditions = [
48
+ or(eq(bookingPaymentSchedules.status, "pending"), eq(bookingPaymentSchedules.status, "due")),
49
+ inArray(bookings.status, PAYABLE_BOOKING_STATUSES),
50
+ ];
51
+ if (envelopes.paymentScheduleDueDate) {
52
+ conditions.push(gte(bookingPaymentSchedules.dueDate, envelopes.paymentScheduleDueDate.from), lte(bookingPaymentSchedules.dueDate, envelopes.paymentScheduleDueDate.to));
53
+ }
54
+ const rows = await db
55
+ .select({
56
+ id: bookingPaymentSchedules.id,
57
+ bookingId: bookingPaymentSchedules.bookingId,
58
+ dueDate: bookingPaymentSchedules.dueDate,
59
+ status: bookingPaymentSchedules.status,
60
+ bookingCreatedAt: bookings.createdAt,
61
+ departureDate: bookings.startDate,
62
+ })
63
+ .from(bookingPaymentSchedules)
64
+ .leftJoin(bookings, eq(bookings.id, bookingPaymentSchedules.bookingId))
65
+ .where(and(...conditions));
66
+ return rows.map((row) => ({
67
+ id: row.id,
68
+ bookingId: row.bookingId,
69
+ dueDate: row.dueDate,
70
+ issuedAt: null,
71
+ departureDate: row.departureDate,
72
+ bookingCreatedAt: row.bookingCreatedAt ? row.bookingCreatedAt.toISOString() : null,
73
+ status: row.status,
74
+ isTerminal: row.status !== "pending" && row.status !== "due",
75
+ }));
76
+ }
77
+ export async function fetchOpenInvoiceTargets(db, envelopes = {}) {
78
+ const conditions = [
79
+ gt(invoices.balanceDueCents, 0),
80
+ or(eq(invoices.invoiceType, "invoice"), eq(invoices.invoiceType, "proforma")),
81
+ or(eq(invoices.status, "issued"), eq(invoices.status, "partially_paid"), eq(invoices.status, "overdue")),
82
+ ];
83
+ if (envelopes.invoiceDueDate) {
84
+ conditions.push(gte(invoices.dueDate, envelopes.invoiceDueDate.from), lte(invoices.dueDate, envelopes.invoiceDueDate.to));
85
+ }
86
+ if (envelopes.invoiceIssueDate) {
87
+ conditions.push(gte(invoices.issueDate, envelopes.invoiceIssueDate.from), lte(invoices.issueDate, envelopes.invoiceIssueDate.to));
88
+ }
89
+ const rows = await db
90
+ .select({
91
+ id: invoices.id,
92
+ bookingId: invoices.bookingId,
93
+ dueDate: invoices.dueDate,
94
+ issueDate: invoices.issueDate,
95
+ balanceDueCents: invoices.balanceDueCents,
96
+ invoiceType: invoices.invoiceType,
97
+ status: invoices.status,
98
+ bookingCreatedAt: bookings.createdAt,
99
+ departureDate: bookings.startDate,
100
+ })
101
+ .from(invoices)
102
+ .leftJoin(bookings, eq(bookings.id, invoices.bookingId))
103
+ .where(and(...conditions));
104
+ return rows.map((row) => ({
105
+ id: row.id,
106
+ bookingId: row.bookingId,
107
+ dueDate: row.dueDate,
108
+ issuedAt: row.issueDate,
109
+ departureDate: row.departureDate,
110
+ bookingCreatedAt: row.bookingCreatedAt ? row.bookingCreatedAt.toISOString() : null,
111
+ status: row.status,
112
+ isTerminal: row.balanceDueCents <= 0,
113
+ }));
114
+ }
115
+ /**
116
+ * Per-rule target fetch that pushes a date envelope into the WHERE when all
117
+ * relevant stages share an anchor we can SQL-filter on (`due_date` for both
118
+ * target types, `invoice_issued_at` for invoices). Other anchors fall through
119
+ * to the unfiltered fetch — they're expected to be rare and the in-app
120
+ * window check still rejects misses.
121
+ */
122
+ export async function fetchTargetsForRule(db, rule, stages = [], today = new Date()) {
123
+ if (rule.targetType === "booking_payment_schedule") {
124
+ const dueEnv = computeAnchorDateEnvelope(stages, today, "due_date");
125
+ return fetchOpenPaymentScheduleTargets(db, dueEnv ? { paymentScheduleDueDate: dueEnv } : {});
126
+ }
127
+ if (rule.targetType === "invoice") {
128
+ const dueEnv = computeAnchorDateEnvelope(stages, today, "due_date");
129
+ const issueEnv = computeAnchorDateEnvelope(stages, today, "invoice_issued_at");
130
+ return fetchOpenInvoiceTargets(db, {
131
+ invoiceDueDate: dueEnv ?? undefined,
132
+ invoiceIssueDate: issueEnv ?? undefined,
133
+ });
134
+ }
135
+ return [];
136
+ }
@@ -0,0 +1,68 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import { type NotificationReminderRule, type NotificationReminderRuleStage, type NotificationReminderStageCadenceInterval, type NotificationReminderStageChannel, type NotificationSettings } from "./schema.js";
3
+ export { computeAnchorDateEnvelope, type DateEnvelopes, fetchOpenInvoiceTargets, fetchOpenPaymentScheduleTargets, fetchTargetsForRule, } from "./service-sequence-targets.js";
4
+ export type ReminderTargetSnapshot = {
5
+ id: string;
6
+ bookingId: string | null;
7
+ dueDate: string | null;
8
+ issuedAt: string | null;
9
+ departureDate: string | null;
10
+ bookingCreatedAt: string | null;
11
+ status: string;
12
+ isTerminal: boolean;
13
+ };
14
+ export type SequenceHistoryEntry = {
15
+ scheduledFor: Date;
16
+ sentAt: Date | null;
17
+ status: string;
18
+ };
19
+ export type StageDecision = {
20
+ fire: false;
21
+ reason: string;
22
+ stage?: NotificationReminderRuleStage;
23
+ } | {
24
+ fire: true;
25
+ stage: NotificationReminderRuleStage;
26
+ anchorDate: Date;
27
+ sendCountAtFire: number;
28
+ };
29
+ export declare const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings;
30
+ export declare function getNotificationSettings(db: PostgresJsDatabase, scope?: string): Promise<NotificationSettings>;
31
+ export declare function listStagesForRule(db: PostgresJsDatabase, reminderRuleId: string): Promise<NotificationReminderRuleStage[]>;
32
+ export declare function listChannelsForStage(db: PostgresJsDatabase, stageId: string): Promise<NotificationReminderStageChannel[]>;
33
+ export declare function pickActiveStage(stages: NotificationReminderRuleStage[], attemptsSoFar: number): NotificationReminderRuleStage | null;
34
+ export declare function inWindow(today: Date, anchorDate: Date, stage: NotificationReminderRuleStage): boolean;
35
+ export declare function pickEscalatingInterval(intervals: NotificationReminderStageCadenceInterval[] | null | undefined, daysUntilDue: number | null): NotificationReminderStageCadenceInterval | null;
36
+ export declare function cadenceElapsed(today: Date, lastSentAt: Date | null, stage: NotificationReminderRuleStage, daysUntilDue: number | null): boolean;
37
+ export declare function resolveAnchor(anchor: NotificationReminderRuleStage["anchor"], target: ReminderTargetSnapshot, history: SequenceHistoryEntry[]): Date | null;
38
+ export declare function evaluateStage(_rule: NotificationReminderRule, stages: NotificationReminderRuleStage[], target: ReminderTargetSnapshot, history: SequenceHistoryEntry[], today: Date): StageDecision;
39
+ export declare function applyQuietHours(now: Date, stage: NotificationReminderRuleStage, settings: NotificationSettings, recipientTimezone?: string | null): {
40
+ scheduledAt: Date;
41
+ deferred: boolean;
42
+ };
43
+ export declare function exceedsRecipientRateLimit(db: PostgresJsDatabase, recipient: string, channel: string, settings: NotificationSettings, now: Date): Promise<boolean>;
44
+ export declare function suppressedByGroup(db: PostgresJsDatabase, recipient: string | null, suppressionGroup: string | null, settings: NotificationSettings, now: Date): Promise<boolean>;
45
+ export declare function loadHistory(db: PostgresJsDatabase, reminderRuleId: string, targetId: string): Promise<SequenceHistoryEntry[]>;
46
+ export declare function listActiveRulesByPriority(db: PostgresJsDatabase): Promise<NotificationReminderRule[]>;
47
+ export type PreviewRow = {
48
+ ruleId: string;
49
+ ruleName: string;
50
+ ruleSlug: string;
51
+ targetType: string;
52
+ targetId: string;
53
+ bookingId: string | null;
54
+ stageId: string;
55
+ stageName: string | null;
56
+ stageOrderIndex: number;
57
+ anchor: string;
58
+ anchorDate: string;
59
+ scheduledAt: string;
60
+ sendCountAtFire: number;
61
+ reasoning: string;
62
+ };
63
+ export declare function previewReminders(db: PostgresJsDatabase, options?: {
64
+ now?: Date;
65
+ ruleId?: string;
66
+ targetId?: string;
67
+ }): Promise<PreviewRow[]>;
68
+ //# sourceMappingURL=service-sequence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-sequence.d.ts","sourceRoot":"","sources":["../src/service-sequence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,316 @@
1
+ import { and, asc, eq, gte, sql } from "drizzle-orm";
2
+ import { notificationReminderRuleStages, notificationReminderRules, notificationReminderRuns, notificationReminderStageChannels, notificationSettings, } from "./schema.js";
3
+ import { fetchTargetsForRule as fetchTargetsForSequenceRule } from "./service-sequence-targets.js";
4
+ import { addUtcDays, startOfUtcDay } from "./service-shared.js";
5
+ export { computeAnchorDateEnvelope, fetchOpenInvoiceTargets, fetchOpenPaymentScheduleTargets, fetchTargetsForRule, } from "./service-sequence-targets.js";
6
+ export const DEFAULT_NOTIFICATION_SETTINGS = {
7
+ id: "nset_default",
8
+ scope: "default",
9
+ quietHoursLocal: null,
10
+ blackoutDates: null,
11
+ skipWeekends: false,
12
+ recipientRateLimitPerDay: null,
13
+ suppressionWindowHours: 24,
14
+ metadata: null,
15
+ createdAt: new Date(0),
16
+ updatedAt: new Date(0),
17
+ };
18
+ export async function getNotificationSettings(db, scope = "default") {
19
+ const [row] = await db
20
+ .select()
21
+ .from(notificationSettings)
22
+ .where(eq(notificationSettings.scope, scope))
23
+ .limit(1);
24
+ return row ?? { ...DEFAULT_NOTIFICATION_SETTINGS, scope };
25
+ }
26
+ export async function listStagesForRule(db, reminderRuleId) {
27
+ return db
28
+ .select()
29
+ .from(notificationReminderRuleStages)
30
+ .where(eq(notificationReminderRuleStages.reminderRuleId, reminderRuleId))
31
+ .orderBy(asc(notificationReminderRuleStages.orderIndex));
32
+ }
33
+ export async function listChannelsForStage(db, stageId) {
34
+ return db
35
+ .select()
36
+ .from(notificationReminderStageChannels)
37
+ .where(eq(notificationReminderStageChannels.stageId, stageId))
38
+ .orderBy(asc(notificationReminderStageChannels.orderIndex), asc(notificationReminderStageChannels.createdAt));
39
+ }
40
+ export function pickActiveStage(stages, attemptsSoFar) {
41
+ if (stages.length === 0)
42
+ return null;
43
+ let cumulative = 0;
44
+ for (const stage of stages) {
45
+ const cap = stage.maxSendsInStage ?? Number.POSITIVE_INFINITY;
46
+ if (attemptsSoFar < cumulative + cap) {
47
+ return stage;
48
+ }
49
+ cumulative += cap;
50
+ }
51
+ return null;
52
+ }
53
+ function isDeliveryAttempt(status) {
54
+ return (status === "queued" ||
55
+ status === "processing" ||
56
+ status === "sent" ||
57
+ status === "skipped" ||
58
+ status === "failed");
59
+ }
60
+ export function inWindow(today, anchorDate, stage) {
61
+ const start = addUtcDays(startOfUtcDay(anchorDate), stage.windowStartDays);
62
+ const end = addUtcDays(startOfUtcDay(anchorDate), stage.windowEndDays);
63
+ const todayStart = startOfUtcDay(today);
64
+ return todayStart >= start && todayStart <= end;
65
+ }
66
+ function daysBetweenUtc(a, b) {
67
+ const ms = startOfUtcDay(a).getTime() - startOfUtcDay(b).getTime();
68
+ return Math.round(ms / (24 * 60 * 60 * 1000));
69
+ }
70
+ export function pickEscalatingInterval(intervals, daysUntilDue) {
71
+ if (!intervals || intervals.length === 0)
72
+ return null;
73
+ for (const interval of intervals) {
74
+ const gtOk = interval.whenDaysUntilDueGT == null || daysUntilDue == null
75
+ ? interval.whenDaysUntilDueGT == null
76
+ : daysUntilDue > interval.whenDaysUntilDueGT;
77
+ const ltOk = interval.whenDaysUntilDueLT == null || daysUntilDue == null
78
+ ? interval.whenDaysUntilDueLT == null
79
+ : daysUntilDue < interval.whenDaysUntilDueLT;
80
+ if (interval.whenDaysUntilDueGT == null && interval.whenDaysUntilDueLT == null) {
81
+ return interval;
82
+ }
83
+ if (gtOk && ltOk)
84
+ return interval;
85
+ }
86
+ // Fall back to the last interval with no constraints (the "default bucket").
87
+ const fallback = intervals.find((i) => i.whenDaysUntilDueGT == null && i.whenDaysUntilDueLT == null);
88
+ return fallback ?? null;
89
+ }
90
+ export function cadenceElapsed(today, lastSentAt, stage, daysUntilDue) {
91
+ if (stage.cadenceKind === "once") {
92
+ return lastSentAt === null;
93
+ }
94
+ if (stage.cadenceKind === "every_n_days") {
95
+ if (lastSentAt === null)
96
+ return true;
97
+ const every = stage.cadenceEveryDays ?? 1;
98
+ return daysBetweenUtc(today, lastSentAt) >= every;
99
+ }
100
+ if (stage.cadenceKind === "escalating") {
101
+ if (lastSentAt === null)
102
+ return true;
103
+ const interval = pickEscalatingInterval(stage.cadenceIntervals, daysUntilDue);
104
+ if (!interval)
105
+ return false;
106
+ return daysBetweenUtc(today, lastSentAt) >= interval.repeatEveryDays;
107
+ }
108
+ return false;
109
+ }
110
+ export function resolveAnchor(anchor, target, history) {
111
+ switch (anchor) {
112
+ case "due_date":
113
+ return target.dueDate ? new Date(`${target.dueDate}T00:00:00Z`) : null;
114
+ case "booking_created_at":
115
+ return target.bookingCreatedAt ? new Date(target.bookingCreatedAt) : null;
116
+ case "departure_date":
117
+ return target.departureDate ? new Date(`${target.departureDate}T00:00:00Z`) : null;
118
+ case "invoice_issued_at":
119
+ return target.issuedAt ? new Date(`${target.issuedAt}T00:00:00Z`) : null;
120
+ case "last_send_at": {
121
+ const sent = history.filter((h) => h.status === "sent" && h.sentAt);
122
+ if (sent.length === 0)
123
+ return null;
124
+ const last = sent.reduce((acc, e) => (e.sentAt && e.sentAt > acc ? e.sentAt : acc), sent[0].sentAt);
125
+ return last;
126
+ }
127
+ default:
128
+ return null;
129
+ }
130
+ }
131
+ export function evaluateStage(_rule, stages, target, history, today) {
132
+ if (target.isTerminal) {
133
+ return { fire: false, reason: "target_terminal_state" };
134
+ }
135
+ const attemptHistory = history.filter((h) => isDeliveryAttempt(h.status));
136
+ const stage = pickActiveStage(stages, attemptHistory.length);
137
+ if (!stage) {
138
+ return { fire: false, reason: "no_active_stage" };
139
+ }
140
+ const anchorDate = resolveAnchor(stage.anchor, target, history);
141
+ if (!anchorDate) {
142
+ return { fire: false, reason: "anchor_unresolved", stage };
143
+ }
144
+ if (!inWindow(today, anchorDate, stage)) {
145
+ return { fire: false, reason: "outside_window", stage };
146
+ }
147
+ const lastAttempt = attemptHistory.reduce((acc, e) => {
148
+ const stamp = e.sentAt ?? e.scheduledFor;
149
+ return acc && acc > stamp ? acc : stamp;
150
+ }, null);
151
+ let daysUntilDue = null;
152
+ if (target.dueDate) {
153
+ daysUntilDue = daysBetweenUtc(new Date(`${target.dueDate}T00:00:00Z`), today);
154
+ }
155
+ if (!cadenceElapsed(today, lastAttempt, stage, daysUntilDue)) {
156
+ return { fire: false, reason: "cadence_not_elapsed", stage };
157
+ }
158
+ return { fire: true, stage, anchorDate, sendCountAtFire: attemptHistory.length + 1 };
159
+ }
160
+ function parseTimeOfDayUtcMinutes(value) {
161
+ const [hh, mm] = value.split(":").map((part) => Number(part));
162
+ return (hh ?? 0) * 60 + (mm ?? 0);
163
+ }
164
+ export function applyQuietHours(now, stage, settings, recipientTimezone) {
165
+ if (!stage.respectQuietHours) {
166
+ return { scheduledAt: now, deferred: false };
167
+ }
168
+ const blackoutDates = settings.blackoutDates ?? [];
169
+ const skipWeekends = settings.skipWeekends ?? false;
170
+ const quiet = settings.quietHoursLocal;
171
+ const tz = recipientTimezone ?? quiet?.tz ?? "UTC";
172
+ const effectiveTz = tz === "recipient" ? "UTC" : tz;
173
+ let candidate = now;
174
+ for (let attempts = 0; attempts < 24; attempts += 1) {
175
+ const local = formatInTimeZone(candidate, effectiveTz);
176
+ const blackout = blackoutDates.includes(local.dateString);
177
+ const isWeekend = local.dayOfWeek === 0 || local.dayOfWeek === 6;
178
+ let inQuiet = false;
179
+ if (quiet) {
180
+ const startMin = parseTimeOfDayUtcMinutes(quiet.start);
181
+ const endMin = parseTimeOfDayUtcMinutes(quiet.end);
182
+ const nowMin = local.hour * 60 + local.minute;
183
+ inQuiet =
184
+ startMin <= endMin
185
+ ? nowMin >= startMin && nowMin < endMin
186
+ : nowMin >= startMin || nowMin < endMin;
187
+ }
188
+ if (!blackout && !(skipWeekends && isWeekend) && !inQuiet) {
189
+ return { scheduledAt: candidate, deferred: candidate !== now };
190
+ }
191
+ candidate = new Date(candidate.getTime() + 60 * 60 * 1000);
192
+ }
193
+ return { scheduledAt: candidate, deferred: true };
194
+ }
195
+ function formatInTimeZone(date, timeZone) {
196
+ const fmt = new Intl.DateTimeFormat("en-US", {
197
+ timeZone,
198
+ year: "numeric",
199
+ month: "2-digit",
200
+ day: "2-digit",
201
+ hour: "2-digit",
202
+ minute: "2-digit",
203
+ weekday: "short",
204
+ hour12: false,
205
+ });
206
+ const parts = Object.fromEntries(fmt.formatToParts(date).map((part) => [part.type, part.value]));
207
+ const dayLookup = {
208
+ Sun: 0,
209
+ Mon: 1,
210
+ Tue: 2,
211
+ Wed: 3,
212
+ Thu: 4,
213
+ Fri: 5,
214
+ Sat: 6,
215
+ };
216
+ return {
217
+ dateString: `${parts.year}-${parts.month}-${parts.day}`,
218
+ hour: Number(parts.hour ?? "0") % 24,
219
+ minute: Number(parts.minute ?? "0"),
220
+ dayOfWeek: dayLookup[parts.weekday ?? "Mon"] ?? 1,
221
+ };
222
+ }
223
+ export async function exceedsRecipientRateLimit(db, recipient, channel, settings, now) {
224
+ const limit = settings.recipientRateLimitPerDay;
225
+ if (limit == null || limit <= 0)
226
+ return false;
227
+ const since = new Date(now.getTime() - 24 * 60 * 60 * 1000);
228
+ const [row] = await db
229
+ .select({ count: sql `count(*)::int` })
230
+ .from(notificationReminderRuns)
231
+ .where(and(eq(notificationReminderRuns.recipient, recipient), eq(notificationReminderRuns.status, "sent"), gte(notificationReminderRuns.processedAt, since),
232
+ // agent-quality: raw-sql reviewed -- owner: notifications; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
233
+ sql `${notificationReminderRuns.metadata}->>'channel' = ${channel}`));
234
+ return (row?.count ?? 0) >= limit;
235
+ }
236
+ export async function suppressedByGroup(db, recipient, suppressionGroup, settings, now) {
237
+ if (!recipient || !suppressionGroup)
238
+ return false;
239
+ const windowHours = settings.suppressionWindowHours ?? 24;
240
+ if (windowHours <= 0)
241
+ return false;
242
+ const since = new Date(now.getTime() - windowHours * 60 * 60 * 1000);
243
+ const [row] = await db
244
+ .select({ id: notificationReminderRuns.id })
245
+ .from(notificationReminderRuns)
246
+ .innerJoin(notificationReminderRules, eq(notificationReminderRuns.reminderRuleId, notificationReminderRules.id))
247
+ .where(and(eq(notificationReminderRuns.recipient, recipient), eq(notificationReminderRuns.status, "sent"), eq(notificationReminderRules.suppressionGroup, suppressionGroup), gte(notificationReminderRuns.processedAt, since)))
248
+ .limit(1);
249
+ return Boolean(row);
250
+ }
251
+ export async function loadHistory(db, reminderRuleId, targetId) {
252
+ const rows = await db
253
+ .select({
254
+ scheduledFor: notificationReminderRuns.scheduledFor,
255
+ processedAt: notificationReminderRuns.processedAt,
256
+ status: notificationReminderRuns.status,
257
+ })
258
+ .from(notificationReminderRuns)
259
+ .where(and(eq(notificationReminderRuns.reminderRuleId, reminderRuleId), eq(notificationReminderRuns.targetId, targetId)))
260
+ .orderBy(asc(notificationReminderRuns.scheduledFor));
261
+ return rows.map((row) => ({
262
+ scheduledFor: row.scheduledFor,
263
+ sentAt: row.status === "sent" ? row.processedAt : null,
264
+ status: row.status,
265
+ }));
266
+ }
267
+ export async function listActiveRulesByPriority(db) {
268
+ return db
269
+ .select()
270
+ .from(notificationReminderRules)
271
+ .where(eq(notificationReminderRules.status, "active"))
272
+ .orderBy(
273
+ // agent-quality: raw-sql reviewed -- owner: notifications; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
274
+ sql `${notificationReminderRules.priority} desc nulls last`, asc(notificationReminderRules.createdAt));
275
+ }
276
+ export async function previewReminders(db, options = {}) {
277
+ const now = options.now ?? new Date();
278
+ const today = startOfUtcDay(now);
279
+ const settings = await getNotificationSettings(db);
280
+ const allRules = await listActiveRulesByPriority(db);
281
+ const rules = options.ruleId ? allRules.filter((r) => r.id === options.ruleId) : allRules;
282
+ const rows = [];
283
+ for (const rule of rules) {
284
+ const stages = await listStagesForRule(db, rule.id);
285
+ if (stages.length === 0)
286
+ continue;
287
+ const targets = await fetchTargetsForSequenceRule(db, rule, stages, today);
288
+ const filteredTargets = options.targetId
289
+ ? targets.filter((t) => t.id === options.targetId)
290
+ : targets;
291
+ for (const target of filteredTargets) {
292
+ const history = await loadHistory(db, rule.id, target.id);
293
+ const decision = evaluateStage(rule, stages, target, history, today);
294
+ if (!decision.fire)
295
+ continue;
296
+ const { scheduledAt } = applyQuietHours(now, decision.stage, settings);
297
+ rows.push({
298
+ ruleId: rule.id,
299
+ ruleName: rule.name,
300
+ ruleSlug: rule.slug,
301
+ targetType: rule.targetType,
302
+ targetId: target.id,
303
+ bookingId: target.bookingId,
304
+ stageId: decision.stage.id,
305
+ stageName: decision.stage.name,
306
+ stageOrderIndex: decision.stage.orderIndex,
307
+ anchor: decision.stage.anchor,
308
+ anchorDate: decision.anchorDate.toISOString(),
309
+ scheduledAt: scheduledAt.toISOString(),
310
+ sendCountAtFire: decision.sendCountAtFire,
311
+ reasoning: `stage[${decision.stage.orderIndex}] anchor=${decision.stage.anchor} window=[${decision.stage.windowStartDays},${decision.stage.windowEndDays}] cadence=${decision.stage.cadenceKind}`,
312
+ });
313
+ }
314
+ }
315
+ return rows;
316
+ }
@@ -0,0 +1,107 @@
1
+ import type { bookingPaymentSchedules } from "@voyant-travel/finance";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ import type { SQLWrapper } from "drizzle-orm/sql";
4
+ import type { z } from "zod";
5
+ import type { notificationReminderRules } from "./schema.js";
6
+ import type { NotificationAttachment, NotificationChannel, NotificationPayload, NotificationProvider, NotificationResult } from "./types.js";
7
+ import type { bookingDocumentBundleItemSchema, insertNotificationReminderRuleSchema, insertNotificationTemplateSchema, notificationDeliveryListQuerySchema, notificationReminderRuleListQuerySchema, notificationReminderRunListQuerySchema, notificationReminderRunRecordSchema, notificationTemplateListQuerySchema, previewNotificationTemplateSchema, runDueRemindersSchema, sendBookingDocumentsNotificationSchema, sendInvoiceNotificationSchema, sendNotificationSchema, sendPaymentSessionNotificationSchema, updateNotificationReminderRuleSchema, updateNotificationTemplateSchema } from "./validation.js";
8
+ export { normalizeNotificationTemplateData } from "./service-template-data.js";
9
+ export type NotificationTemplateListQuery = z.infer<typeof notificationTemplateListQuerySchema>;
10
+ export type NotificationDeliveryListQuery = z.infer<typeof notificationDeliveryListQuerySchema>;
11
+ export type CreateNotificationTemplateInput = z.infer<typeof insertNotificationTemplateSchema>;
12
+ export type UpdateNotificationTemplateInput = z.infer<typeof updateNotificationTemplateSchema>;
13
+ export type SendNotificationInput = z.infer<typeof sendNotificationSchema>;
14
+ export type NotificationReminderRuleListQuery = z.infer<typeof notificationReminderRuleListQuerySchema>;
15
+ export type NotificationReminderRunListQuery = z.infer<typeof notificationReminderRunListQuerySchema>;
16
+ export type NotificationReminderRunRecord = z.infer<typeof notificationReminderRunRecordSchema>;
17
+ export type CreateNotificationReminderRuleInput = z.infer<typeof insertNotificationReminderRuleSchema>;
18
+ export type UpdateNotificationReminderRuleInput = z.infer<typeof updateNotificationReminderRuleSchema>;
19
+ export type RunDueRemindersInput = z.infer<typeof runDueRemindersSchema>;
20
+ export type PreviewNotificationTemplateInput = z.infer<typeof previewNotificationTemplateSchema>;
21
+ export type SendPaymentSessionNotificationInput = z.infer<typeof sendPaymentSessionNotificationSchema>;
22
+ export type SendInvoiceNotificationInput = z.infer<typeof sendInvoiceNotificationSchema>;
23
+ export type SendBookingDocumentsNotificationInput = z.infer<typeof sendBookingDocumentsNotificationSchema>;
24
+ export type BookingDocumentBundleItem = z.infer<typeof bookingDocumentBundleItemSchema>;
25
+ export type ReminderSweepResult = {
26
+ processed: number;
27
+ sent: number;
28
+ skipped: number;
29
+ failed: number;
30
+ };
31
+ export type ReminderQueueResult = {
32
+ processed: number;
33
+ queued: number;
34
+ skipped: number;
35
+ failed: number;
36
+ };
37
+ export type NotificationReminderRuleRow = typeof notificationReminderRules.$inferSelect;
38
+ export type BookingPaymentScheduleRow = typeof bookingPaymentSchedules.$inferSelect;
39
+ export declare class NotificationError extends Error {
40
+ constructor(message: string);
41
+ }
42
+ export interface NotificationService {
43
+ send(payload: NotificationPayload): Promise<NotificationResult>;
44
+ sendWith(providerName: string, payload: NotificationPayload): Promise<NotificationResult>;
45
+ getProvider(channel: NotificationChannel): NotificationProvider | undefined;
46
+ getProviderByName?(providerName: string): NotificationProvider | undefined;
47
+ }
48
+ export declare function createNotificationService(providers: ReadonlyArray<NotificationProvider>): NotificationService;
49
+ export declare function summarizeNotificationAttachments(attachments: ReadonlyArray<NotificationAttachment> | null | undefined): {
50
+ filename: string;
51
+ path: string | null;
52
+ contentType: string | null;
53
+ disposition: "attachment" | "inline" | null;
54
+ contentId: string | null;
55
+ }[];
56
+ export declare function renderNotificationTemplate(template: string | null | undefined, data: Record<string, unknown>): string | null;
57
+ export declare function previewNotificationTemplate(input: PreviewNotificationTemplateInput): {
58
+ channel: "email" | "sms";
59
+ provider: string | null;
60
+ fromAddress: string | null;
61
+ subject: string | null;
62
+ html: string | null;
63
+ text: string | null;
64
+ };
65
+ export declare function toTimestamp(value?: string | null): Date | null;
66
+ export declare function startOfUtcDay(value: Date): Date;
67
+ export declare function addUtcDays(value: Date, days: number): Date;
68
+ export declare function toDateString(value: Date): string;
69
+ export declare function buildReminderDedupeKey(ruleId: string, targetId: string, runDate: string): string;
70
+ export declare function resolveReminderRecipient(booking: {
71
+ contactFirstName: string | null;
72
+ contactLastName: string | null;
73
+ contactEmail: string | null;
74
+ contactPhone: string | null;
75
+ contactPreferredLanguage: string | null;
76
+ } | null, participants: Array<{
77
+ email: string | null;
78
+ isPrimary: boolean;
79
+ participantType: string;
80
+ firstName: string;
81
+ lastName: string;
82
+ }>): {
83
+ email: string | null;
84
+ isPrimary: boolean;
85
+ participantType: string;
86
+ firstName: string;
87
+ lastName: string;
88
+ } | null;
89
+ export declare function listBookingNotificationParticipants(db: PostgresJsDatabase, bookingId: string): Promise<{
90
+ id: string;
91
+ firstName: string;
92
+ lastName: string;
93
+ email: string | null;
94
+ participantType: "traveler" | "other" | "occupant";
95
+ isPrimary: boolean;
96
+ }[]>;
97
+ export declare function listBookingNotificationItems(db: PostgresJsDatabase, bookingId: string): Promise<unknown[]>;
98
+ export declare function paginate<T>(rowsPromise: Promise<T[]>, totalPromise: Promise<Array<{
99
+ total: number;
100
+ }>>, limit: number, offset: number): Promise<{
101
+ data: T[];
102
+ total: number;
103
+ limit: number;
104
+ offset: number;
105
+ }>;
106
+ export declare function buildWhereClause<T extends SQLWrapper>(conditions: Array<T | undefined>): import("drizzle-orm").SQL<unknown> | undefined;
107
+ //# sourceMappingURL=service-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAErE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAE5D,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EACV,+BAA+B,EAC/B,oCAAoC,EACpC,gCAAgC,EAChC,mCAAmC,EACnC,uCAAuC,EACvC,sCAAsC,EACtC,mCAAmC,EACnC,mCAAmC,EACnC,iCAAiC,EACjC,qBAAqB,EACrB,sCAAsC,EACtC,6BAA6B,EAC7B,sBAAsB,EACtB,oCAAoC,EACpC,oCAAoC,EACpC,gCAAgC,EACjC,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAA;AAE9E,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAC/F,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAC/F,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAC9F,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAC9F,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAC1E,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAA;AACD,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CACpD,OAAO,sCAAsC,CAC9C,CAAA;AACD,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAC/F,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,oCAAoC,CAC5C,CAAA;AACD,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,oCAAoC,CAC5C,CAAA;AACD,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AACxE,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAA;AAChG,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,oCAAoC,CAC5C,CAAA;AACD,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACxF,MAAM,MAAM,qCAAqC,GAAG,CAAC,CAAC,KAAK,CACzD,OAAO,sCAAsC,CAC9C,CAAA;AACD,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAA;AAEvF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,OAAO,yBAAyB,CAAC,YAAY,CAAA;AACvF,MAAM,MAAM,yBAAyB,GAAG,OAAO,uBAAuB,CAAC,YAAY,CAAA;AAEnF,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACzF,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,oBAAoB,GAAG,SAAS,CAAA;IAC3E,iBAAiB,CAAC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAA;CAC3E;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAC7C,mBAAmB,CAqCrB;AAED,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,aAAa,CAAC,sBAAsB,CAAC,GAAG,IAAI,GAAG,SAAS;;;;;;IAatE;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAG9B;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,gCAAgC;;;;;;;EAUlF;AAED,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,eAEhD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,QAExC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAEnD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,UAEvC;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAEvF;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE;IACP,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAA;CACxC,GAAG,IAAI,EACR,YAAY,EAAE,KAAK,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAC;WALO,MAAM,GAAG,IAAI;eACT,OAAO;qBACD,MAAM;eACZ,MAAM;cACP,MAAM;SAmCnB;AAED,wBAAsB,mCAAmC,CACvD,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM;;;;;;;KAclB;AAED,wBAAsB,4BAA4B,CAAC,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,sBAkB3F;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,WAAW,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,EACzB,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,EAC/C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM;;;;;GASf;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,kDAGtF"}