@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,413 @@
1
+ import { bookings } from "@voyant-travel/bookings/schema";
2
+ import { invoices, paymentSessions } from "@voyant-travel/finance";
3
+ import { desc, eq, sql } from "drizzle-orm";
4
+ import { notificationDeliveries } from "./schema.js";
5
+ import { attachmentsFromMetadata, metadataWithoutFailureLog, normalizeDeliveryAttachments, resolveNotificationPaymentUrl, serializeNotificationError, } from "./service-delivery-metadata.js";
6
+ import { buildWhereClause, listBookingNotificationItems, listBookingNotificationParticipants, NotificationError, paginate, renderNotificationTemplate, resolveReminderRecipient, summarizeNotificationAttachments, toTimestamp, } from "./service-shared.js";
7
+ import { getTemplateById, getTemplateBySlug } from "./service-templates.js";
8
+ export { resolveNotificationPaymentUrl } from "./service-delivery-metadata.js";
9
+ export async function listDeliveries(db, query) {
10
+ const conditions = [];
11
+ if (query.channel)
12
+ conditions.push(eq(notificationDeliveries.channel, query.channel));
13
+ if (query.provider)
14
+ conditions.push(eq(notificationDeliveries.provider, query.provider));
15
+ if (query.status)
16
+ conditions.push(eq(notificationDeliveries.status, query.status));
17
+ if (query.templateSlug)
18
+ conditions.push(eq(notificationDeliveries.templateSlug, query.templateSlug));
19
+ if (query.targetType)
20
+ conditions.push(eq(notificationDeliveries.targetType, query.targetType));
21
+ if (query.targetId)
22
+ conditions.push(eq(notificationDeliveries.targetId, query.targetId));
23
+ if (query.bookingId)
24
+ conditions.push(eq(notificationDeliveries.bookingId, query.bookingId));
25
+ if (query.invoiceId)
26
+ conditions.push(eq(notificationDeliveries.invoiceId, query.invoiceId));
27
+ if (query.paymentSessionId) {
28
+ conditions.push(eq(notificationDeliveries.paymentSessionId, query.paymentSessionId));
29
+ }
30
+ if (query.personId)
31
+ conditions.push(eq(notificationDeliveries.personId, query.personId));
32
+ if (query.organizationId) {
33
+ conditions.push(eq(notificationDeliveries.organizationId, query.organizationId));
34
+ }
35
+ const where = buildWhereClause(conditions);
36
+ return paginate(db
37
+ .select()
38
+ .from(notificationDeliveries)
39
+ .where(where)
40
+ .limit(query.limit)
41
+ .offset(query.offset)
42
+ .orderBy(desc(notificationDeliveries.createdAt)), db.select({ total: sql `count(*)::int` }).from(notificationDeliveries).where(where), query.limit, query.offset);
43
+ }
44
+ export async function getDeliveryById(db, id) {
45
+ const [row] = await db
46
+ .select()
47
+ .from(notificationDeliveries)
48
+ .where(eq(notificationDeliveries.id, id))
49
+ .limit(1);
50
+ return row ?? null;
51
+ }
52
+ export async function resendDelivery(db, dispatcher, id) {
53
+ const original = await getDeliveryById(db, id);
54
+ if (!original)
55
+ return null;
56
+ const previousMetadata = metadataWithoutFailureLog(original.metadata);
57
+ return sendNotification(db, dispatcher, {
58
+ templateId: original.templateId,
59
+ templateSlug: original.templateSlug,
60
+ channel: original.channel,
61
+ provider: original.provider,
62
+ to: original.toAddress,
63
+ from: original.fromAddress,
64
+ subject: original.subject,
65
+ html: original.htmlBody,
66
+ text: original.textBody,
67
+ attachments: attachmentsFromMetadata(original.metadata),
68
+ data: original.payloadData,
69
+ targetType: original.targetType,
70
+ targetId: original.targetId,
71
+ bookingId: original.bookingId,
72
+ invoiceId: original.invoiceId,
73
+ paymentSessionId: original.paymentSessionId,
74
+ personId: original.personId,
75
+ organizationId: original.organizationId,
76
+ metadata: {
77
+ ...(previousMetadata ?? {}),
78
+ resendOfDeliveryId: original.id,
79
+ previousStatus: original.status,
80
+ },
81
+ scheduledFor: null,
82
+ });
83
+ }
84
+ export async function sendNotification(db, dispatcher, input) {
85
+ let template = null;
86
+ if (input.templateId) {
87
+ template = await getTemplateById(db, input.templateId);
88
+ }
89
+ else if (input.templateSlug) {
90
+ template = await getTemplateBySlug(db, input.templateSlug);
91
+ }
92
+ if ((input.templateId || input.templateSlug) && !template) {
93
+ throw new NotificationError("Notification template not found");
94
+ }
95
+ const data = input.data ?? {};
96
+ const channel = input.channel ?? template?.channel;
97
+ if (!channel) {
98
+ throw new NotificationError("Notification channel is required");
99
+ }
100
+ const defaultProvider = dispatcher.getProvider(channel);
101
+ const provider = input.provider ?? template?.provider ?? defaultProvider?.name;
102
+ if (!provider) {
103
+ throw new NotificationError(`No notification provider available for channel "${channel}"`);
104
+ }
105
+ if (provider !== defaultProvider?.name && dispatcher.getProviderByName?.(provider) == null) {
106
+ throw new NotificationError(`No notification provider registered with name "${provider}"`);
107
+ }
108
+ const subject = input.subject ?? renderNotificationTemplate(template?.subjectTemplate, data);
109
+ const html = input.html ?? renderNotificationTemplate(template?.htmlTemplate, data);
110
+ const text = input.text ?? renderNotificationTemplate(template?.textTemplate, data);
111
+ const attachments = normalizeDeliveryAttachments(input.attachments);
112
+ const attachmentSummary = summarizeNotificationAttachments(attachments);
113
+ const [pending] = await db
114
+ .insert(notificationDeliveries)
115
+ .values({
116
+ templateId: template?.id ?? null,
117
+ templateSlug: template?.slug ?? input.templateSlug ?? null,
118
+ targetType: input.targetType,
119
+ targetId: input.targetId ?? null,
120
+ personId: input.personId ?? null,
121
+ organizationId: input.organizationId ?? null,
122
+ bookingId: input.bookingId ?? null,
123
+ invoiceId: input.invoiceId ?? null,
124
+ paymentSessionId: input.paymentSessionId ?? null,
125
+ channel,
126
+ provider,
127
+ providerMessageId: null,
128
+ status: "pending",
129
+ toAddress: input.to,
130
+ fromAddress: input.from ?? template?.fromAddress ?? null,
131
+ subject: subject ?? null,
132
+ htmlBody: html ?? null,
133
+ textBody: text ?? null,
134
+ payloadData: data,
135
+ metadata: (input.metadata ?? null) || attachmentSummary.length > 0
136
+ ? {
137
+ ...(input.metadata ?? {}),
138
+ attachmentCount: attachmentSummary.length,
139
+ attachments: attachmentSummary,
140
+ }
141
+ : null,
142
+ errorMessage: null,
143
+ scheduledFor: toTimestamp(input.scheduledFor),
144
+ sentAt: null,
145
+ failedAt: null,
146
+ })
147
+ .returning();
148
+ if (!pending) {
149
+ throw new NotificationError("Failed to create notification delivery");
150
+ }
151
+ try {
152
+ const result = provider === defaultProvider?.name
153
+ ? await dispatcher.send({
154
+ to: input.to,
155
+ channel,
156
+ provider,
157
+ template: template?.slug ?? input.templateSlug ?? "direct",
158
+ data,
159
+ from: input.from ?? template?.fromAddress ?? undefined,
160
+ subject: subject ?? undefined,
161
+ html: html ?? undefined,
162
+ text: text ?? undefined,
163
+ attachments,
164
+ })
165
+ : await dispatcher.sendWith(provider, {
166
+ to: input.to,
167
+ channel,
168
+ provider,
169
+ template: template?.slug ?? input.templateSlug ?? "direct",
170
+ data,
171
+ from: input.from ?? template?.fromAddress ?? undefined,
172
+ subject: subject ?? undefined,
173
+ html: html ?? undefined,
174
+ text: text ?? undefined,
175
+ attachments,
176
+ });
177
+ const [sent] = await db
178
+ .update(notificationDeliveries)
179
+ .set({
180
+ status: "sent",
181
+ providerMessageId: result.id ?? null,
182
+ sentAt: new Date(),
183
+ errorMessage: null,
184
+ updatedAt: new Date(),
185
+ })
186
+ .where(eq(notificationDeliveries.id, pending.id))
187
+ .returning();
188
+ return sent ?? null;
189
+ }
190
+ catch (error) {
191
+ const message = error instanceof Error ? error.message : "Notification send failed";
192
+ const failureLog = serializeNotificationError(error);
193
+ const [failed] = await db
194
+ .update(notificationDeliveries)
195
+ .set({
196
+ status: "failed",
197
+ failedAt: new Date(),
198
+ errorMessage: message,
199
+ metadata: {
200
+ ...(pending.metadata ?? {}),
201
+ failureLog,
202
+ },
203
+ updatedAt: new Date(),
204
+ })
205
+ .where(eq(notificationDeliveries.id, pending.id))
206
+ .returning();
207
+ throw new NotificationError(failed?.errorMessage ?? message);
208
+ }
209
+ }
210
+ export async function sendPaymentSessionNotification(db, dispatcher, sessionId, input) {
211
+ const [session] = await db
212
+ .select()
213
+ .from(paymentSessions)
214
+ .where(eq(paymentSessions.id, sessionId))
215
+ .limit(1);
216
+ if (!session) {
217
+ return null;
218
+ }
219
+ const booking = session.bookingId
220
+ ? ((await db.select().from(bookings).where(eq(bookings.id, session.bookingId)).limit(1))[0] ??
221
+ null)
222
+ : null;
223
+ const invoice = session.invoiceId
224
+ ? ((await db.select().from(invoices).where(eq(invoices.id, session.invoiceId)).limit(1))[0] ??
225
+ null)
226
+ : null;
227
+ const [participants, items] = booking
228
+ ? await Promise.all([
229
+ listBookingNotificationParticipants(db, booking.id),
230
+ listBookingNotificationItems(db, booking.id),
231
+ ])
232
+ : [[], []];
233
+ const recipient = resolveReminderRecipient(booking ?? null, participants);
234
+ const to = input.to ?? session.payerEmail ?? recipient?.email ?? null;
235
+ if (!to) {
236
+ throw new NotificationError("No recipient available for payment session notification");
237
+ }
238
+ return sendNotification(db, dispatcher, {
239
+ templateId: input.templateId ?? null,
240
+ templateSlug: input.templateSlug ?? null,
241
+ channel: input.channel,
242
+ provider: input.provider ?? null,
243
+ to,
244
+ from: input.from ?? null,
245
+ subject: input.subject ?? null,
246
+ html: input.html ?? null,
247
+ text: input.text ?? null,
248
+ data: {
249
+ paymentSession: {
250
+ id: session.id,
251
+ status: session.status,
252
+ provider: session.provider,
253
+ currency: session.currency,
254
+ amountCents: session.amountCents,
255
+ paymentUrl: resolveNotificationPaymentUrl(session.id, {
256
+ paymentLinkBaseUrl: input.paymentLinkBaseUrl,
257
+ redirectUrl: session.redirectUrl,
258
+ }),
259
+ redirectUrl: session.redirectUrl,
260
+ returnUrl: session.returnUrl,
261
+ cancelUrl: session.cancelUrl,
262
+ expiresAt: session.expiresAt,
263
+ paymentMethod: session.paymentMethod,
264
+ externalReference: session.externalReference,
265
+ },
266
+ booking: booking
267
+ ? {
268
+ id: booking.id,
269
+ bookingNumber: booking.bookingNumber,
270
+ startDate: booking.startDate,
271
+ endDate: booking.endDate,
272
+ sellCurrency: booking.sellCurrency,
273
+ sellAmountCents: booking.sellAmountCents,
274
+ }
275
+ : null,
276
+ invoice: invoice
277
+ ? {
278
+ id: invoice.id,
279
+ invoiceNumber: invoice.invoiceNumber,
280
+ invoiceType: invoice.invoiceType,
281
+ status: invoice.status,
282
+ currency: invoice.currency,
283
+ totalCents: invoice.totalCents,
284
+ balanceDueCents: invoice.balanceDueCents,
285
+ issueDate: invoice.issueDate,
286
+ dueDate: invoice.dueDate,
287
+ }
288
+ : null,
289
+ traveler: recipient
290
+ ? {
291
+ firstName: recipient.firstName,
292
+ lastName: recipient.lastName,
293
+ email: recipient.email,
294
+ participantType: recipient.participantType,
295
+ isPrimary: recipient.isPrimary,
296
+ }
297
+ : null,
298
+ travelers: participants,
299
+ items,
300
+ ...(input.data ?? {}),
301
+ },
302
+ targetType: "payment_session",
303
+ targetId: session.id,
304
+ bookingId: session.bookingId ?? null,
305
+ invoiceId: session.invoiceId ?? null,
306
+ paymentSessionId: session.id,
307
+ personId: session.payerPersonId ?? booking?.personId ?? null,
308
+ organizationId: session.payerOrganizationId ?? booking?.organizationId ?? null,
309
+ metadata: input.metadata ?? null,
310
+ scheduledFor: input.scheduledFor ?? null,
311
+ });
312
+ }
313
+ export async function sendInvoiceNotification(db, dispatcher, invoiceId, input) {
314
+ const [invoice] = await db.select().from(invoices).where(eq(invoices.id, invoiceId)).limit(1);
315
+ if (!invoice) {
316
+ return null;
317
+ }
318
+ const [booking] = await db
319
+ .select()
320
+ .from(bookings)
321
+ .where(eq(bookings.id, invoice.bookingId))
322
+ .limit(1);
323
+ const [participants, items] = booking
324
+ ? await Promise.all([
325
+ listBookingNotificationParticipants(db, booking.id),
326
+ listBookingNotificationItems(db, booking.id),
327
+ ])
328
+ : [[], []];
329
+ const recipient = resolveReminderRecipient(booking ?? null, participants);
330
+ const [latestSession] = await db
331
+ .select()
332
+ .from(paymentSessions)
333
+ .where(eq(paymentSessions.invoiceId, invoice.id))
334
+ .orderBy(desc(paymentSessions.createdAt))
335
+ .limit(1);
336
+ const to = input.to ?? latestSession?.payerEmail ?? recipient?.email ?? null;
337
+ if (!to) {
338
+ throw new NotificationError("No recipient available for invoice notification");
339
+ }
340
+ return sendNotification(db, dispatcher, {
341
+ templateId: input.templateId ?? null,
342
+ templateSlug: input.templateSlug ?? null,
343
+ channel: input.channel,
344
+ provider: input.provider ?? null,
345
+ to,
346
+ from: input.from ?? null,
347
+ subject: input.subject ?? null,
348
+ html: input.html ?? null,
349
+ text: input.text ?? null,
350
+ data: {
351
+ invoice: {
352
+ id: invoice.id,
353
+ invoiceNumber: invoice.invoiceNumber,
354
+ invoiceType: invoice.invoiceType,
355
+ status: invoice.status,
356
+ currency: invoice.currency,
357
+ subtotalCents: invoice.subtotalCents,
358
+ taxCents: invoice.taxCents,
359
+ totalCents: invoice.totalCents,
360
+ paidCents: invoice.paidCents,
361
+ balanceDueCents: invoice.balanceDueCents,
362
+ issueDate: invoice.issueDate,
363
+ dueDate: invoice.dueDate,
364
+ },
365
+ booking: booking
366
+ ? {
367
+ id: booking.id,
368
+ bookingNumber: booking.bookingNumber,
369
+ startDate: booking.startDate,
370
+ endDate: booking.endDate,
371
+ sellCurrency: booking.sellCurrency,
372
+ sellAmountCents: booking.sellAmountCents,
373
+ }
374
+ : null,
375
+ paymentSession: latestSession
376
+ ? {
377
+ id: latestSession.id,
378
+ status: latestSession.status,
379
+ provider: latestSession.provider,
380
+ paymentUrl: resolveNotificationPaymentUrl(latestSession.id, {
381
+ paymentLinkBaseUrl: input.paymentLinkBaseUrl,
382
+ redirectUrl: latestSession.redirectUrl,
383
+ }),
384
+ redirectUrl: latestSession.redirectUrl,
385
+ expiresAt: latestSession.expiresAt,
386
+ amountCents: latestSession.amountCents,
387
+ currency: latestSession.currency,
388
+ }
389
+ : null,
390
+ traveler: recipient
391
+ ? {
392
+ firstName: recipient.firstName,
393
+ lastName: recipient.lastName,
394
+ email: recipient.email,
395
+ participantType: recipient.participantType,
396
+ isPrimary: recipient.isPrimary,
397
+ }
398
+ : null,
399
+ travelers: participants,
400
+ items,
401
+ ...(input.data ?? {}),
402
+ },
403
+ targetType: "invoice",
404
+ targetId: invoice.id,
405
+ bookingId: invoice.bookingId,
406
+ invoiceId: invoice.id,
407
+ paymentSessionId: latestSession?.id ?? null,
408
+ personId: invoice.personId ?? booking?.personId ?? null,
409
+ organizationId: invoice.organizationId ?? booking?.organizationId ?? null,
410
+ metadata: input.metadata ?? null,
411
+ scheduledFor: input.scheduledFor ?? null,
412
+ });
413
+ }
@@ -0,0 +1,42 @@
1
+ import type { NotificationAttachment } from "./types.js";
2
+ export declare function normalizeDeliveryAttachments(attachments: Array<{
3
+ filename: string;
4
+ contentBase64?: string | null;
5
+ path?: string | null;
6
+ contentType?: string | null;
7
+ disposition?: "attachment" | "inline" | null;
8
+ contentId?: string | null;
9
+ }> | null | undefined): NotificationAttachment[] | undefined;
10
+ export declare function serializeNotificationError(error: unknown): {
11
+ code: string | number | boolean | null;
12
+ status: string | number | boolean | null;
13
+ statusCode: string | number | boolean | null;
14
+ responseStatus: string | number | boolean | null;
15
+ responseBody: string | number | boolean | null;
16
+ data: string | number | boolean | null;
17
+ notificationRequest: string | number | boolean | null;
18
+ name: string;
19
+ message: string;
20
+ stack: string | null;
21
+ cause: string | number | boolean | {
22
+ name: string;
23
+ message: string;
24
+ stack: string | null;
25
+ } | null;
26
+ };
27
+ export declare function resolveNotificationPaymentUrl(paymentSessionId: string, options?: {
28
+ paymentLinkBaseUrl?: string | null;
29
+ redirectUrl?: string | null;
30
+ }): string | null;
31
+ export declare function metadataWithoutFailureLog(metadata: Record<string, unknown> | null | undefined): {
32
+ [x: string]: unknown;
33
+ } | null;
34
+ export declare function attachmentsFromMetadata(metadata: Record<string, unknown> | null | undefined): {
35
+ contentId?: string | undefined;
36
+ disposition?: "inline" | "attachment" | undefined;
37
+ contentType?: string | undefined;
38
+ path?: string | undefined;
39
+ contentBase64?: string | undefined;
40
+ filename: string;
41
+ }[] | undefined;
42
+ //# sourceMappingURL=service-delivery-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-delivery-metadata.d.ts","sourceRoot":"","sources":["../src/service-delivery-metadata.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAExD,wBAAgB,4BAA4B,CAC1C,WAAW,EACP,KAAK,CAAC;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,YAAY,GAAG,QAAQ,GAAG,IAAI,CAAA;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAC,GACF,IAAI,GACJ,SAAS,GACZ,sBAAsB,EAAE,GAAG,SAAS,CAatC;AAoBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;EAiCxD;AAED,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,iBAOlF;AAcD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS;;SAK7F;AAYD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS;;;;;;;gBAY3F"}
@@ -0,0 +1,114 @@
1
+ import { buildPaymentLinkUrl } from "@voyant-travel/finance/payment-link";
2
+ export function normalizeDeliveryAttachments(attachments) {
3
+ if (!attachments || attachments.length === 0) {
4
+ return undefined;
5
+ }
6
+ return attachments.map((attachment) => ({
7
+ filename: attachment.filename,
8
+ ...(attachment.contentBase64 ? { contentBase64: attachment.contentBase64 } : {}),
9
+ ...(attachment.path ? { path: attachment.path } : {}),
10
+ ...(attachment.contentType ? { contentType: attachment.contentType } : {}),
11
+ ...(attachment.disposition ? { disposition: attachment.disposition } : {}),
12
+ ...(attachment.contentId ? { contentId: attachment.contentId } : {}),
13
+ }));
14
+ }
15
+ function truncateLogValue(value, maxLength = 4000) {
16
+ if (value.length <= maxLength)
17
+ return value;
18
+ return `${value.slice(0, maxLength)}…`;
19
+ }
20
+ function readErrorField(error, field) {
21
+ if (!error || typeof error !== "object" || !(field in error))
22
+ return null;
23
+ const value = error[field];
24
+ if (typeof value === "string")
25
+ return truncateLogValue(value);
26
+ if (typeof value === "number" || typeof value === "boolean")
27
+ return value;
28
+ if (value == null)
29
+ return null;
30
+ try {
31
+ return truncateLogValue(JSON.stringify(value));
32
+ }
33
+ catch {
34
+ return String(value);
35
+ }
36
+ }
37
+ export function serializeNotificationError(error) {
38
+ const base = error instanceof Error
39
+ ? {
40
+ name: error.name,
41
+ message: truncateLogValue(error.message),
42
+ stack: error.stack ? truncateLogValue(error.stack) : null,
43
+ cause: error.cause instanceof Error
44
+ ? {
45
+ name: error.cause.name,
46
+ message: truncateLogValue(error.cause.message),
47
+ stack: error.cause.stack ? truncateLogValue(error.cause.stack) : null,
48
+ }
49
+ : readErrorField(error, "cause"),
50
+ }
51
+ : {
52
+ name: typeof error,
53
+ message: truncateLogValue(String(error)),
54
+ stack: null,
55
+ cause: null,
56
+ };
57
+ return {
58
+ ...base,
59
+ code: readErrorField(error, "code"),
60
+ status: readErrorField(error, "status"),
61
+ statusCode: readErrorField(error, "statusCode"),
62
+ responseStatus: readErrorField(error, "responseStatus"),
63
+ responseBody: readErrorField(error, "responseBody") ?? readErrorField(error, "body"),
64
+ data: readErrorField(error, "data"),
65
+ notificationRequest: readErrorField(error, "notificationRequest"),
66
+ };
67
+ }
68
+ export function resolveNotificationPaymentUrl(paymentSessionId, options = {}) {
69
+ if (options.paymentLinkBaseUrl?.trim()) {
70
+ return buildPaymentLinkUrl(paymentSessionId, { baseUrl: options.paymentLinkBaseUrl });
71
+ }
72
+ return normalizeAbsolutePaymentUrl(options.redirectUrl);
73
+ }
74
+ function normalizeAbsolutePaymentUrl(value) {
75
+ const trimmed = value?.trim();
76
+ if (!trimmed)
77
+ return null;
78
+ try {
79
+ const url = new URL(trimmed);
80
+ return url.protocol === "http:" || url.protocol === "https:" ? trimmed : null;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ export function metadataWithoutFailureLog(metadata) {
87
+ if (!metadata)
88
+ return null;
89
+ const rest = { ...metadata };
90
+ delete rest.failureLog;
91
+ return rest;
92
+ }
93
+ function isAttachmentSummary(value) {
94
+ if (!value || typeof value !== "object" || Array.isArray(value))
95
+ return false;
96
+ const record = value;
97
+ return (typeof record.filename === "string" &&
98
+ record.filename.length > 0 &&
99
+ (typeof record.path === "string" || typeof record.contentBase64 === "string"));
100
+ }
101
+ export function attachmentsFromMetadata(metadata) {
102
+ const rawAttachments = metadata?.attachments;
103
+ if (!Array.isArray(rawAttachments))
104
+ return undefined;
105
+ const attachments = rawAttachments.filter(isAttachmentSummary).map((attachment) => ({
106
+ filename: attachment.filename,
107
+ ...(attachment.contentBase64 ? { contentBase64: attachment.contentBase64 } : {}),
108
+ ...(attachment.path ? { path: attachment.path } : {}),
109
+ ...(attachment.contentType ? { contentType: attachment.contentType } : {}),
110
+ ...(attachment.disposition ? { disposition: attachment.disposition } : {}),
111
+ ...(attachment.contentId ? { contentId: attachment.contentId } : {}),
112
+ }));
113
+ return attachments.length > 0 ? attachments : undefined;
114
+ }
@@ -0,0 +1,33 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { z } from "zod";
3
+ import type { composeNotificationReminderRuleSchema } from "./validation.js";
4
+ export interface ReminderRuleAuthoringIssue {
5
+ code: string;
6
+ field?: string;
7
+ message: string;
8
+ fix?: string;
9
+ }
10
+ export type ComposeNotificationReminderRuleInput = z.infer<typeof composeNotificationReminderRuleSchema>;
11
+ export type ComposeNotificationReminderRuleResult = {
12
+ ruleId: string;
13
+ stages: Array<{
14
+ id: string;
15
+ orderIndex: number;
16
+ channels: Array<{
17
+ id: string;
18
+ orderIndex: number;
19
+ }>;
20
+ }>;
21
+ };
22
+ export type ComposeNotificationReminderRuleOutcome = {
23
+ status: "ok";
24
+ result: ComposeNotificationReminderRuleResult;
25
+ reused: boolean;
26
+ } | {
27
+ status: "invalid";
28
+ issues: ReminderRuleAuthoringIssue[];
29
+ };
30
+ export declare function composeNotificationReminderRule(db: PostgresJsDatabase, input: ComposeNotificationReminderRuleInput, options?: {
31
+ idempotencyKey?: string;
32
+ }): Promise<ComposeNotificationReminderRuleOutcome>;
33
+ //# sourceMappingURL=service-reminder-authoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-reminder-authoring.d.ts","sourceRoot":"","sources":["../src/service-reminder-authoring.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAU5B,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,iBAAiB,CAAA;AAE5E,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,oCAAoC,GAAG,CAAC,CAAC,KAAK,CACxD,OAAO,qCAAqC,CAC7C,CAAA;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAA;QACV,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,KAAK,CAAC;YACd,EAAE,EAAE,MAAM,CAAA;YACV,UAAU,EAAE,MAAM,CAAA;SACnB,CAAC,CAAA;KACH,CAAC,CAAA;CACH,CAAA;AAED,MAAM,MAAM,sCAAsC,GAC9C;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,qCAAqC,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAChF;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,0BAA0B,EAAE,CAAA;CAAE,CAAA;AA8Z/D,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,oCAAoC,EAC3C,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,OAAO,CAAC,sCAAsC,CAAC,CAWjD"}