@voyant-travel/charters 0.117.2

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 (108) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +16 -0
  3. package/dist/adapters/index.d.ts +254 -0
  4. package/dist/adapters/index.d.ts.map +1 -0
  5. package/dist/adapters/index.js +16 -0
  6. package/dist/adapters/memoize.d.ts +28 -0
  7. package/dist/adapters/memoize.d.ts.map +1 -0
  8. package/dist/adapters/memoize.js +121 -0
  9. package/dist/adapters/mock.d.ts +50 -0
  10. package/dist/adapters/mock.d.ts.map +1 -0
  11. package/dist/adapters/mock.js +194 -0
  12. package/dist/adapters/registry.d.ts +24 -0
  13. package/dist/adapters/registry.d.ts.map +1 -0
  14. package/dist/adapters/registry.js +40 -0
  15. package/dist/booking-extension.d.ts +895 -0
  16. package/dist/booking-extension.d.ts.map +1 -0
  17. package/dist/booking-extension.js +339 -0
  18. package/dist/catalog-policy.d.ts +23 -0
  19. package/dist/catalog-policy.d.ts.map +1 -0
  20. package/dist/catalog-policy.js +400 -0
  21. package/dist/content-shape.d.ts +5 -0
  22. package/dist/content-shape.d.ts.map +1 -0
  23. package/dist/content-shape.js +13 -0
  24. package/dist/draft-shape.d.ts +29 -0
  25. package/dist/draft-shape.d.ts.map +1 -0
  26. package/dist/draft-shape.js +63 -0
  27. package/dist/index.d.ts +31 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +55 -0
  30. package/dist/lib/key.d.ts +22 -0
  31. package/dist/lib/key.d.ts.map +1 -0
  32. package/dist/lib/key.js +24 -0
  33. package/dist/routes-public.d.ts +785 -0
  34. package/dist/routes-public.d.ts.map +1 -0
  35. package/dist/routes-public.js +234 -0
  36. package/dist/routes.d.ts +1744 -0
  37. package/dist/routes.d.ts.map +1 -0
  38. package/dist/routes.js +543 -0
  39. package/dist/schema-core.d.ts +815 -0
  40. package/dist/schema-core.d.ts.map +1 -0
  41. package/dist/schema-core.js +98 -0
  42. package/dist/schema-itinerary.d.ts +239 -0
  43. package/dist/schema-itinerary.d.ts.map +1 -0
  44. package/dist/schema-itinerary.js +30 -0
  45. package/dist/schema-pricing.d.ts +385 -0
  46. package/dist/schema-pricing.d.ts.map +1 -0
  47. package/dist/schema-pricing.js +62 -0
  48. package/dist/schema-shared.d.ts +8 -0
  49. package/dist/schema-shared.d.ts.map +1 -0
  50. package/dist/schema-shared.js +37 -0
  51. package/dist/schema-sourced-content.d.ts +253 -0
  52. package/dist/schema-sourced-content.d.ts.map +1 -0
  53. package/dist/schema-sourced-content.js +44 -0
  54. package/dist/schema-yachts.d.ts +367 -0
  55. package/dist/schema-yachts.d.ts.map +1 -0
  56. package/dist/schema-yachts.js +30 -0
  57. package/dist/schema.d.ts +8 -0
  58. package/dist/schema.d.ts.map +1 -0
  59. package/dist/schema.js +7 -0
  60. package/dist/service-bookings-helpers.d.ts +20 -0
  61. package/dist/service-bookings-helpers.d.ts.map +1 -0
  62. package/dist/service-bookings-helpers.js +67 -0
  63. package/dist/service-bookings-local.d.ts +5 -0
  64. package/dist/service-bookings-local.d.ts.map +1 -0
  65. package/dist/service-bookings-local.js +177 -0
  66. package/dist/service-bookings-types.d.ts +88 -0
  67. package/dist/service-bookings-types.d.ts.map +1 -0
  68. package/dist/service-bookings-types.js +1 -0
  69. package/dist/service-bookings.d.ts +36 -0
  70. package/dist/service-bookings.d.ts.map +1 -0
  71. package/dist/service-bookings.js +267 -0
  72. package/dist/service-catalog-plane.d.ts +58 -0
  73. package/dist/service-catalog-plane.d.ts.map +1 -0
  74. package/dist/service-catalog-plane.js +145 -0
  75. package/dist/service-content-synthesizer.d.ts +42 -0
  76. package/dist/service-content-synthesizer.d.ts.map +1 -0
  77. package/dist/service-content-synthesizer.js +122 -0
  78. package/dist/service-content.d.ts +43 -0
  79. package/dist/service-content.d.ts.map +1 -0
  80. package/dist/service-content.js +248 -0
  81. package/dist/service-myba.d.ts +85 -0
  82. package/dist/service-myba.d.ts.map +1 -0
  83. package/dist/service-myba.js +88 -0
  84. package/dist/service-pricing.d.ts +64 -0
  85. package/dist/service-pricing.d.ts.map +1 -0
  86. package/dist/service-pricing.js +167 -0
  87. package/dist/service.d.ts +131 -0
  88. package/dist/service.d.ts.map +1 -0
  89. package/dist/service.js +279 -0
  90. package/dist/validation-core.d.ts +152 -0
  91. package/dist/validation-core.d.ts.map +1 -0
  92. package/dist/validation-core.js +66 -0
  93. package/dist/validation-itinerary.d.ts +43 -0
  94. package/dist/validation-itinerary.d.ts.map +1 -0
  95. package/dist/validation-itinerary.js +19 -0
  96. package/dist/validation-pricing.d.ts +103 -0
  97. package/dist/validation-pricing.d.ts.map +1 -0
  98. package/dist/validation-pricing.js +28 -0
  99. package/dist/validation-shared.d.ts +61 -0
  100. package/dist/validation-shared.d.ts.map +1 -0
  101. package/dist/validation-shared.js +60 -0
  102. package/dist/validation-yachts.d.ts +76 -0
  103. package/dist/validation-yachts.d.ts.map +1 -0
  104. package/dist/validation-yachts.js +36 -0
  105. package/dist/validation.d.ts +6 -0
  106. package/dist/validation.d.ts.map +1 -0
  107. package/dist/validation.js +5 -0
  108. package/package.json +116 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-extension.d.ts","sourceRoot":"","sources":["../src/booking-extension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,339 @@
