@voyantjs/notifications 0.6.7 → 0.6.9

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.
@@ -1,5 +1,6 @@
1
- import { bookingParticipants } from "@voyantjs/bookings/schema";
1
+ import { bookingItems, bookingTravelers } from "@voyantjs/bookings/schema";
2
2
  import { and, desc, eq } from "drizzle-orm";
3
+ import { renderLiquidTemplate } from "./liquid.js";
3
4
  export class NotificationError extends Error {
4
5
  constructor(message) {
5
6
  super(message);
@@ -48,35 +49,265 @@ export function summarizeNotificationAttachments(attachments) {
48
49
  contentId: attachment.contentId ?? null,
49
50
  }));
50
51
  }
51
- function resolveMustachePath(path, scope) {
52
- const parts = path.match(/[^.[\]]+/g) ?? [];
53
- let current = scope;
54
- for (const part of parts) {
55
- if (current == null || typeof current !== "object")
56
- return undefined;
57
- current = current[part];
58
- }
59
- return current;
60
- }
61
- function stringifyRenderedValue(value) {
62
- if (value == null)
63
- return "";
64
- if (typeof value === "string")
65
- return value;
66
- if (typeof value === "number" || typeof value === "boolean")
67
- return String(value);
68
- return JSON.stringify(value);
69
- }
70
52
  export function renderNotificationTemplate(template, data) {
71
- if (!template)
72
- return null;
73
- return template.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, path) => {
74
- return stringifyRenderedValue(resolveMustachePath(path.trim(), data));
75
- });
53
+ return renderLiquidTemplate(template, normalizeNotificationTemplateData(data));
54
+ }
55
+ export function previewNotificationTemplate(input) {
56
+ const data = normalizeNotificationTemplateData(input.data ?? {});
57
+ return {
58
+ channel: input.channel,
59
+ provider: input.provider ?? null,
60
+ fromAddress: input.fromAddress ?? null,
61
+ subject: renderNotificationTemplate(input.subjectTemplate, data),
62
+ html: renderNotificationTemplate(input.htmlTemplate, data),
63
+ text: renderNotificationTemplate(input.textTemplate, data),
64
+ };
76
65
  }
77
66
  export function toTimestamp(value) {
78
67
  return value ? new Date(value) : null;
79
68
  }
