@voyant-travel/inventory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/action-ledger-drift.d.ts +29 -0
- package/dist/action-ledger-drift.d.ts.map +1 -0
- package/dist/action-ledger-drift.js +338 -0
- package/dist/action-ledger.d.ts +104 -0
- package/dist/action-ledger.d.ts.map +1 -0
- package/dist/action-ledger.js +100 -0
- package/dist/authoring/builder.d.ts +37 -0
- package/dist/authoring/builder.d.ts.map +1 -0
- package/dist/authoring/builder.js +248 -0
- package/dist/authoring/clone-content.d.ts +38 -0
- package/dist/authoring/clone-content.d.ts.map +1 -0
- package/dist/authoring/clone-content.js +367 -0
- package/dist/authoring/clone-pricing.d.ts +9 -0
- package/dist/authoring/clone-pricing.d.ts.map +1 -0
- package/dist/authoring/clone-pricing.js +242 -0
- package/dist/authoring/clone.d.ts +45 -0
- package/dist/authoring/clone.d.ts.map +1 -0
- package/dist/authoring/clone.js +142 -0
- package/dist/authoring/errors.d.ts +21 -0
- package/dist/authoring/errors.d.ts.map +1 -0
- package/dist/authoring/errors.js +13 -0
- package/dist/authoring/extension.d.ts +248 -0
- package/dist/authoring/extension.d.ts.map +1 -0
- package/dist/authoring/extension.js +116 -0
- package/dist/authoring/index.d.ts +12 -0
- package/dist/authoring/index.d.ts.map +1 -0
- package/dist/authoring/index.js +11 -0
- package/dist/authoring/schema.d.ts +85 -0
- package/dist/authoring/schema.d.ts.map +1 -0
- package/dist/authoring/schema.js +16 -0
- package/dist/authoring/service.d.ts +28 -0
- package/dist/authoring/service.d.ts.map +1 -0
- package/dist/authoring/service.js +66 -0
- package/dist/authoring/spec.d.ts +524 -0
- package/dist/authoring/spec.d.ts.map +1 -0
- package/dist/authoring/spec.js +167 -0
- package/dist/authoring/validate.d.ts +17 -0
- package/dist/authoring/validate.d.ts.map +1 -0
- package/dist/authoring/validate.js +83 -0
- package/dist/authoring.d.ts +2 -0
- package/dist/authoring.d.ts.map +1 -0
- package/dist/authoring.js +1 -0
- package/dist/booking-engine/handler-support.d.ts +91 -0
- package/dist/booking-engine/handler-support.d.ts.map +1 -0
- package/dist/booking-engine/handler-support.js +355 -0
- package/dist/booking-engine/handler.d.ts +404 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +398 -0
- package/dist/booking-engine/index.d.ts +8 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +7 -0
- package/dist/booking-engine.d.ts +2 -0
- package/dist/booking-engine.d.ts.map +1 -0
- package/dist/booking-engine.js +1 -0
- package/dist/booking-extension.d.ts +278 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +161 -0
- package/dist/catalog-policy-departures.d.ts +52 -0
- package/dist/catalog-policy-departures.d.ts.map +1 -0
- package/dist/catalog-policy-departures.js +169 -0
- package/dist/catalog-policy-destinations.d.ts +43 -0
- package/dist/catalog-policy-destinations.d.ts.map +1 -0
- package/dist/catalog-policy-destinations.js +165 -0
- package/dist/catalog-policy-pricing.d.ts +55 -0
- package/dist/catalog-policy-pricing.d.ts.map +1 -0
- package/dist/catalog-policy-pricing.js +109 -0
- package/dist/catalog-policy-promotions.d.ts +52 -0
- package/dist/catalog-policy-promotions.d.ts.map +1 -0
- package/dist/catalog-policy-promotions.js +270 -0
- package/dist/catalog-policy-taxonomy.d.ts +51 -0
- package/dist/catalog-policy-taxonomy.d.ts.map +1 -0
- package/dist/catalog-policy-taxonomy.js +191 -0
- package/dist/catalog-policy.d.ts +33 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +733 -0
- package/dist/content-shape.d.ts +15 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +28 -0
- package/dist/draft-shape.d.ts +43 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +48 -0
- package/dist/events.d.ts +37 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +32 -0
- package/dist/extras/catalog-policy.d.ts +30 -0
- package/dist/extras/catalog-policy.d.ts.map +1 -0
- package/dist/extras/catalog-policy.js +319 -0
- package/dist/extras/content-shape.d.ts +5 -0
- package/dist/extras/content-shape.d.ts.map +1 -0
- package/dist/extras/content-shape.js +13 -0
- package/dist/extras/draft-shape.d.ts +34 -0
- package/dist/extras/draft-shape.d.ts.map +1 -0
- package/dist/extras/draft-shape.js +69 -0
- package/dist/extras/routes.d.ts +380 -0
- package/dist/extras/routes.d.ts.map +1 -0
- package/dist/extras/routes.js +59 -0
- package/dist/extras/schema-sourced-content.d.ts +254 -0
- package/dist/extras/schema-sourced-content.d.ts.map +1 -0
- package/dist/extras/schema-sourced-content.js +45 -0
- package/dist/extras/schema.d.ts +628 -0
- package/dist/extras/schema.d.ts.map +1 -0
- package/dist/extras/schema.js +87 -0
- package/dist/extras/service-catalog-plane.d.ts +77 -0
- package/dist/extras/service-catalog-plane.d.ts.map +1 -0
- package/dist/extras/service-catalog-plane.js +219 -0
- package/dist/extras/service-content-synthesizer.d.ts +41 -0
- package/dist/extras/service-content-synthesizer.d.ts.map +1 -0
- package/dist/extras/service-content-synthesizer.js +138 -0
- package/dist/extras/service-content.d.ts +48 -0
- package/dist/extras/service-content.d.ts.map +1 -0
- package/dist/extras/service-content.js +253 -0
- package/dist/extras/service.d.ts +185 -0
- package/dist/extras/service.d.ts.map +1 -0
- package/dist/extras/service.js +96 -0
- package/dist/extras/validation.d.ts +437 -0
- package/dist/extras/validation.d.ts.map +1 -0
- package/dist/extras/validation.js +149 -0
- package/dist/extras.d.ts +267 -0
- package/dist/extras.d.ts.map +1 -0
- package/dist/extras.js +19 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/interface.d.ts +5869 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +54 -0
- package/dist/public-routes.d.ts +2 -0
- package/dist/public-routes.d.ts.map +1 -0
- package/dist/public-routes.js +1 -0
- package/dist/public-validation.d.ts +2 -0
- package/dist/public-validation.d.ts.map +1 -0
- package/dist/public-validation.js +1 -0
- package/dist/read-model.d.ts +25 -0
- package/dist/read-model.d.ts.map +1 -0
- package/dist/read-model.js +99 -0
- package/dist/route-env.d.ts +22 -0
- package/dist/route-env.d.ts.map +1 -0
- package/dist/route-env.js +1 -0
- package/dist/routes-associations.d.ts +164 -0
- package/dist/routes-associations.d.ts.map +1 -0
- package/dist/routes-associations.js +100 -0
- package/dist/routes-catalog.d.ts +436 -0
- package/dist/routes-catalog.d.ts.map +1 -0
- package/dist/routes-catalog.js +104 -0
- package/dist/routes-configuration.d.ts +773 -0
- package/dist/routes-configuration.d.ts.map +1 -0
- package/dist/routes-configuration.js +364 -0
- package/dist/routes-content.d.ts +74 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +117 -0
- package/dist/routes-core.d.ts +331 -0
- package/dist/routes-core.d.ts.map +1 -0
- package/dist/routes-core.js +95 -0
- package/dist/routes-itinerary.d.ts +759 -0
- package/dist/routes-itinerary.d.ts.map +1 -0
- package/dist/routes-itinerary.js +387 -0
- package/dist/routes-maintenance.d.ts +32 -0
- package/dist/routes-maintenance.d.ts.map +1 -0
- package/dist/routes-maintenance.js +14 -0
- package/dist/routes-media.d.ts +634 -0
- package/dist/routes-media.d.ts.map +1 -0
- package/dist/routes-media.js +245 -0
- package/dist/routes-merchandising.d.ts +1120 -0
- package/dist/routes-merchandising.d.ts.map +1 -0
- package/dist/routes-merchandising.js +377 -0
- package/dist/routes-options.d.ts +363 -0
- package/dist/routes-options.d.ts.map +1 -0
- package/dist/routes-options.js +173 -0
- package/dist/routes-public.d.ts +776 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +119 -0
- package/dist/routes-translations.d.ts +489 -0
- package/dist/routes-translations.d.ts.map +1 -0
- package/dist/routes-translations.js +258 -0
- package/dist/routes.d.ts +5097 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +64 -0
- package/dist/schema-core.d.ts +1238 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +157 -0
- package/dist/schema-itinerary.d.ts +1169 -0
- package/dist/schema-itinerary.d.ts.map +1 -0
- package/dist/schema-itinerary.js +130 -0
- package/dist/schema-relations.d.ts +117 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +192 -0
- package/dist/schema-settings.d.ts +1800 -0
- package/dist/schema-settings.d.ts.map +1 -0
- package/dist/schema-settings.js +220 -0
- package/dist/schema-shared.d.ts +15 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +91 -0
- package/dist/schema-sourced-content.d.ts +262 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +69 -0
- package/dist/schema-taxonomy.d.ts +1363 -0
- package/dist/schema-taxonomy.d.ts.map +1 -0
- package/dist/schema-taxonomy.js +203 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +9 -0
- package/dist/service-aggregates.d.ts +29 -0
- package/dist/service-aggregates.d.ts.map +1 -0
- package/dist/service-aggregates.js +56 -0
- package/dist/service-catalog-plane-destinations.d.ts +30 -0
- package/dist/service-catalog-plane-destinations.d.ts.map +1 -0
- package/dist/service-catalog-plane-destinations.js +143 -0
- package/dist/service-catalog-plane-taxonomy.d.ts +73 -0
- package/dist/service-catalog-plane-taxonomy.d.ts.map +1 -0
- package/dist/service-catalog-plane-taxonomy.js +242 -0
- package/dist/service-catalog-plane.d.ts +179 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +431 -0
- package/dist/service-catalog.d.ts +251 -0
- package/dist/service-catalog.d.ts.map +1 -0
- package/dist/service-catalog.js +517 -0
- package/dist/service-configuration.d.ts +261 -0
- package/dist/service-configuration.d.ts.map +1 -0
- package/dist/service-configuration.js +343 -0
- package/dist/service-content-owned.d.ts +68 -0
- package/dist/service-content-owned.d.ts.map +1 -0
- package/dist/service-content-owned.js +329 -0
- package/dist/service-content-synthesizer.d.ts +90 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +178 -0
- package/dist/service-content.d.ts +106 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +388 -0
- package/dist/service-core.d.ts +194 -0
- package/dist/service-core.d.ts.map +1 -0
- package/dist/service-core.js +213 -0
- package/dist/service-delivery-formats.d.ts +58 -0
- package/dist/service-delivery-formats.d.ts.map +1 -0
- package/dist/service-delivery-formats.js +107 -0
- package/dist/service-destinations.d.ts +223 -0
- package/dist/service-destinations.d.ts.map +1 -0
- package/dist/service-destinations.js +310 -0
- package/dist/service-itinerary-history.d.ts +457 -0
- package/dist/service-itinerary-history.d.ts.map +1 -0
- package/dist/service-itinerary-history.js +135 -0
- package/dist/service-itinerary.d.ts +1149 -0
- package/dist/service-itinerary.d.ts.map +1 -0
- package/dist/service-itinerary.js +419 -0
- package/dist/service-media.d.ts +272 -0
- package/dist/service-media.d.ts.map +1 -0
- package/dist/service-media.js +320 -0
- package/dist/service-merchandising.d.ts +184 -0
- package/dist/service-merchandising.d.ts.map +1 -0
- package/dist/service-merchandising.js +181 -0
- package/dist/service-option-translations.d.ts +268 -0
- package/dist/service-option-translations.d.ts.map +1 -0
- package/dist/service-option-translations.js +300 -0
- package/dist/service-options.d.ts +181 -0
- package/dist/service-options.d.ts.map +1 -0
- package/dist/service-options.js +179 -0
- package/dist/service-product-destinations.d.ts +37 -0
- package/dist/service-product-destinations.d.ts.map +1 -0
- package/dist/service-product-destinations.js +94 -0
- package/dist/service-public.d.ts +664 -0
- package/dist/service-public.d.ts.map +1 -0
- package/dist/service-public.js +374 -0
- package/dist/service-taxonomy.d.ts +197 -0
- package/dist/service-taxonomy.d.ts.map +1 -0
- package/dist/service-taxonomy.js +221 -0
- package/dist/service.d.ts +3929 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +28 -0
- package/dist/tasks/brochure-printers.d.ts +31 -0
- package/dist/tasks/brochure-printers.d.ts.map +1 -0
- package/dist/tasks/brochure-printers.js +149 -0
- package/dist/tasks/brochure-templates.d.ts +36 -0
- package/dist/tasks/brochure-templates.d.ts.map +1 -0
- package/dist/tasks/brochure-templates.js +110 -0
- package/dist/tasks/brochures.d.ts +43 -0
- package/dist/tasks/brochures.d.ts.map +1 -0
- package/dist/tasks/brochures.js +72 -0
- package/dist/tasks/generate-pdf.d.ts +8 -0
- package/dist/tasks/generate-pdf.d.ts.map +1 -0
- package/dist/tasks/generate-pdf.js +106 -0
- package/dist/tasks/index.d.ts +5 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/pdf-text.d.ts +2 -0
- package/dist/tasks/pdf-text.d.ts.map +1 -0
- package/dist/tasks/pdf-text.js +40 -0
- package/dist/tasks.d.ts +2 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +1 -0
- package/dist/validation-catalog.d.ts +2 -0
- package/dist/validation-catalog.d.ts.map +1 -0
- package/dist/validation-catalog.js +3 -0
- package/dist/validation-config.d.ts +2 -0
- package/dist/validation-config.d.ts.map +1 -0
- package/dist/validation-config.js +3 -0
- package/dist/validation-content.d.ts +2 -0
- package/dist/validation-content.d.ts.map +1 -0
- package/dist/validation-content.js +3 -0
- package/dist/validation-core.d.ts +2 -0
- package/dist/validation-core.d.ts.map +1 -0
- package/dist/validation-core.js +3 -0
- package/dist/validation-public.d.ts +2 -0
- package/dist/validation-public.d.ts.map +1 -0
- package/dist/validation-public.js +3 -0
- package/dist/validation-shared.d.ts +2 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +3 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +3 -0
- package/package.json +204 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog plane field policy for product → promotional-offer denormalization.
|
|
3
|
+
*
|
|
4
|
+
* Promotions are projected as **annotations only** — this policy does NOT
|
|
5
|
+
* touch the existing `priceFromAmountCents` field (that's emitted by the
|
|
6
|
+
* pricing extension and the two extensions can't read each other's output;
|
|
7
|
+
* see `docs/architecture/promotions-architecture.md` §3.7). Instead, we
|
|
8
|
+
* emit `bestOffer*` + `originalPriceFromAmountCents` and storefront
|
|
9
|
+
* consumers compute the effective price client-side:
|
|
10
|
+
*
|
|
11
|
+
* effective = bestOfferDiscountKind === "percentage"
|
|
12
|
+
* ? round(price × (1 - bestOfferDiscountPercent/100))
|
|
13
|
+
* : price - bestOfferDiscountAmountCents
|
|
14
|
+
*
|
|
15
|
+
* Catalog filter / sort behavior continues to use `priceFromAmountCents`
|
|
16
|
+
* (the list price). A customer searching `< $200` won't find a
|
|
17
|
+
* `$250 → $180` discounted product via the filter — acknowledged v1
|
|
18
|
+
* limitation, see §15.1 of the architecture doc.
|
|
19
|
+
*
|
|
20
|
+
* Conditional offers are surfaced separately so storefront cards can
|
|
21
|
+
* render hints like "From 4 pax: extra 5% off" — the catalog plane
|
|
22
|
+
* doesn't know pax at index time, so any offer with a `minPax` condition
|
|
23
|
+
* lands here instead of in `bestOffer*`.
|
|
24
|
+
*
|
|
25
|
+
* Wire this policy into the products registry by composing with
|
|
26
|
+
* `productCatalogPolicy`:
|
|
27
|
+
*
|
|
28
|
+
* const registry = createFieldPolicyRegistry([
|
|
29
|
+
* ...productCatalogPolicy,
|
|
30
|
+
* ...productPromotionsCatalogPolicy,
|
|
31
|
+
* ])
|
|
32
|
+
*
|
|
33
|
+
* and wire `createProductPromotionsProjectionExtension` (from
|
|
34
|
+
* `@voyant-travel/commerce` promotion projection support) into
|
|
35
|
+
* `createProductDocumentBuilder` so the values land in the doc.
|
|
36
|
+
*
|
|
37
|
+
* Out of scope here:
|
|
38
|
+
* - Localized offer names. Offers are operator-managed in one language
|
|
39
|
+
* for v1 (`localized: false`); a `promotional_offer_translations`
|
|
40
|
+
* table mirrors `destinations_translations` if/when needed.
|
|
41
|
+
* - Discount-aware filter / sort. Requires the §15.1 ordered-extensions
|
|
42
|
+
* contract change; deferred.
|
|
43
|
+
*/
|
|
44
|
+
import { defineFieldPolicy } from "@voyant-travel/catalog/contract";
|
|
45
|
+
const PRODUCT_PROMOTIONS_FIELD_POLICY = [
|
|
46
|
+
// ── Best-offer existence flag ────────────────────────────────────────────
|
|
47
|
+
// Storefront filters use this for "show only products on sale" and to
|
|
48
|
+
// suppress an empty badge.
|
|
49
|
+
{
|
|
50
|
+
path: "hasOffer",
|
|
51
|
+
class: "structural",
|
|
52
|
+
merge: "source-only",
|
|
53
|
+
drift: "low",
|
|
54
|
+
reindex: "facet-affecting",
|
|
55
|
+
snapshot: "on-book",
|
|
56
|
+
query: "indexed-column",
|
|
57
|
+
localized: false,
|
|
58
|
+
visibility: ["staff", "customer", "partner"],
|
|
59
|
+
editRole: "none",
|
|
60
|
+
overrideFriction: "none",
|
|
61
|
+
sourceFreshness: "sync",
|
|
62
|
+
},
|
|
63
|
+
// ── Best-offer ID ────────────────────────────────────────────────────────
|
|
64
|
+
// Used for deep-linking from a search card to the offer-detail page.
|
|
65
|
+
{
|
|
66
|
+
path: "bestOfferId",
|
|
67
|
+
class: "structural",
|
|
68
|
+
merge: "source-only",
|
|
69
|
+
drift: "low",
|
|
70
|
+
reindex: "facet-affecting",
|
|
71
|
+
snapshot: "on-book",
|
|
72
|
+
query: "indexed-column",
|
|
73
|
+
localized: false,
|
|
74
|
+
visibility: ["staff", "customer", "partner"],
|
|
75
|
+
editRole: "none",
|
|
76
|
+
overrideFriction: "none",
|
|
77
|
+
sourceFreshness: "sync",
|
|
78
|
+
},
|
|
79
|
+
// ── Best-offer name (display) ────────────────────────────────────────────
|
|
80
|
+
// Single-locale in v1 — see file-header notes.
|
|
81
|
+
{
|
|
82
|
+
path: "bestOfferName",
|
|
83
|
+
class: "structural",
|
|
84
|
+
merge: "source-only",
|
|
85
|
+
drift: "low",
|
|
86
|
+
reindex: "facet-affecting",
|
|
87
|
+
snapshot: "on-book",
|
|
88
|
+
query: "indexed-column",
|
|
89
|
+
localized: false,
|
|
90
|
+
visibility: ["staff", "customer", "partner"],
|
|
91
|
+
editRole: "none",
|
|
92
|
+
overrideFriction: "none",
|
|
93
|
+
sourceFreshness: "sync",
|
|
94
|
+
},
|
|
95
|
+
// ── Best-offer discount kind ─────────────────────────────────────────────
|
|
96
|
+
// `"percentage"` | `"fixed_amount"` | `null`. Drives storefront's badge
|
|
97
|
+
// rendering (% vs $).
|
|
98
|
+
{
|
|
99
|
+
path: "bestOfferDiscountKind",
|
|
100
|
+
class: "structural",
|
|
101
|
+
merge: "source-only",
|
|
102
|
+
drift: "low",
|
|
103
|
+
reindex: "facet-affecting",
|
|
104
|
+
snapshot: "on-book",
|
|
105
|
+
query: "indexed-column",
|
|
106
|
+
localized: false,
|
|
107
|
+
visibility: ["staff", "customer", "partner"],
|
|
108
|
+
editRole: "none",
|
|
109
|
+
overrideFriction: "none",
|
|
110
|
+
sourceFreshness: "sync",
|
|
111
|
+
},
|
|
112
|
+
// ── Best-offer percentage (when kind = percentage) ───────────────────────
|
|
113
|
+
{
|
|
114
|
+
path: "bestOfferDiscountPercent",
|
|
115
|
+
class: "structural",
|
|
116
|
+
merge: "source-only",
|
|
117
|
+
drift: "low",
|
|
118
|
+
reindex: "facet-affecting",
|
|
119
|
+
snapshot: "on-book",
|
|
120
|
+
query: "indexed-column",
|
|
121
|
+
localized: false,
|
|
122
|
+
visibility: ["staff", "customer", "partner"],
|
|
123
|
+
editRole: "none",
|
|
124
|
+
overrideFriction: "none",
|
|
125
|
+
sourceFreshness: "sync",
|
|
126
|
+
},
|
|
127
|
+
// ── Best-offer cents off (when kind = fixed_amount) ──────────────────────
|
|
128
|
+
{
|
|
129
|
+
path: "bestOfferDiscountAmountCents",
|
|
130
|
+
class: "structural",
|
|
131
|
+
merge: "source-only",
|
|
132
|
+
drift: "low",
|
|
133
|
+
reindex: "facet-affecting",
|
|
134
|
+
snapshot: "on-book",
|
|
135
|
+
query: "indexed-column",
|
|
136
|
+
localized: false,
|
|
137
|
+
visibility: ["staff", "customer", "partner"],
|
|
138
|
+
editRole: "none",
|
|
139
|
+
overrideFriction: "none",
|
|
140
|
+
sourceFreshness: "sync",
|
|
141
|
+
},
|
|
142
|
+
// ── Original (un-discounted) price ───────────────────────────────────────
|
|
143
|
+
// Populated only when an offer applies (so consumers know what to
|
|
144
|
+
// strike through). When `null`, no offer applies and consumers should
|
|
145
|
+
// render `priceFromAmountCents` plain.
|
|
146
|
+
{
|
|
147
|
+
path: "originalPriceFromAmountCents",
|
|
148
|
+
class: "structural",
|
|
149
|
+
merge: "source-only",
|
|
150
|
+
drift: "low",
|
|
151
|
+
reindex: "facet-affecting",
|
|
152
|
+
snapshot: "on-book",
|
|
153
|
+
query: "indexed-column",
|
|
154
|
+
localized: false,
|
|
155
|
+
visibility: ["staff", "customer", "partner"],
|
|
156
|
+
editRole: "none",
|
|
157
|
+
overrideFriction: "none",
|
|
158
|
+
sourceFreshness: "sync",
|
|
159
|
+
},
|
|
160
|
+
// ── Conditional-offer existence flag ─────────────────────────────────────
|
|
161
|
+
// Surfaced when an offer has a `minPax` condition the catalog plane
|
|
162
|
+
// can't satisfy at index time. Storefront uses this for the "From N
|
|
163
|
+
// pax: extra X% off" hint.
|
|
164
|
+
{
|
|
165
|
+
path: "hasConditionalOffer",
|
|
166
|
+
class: "structural",
|
|
167
|
+
merge: "source-only",
|
|
168
|
+
drift: "low",
|
|
169
|
+
reindex: "facet-affecting",
|
|
170
|
+
snapshot: "on-book",
|
|
171
|
+
query: "indexed-column",
|
|
172
|
+
localized: false,
|
|
173
|
+
visibility: ["staff", "customer", "partner"],
|
|
174
|
+
editRole: "none",
|
|
175
|
+
overrideFriction: "none",
|
|
176
|
+
sourceFreshness: "sync",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: "conditionalOfferId",
|
|
180
|
+
class: "structural",
|
|
181
|
+
merge: "source-only",
|
|
182
|
+
drift: "low",
|
|
183
|
+
reindex: "facet-affecting",
|
|
184
|
+
snapshot: "on-book",
|
|
185
|
+
query: "indexed-column",
|
|
186
|
+
localized: false,
|
|
187
|
+
visibility: ["staff", "customer", "partner"],
|
|
188
|
+
editRole: "none",
|
|
189
|
+
overrideFriction: "none",
|
|
190
|
+
sourceFreshness: "sync",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
path: "conditionalOfferName",
|
|
194
|
+
class: "structural",
|
|
195
|
+
merge: "source-only",
|
|
196
|
+
drift: "low",
|
|
197
|
+
reindex: "facet-affecting",
|
|
198
|
+
snapshot: "on-book",
|
|
199
|
+
query: "indexed-column",
|
|
200
|
+
localized: false,
|
|
201
|
+
visibility: ["staff", "customer", "partner"],
|
|
202
|
+
editRole: "none",
|
|
203
|
+
overrideFriction: "none",
|
|
204
|
+
sourceFreshness: "sync",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
path: "conditionalOfferDiscountKind",
|
|
208
|
+
class: "structural",
|
|
209
|
+
merge: "source-only",
|
|
210
|
+
drift: "low",
|
|
211
|
+
reindex: "facet-affecting",
|
|
212
|
+
snapshot: "on-book",
|
|
213
|
+
query: "indexed-column",
|
|
214
|
+
localized: false,
|
|
215
|
+
visibility: ["staff", "customer", "partner"],
|
|
216
|
+
editRole: "none",
|
|
217
|
+
overrideFriction: "none",
|
|
218
|
+
sourceFreshness: "sync",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
path: "conditionalOfferDiscountPercent",
|
|
222
|
+
class: "structural",
|
|
223
|
+
merge: "source-only",
|
|
224
|
+
drift: "low",
|
|
225
|
+
reindex: "facet-affecting",
|
|
226
|
+
snapshot: "on-book",
|
|
227
|
+
query: "indexed-column",
|
|
228
|
+
localized: false,
|
|
229
|
+
visibility: ["staff", "customer", "partner"],
|
|
230
|
+
editRole: "none",
|
|
231
|
+
overrideFriction: "none",
|
|
232
|
+
sourceFreshness: "sync",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
path: "conditionalOfferDiscountAmountCents",
|
|
236
|
+
class: "structural",
|
|
237
|
+
merge: "source-only",
|
|
238
|
+
drift: "low",
|
|
239
|
+
reindex: "facet-affecting",
|
|
240
|
+
snapshot: "on-book",
|
|
241
|
+
query: "indexed-column",
|
|
242
|
+
localized: false,
|
|
243
|
+
visibility: ["staff", "customer", "partner"],
|
|
244
|
+
editRole: "none",
|
|
245
|
+
overrideFriction: "none",
|
|
246
|
+
sourceFreshness: "sync",
|
|
247
|
+
},
|
|
248
|
+
// ── Conditional-offer minPax threshold ───────────────────────────────────
|
|
249
|
+
// Drives the "From N pax" message text.
|
|
250
|
+
{
|
|
251
|
+
path: "conditionalOfferMinPax",
|
|
252
|
+
class: "structural",
|
|
253
|
+
merge: "source-only",
|
|
254
|
+
drift: "low",
|
|
255
|
+
reindex: "facet-affecting",
|
|
256
|
+
snapshot: "on-book",
|
|
257
|
+
query: "indexed-column",
|
|
258
|
+
localized: false,
|
|
259
|
+
visibility: ["staff", "customer", "partner"],
|
|
260
|
+
editRole: "none",
|
|
261
|
+
overrideFriction: "none",
|
|
262
|
+
sourceFreshness: "sync",
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
/**
|
|
266
|
+
* Resolved promotions policy. Compose with `productCatalogPolicy` when
|
|
267
|
+
* building the products registry.
|
|
268
|
+
*/
|
|
269
|
+
export const productPromotionsCatalogPolicy = defineFieldPolicy(PRODUCT_PROMOTIONS_FIELD_POLICY);
|
|
270
|
+
export { PRODUCT_PROMOTIONS_FIELD_POLICY };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog plane field policy for product → taxonomy denormalization
|
|
3
|
+
* (categories + tags).
|
|
4
|
+
*
|
|
5
|
+
* These fields don't live on the `products` table — they're joined from
|
|
6
|
+
* `product_category_products` → `product_categories` (with parent walk for
|
|
7
|
+
* hierarchy denormalization) and `product_tag_products` → `product_tags` at
|
|
8
|
+
* index time and projected onto the product search document. See
|
|
9
|
+
* `docs/architecture/catalog-architecture.md` §5.4 — the search index is the
|
|
10
|
+
* canonical place for cross-entity denormalization.
|
|
11
|
+
*
|
|
12
|
+
* Storefront product cards filter and display by taxonomy:
|
|
13
|
+
* - "Adventure tours" (filter: `categories[]` includes "Adventure")
|
|
14
|
+
* - A product tagged "Hiking" (parent "Adventure") MUST also surface under
|
|
15
|
+
* the "Adventure" filter — Typesense can't recurse, so the projection
|
|
16
|
+
* denormalizes the full ancestor chain into `categories[]` /
|
|
17
|
+
* `categoryIds[]`.
|
|
18
|
+
* - "Family-friendly" (filter: `tags[]` includes "Family-friendly")
|
|
19
|
+
*
|
|
20
|
+
* Wire this policy into the products registry by composing with
|
|
21
|
+
* `productCatalogPolicy`:
|
|
22
|
+
*
|
|
23
|
+
* const registry = createFieldPolicyRegistry([
|
|
24
|
+
* ...productCatalogPolicy,
|
|
25
|
+
* ...productTaxonomyCatalogPolicy,
|
|
26
|
+
* ])
|
|
27
|
+
*
|
|
28
|
+
* and wire `createProductTaxonomyProjectionExtension` into
|
|
29
|
+
* `createProductDocumentBuilder` so the values land in the doc.
|
|
30
|
+
*
|
|
31
|
+
* Localization (#502): `product_category_translations` and
|
|
32
|
+
* `product_tag_translations` tables back the locale-aware label fields.
|
|
33
|
+
* Slice-locale rows win; if no row exists for the slice, the projection
|
|
34
|
+
* falls back to the canonical `productCategories.name` /
|
|
35
|
+
* `productTags.name`. Slug stays single-locale on `productCategories.slug`
|
|
36
|
+
* (per #502 non-goals — operators want stable URLs that don't shift when
|
|
37
|
+
* translations are edited).
|
|
38
|
+
*
|
|
39
|
+
* Out of scope here:
|
|
40
|
+
* - Per-locale slugs. One canonical slug per category.
|
|
41
|
+
* - Tag hierarchy. Tags are flat by design.
|
|
42
|
+
*/
|
|
43
|
+
import { type FieldPolicyInput } from "@voyant-travel/catalog/contract";
|
|
44
|
+
declare const PRODUCT_TAXONOMY_FIELD_POLICY: FieldPolicyInput[];
|
|
45
|
+
/**
|
|
46
|
+
* Resolved taxonomy policy. Compose with `productCatalogPolicy` when
|
|
47
|
+
* building the products registry.
|
|
48
|
+
*/
|
|
49
|
+
export declare const productTaxonomyCatalogPolicy: import("@voyant-travel/catalog").FieldPolicy[];
|
|
50
|
+
export { PRODUCT_TAXONOMY_FIELD_POLICY };
|
|
51
|
+
//# sourceMappingURL=catalog-policy-taxonomy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-policy-taxonomy.d.ts","sourceRoot":"","sources":["../src/catalog-policy-taxonomy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAE1F,QAAA,MAAM,6BAA6B,EAAE,gBAAgB,EAiJpD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,gDAAmD,CAAA;AAE5F,OAAO,EAAE,6BAA6B,EAAE,CAAA"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog plane field policy for product → taxonomy denormalization
|
|
3
|
+
* (categories + tags).
|
|
4
|
+
*
|
|
5
|
+
* These fields don't live on the `products` table — they're joined from
|
|
6
|
+
* `product_category_products` → `product_categories` (with parent walk for
|
|
7
|
+
* hierarchy denormalization) and `product_tag_products` → `product_tags` at
|
|
8
|
+
* index time and projected onto the product search document. See
|
|
9
|
+
* `docs/architecture/catalog-architecture.md` §5.4 — the search index is the
|
|
10
|
+
* canonical place for cross-entity denormalization.
|
|
11
|
+
*
|
|
12
|
+
* Storefront product cards filter and display by taxonomy:
|
|
13
|
+
* - "Adventure tours" (filter: `categories[]` includes "Adventure")
|
|
14
|
+
* - A product tagged "Hiking" (parent "Adventure") MUST also surface under
|
|
15
|
+
* the "Adventure" filter — Typesense can't recurse, so the projection
|
|
16
|
+
* denormalizes the full ancestor chain into `categories[]` /
|
|
17
|
+
* `categoryIds[]`.
|
|
18
|
+
* - "Family-friendly" (filter: `tags[]` includes "Family-friendly")
|
|
19
|
+
*
|
|
20
|
+
* Wire this policy into the products registry by composing with
|
|
21
|
+
* `productCatalogPolicy`:
|
|
22
|
+
*
|
|
23
|
+
* const registry = createFieldPolicyRegistry([
|
|
24
|
+
* ...productCatalogPolicy,
|
|
25
|
+
* ...productTaxonomyCatalogPolicy,
|
|
26
|
+
* ])
|
|
27
|
+
*
|
|
28
|
+
* and wire `createProductTaxonomyProjectionExtension` into
|
|
29
|
+
* `createProductDocumentBuilder` so the values land in the doc.
|
|
30
|
+
*
|
|
31
|
+
* Localization (#502): `product_category_translations` and
|
|
32
|
+
* `product_tag_translations` tables back the locale-aware label fields.
|
|
33
|
+
* Slice-locale rows win; if no row exists for the slice, the projection
|
|
34
|
+
* falls back to the canonical `productCategories.name` /
|
|
35
|
+
* `productTags.name`. Slug stays single-locale on `productCategories.slug`
|
|
36
|
+
* (per #502 non-goals — operators want stable URLs that don't shift when
|
|
37
|
+
* translations are edited).
|
|
38
|
+
*
|
|
39
|
+
* Out of scope here:
|
|
40
|
+
* - Per-locale slugs. One canonical slug per category.
|
|
41
|
+
* - Tag hierarchy. Tags are flat by design.
|
|
42
|
+
*/
|
|
43
|
+
import { defineFieldPolicy } from "@voyant-travel/catalog/contract";
|
|
44
|
+
const PRODUCT_TAXONOMY_FIELD_POLICY = [
|
|
45
|
+
// ── Category labels (denormalized with ancestors, locale-aware) ──────────
|
|
46
|
+
// One entry per category in the linked-category-plus-ancestors set, deduped.
|
|
47
|
+
// A product linked to "Hiking" (parent: "Adventure") emits both
|
|
48
|
+
// ["Hiking", "Adventure"] so any-level filter matches a single equality.
|
|
49
|
+
// Locale-keyed since #502 — falls back to canonical name when no
|
|
50
|
+
// translation exists for the slice's locale.
|
|
51
|
+
{
|
|
52
|
+
path: "categories[]",
|
|
53
|
+
class: "structural",
|
|
54
|
+
merge: "source-only",
|
|
55
|
+
drift: "low",
|
|
56
|
+
reindex: "entry-locale",
|
|
57
|
+
snapshot: "on-book",
|
|
58
|
+
query: "indexed-column",
|
|
59
|
+
localized: true,
|
|
60
|
+
visibility: ["staff", "customer", "partner"],
|
|
61
|
+
editRole: "none",
|
|
62
|
+
overrideFriction: "none",
|
|
63
|
+
sourceFreshness: "sync",
|
|
64
|
+
},
|
|
65
|
+
// ── Category IDs (denormalized with ancestors) ───────────────────────────
|
|
66
|
+
// Same denormalization as `categories[]` but for ID-based filters
|
|
67
|
+
// (landing pages pinned to a category id rather than a label).
|
|
68
|
+
{
|
|
69
|
+
path: "categoryIds[]",
|
|
70
|
+
class: "structural",
|
|
71
|
+
merge: "source-only",
|
|
72
|
+
drift: "low",
|
|
73
|
+
reindex: "facet-affecting",
|
|
74
|
+
snapshot: "on-book",
|
|
75
|
+
query: "indexed-column",
|
|
76
|
+
localized: false,
|
|
77
|
+
visibility: ["staff", "customer", "partner"],
|
|
78
|
+
editRole: "none",
|
|
79
|
+
overrideFriction: "none",
|
|
80
|
+
sourceFreshness: "sync",
|
|
81
|
+
},
|
|
82
|
+
// ── Category slugs (locale-stable, denormalized with ancestors) ──────────
|
|
83
|
+
// Storefront category-page links want stable URLs that don't shift when
|
|
84
|
+
// labels are edited. `product_categories.slug` exists today.
|
|
85
|
+
{
|
|
86
|
+
path: "categorySlugs[]",
|
|
87
|
+
class: "structural",
|
|
88
|
+
merge: "source-only",
|
|
89
|
+
drift: "low",
|
|
90
|
+
reindex: "facet-affecting",
|
|
91
|
+
snapshot: "on-book",
|
|
92
|
+
query: "indexed-column",
|
|
93
|
+
localized: false,
|
|
94
|
+
visibility: ["staff", "customer", "partner"],
|
|
95
|
+
editRole: "none",
|
|
96
|
+
overrideFriction: "none",
|
|
97
|
+
sourceFreshness: "sync",
|
|
98
|
+
},
|
|
99
|
+
// ── Primary category (single, for badge display) ─────────────────────────
|
|
100
|
+
// Picked as the directly-linked category with the lowest sortOrder on
|
|
101
|
+
// `product_category_products` (the operator-controlled per-product
|
|
102
|
+
// ordering); ties broken by category name. Ancestors are NOT considered
|
|
103
|
+
// for primary — operators pin a leaf via the link table. Null when the
|
|
104
|
+
// product has no category links.
|
|
105
|
+
{
|
|
106
|
+
path: "primaryCategoryId",
|
|
107
|
+
class: "structural",
|
|
108
|
+
merge: "source-only",
|
|
109
|
+
drift: "low",
|
|
110
|
+
reindex: "facet-affecting",
|
|
111
|
+
snapshot: "on-book",
|
|
112
|
+
query: "indexed-column",
|
|
113
|
+
localized: false,
|
|
114
|
+
visibility: ["staff", "customer", "partner"],
|
|
115
|
+
editRole: "none",
|
|
116
|
+
overrideFriction: "none",
|
|
117
|
+
sourceFreshness: "sync",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
// Locale-aware since #502 — the primary's name follows the same
|
|
121
|
+
// translation lookup as `categories[]`.
|
|
122
|
+
path: "primaryCategoryName",
|
|
123
|
+
class: "structural",
|
|
124
|
+
merge: "source-only",
|
|
125
|
+
drift: "low",
|
|
126
|
+
reindex: "entry-locale",
|
|
127
|
+
snapshot: "on-book",
|
|
128
|
+
query: "indexed-column",
|
|
129
|
+
localized: true,
|
|
130
|
+
visibility: ["staff", "customer", "partner"],
|
|
131
|
+
editRole: "none",
|
|
132
|
+
overrideFriction: "none",
|
|
133
|
+
sourceFreshness: "sync",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
path: "primaryCategorySlug",
|
|
137
|
+
class: "structural",
|
|
138
|
+
merge: "source-only",
|
|
139
|
+
drift: "low",
|
|
140
|
+
reindex: "facet-affecting",
|
|
141
|
+
snapshot: "on-book",
|
|
142
|
+
query: "indexed-column",
|
|
143
|
+
localized: false,
|
|
144
|
+
visibility: ["staff", "customer", "partner"],
|
|
145
|
+
editRole: "none",
|
|
146
|
+
overrideFriction: "none",
|
|
147
|
+
sourceFreshness: "sync",
|
|
148
|
+
},
|
|
149
|
+
// ── Tag labels ───────────────────────────────────────────────────────────
|
|
150
|
+
// `products.tags` (a free-form `text[]` column) already projects to
|
|
151
|
+
// `tags[]` from the base product policy. The structured `product_tags`
|
|
152
|
+
// table is a separate, normalized taxonomy — its labels land here under
|
|
153
|
+
// `tagLabels[]` to avoid colliding with the legacy column. One entry per
|
|
154
|
+
// linked tag.
|
|
155
|
+
{
|
|
156
|
+
// Locale-aware since #502 — falls back to canonical `productTags.name`
|
|
157
|
+
// when no `product_tag_translations` row exists for the slice's locale.
|
|
158
|
+
path: "tagLabels[]",
|
|
159
|
+
class: "structural",
|
|
160
|
+
merge: "source-only",
|
|
161
|
+
drift: "low",
|
|
162
|
+
reindex: "entry-locale",
|
|
163
|
+
snapshot: "on-book",
|
|
164
|
+
query: "indexed-column",
|
|
165
|
+
localized: true,
|
|
166
|
+
visibility: ["staff", "customer", "partner"],
|
|
167
|
+
editRole: "none",
|
|
168
|
+
overrideFriction: "none",
|
|
169
|
+
sourceFreshness: "sync",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
path: "tagIds[]",
|
|
173
|
+
class: "structural",
|
|
174
|
+
merge: "source-only",
|
|
175
|
+
drift: "low",
|
|
176
|
+
reindex: "facet-affecting",
|
|
177
|
+
snapshot: "on-book",
|
|
178
|
+
query: "indexed-column",
|
|
179
|
+
localized: false,
|
|
180
|
+
visibility: ["staff", "customer", "partner"],
|
|
181
|
+
editRole: "none",
|
|
182
|
+
overrideFriction: "none",
|
|
183
|
+
sourceFreshness: "sync",
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
/**
|
|
187
|
+
* Resolved taxonomy policy. Compose with `productCatalogPolicy` when
|
|
188
|
+
* building the products registry.
|
|
189
|
+
*/
|
|
190
|
+
export const productTaxonomyCatalogPolicy = defineFieldPolicy(PRODUCT_TAXONOMY_FIELD_POLICY);
|
|
191
|
+
export { PRODUCT_TAXONOMY_FIELD_POLICY };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog plane field policy for Inventory-owned products.
|
|
3
|
+
*
|
|
4
|
+
* Declares every product field's governance under the
|
|
5
|
+
* `@voyant-travel/catalog` 12-attribute contract. Phase B shake-out
|
|
6
|
+
* adoption — see `docs/architecture/catalog-architecture.md` §9.1.
|
|
7
|
+
*
|
|
8
|
+
* Scope of this file:
|
|
9
|
+
* - The root `products` table (from `schema-core.ts`).
|
|
10
|
+
* - Provenance + identity fields the catalog plane needs to track.
|
|
11
|
+
*
|
|
12
|
+
* Out of scope (deferred to follow-up adoption passes):
|
|
13
|
+
* - `productOptions`, `optionUnits`, `productDays`, `productNotes`,
|
|
14
|
+
* `productVersions` — promoted child entities (per composition rule §6.2);
|
|
15
|
+
* each gets its own micro-registry when wired in.
|
|
16
|
+
* - The split of `tags` into `marketing_tags` + `facet_tags` per the
|
|
17
|
+
* human-readable / machine-evaluable rule (§7.1). Today's schema has a
|
|
18
|
+
* single `tags` column; declared here as merchandisable with a TODO.
|
|
19
|
+
*/
|
|
20
|
+
import { type FieldPolicyInput } from "@voyant-travel/catalog/contract";
|
|
21
|
+
/**
|
|
22
|
+
* Field-policy declarations for `products`. Pass through `defineFieldPolicy`
|
|
23
|
+
* to apply inheritance and produce the runtime registry.
|
|
24
|
+
*/
|
|
25
|
+
declare const PRODUCT_FIELD_POLICY: FieldPolicyInput[];
|
|
26
|
+
/**
|
|
27
|
+
* Resolved field-policy registry for products. Verticals adopt the catalog
|
|
28
|
+
* plane by exporting this; templates wire it into the indexer, overlay
|
|
29
|
+
* resolver, and snapshot capture pipeline.
|
|
30
|
+
*/
|
|
31
|
+
export declare const productCatalogPolicy: import("@voyant-travel/catalog").FieldPolicy[];
|
|
32
|
+
export { PRODUCT_FIELD_POLICY };
|
|
33
|
+
//# 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":"AACA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAE1F;;;GAGG;AACH,QAAA,MAAM,oBAAoB,EAAE,gBAAgB,EAisB3C,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAA0C,CAAA;AAE3E,OAAO,EAAE,oBAAoB,EAAE,CAAA"}
|