1
+ import { typeIdRef } from "@voyant-travel/db/lib/typeid-column";
2
+ import { parseJsonBody } from "@voyant-travel/hono";
3
+ import { eq } from "drizzle-orm";
4
+ import { char, index, jsonb, numeric, pgTable, smallint, text, timestamp, } from "drizzle-orm/pg-core";
5
+ import { Hono } from "hono";
6
+ import { z } from "zod";
7
+ import { charterBookingModeEnum, charterSourceEnum } from "./schema-shared.js";
8
+ // ---------- schema ----------
9
+ /**
10
+ * 1:1 booking extension for charters. Holds the mode discriminator
11
+ * (`per_suite` | `whole_yacht`), provenance, snapshot pricing fields
12
+ * for both modes, and APA reconciliation state for whole-yacht charters.
13
+ *
14
+ * Soft FKs to charter_voyages / charter_suites (text columns; no
15
+ * cross-module .references()) follow the schema discipline rule.
16
+ *
17
+ * APA fields are only meaningful when bookingMode = 'whole_yacht'. They
18
+ * stay null for per_suite bookings.
19
+ */
20
+ export const bookingCharterDetails = pgTable("booking_charter_details", {
21
+ bookingId: text("booking_id").primaryKey(),
22
+ bookingMode: charterBookingModeEnum("booking_mode").notNull(),
23
+ // Provenance — local rows reference local TypeIDs; external rows carry
24
+ // a sourceRef back to the upstream adapter and have nullable local FKs.
25
+ source: charterSourceEnum("source").notNull().default("local"),
26
+ sourceProvider: text("source_provider"),
27
+ sourceRef: jsonb("source_ref").$type(),
28
+ voyageId: typeIdRef("voyage_id"),
29
+ suiteId: typeIdRef("suite_id"),
30
+ yachtId: typeIdRef("yacht_id"),
31
+ // Display hints — populated for both local and external so the UI can
32
+ // render booking history without re-resolving the voyage every time.
33
+ voyageDisplayName: text("voyage_display_name"),
34
+ suiteDisplayName: text("suite_display_name"),
35
+ yachtName: text("yacht_name"),
36
+ charterAreaSnapshot: text("charter_area_snapshot"),
37
+ guestCount: smallint("guest_count").notNull(),
38
+ quotedCurrency: char("quoted_currency", { length: 3 }).notNull(),
39
+ // per_suite pricing snapshot
40
+ quotedSuitePrice: numeric("quoted_suite_price", { precision: 12, scale: 2 }),
41
+ quotedPortFee: numeric("quoted_port_fee", { precision: 12, scale: 2 }),
42
+ // whole_yacht pricing snapshot
43
+ quotedCharterFee: numeric("quoted_charter_fee", { precision: 15, scale: 2 }),
44
+ apaPercent: numeric("apa_percent", { precision: 5, scale: 2 }),
45
+ apaAmount: numeric("apa_amount", { precision: 15, scale: 2 }),
46
+ quotedTotal: numeric("quoted_total", { precision: 15, scale: 2 }).notNull(),
47
+ // MYBA contract — soft FK; nullable until generateContract() runs.
48
+ mybaTemplateIdSnapshot: text("myba_template_id_snapshot"),
49
+ mybaContractId: typeIdRef("myba_contract_id"),
50
+ // APA reconciliation state (whole_yacht only). All optional & default 0.
51
+ apaPaidAmount: numeric("apa_paid_amount", { precision: 15, scale: 2 }).default("0.00"),
52
+ apaSpentAmount: numeric("apa_spent_amount", { precision: 15, scale: 2 }).default("0.00"),
53
+ apaRefundAmount: numeric("apa_refund_amount", { precision: 15, scale: 2 }).default("0.00"),
54
+ apaSettledAt: timestamp("apa_settled_at", { withTimezone: true }),
55
+ connectorBookingRef: text("connector_booking_ref"),
56
+ connectorStatus: text("connector_status"),
57
+ notes: text("notes"),
58
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
59
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
60
+ }, (t) => [
61
+ index("idx_bchd_mode").on(t.bookingMode),
62
+ index("idx_bchd_source").on(t.source),
63
+ index("idx_bchd_voyage").on(t.voyageId),
64
+ index("idx_bchd_suite").on(t.suiteId),
65
+ index("idx_bchd_yacht").on(t.yachtId),
66
+ index("idx_bchd_myba_contract").on(t.mybaContractId),
67
+ index("idx_bchd_connector_ref").on(t.connectorBookingRef),
68
+ index("idx_bchd_provider").on(t.sourceProvider),
69
+ ]);
70
+ // ---------- validation ----------
71
+ const sourceRefValueSchema = z
72
+ .object({
73
+ connectionId: z.string().optional(),
74
+ externalId: z.string(),
75
+ })
76
+ .catchall(z.unknown());
77
+ const moneyString = z.string().regex(/^-?\d+(\.\d{1,2})?$/);
78
+ export const charterDetailUpsertSchema = z
79
+ .object({
80
+ bookingMode: z.enum(["per_suite", "whole_yacht"]),
81
+ source: z.enum(["local", "external"]).default("local"),
82
+ sourceProvider: z.string().optional().nullable(),
83
+ sourceRef: sourceRefValueSchema.optional().nullable(),
84
+ voyageId: z.string().optional().nullable(),
85
+ suiteId: z.string().optional().nullable(),
86
+ yachtId: z.string().optional().nullable(),
87
+ voyageDisplayName: z.string().optional().nullable(),
88
+ suiteDisplayName: z.string().optional().nullable(),
89
+ yachtName: z.string().optional().nullable(),
90
+ charterAreaSnapshot: z.string().optional().nullable(),
91
+ guestCount: z.number().int().min(1).max(50),
92
+ quotedCurrency: z.string().length(3),
93
+ quotedSuitePrice: moneyString.optional().nullable(),
94
+ quotedPortFee: moneyString.optional().nullable(),
95
+ quotedCharterFee: moneyString.optional().nullable(),
96
+ apaPercent: moneyString.optional().nullable(),
97
+ apaAmount: moneyString.optional().nullable(),
98
+ quotedTotal: moneyString,
99
+ mybaTemplateIdSnapshot: z.string().optional().nullable(),
100
+ mybaContractId: z.string().optional().nullable(),
101
+ apaPaidAmount: moneyString.optional().nullable(),
102
+ apaSpentAmount: moneyString.optional().nullable(),
103
+ apaRefundAmount: moneyString.optional().nullable(),
104
+ connectorBookingRef: z.string().optional().nullable(),
105
+ connectorStatus: z.string().optional().nullable(),
106
+ notes: z.string().optional().nullable(),
107
+ })
108
+ .superRefine((value, ctx) => {
109
+ if (value.source === "local") {
110
+ if (!value.voyageId) {
111
+ ctx.addIssue({
112
+ code: "custom",
113
+ path: ["voyageId"],
114
+ message: "voyageId is required when source='local'",
115
+ });
116
+ }
117
+ if (value.bookingMode === "per_suite" && !value.suiteId) {
118
+ ctx.addIssue({
119
+ code: "custom",
120
+ path: ["suiteId"],
121
+ message: "suiteId is required for local per_suite bookings",
122
+ });
123
+ }
124
+ }
125
+ else {
126
+ if (!value.sourceProvider) {
127
+ ctx.addIssue({
128
+ code: "custom",
129
+ path: ["sourceProvider"],
130
+ message: "sourceProvider is required when source='external'",
131
+ });
132
+ }
133
+ if (!value.sourceRef) {
134
+ ctx.addIssue({
135
+ code: "custom",
136
+ path: ["sourceRef"],
137
+ message: "sourceRef is required when source='external'",
138
+ });
139
+ }
140
+ }
141
+ if (value.bookingMode === "whole_yacht") {
142
+ if (!value.quotedCharterFee) {
143
+ ctx.addIssue({
144
+ code: "custom",
145
+ path: ["quotedCharterFee"],
146
+ message: "quotedCharterFee is required for whole_yacht bookings",
147
+ });
148
+ }
149
+ if (!value.apaPercent || !value.apaAmount) {
150
+ ctx.addIssue({
151
+ code: "custom",
152
+ path: ["apaPercent"],
153
+ message: "apaPercent + apaAmount required for whole_yacht bookings",
154
+ });
155
+ }
156
+ }
157
+ else if (value.bookingMode === "per_suite" && !value.quotedSuitePrice) {
158
+ ctx.addIssue({
159
+ code: "custom",
160
+ path: ["quotedSuitePrice"],
161
+ message: "quotedSuitePrice is required for per_suite bookings",
162
+ });
163
+ }
164
+ });
165
+ const apaPaymentSchema = z.object({
166
+ amount: moneyString,
167
+ note: z.string().optional().nullable(),
168
+ });
169
+ const apaReconcileSchema = z.object({
170
+ spentAmount: moneyString.optional(),
171
+ refundAmount: moneyString.optional(),
172
+ settle: z.boolean().default(false),
173
+ note: z.string().optional().nullable(),
174
+ });
175
+ // ---------- services ----------
176
+ export const bookingCharterDetailsService = {
177
+ async get(db, bookingId) {
178
+ const [row] = await db
179
+ .select()
180
+ .from(bookingCharterDetails)
181
+ .where(eq(bookingCharterDetails.bookingId, bookingId))
182
+ .limit(1);
183
+ return row ?? null;
184
+ },
185
+ async upsert(db, bookingId, data) {
186
+ const payload = { ...data, bookingId };
187
+ const [existing] = await db
188
+ .select({ bookingId: bookingCharterDetails.bookingId })
189
+ .from(bookingCharterDetails)
190
+ .where(eq(bookingCharterDetails.bookingId, bookingId))
191
+ .limit(1);
192
+ if (existing) {
193
+ const [row] = await db
194
+ .update(bookingCharterDetails)
195
+ .set({ ...payload, updatedAt: new Date() })
196
+ .where(eq(bookingCharterDetails.bookingId, bookingId))
197
+ .returning();
198
+ if (!row)
199
+ throw new Error("Failed to update booking charter details");
200
+ return row;
201
+ }
202
+ const [row] = await db.insert(bookingCharterDetails).values(payload).returning();
203
+ if (!row)
204
+ throw new Error("Failed to insert booking charter details");
205
+ return row;
206
+ },
207
+ async remove(db, bookingId) {
208
+ const result = await db
209
+ .delete(bookingCharterDetails)
210
+ .where(eq(bookingCharterDetails.bookingId, bookingId))
211
+ .returning({ id: bookingCharterDetails.bookingId });
212
+ return result.length > 0;
213
+ },
214
+ /**
215
+ * Record an APA payment. Adds to apaPaidAmount; does not validate
216
+ * against quoted apaAmount because real-world APA settlements may
217
+ * involve top-ups during the charter.
218
+ */
219
+ async recordApaPayment(db, bookingId, args) {
220
+ const existing = await this.get(db, bookingId);
221
+ if (!existing)
222
+ return null;
223
+ if (existing.bookingMode !== "whole_yacht") {
224
+ throw new Error("APA payments only apply to whole_yacht bookings");
225
+ }
226
+ const newPaid = addDecimal(existing.apaPaidAmount ?? "0.00", args.amount);
227
+ const [row] = await db
228
+ .update(bookingCharterDetails)
229
+ .set({
230
+ apaPaidAmount: newPaid,
231
+ notes: args.note
232
+ ? appendNote(existing.notes, `APA payment ${args.amount}: ${args.note}`)
233
+ : existing.notes,
234
+ updatedAt: new Date(),
235
+ })
236
+ .where(eq(bookingCharterDetails.bookingId, bookingId))
237
+ .returning();
238
+ return row ?? null;
239
+ },
240
+ /**
241
+ * Post-charter APA reconciliation. Records what was actually spent on
242
+ * board + any refund due back to the charterer. When `settle: true`
243
+ * stamps `apaSettledAt`. Caller is responsible for kicking off any
244
+ * downstream finance flow (refund issuance, etc.).
245
+ */
246
+ async reconcileApa(db, bookingId, args) {
247
+ const existing = await this.get(db, bookingId);
248
+ if (!existing)
249
+ return null;
250
+ if (existing.bookingMode !== "whole_yacht") {
251
+ throw new Error("APA reconciliation only applies to whole_yacht bookings");
252
+ }
253
+ const set = { updatedAt: new Date() };
254
+ if (args.spentAmount !== undefined)
255
+ set.apaSpentAmount = args.spentAmount;
256
+ if (args.refundAmount !== undefined)
257
+ set.apaRefundAmount = args.refundAmount;
258
+ if (args.settle)
259
+ set.apaSettledAt = new Date();
260
+ if (args.note)
261
+ set.notes = appendNote(existing.notes, `APA reconcile: ${args.note}`);
262
+ const [row] = await db
263
+ .update(bookingCharterDetails)
264
+ .set(set)
265
+ .where(eq(bookingCharterDetails.bookingId, bookingId))
266
+ .returning();
267
+ return row ?? null;
268
+ },
269
+ };
270
+ function addDecimal(a, b) {
271
+ // Both validated to ^-?\d+(\.\d{1,2})?$. Convert to cents (BigInt) then back.
272
+ return centsToString(stringToCents(a) + stringToCents(b));
273
+ }
274
+ function stringToCents(s) {
275
+ const negative = s.startsWith("-");
276
+ const abs = negative ? s.slice(1) : s;
277
+ const [whole = "0", frac = ""] = abs.split(".");
278
+ const fracPadded = `${frac}00`.slice(0, 2);
279
+ const cents = BigInt(whole) * 100n + BigInt(fracPadded);
280
+ return negative ? -cents : cents;
281
+ }
282
+ function centsToString(c) {
283
+ const negative = c < 0n;
284
+ const abs = negative ? -c : c;
285
+ const whole = abs / 100n;
286
+ const frac = (abs % 100n).toString().padStart(2, "0");
287
+ return `${negative ? "-" : ""}${whole.toString()}.${frac}`;
288
+ }
289
+ function appendNote(existing, addition) {
290
+ if (!existing)
291
+ return addition;
292
+ return `${existing}\n${addition}`;
293
+ }
294
+ export const chartersBookingExtensionRoutes = new Hono()
295
+ .get("/:bookingId/charter-details", async (c) => {
296
+ const row = await bookingCharterDetailsService.get(c.get("db"), c.req.param("bookingId"));
297
+ if (!row)
298
+ return c.json({ error: "not_found" }, 404);
299
+ return c.json({ data: row });
300
+ })
301
+ .put("/:bookingId/charter-details", async (c) => {
302
+ const data = await parseJsonBody(c, charterDetailUpsertSchema);
303
+ const row = await bookingCharterDetailsService.upsert(c.get("db"), c.req.param("bookingId"), data);
304
+ return c.json({ data: row });
305
+ })
306
+ .delete("/:bookingId/charter-details", async (c) => {
307
+ const ok = await bookingCharterDetailsService.remove(c.get("db"), c.req.param("bookingId"));
308
+ if (!ok)
309
+ return c.json({ error: "not_found" }, 404);
310
+ return c.body(null, 204);
311
+ })
312
+ .post("/:bookingId/charter-details/apa/payment", async (c) => {
313
+ const data = await parseJsonBody(c, apaPaymentSchema);
314
+ const row = await bookingCharterDetailsService.recordApaPayment(c.get("db"), c.req.param("bookingId"), { amount: data.amount, note: data.note ?? null });
315
+ if (!row)
316
+ return c.json({ error: "not_found" }, 404);
317
+ return c.json({ data: row });
318
+ })
319
+ .post("/:bookingId/charter-details/apa/reconcile", async (c) => {
320
+ const data = await parseJsonBody(c, apaReconcileSchema);
321
+ const row = await bookingCharterDetailsService.reconcileApa(c.get("db"), c.req.param("bookingId"), {
322
+ spentAmount: data.spentAmount,
323
+ refundAmount: data.refundAmount,
324
+ settle: data.settle,
325
+ note: data.note ?? null,
326
+ });
327
+ if (!row)
328
+ return c.json({ error: "not_found" }, 404);
329
+ return c.json({ data: row });
330
+ });
331
+ // ---------- HonoExtension export ----------
332
+ const chartersBookingExtensionDef = {
333
+ name: "charters-booking",
334
+ module: "bookings",
335
+ };
336
+ export const chartersBookingExtension = {
337
+ extension: chartersBookingExtensionDef,
338
+ adminRoutes: chartersBookingExtensionRoutes,
339
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Catalog plane field policy for `packages/charters`.
3
+ *
4
+ * The charters vertical models luxury yacht-style products: per-suite or
5
+ * whole-yacht booking, MYBA contracts, APA (Advance Provisioning Allowance)
6
+ * as a first-class concern. See `docs/architecture/charters-module.md` for
7
+ * the vertical's design.
8
+ *
9
+ * Scope of this file:
10
+ * - The root `charter_products` table (from `schema-core.ts`).
11
+ * - Provenance + identity fields the catalog plane needs to track.
12
+ *
13
+ * Out of scope (deferred to follow-up adoption passes):
14
+ * - `charter_voyages` — own lifecycle (sales status), own query surface.
15
+ * - `charter_yachts` — vessel reference data; cross-module link target.
16
+ * - `charter_suites` — selectable variant axis with own micro-registry.
17
+ * - `charter_schedule_days` — composite list (mixed-class leaves).
18
+ */
19
+ import { type FieldPolicyInput } from "@voyant-travel/catalog/contract";
20
+ declare const CHARTER_FIELD_POLICY: FieldPolicyInput[];
21
+ export declare const charterCatalogPolicy: import("@voyant-travel/catalog").FieldPolicy[];
22
+ export { CHARTER_FIELD_POLICY };
23
+ //# sourceMappingURL=catalog-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAE1F,QAAA,MAAM,oBAAoB,EAAE,gBAAgB,EA+X3C,CAAA;AAED,eAAO,MAAM,oBAAoB,gDAA0C,CAAA;AAE3E,OAAO,EAAE,oBAAoB,EAAE,CAAA"}