69
+ function centsToAmount(value) {
70
+ if (typeof value !== "number")
71
+ return null;
72
+ return value / 100;
73
+ }
74
+ function buildFullName(firstName, lastName) {
75
+ return [firstName, lastName]
76
+ .map((part) => (typeof part === "string" ? part.trim() : ""))
77
+ .filter(Boolean)
78
+ .join(" ");
79
+ }
80
+ function parseDate(value) {
81
+ if (!value)
82
+ return null;
83
+ const date = value instanceof Date ? value : new Date(String(value));
84
+ return Number.isNaN(date.getTime()) ? null : date;
85
+ }
86
+ function dateDiffInDays(from, to) {
87
+ const diff = to.getTime() - from.getTime();
88
+ return Math.ceil(diff / (24 * 60 * 60 * 1000));
89
+ }
90
+ function enrichTraveler(value) {
91
+ if (!value || typeof value !== "object" || Array.isArray(value))
92
+ return value;
93
+ const traveler = value;
94
+ const fullName = buildFullName(traveler.firstName, traveler.lastName);
95
+ return {
96
+ ...traveler,
97
+ fullName: fullName || null,
98
+ name: fullName || null,
99
+ role: traveler.participantType ?? null,
100
+ };
101
+ }
102
+ function enrichBooking(value) {
103
+ if (!value || typeof value !== "object" || Array.isArray(value))
104
+ return value;
105
+ const booking = value;
106
+ const bookingNumber = typeof booking.bookingNumber === "string"
107
+ ? booking.bookingNumber
108
+ : typeof booking.reference === "string"
109
+ ? booking.reference
110
+ : typeof booking.code === "string"
111
+ ? booking.code
112
+ : typeof booking.number === "string"
113
+ ? booking.number
114
+ : null;
115
+ const currency = typeof booking.currency === "string"
116
+ ? booking.currency
117
+ : typeof booking.sellCurrency === "string"
118
+ ? booking.sellCurrency
119
+ : null;
120
+ const totalAmount = centsToAmount(booking.totalAmountCents) ??
121
+ centsToAmount(booking.sellAmountCents) ??
122
+ (typeof booking.totalAmount === "number" ? booking.totalAmount : null);
123
+ const startDate = parseDate(booking.startDate);
124
+ const endDate = parseDate(booking.endDate);
125
+ const dateRange = startDate && endDate
126
+ ? `${startDate.toISOString().slice(0, 10)} - ${endDate.toISOString().slice(0, 10)}`
127
+ : null;
128
+ return {
129
+ ...booking,
130
+ code: bookingNumber,
131
+ number: bookingNumber,
132
+ reference: bookingNumber,
133
+ currency,
134
+ total: totalAmount,
135
+ totalAmount,
136
+ totalPrice: totalAmount,
137
+ dateRange,
138
+ };
139
+ }
140
+ function enrichInvoice(value) {
141
+ if (!value || typeof value !== "object" || Array.isArray(value))
142
+ return value;
143
+ const invoice = value;
144
+ return {
145
+ ...invoice,
146
+ number: typeof invoice.number === "string"
147
+ ? invoice.number
148
+ : typeof invoice.invoiceNumber === "string"
149
+ ? invoice.invoiceNumber
150
+ : null,
151
+ type: typeof invoice.type === "string"
152
+ ? invoice.type
153
+ : typeof invoice.invoiceType === "string"
154
+ ? invoice.invoiceType
155
+ : null,
156
+ subtotalAmount: centsToAmount(invoice.subtotalCents) ??
157
+ (typeof invoice.subtotalAmount === "number" ? invoice.subtotalAmount : null),
158
+ taxAmount: centsToAmount(invoice.taxCents) ??
159
+ (typeof invoice.taxAmount === "number" ? invoice.taxAmount : null),
160
+ totalAmount: centsToAmount(invoice.totalCents) ??
161
+ (typeof invoice.totalAmount === "number" ? invoice.totalAmount : null),
162
+ paidAmount: centsToAmount(invoice.paidCents) ??
163
+ (typeof invoice.paidAmount === "number" ? invoice.paidAmount : null),
164
+ balanceDueAmount: centsToAmount(invoice.balanceDueCents) ??
165
+ (typeof invoice.balanceDueAmount === "number" ? invoice.balanceDueAmount : null),
166
+ };
167
+ }
168
+ function enrichPaymentSession(value) {
169
+ if (!value || typeof value !== "object" || Array.isArray(value))
170
+ return value;
171
+ const session = value;
172
+ return {
173
+ ...session,
174
+ amount: centsToAmount(session.amountCents) ??
175
+ (typeof session.amount === "number" ? session.amount : null),
176
+ paymentUrl: typeof session.paymentUrl === "string"
177
+ ? session.paymentUrl
178
+ : typeof session.redirectUrl === "string"
179
+ ? session.redirectUrl
180
+ : null,
181
+ reference: typeof session.reference === "string"
182
+ ? session.reference
183
+ : typeof session.externalReference === "string"
184
+ ? session.externalReference
185
+ : null,
186
+ method: typeof session.method === "string"
187
+ ? session.method
188
+ : typeof session.paymentMethod === "string"
189
+ ? session.paymentMethod
190
+ : null,
191
+ };
192
+ }
193
+ function enrichPaymentSchedule(value) {
194
+ if (!value || typeof value !== "object" || Array.isArray(value))
195
+ return value;
196
+ const schedule = value;
197
+ const dueDate = parseDate(schedule.dueDate);
198
+ const today = new Date();
199
+ return {
200
+ ...schedule,
201
+ amountDue: centsToAmount(schedule.amountCents) ??
202
+ (typeof schedule.amountDue === "number" ? schedule.amountDue : null),
203
+ type: typeof schedule.type === "string"
204
+ ? schedule.type
205
+ : typeof schedule.scheduleType === "string"
206
+ ? schedule.scheduleType
207
+ : null,
208
+ daysLeft: dueDate ? dateDiffInDays(today, dueDate) : null,
209
+ };
210
+ }
211
+ function enrichDocument(value) {
212
+ if (!value || typeof value !== "object" || Array.isArray(value))
213
+ return value;
214
+ return value;
215
+ }
216
+ function enrichBookingItem(value) {
217
+ if (!value || typeof value !== "object" || Array.isArray(value))
218
+ return value;
219
+ const item = value;
220
+ return {
221
+ ...item,
222
+ description: typeof item.description === "string" && item.description.trim().length > 0
223
+ ? item.description
224
+ : typeof item.title === "string"
225
+ ? item.title
226
+ : null,
227
+ currency: typeof item.currency === "string"
228
+ ? item.currency
229
+ : typeof item.sellCurrency === "string"
230
+ ? item.sellCurrency
231
+ : null,
232
+ unitPrice: centsToAmount(item.unitSellAmountCents) ??
233
+ (typeof item.unitPrice === "number" ? item.unitPrice : null),
234
+ total: centsToAmount(item.totalSellAmountCents) ??
235
+ (typeof item.total === "number" ? item.total : null),
236
+ };
237
+ }
238
+ export function normalizeNotificationTemplateData(data) {
239
+ const traveler = enrichTraveler(data.traveler);
240
+ const travelers = Array.isArray(data.travelers)
241
+ ? data.travelers.map((entry) => enrichTraveler(entry))
242
+ : traveler
243
+ ? [traveler]
244
+ : [];
245
+ const booking = enrichBooking(data.booking);
246
+ const invoice = enrichInvoice(data.invoice);
247
+ const paymentSession = enrichPaymentSession(data.paymentSession);
248
+ const paymentSchedule = enrichPaymentSchedule(data.paymentSchedule);
249
+ const documents = Array.isArray(data.documents)
250
+ ? data.documents.map((document) => enrichDocument(document))
251
+ : [];
252
+ const items = Array.isArray(data.items) ? data.items.map((item) => enrichBookingItem(item)) : [];
253
+ const payment = paymentSchedule && typeof paymentSchedule === "object"
254
+ ? {
255
+ amount: paymentSchedule.amountDue ?? null,
256
+ currency: paymentSchedule.currency ?? null,
257
+ dueDate: paymentSchedule.dueDate ?? null,
258
+ daysLeft: paymentSchedule.daysLeft ?? null,
259
+ reference: booking?.reference ??
260
+ invoice?.number ??
261
+ null,
262
+ method: paymentSession?.method ??
263
+ paymentSession?.provider ??
264
+ null,
265
+ link: paymentSession?.paymentUrl ?? null,
266
+ payMode: paymentSchedule.type ?? null,
267
+ paidAmount: invoice?.paidAmount ?? null,
268
+ balanceDue: invoice?.balanceDueAmount ?? null,
269
+ isPaidInFull: invoice?.balanceDueAmount === 0,
270
+ }
271
+ : paymentSession && typeof paymentSession === "object"
272
+ ? {
273
+ amount: paymentSession.amount ?? null,
274
+ currency: paymentSession.currency ?? null,
275
+ dueDate: null,
276
+ daysLeft: null,
277
+ reference: paymentSession.reference ?? null,
278
+ method: paymentSession.method ?? null,
279
+ link: paymentSession.paymentUrl ?? null,
280
+ payMode: null,
281
+ paidAmount: null,
282
+ balanceDue: invoice?.balanceDueAmount ?? null,
283
+ isPaidInFull: invoice?.balanceDueAmount ===
284
+ 0,
285
+ }
286
+ : null;
287
+ const product = items.length > 0 && items[0] && typeof items[0] === "object"
288
+ ? {
289
+ title: items[0].title ??
290
+ items[0].description ??
291
+ null,
292
+ }
293
+ : null;
294
+ return {
295
+ ...data,
296
+ traveler,
297
+ travelers,
298
+ billingPerson: traveler,
299
+ billing: traveler,
300
+ booking,
301
+ invoice,
302
+ paymentSession,
303
+ paymentSchedule,
304
+ payment,
305
+ documents,
306
+ documentsCount: documents.length,
307
+ items,
308
+ product,
309
+ };
310
+ }
80
311
  export function startOfUtcDay(value) {
81
312
  return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()));
82
313
  }
@@ -89,37 +320,66 @@ export function toDateString(value) {
89
320
  export function buildReminderDedupeKey(ruleId, targetId, runDate) {
90
321
  return `${ruleId}:${targetId}:${runDate}`;
91
322
  }
92
- export function resolveReminderRecipient(participants) {
93
- const withEmail = participants.filter((participant) => participant.email);
323
+ export function resolveReminderRecipient(booking, participants) {
324
+ if (booking?.contactEmail) {
325
+ return {
326
+ email: booking.contactEmail,
327
+ firstName: booking.contactFirstName ?? "",
328
+ lastName: booking.contactLastName ?? "",
329
+ participantType: "booking_contact",
330
+ isPrimary: true,
331
+ };
332
+ }
333
+ const withEmail = participants.filter((traveler) => traveler.email);
94
334
  if (withEmail.length === 0) {
95
335
  return null;
96
336
  }
97
- const primary = withEmail.find((participant) => participant.isPrimary);
337
+ const nonStaffWithEmail = withEmail.filter((traveler) => traveler.participantType !== "staff");
338
+ const primary = nonStaffWithEmail.find((traveler) => traveler.isPrimary) ??
339
+ withEmail.find((traveler) => traveler.isPrimary);
98
340
  if (primary) {
99
341
  return primary;
100
342
  }
101
- const preferredTypes = ["booker", "contact", "traveler", "occupant"];
343
+ const preferredTypes = ["traveler", "occupant", "other"];
102
344
  for (const type of preferredTypes) {
103
- const match = withEmail.find((participant) => participant.participantType === type);
345
+ const match = nonStaffWithEmail.find((traveler) => traveler.participantType === type);
104
346
  if (match) {
105
347
  return match;
106
348
  }
107
349
  }
108
- return withEmail[0] ?? null;
350
+ return nonStaffWithEmail[0] ?? withEmail[0] ?? null;
109
351
  }
110
352
  export async function listBookingNotificationParticipants(db, bookingId) {
111
353
  return db
112
354
  .select({
113
- id: bookingParticipants.id,
114
- firstName: bookingParticipants.firstName,
115
- lastName: bookingParticipants.lastName,
116
- email: bookingParticipants.email,
117
- participantType: bookingParticipants.participantType,
118
- isPrimary: bookingParticipants.isPrimary,
355
+ id: bookingTravelers.id,
356
+ firstName: bookingTravelers.firstName,
357
+ lastName: bookingTravelers.lastName,
358
+ email: bookingTravelers.email,
359
+ participantType: bookingTravelers.participantType,
360
+ isPrimary: bookingTravelers.isPrimary,
361
+ })
362
+ .from(bookingTravelers)
363
+ .where(eq(bookingTravelers.bookingId, bookingId))
364
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt);
365
+ }
366
+ export async function listBookingNotificationItems(db, bookingId) {
367
+ const rows = await db
368
+ .select({
369
+ id: bookingItems.id,
370
+ title: bookingItems.title,
371
+ description: bookingItems.description,
372
+ quantity: bookingItems.quantity,
373
+ itemType: bookingItems.itemType,
374
+ serviceDate: bookingItems.serviceDate,
375
+ sellCurrency: bookingItems.sellCurrency,
376
+ unitSellAmountCents: bookingItems.unitSellAmountCents,
377
+ totalSellAmountCents: bookingItems.totalSellAmountCents,
119
378
  })
120
- .from(bookingParticipants)
121
- .where(eq(bookingParticipants.bookingId, bookingId))
122
- .orderBy(desc(bookingParticipants.isPrimary), bookingParticipants.createdAt);
379
+ .from(bookingItems)
380
+ .where(eq(bookingItems.bookingId, bookingId))
381
+ .orderBy(bookingItems.createdAt);
382
+ return rows.map((row) => enrichBookingItem(row));
123
383
  }
124
384
  export async function paginate(rowsPromise, totalPromise, limit, offset) {
125
385
  const [data, totalRows] = await Promise.all([rowsPromise, totalPromise]);
package/dist/service.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { createDefaultBookingDocumentAttachment } from "./service-booking-documents.js";
2
2
  export type { NotificationService } from "./service-shared.js";
3
- export { createNotificationService, NotificationError, renderNotificationTemplate, summarizeNotificationAttachments, } from "./service-shared.js";
3
+ export { createNotificationService, NotificationError, previewNotificationTemplate, renderNotificationTemplate, summarizeNotificationAttachments, } from "./service-shared.js";
4
4
  import { createDefaultBookingDocumentAttachment } from "./service-booking-documents.js";
5
5
  import { getDeliveryById, listDeliveries, sendInvoiceNotification, sendNotification, sendPaymentSessionNotification } from "./service-deliveries.js";
6
6
  import { runDueReminders } from "./service-reminders.js";
7
+ import { previewNotificationTemplate } from "./service-shared.js";
7
8
  import { createReminderRule, createTemplate, getReminderRuleById, getReminderRunById, getTemplateById, getTemplateBySlug, listReminderRules, listReminderRuns, listTemplates, updateReminderRule, updateTemplate } from "./service-templates.js";
8
9
  export declare const notificationsService: {
9
10
  listTemplates: typeof listTemplates;
@@ -11,6 +12,7 @@ export declare const notificationsService: {
11
12
  getTemplateBySlug: typeof getTemplateBySlug;
12
13
  createTemplate: typeof createTemplate;
13
14
  updateTemplate: typeof updateTemplate;
15
+ previewNotificationTemplate: typeof previewNotificationTemplate;
14
16
  listDeliveries: typeof listDeliveries;
15
17
  getDeliveryById: typeof getDeliveryById;
16
18
  sendNotification: typeof sendNotification;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sCAAsC,EAAE,MAAM,gCAAgC,CAAA;AACvF,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAEL,sCAAsC,EACvC,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,8BAA8B,EAC/B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,cAAc,EACf,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsBhC,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sCAAsC,EAAE,MAAM,gCAAgC,CAAA;AACvF,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAEL,sCAAsC,EACvC,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,8BAA8B,EAC/B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,cAAc,EACf,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuBhC,CAAA"}
package/dist/service.js CHANGED
@@ -1,8 +1,9 @@
1
1
  export { createDefaultBookingDocumentAttachment } from "./service-booking-documents.js";
2
- export { createNotificationService, NotificationError, renderNotificationTemplate, summarizeNotificationAttachments, } from "./service-shared.js";
2
+ export { createNotificationService, NotificationError, previewNotificationTemplate, renderNotificationTemplate, summarizeNotificationAttachments, } from "./service-shared.js";
3
3
  import { bookingDocumentNotificationsService, createDefaultBookingDocumentAttachment, } from "./service-booking-documents.js";
4
4
  import { getDeliveryById, listDeliveries, sendInvoiceNotification, sendNotification, sendPaymentSessionNotification, } from "./service-deliveries.js";
5
5
  import { runDueReminders } from "./service-reminders.js";
6
+ import { previewNotificationTemplate } from "./service-shared.js";
6
7
  import { createReminderRule, createTemplate, getReminderRuleById, getReminderRunById, getTemplateById, getTemplateBySlug, listReminderRules, listReminderRuns, listTemplates, updateReminderRule, updateTemplate, } from "./service-templates.js";
7
8
  export const notificationsService = {
8
9
  listTemplates,
@@ -10,6 +11,7 @@ export const notificationsService = {
10
11
  getTemplateBySlug,
11
12
  createTemplate,
12
13
  updateTemplate,
14
+ previewNotificationTemplate,
13
15
  listDeliveries,
14
16
  getDeliveryById,
15
17
  sendNotification,
@@ -0,0 +1,23 @@
1
+ export type NotificationTemplateVariableType = "string" | "number" | "currency" | "date" | "datetime" | "boolean" | "email" | "phone" | "url" | "array";
2
+ export interface NotificationTemplateVariableDefinition {
3
+ key: string;
4
+ label: string;
5
+ example: string;
6
+ type: NotificationTemplateVariableType;
7
+ description?: string;
8
+ }
9
+ export interface NotificationTemplateVariableCategory {
10
+ id: string;
11
+ label: string;
12
+ description?: string;
13
+ variables: NotificationTemplateVariableDefinition[];
14
+ }
15
+ export interface NotificationLiquidSnippet {
16
+ id: string;
17
+ label: string;
18
+ description: string;
19
+ code: string;
20
+ }
21
+ export declare const notificationTemplateVariableCatalog: NotificationTemplateVariableCategory[];
22
+ export declare const notificationLiquidSnippets: NotificationLiquidSnippet[];
23
+ //# sourceMappingURL=template-authoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-authoring.d.ts","sourceRoot":"","sources":["../src/template-authoring.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gCAAgC,GACxC,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,MAAM,GACN,UAAU,GACV,SAAS,GACT,OAAO,GACP,OAAO,GACP,KAAK,GACL,OAAO,CAAA;AAEX,MAAM,WAAW,sCAAsC;IACrD,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gCAAgC,CAAA;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,oCAAoC;IACnD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,sCAAsC,EAAE,CAAA;CACpD;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,eAAO,MAAM,mCAAmC,EAAE,oCAAoC,EA4UrF,CAAA;AAED,eAAO,MAAM,0BAA0B,EAAE,yBAAyB,EAuDjE,CAAA"}