@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 @@
|
|
|
1
|
+
{"version":3,"file":"schema-taxonomy.d.ts","sourceRoot":"","sources":["../src/schema-taxonomy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { typeId, typeIdRef } from "@voyant-travel/db/lib/typeid-column";
|
|
2
|
+
import { boolean, doublePrecision, index, integer, jsonb, pgTable, primaryKey, text, timestamp, uniqueIndex, } from "drizzle-orm/pg-core";
|
|
3
|
+
import { products } from "./schema-core.js";
|
|
4
|
+
export const productTypes = pgTable("product_types", {
|
|
5
|
+
id: typeId("product_types"),
|
|
6
|
+
name: text("name").notNull(),
|
|
7
|
+
code: text("code").notNull(),
|
|
8
|
+
description: text("description"),
|
|
9
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
10
|
+
active: boolean("active").notNull().default(true),
|
|
11
|
+
metadata: jsonb("metadata").$type(),
|
|
12
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
13
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
14
|
+
}, (table) => [
|
|
15
|
+
uniqueIndex("uidx_product_types_code").on(table.code),
|
|
16
|
+
index("idx_product_types_active").on(table.active),
|
|
17
|
+
index("idx_product_types_sort_name").on(table.sortOrder, table.name),
|
|
18
|
+
index("idx_product_types_active_sort_name").on(table.active, table.sortOrder, table.name),
|
|
19
|
+
]);
|
|
20
|
+
export const productCategories = pgTable("product_categories", {
|
|
21
|
+
id: typeId("product_categories"),
|
|
22
|
+
parentId: text("parent_id"),
|
|
23
|
+
name: text("name").notNull(),
|
|
24
|
+
slug: text("slug").notNull(),
|
|
25
|
+
description: text("description"),
|
|
26
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
27
|
+
active: boolean("active").notNull().default(true),
|
|
28
|
+
/**
|
|
29
|
+
* Customer-facing payment policy override. When set, bookings
|
|
30
|
+
* for products in this category inherit these terms (unless
|
|
31
|
+
* the listing or booking sets its own override). Shape mirrors
|
|
32
|
+
* `PaymentPolicy` from `@voyant-travel/finance`.
|
|
33
|
+
*
|
|
34
|
+
* `null` means "inherit from supplier / operator default".
|
|
35
|
+
*
|
|
36
|
+
* Multi-category products: the cascade picks the FIRST category
|
|
37
|
+
* (ordered by `productCategoryProducts.sortOrder` ascending) that
|
|
38
|
+
* has a non-null policy.
|
|
39
|
+
*/
|
|
40
|
+
customerPaymentPolicy: jsonb("customer_payment_policy"),
|
|
41
|
+
metadata: jsonb("metadata").$type(),
|
|
42
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
43
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
44
|
+
}, (table) => [
|
|
45
|
+
uniqueIndex("uidx_product_categories_slug").on(table.slug),
|
|
46
|
+
index("idx_product_categories_parent").on(table.parentId),
|
|
47
|
+
index("idx_product_categories_active").on(table.active),
|
|
48
|
+
index("idx_product_categories_sort_name").on(table.sortOrder, table.name),
|
|
49
|
+
index("idx_product_categories_active_sort_name").on(table.active, table.sortOrder, table.name),
|
|
50
|
+
index("idx_product_categories_parent_sort_name").on(table.parentId, table.sortOrder, table.name),
|
|
51
|
+
// Trigram GIN indexes back the `ILIKE '%term%'` category search
|
|
52
|
+
// (name OR slug — both branches need an index for a BitmapOr plan).
|
|
53
|
+
// Requires the pg_trgm extension.
|
|
54
|
+
index("idx_product_categories_name_trgm").using("gin", table.name.op("gin_trgm_ops")),
|
|
55
|
+
index("idx_product_categories_slug_trgm").using("gin", table.slug.op("gin_trgm_ops")),
|
|
56
|
+
]);
|
|
57
|
+
/**
|
|
58
|
+
* Locale-aware category labels. Mirrors `destinationTranslations`.
|
|
59
|
+
*
|
|
60
|
+
* The catalog plane's taxonomy projection (`catalog-policy-taxonomy.ts` +
|
|
61
|
+
* `service-catalog-plane-taxonomy.ts`) reads this table per-slice locale
|
|
62
|
+
* and falls back to `productCategories.name` when no row exists for a
|
|
63
|
+
* given `(categoryId, languageTag)`. Slug stays single-locale on
|
|
64
|
+
* `productCategories.slug` per #502 non-goals — operators want stable
|
|
65
|
+
* URLs that don't shift when translations are edited.
|
|
66
|
+
*/
|
|
67
|
+
export const productCategoryTranslations = pgTable("product_category_translations", {
|
|
68
|
+
id: typeId("product_category_translations"),
|
|
69
|
+
categoryId: typeIdRef("category_id")
|
|
70
|
+
.notNull()
|
|
71
|
+
.references(() => productCategories.id, { onDelete: "cascade" }),
|
|
72
|
+
languageTag: text("language_tag").notNull(),
|
|
73
|
+
name: text("name").notNull(),
|
|
74
|
+
description: text("description"),
|
|
75
|
+
seoTitle: text("seo_title"),
|
|
76
|
+
seoDescription: text("seo_description"),
|
|
77
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
78
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
79
|
+
}, (table) => [
|
|
80
|
+
uniqueIndex("uidx_product_category_translations_locale").on(table.categoryId, table.languageTag),
|
|
81
|
+
index("idx_product_category_translations_language").on(table.languageTag),
|
|
82
|
+
index("idx_product_category_translations_category_language_created").on(table.categoryId, table.languageTag, table.createdAt),
|
|
83
|
+
index("idx_product_category_translations_language_created").on(table.languageTag, table.createdAt),
|
|
84
|
+
]);
|
|
85
|
+
export const productTags = pgTable("product_tags", {
|
|
86
|
+
id: typeId("product_tags"),
|
|
87
|
+
name: text("name").notNull(),
|
|
88
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
89
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
90
|
+
}, (table) => [
|
|
91
|
+
uniqueIndex("uidx_product_tags_name").on(table.name),
|
|
92
|
+
// Backs the `ILIKE '%term%'` tag search. Requires the pg_trgm extension.
|
|
93
|
+
index("idx_product_tags_name_trgm").using("gin", table.name.op("gin_trgm_ops")),
|
|
94
|
+
]);
|
|
95
|
+
/**
|
|
96
|
+
* Locale-aware tag labels. Slimmer than category translations — tags are
|
|
97
|
+
* short labels with no description / SEO blurbs (per #502 non-goals).
|
|
98
|
+
*/
|
|
99
|
+
export const productTagTranslations = pgTable("product_tag_translations", {
|
|
100
|
+
id: typeId("product_tag_translations"),
|
|
101
|
+
tagId: typeIdRef("tag_id")
|
|
102
|
+
.notNull()
|
|
103
|
+
.references(() => productTags.id, { onDelete: "cascade" }),
|
|
104
|
+
languageTag: text("language_tag").notNull(),
|
|
105
|
+
name: text("name").notNull(),
|
|
106
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
107
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
108
|
+
}, (table) => [
|
|
109
|
+
uniqueIndex("uidx_product_tag_translations_locale").on(table.tagId, table.languageTag),
|
|
110
|
+
index("idx_product_tag_translations_language").on(table.languageTag),
|
|
111
|
+
index("idx_product_tag_translations_tag_language_created").on(table.tagId, table.languageTag, table.createdAt),
|
|
112
|
+
index("idx_product_tag_translations_language_created").on(table.languageTag, table.createdAt),
|
|
113
|
+
]);
|
|
114
|
+
export const destinations = pgTable("destinations", {
|
|
115
|
+
id: typeId("destinations"),
|
|
116
|
+
parentId: typeIdRef("parent_id"),
|
|
117
|
+
slug: text("slug").notNull(),
|
|
118
|
+
code: text("code"),
|
|
119
|
+
canonicalPlaceId: text("canonical_place_id"),
|
|
120
|
+
destinationType: text("destination_type").notNull().default("destination"),
|
|
121
|
+
latitude: doublePrecision("latitude"),
|
|
122
|
+
longitude: doublePrecision("longitude"),
|
|
123
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
124
|
+
active: boolean("active").notNull().default(true),
|
|
125
|
+
metadata: jsonb("metadata").$type(),
|
|
126
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
127
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
128
|
+
}, (table) => [
|
|
129
|
+
uniqueIndex("uidx_destinations_slug").on(table.slug),
|
|
130
|
+
uniqueIndex("uidx_destinations_code").on(table.code),
|
|
131
|
+
index("idx_destinations_parent").on(table.parentId),
|
|
132
|
+
index("idx_destinations_active").on(table.active),
|
|
133
|
+
index("idx_destinations_canonical_place").on(table.canonicalPlaceId),
|
|
134
|
+
index("idx_destinations_sort_slug").on(table.sortOrder, table.slug),
|
|
135
|
+
index("idx_destinations_active_sort_slug").on(table.active, table.sortOrder, table.slug),
|
|
136
|
+
index("idx_destinations_type_sort_slug").on(table.destinationType, table.sortOrder, table.slug),
|
|
137
|
+
index("idx_destinations_parent_sort_slug").on(table.parentId, table.sortOrder, table.slug),
|
|
138
|
+
]);
|
|
139
|
+
export const destinationTranslations = pgTable("destination_translations", {
|
|
140
|
+
id: typeId("destination_translations"),
|
|
141
|
+
destinationId: typeIdRef("destination_id")
|
|
142
|
+
.notNull()
|
|
143
|
+
.references(() => destinations.id, { onDelete: "cascade" }),
|
|
144
|
+
languageTag: text("language_tag").notNull(),
|
|
145
|
+
name: text("name").notNull(),
|
|
146
|
+
description: text("description"),
|
|
147
|
+
seoTitle: text("seo_title"),
|
|
148
|
+
seoDescription: text("seo_description"),
|
|
149
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
150
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
151
|
+
}, (table) => [
|
|
152
|
+
uniqueIndex("uidx_destination_translations_locale").on(table.destinationId, table.languageTag),
|
|
153
|
+
index("idx_destination_translations_language").on(table.languageTag),
|
|
154
|
+
index("idx_destination_translations_destination_language_created").on(table.destinationId, table.languageTag, table.createdAt),
|
|
155
|
+
index("idx_destination_translations_language_created").on(table.languageTag, table.createdAt),
|
|
156
|
+
// Trigram GIN indexes back the `ILIKE '%term%'` destination search
|
|
157
|
+
// (name OR description — both branches need an index for a BitmapOr
|
|
158
|
+
// plan). Requires the pg_trgm extension.
|
|
159
|
+
index("idx_destination_translations_name_trgm").using("gin", table.name.op("gin_trgm_ops")),
|
|
160
|
+
index("idx_destination_translations_description_trgm").using("gin", table.description.op("gin_trgm_ops")),
|
|
161
|
+
]);
|
|
162
|
+
export const productCategoryProducts = pgTable("product_category_products", {
|
|
163
|
+
productId: typeIdRef("product_id")
|
|
164
|
+
.notNull()
|
|
165
|
+
.references(() => products.id, { onDelete: "cascade" }),
|
|
166
|
+
categoryId: typeIdRef("category_id")
|
|
167
|
+
.notNull()
|
|
168
|
+
.references(() => productCategories.id, { onDelete: "cascade" }),
|
|
169
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
170
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
171
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
172
|
+
}, (table) => [
|
|
173
|
+
primaryKey({ columns: [table.productId, table.categoryId] }),
|
|
174
|
+
index("idx_pcp_product_sort").on(table.productId, table.sortOrder),
|
|
175
|
+
index("idx_pcp_category").on(table.categoryId),
|
|
176
|
+
]);
|
|
177
|
+
export const productTagProducts = pgTable("product_tag_products", {
|
|
178
|
+
productId: typeIdRef("product_id")
|
|
179
|
+
.notNull()
|
|
180
|
+
.references(() => products.id, { onDelete: "cascade" }),
|
|
181
|
+
tagId: typeIdRef("tag_id")
|
|
182
|
+
.notNull()
|
|
183
|
+
.references(() => productTags.id, { onDelete: "cascade" }),
|
|
184
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
185
|
+
}, (table) => [
|
|
186
|
+
primaryKey({ columns: [table.productId, table.tagId] }),
|
|
187
|
+
index("idx_ptp_tag").on(table.tagId),
|
|
188
|
+
]);
|
|
189
|
+
export const productDestinations = pgTable("product_destinations", {
|
|
190
|
+
productId: typeIdRef("product_id")
|
|
191
|
+
.notNull()
|
|
192
|
+
.references(() => products.id, { onDelete: "cascade" }),
|
|
193
|
+
destinationId: typeIdRef("destination_id")
|
|
194
|
+
.notNull()
|
|
195
|
+
.references(() => destinations.id, { onDelete: "cascade" }),
|
|
196
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
197
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
198
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
199
|
+
}, (table) => [
|
|
200
|
+
primaryKey({ columns: [table.productId, table.destinationId] }),
|
|
201
|
+
index("idx_product_destinations_product_sort").on(table.productId, table.sortOrder),
|
|
202
|
+
index("idx_product_destinations_destination_sort").on(table.destinationId, table.sortOrder),
|
|
203
|
+
]);
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { bookingItemProductDetails, bookingProductDetails } from "./booking-extension.js";
|
|
2
|
+
export * from "./extras/schema.js";
|
|
3
|
+
export * from "./schema-core.js";
|
|
4
|
+
export * from "./schema-itinerary.js";
|
|
5
|
+
export * from "./schema-relations.js";
|
|
6
|
+
export * from "./schema-settings.js";
|
|
7
|
+
export * from "./schema-shared.js";
|
|
8
|
+
export { type InsertProductsSourcedContent, PRODUCTS_CONTENT_MARKET_ANY, type ProductsSourcedContentFetchStatus, productsSourcedContentTable, type SelectProductsSourcedContent, } from "./schema-sourced-content.js";
|
|
9
|
+
export * from "./schema-taxonomy.js";
|
|
10
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACzF,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,OAAO,EACL,KAAK,4BAA4B,EACjC,2BAA2B,EAC3B,KAAK,iCAAiC,EACtC,2BAA2B,EAC3B,KAAK,4BAA4B,GAClC,MAAM,6BAA6B,CAAA;AACpC,cAAc,sBAAsB,CAAA"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { bookingItemProductDetails, bookingProductDetails } from "./booking-extension.js";
|
|
2
|
+
export * from "./extras/schema.js";
|
|
3
|
+
export * from "./schema-core.js";
|
|
4
|
+
export * from "./schema-itinerary.js";
|
|
5
|
+
export * from "./schema-relations.js";
|
|
6
|
+
export * from "./schema-settings.js";
|
|
7
|
+
export * from "./schema-shared.js";
|
|
8
|
+
export { PRODUCTS_CONTENT_MARKET_ANY, productsSourcedContentTable, } from "./schema-sourced-content.js";
|
|
9
|
+
export * from "./schema-taxonomy.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { products } from "./schema-core.js";
|
|
3
|
+
type ProductStatus = (typeof products.$inferSelect)["status"];
|
|
4
|
+
export interface ProductAggregates {
|
|
5
|
+
total: number;
|
|
6
|
+
countsByStatus: Array<{
|
|
7
|
+
status: ProductStatus;
|
|
8
|
+
count: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** Shorthand for the `active` bucket — dashboard KPI card. */
|
|
11
|
+
active: number;
|
|
12
|
+
/**
|
|
13
|
+
* Products publicly listed on the storefront: `status = active` AND
|
|
14
|
+
* `activated = true` AND `visibility = 'public'`. Distinct from `active`,
|
|
15
|
+
* which includes internal-only active products.
|
|
16
|
+
*/
|
|
17
|
+
publicActive: number;
|
|
18
|
+
/** Product creation count bucketed by UTC yearMonth, oldest first. */
|
|
19
|
+
monthlyCreatedCounts: Array<{
|
|
20
|
+
yearMonth: string;
|
|
21
|
+
count: number;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export declare function getProductAggregates(db: PostgresJsDatabase, options?: {
|
|
25
|
+
from?: string;
|
|
26
|
+
to?: string;
|
|
27
|
+
}): Promise<ProductAggregates>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=service-aggregates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-aggregates.d.ts","sourceRoot":"","sources":["../src/service-aggregates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,KAAK,aAAa,GAAG,CAAC,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAA;AAI7D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC/D,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,sEAAsE;IACtE,oBAAoB,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClE;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,kBAAkB,EACtB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3C,OAAO,CAAC,iBAAiB,CAAC,CA4D5B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
2
|
+
import { products } from "./schema-core.js";
|
|
3
|
+
const ALL_PRODUCT_STATUSES = ["draft", "active", "archived"];
|
|
4
|
+
export async function getProductAggregates(db, options = {}) {
|
|
5
|
+
const fromDate = options.from ? new Date(options.from) : undefined;
|
|
6
|
+
const toDate = options.to ? new Date(options.to) : undefined;
|
|
7
|
+
const rangeConditions = [];
|
|
8
|
+
// agent-quality: raw-sql reviewed -- owner: inventory; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
9
|
+
if (fromDate)
|
|
10
|
+
rangeConditions.push(sql `${products.createdAt} >= ${fromDate.toISOString()}`);
|
|
11
|
+
// agent-quality: raw-sql reviewed -- owner: inventory; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
12
|
+
if (toDate)
|
|
13
|
+
rangeConditions.push(sql `${products.createdAt} < ${toDate.toISOString()}`);
|
|
14
|
+
const rangeWhere = rangeConditions.length ? and(...rangeConditions) : undefined;
|
|
15
|
+
// Publicly-listed count ignores the date range — it's a point-in-time KPI
|
|
16
|
+
// ("what's live on the storefront right now"). The range-bound `active`
|
|
17
|
+
// bucket serves the "how many active products did we create this quarter"
|
|
18
|
+
// question instead.
|
|
19
|
+
const [[totalRow], statusRows, [publicActiveRow], monthlyCreatedCountsRows] = await Promise.all([
|
|
20
|
+
db.select({ count: sql `count(*)::int` }).from(products).where(rangeWhere),
|
|
21
|
+
db
|
|
22
|
+
.select({ status: products.status, count: sql `count(*)::int` })
|
|
23
|
+
.from(products)
|
|
24
|
+
.where(rangeWhere)
|
|
25
|
+
.groupBy(products.status),
|
|
26
|
+
db
|
|
27
|
+
.select({ count: sql `count(*)::int` })
|
|
28
|
+
.from(products)
|
|
29
|
+
.where(and(eq(products.status, "active"), eq(products.activated, true), eq(products.visibility, "public"))),
|
|
30
|
+
db
|
|
31
|
+
.select({
|
|
32
|
+
yearMonth: sql `to_char(${products.createdAt} at time zone 'UTC', 'YYYY-MM')`,
|
|
33
|
+
count: sql `count(*)::int`,
|
|
34
|
+
})
|
|
35
|
+
.from(products)
|
|
36
|
+
.where(rangeWhere)
|
|
37
|
+
// agent-quality: raw-sql reviewed -- owner: inventory; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
38
|
+
.groupBy(sql `to_char(${products.createdAt} at time zone 'UTC', 'YYYY-MM')`)
|
|
39
|
+
// agent-quality: raw-sql reviewed -- owner: inventory; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
40
|
+
.orderBy(sql `to_char(${products.createdAt} at time zone 'UTC', 'YYYY-MM')`),
|
|
41
|
+
]);
|
|
42
|
+
const statusMap = new Map(statusRows.map((r) => [r.status, r.count]));
|
|
43
|
+
return {
|
|
44
|
+
total: totalRow?.count ?? 0,
|
|
45
|
+
countsByStatus: ALL_PRODUCT_STATUSES.map((status) => ({
|
|
46
|
+
status,
|
|
47
|
+
count: statusMap.get(status) ?? 0,
|
|
48
|
+
})),
|
|
49
|
+
active: statusMap.get("active") ?? 0,
|
|
50
|
+
publicActive: publicActiveRow?.count ?? 0,
|
|
51
|
+
monthlyCreatedCounts: monthlyCreatedCountsRows.map((row) => ({
|
|
52
|
+
yearMonth: row.yearMonth,
|
|
53
|
+
count: row.count,
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection extension that joins product → destinations and contributes
|
|
3
|
+
* locale-aware destination fields (regions, countries, cities, slugs, ids)
|
|
4
|
+
* onto the product search document.
|
|
5
|
+
*
|
|
6
|
+
* Wire via `createProductDocumentBuilder({ extensions: [destinationsExtension] })`.
|
|
7
|
+
* Requires the registry to include `productDestinationsCatalogPolicy` —
|
|
8
|
+
* otherwise the contributed fields are silently dropped by the indexer's
|
|
9
|
+
* field-policy filter.
|
|
10
|
+
*
|
|
11
|
+
* Locale handling: `destination_translations` is keyed by `(destination_id,
|
|
12
|
+
* language_tag)`. The projection looks up the slice's locale; missing
|
|
13
|
+
* translations fall back to the destination's canonical `slug`. Operators
|
|
14
|
+
* who want the locale lookup to fall back to a different language (e.g.
|
|
15
|
+
* `fr-CA` → `fr` → `en`) should pre-populate the translation row;
|
|
16
|
+
* multi-tier fallback isn't built into the joins.
|
|
17
|
+
*
|
|
18
|
+
* Today's destinations table has no coordinate columns — geopoints come
|
|
19
|
+
* from `product_locations` and will ship as a separate projection
|
|
20
|
+
* extension once that policy lands.
|
|
21
|
+
*/
|
|
22
|
+
import type { ProductProjectionExtension } from "./service-catalog-plane.js";
|
|
23
|
+
/**
|
|
24
|
+
* Construct the destinations projection extension.
|
|
25
|
+
*
|
|
26
|
+
* Returns a `ProductProjectionExtension` ready to pass to
|
|
27
|
+
* `createProductDocumentBuilder`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createProductDestinationsProjectionExtension(): ProductProjectionExtension;
|
|
30
|
+
//# sourceMappingURL=service-catalog-plane-destinations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-catalog-plane-destinations.d.ts","sourceRoot":"","sources":["../src/service-catalog-plane-destinations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AAsH5E;;;;;GAKG;AACH,wBAAgB,4CAA4C,IAAI,0BAA0B,CAgCzF"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection extension that joins product → destinations and contributes
|
|
3
|
+
* locale-aware destination fields (regions, countries, cities, slugs, ids)
|
|
4
|
+
* onto the product search document.
|
|
5
|
+
*
|
|
6
|
+
* Wire via `createProductDocumentBuilder({ extensions: [destinationsExtension] })`.
|
|
7
|
+
* Requires the registry to include `productDestinationsCatalogPolicy` —
|
|
8
|
+
* otherwise the contributed fields are silently dropped by the indexer's
|
|
9
|
+
* field-policy filter.
|
|
10
|
+
*
|
|
11
|
+
* Locale handling: `destination_translations` is keyed by `(destination_id,
|
|
12
|
+
* language_tag)`. The projection looks up the slice's locale; missing
|
|
13
|
+
* translations fall back to the destination's canonical `slug`. Operators
|
|
14
|
+
* who want the locale lookup to fall back to a different language (e.g.
|
|
15
|
+
* `fr-CA` → `fr` → `en`) should pre-populate the translation row;
|
|
16
|
+
* multi-tier fallback isn't built into the joins.
|
|
17
|
+
*
|
|
18
|
+
* Today's destinations table has no coordinate columns — geopoints come
|
|
19
|
+
* from `product_locations` and will ship as a separate projection
|
|
20
|
+
* extension once that policy lands.
|
|
21
|
+
*/
|
|
22
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
23
|
+
import { destinations, destinationTranslations, productDestinations } from "./schema-taxonomy.js";
|
|
24
|
+
async function joinProductDestinations(db, productId, languageTag) {
|
|
25
|
+
// Sub-query the linked destination ids first so the locale lookup is
|
|
26
|
+
// single-shot. Drizzle's join builder doesn't compose left joins on
|
|
27
|
+
// composite predicates cleanly across all dialects, so two queries +
|
|
28
|
+
// an in-memory merge keeps the contract obvious.
|
|
29
|
+
const links = await db
|
|
30
|
+
.select({
|
|
31
|
+
destinationId: productDestinations.destinationId,
|
|
32
|
+
destinationType: destinations.destinationType,
|
|
33
|
+
slug: destinations.slug,
|
|
34
|
+
canonicalPlaceId: destinations.canonicalPlaceId,
|
|
35
|
+
active: destinations.active,
|
|
36
|
+
})
|
|
37
|
+
.from(productDestinations)
|
|
38
|
+
.innerJoin(destinations, eq(productDestinations.destinationId, destinations.id))
|
|
39
|
+
.where(eq(productDestinations.productId, productId));
|
|
40
|
+
const activeLinks = links.filter((row) => row.active);
|
|
41
|
+
if (activeLinks.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const destinationIds = activeLinks.map((row) => row.destinationId);
|
|
44
|
+
const translations = await db
|
|
45
|
+
.select({
|
|
46
|
+
destinationId: destinationTranslations.destinationId,
|
|
47
|
+
name: destinationTranslations.name,
|
|
48
|
+
})
|
|
49
|
+
.from(destinationTranslations)
|
|
50
|
+
.where(and(inArray(destinationTranslations.destinationId, destinationIds), eq(destinationTranslations.languageTag, languageTag)));
|
|
51
|
+
const nameByDestinationId = new Map();
|
|
52
|
+
for (const row of translations) {
|
|
53
|
+
nameByDestinationId.set(row.destinationId, row.name);
|
|
54
|
+
}
|
|
55
|
+
return activeLinks.map((row) => ({
|
|
56
|
+
destinationId: row.destinationId,
|
|
57
|
+
destinationType: row.destinationType,
|
|
58
|
+
slug: row.slug,
|
|
59
|
+
canonicalPlaceId: row.canonicalPlaceId ?? null,
|
|
60
|
+
translatedName: nameByDestinationId.get(row.destinationId) ?? null,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
/** Bucket destinations by `destinationType` and return locale-aware names. */
|
|
64
|
+
function bucketByType(rows) {
|
|
65
|
+
const regions = [];
|
|
66
|
+
const countries = [];
|
|
67
|
+
const cities = [];
|
|
68
|
+
const ports = [];
|
|
69
|
+
const waterways = [];
|
|
70
|
+
const slugs = [];
|
|
71
|
+
const ids = [];
|
|
72
|
+
const canonicalPlaceIds = [];
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
const label = row.translatedName ?? row.slug;
|
|
75
|
+
switch (row.destinationType) {
|
|
76
|
+
case "region":
|
|
77
|
+
regions.push(label);
|
|
78
|
+
break;
|
|
79
|
+
case "country":
|
|
80
|
+
countries.push(label);
|
|
81
|
+
break;
|
|
82
|
+
case "city":
|
|
83
|
+
cities.push(label);
|
|
84
|
+
break;
|
|
85
|
+
case "port":
|
|
86
|
+
ports.push(label);
|
|
87
|
+
break;
|
|
88
|
+
case "river":
|
|
89
|
+
case "sea":
|
|
90
|
+
case "ocean":
|
|
91
|
+
case "canal":
|
|
92
|
+
case "lake":
|
|
93
|
+
waterways.push(label);
|
|
94
|
+
break;
|
|
95
|
+
// "destination" (the catch-all default) doesn't bucket into any of
|
|
96
|
+
// the typed lists. Its slug + id still land in the doc so storefronts
|
|
97
|
+
// can filter generically.
|
|
98
|
+
}
|
|
99
|
+
slugs.push(row.slug);
|
|
100
|
+
ids.push(row.destinationId);
|
|
101
|
+
if (row.canonicalPlaceId) {
|
|
102
|
+
canonicalPlaceIds.push(row.canonicalPlaceId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { regions, countries, cities, ports, waterways, slugs, ids, canonicalPlaceIds };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Construct the destinations projection extension.
|
|
109
|
+
*
|
|
110
|
+
* Returns a `ProductProjectionExtension` ready to pass to
|
|
111
|
+
* `createProductDocumentBuilder`.
|
|
112
|
+
*/
|
|
113
|
+
export function createProductDestinationsProjectionExtension() {
|
|
114
|
+
return {
|
|
115
|
+
name: "products:destinations",
|
|
116
|
+
async project(db, productId, slice) {
|
|
117
|
+
const rows = await joinProductDestinations(db, productId, slice.locale);
|
|
118
|
+
if (rows.length === 0) {
|
|
119
|
+
return new Map([
|
|
120
|
+
["regions[]", []],
|
|
121
|
+
["countries[]", []],
|
|
122
|
+
["cities[]", []],
|
|
123
|
+
["ports[]", []],
|
|
124
|
+
["waterways[]", []],
|
|
125
|
+
["destinationSlugs[]", []],
|
|
126
|
+
["destinationIds[]", []],
|
|
127
|
+
["destinationCanonicalPlaceIds[]", []],
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
const { regions, countries, cities, ports, waterways, slugs, ids, canonicalPlaceIds } = bucketByType(rows);
|
|
131
|
+
return new Map([
|
|
132
|
+
["regions[]", regions],
|
|
133
|
+
["countries[]", countries],
|
|
134
|
+
["cities[]", cities],
|
|
135
|
+
["ports[]", ports],
|
|
136
|
+
["waterways[]", waterways],
|
|
137
|
+
["destinationSlugs[]", slugs],
|
|
138
|
+
["destinationIds[]", ids],
|
|
139
|
+
["destinationCanonicalPlaceIds[]", canonicalPlaceIds],
|
|
140
|
+
]);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection extension that joins product → categories (with ancestor walk)
|
|
3
|
+
* and product → tags, contributing taxonomy fields onto the product search
|
|
4
|
+
* document.
|
|
5
|
+
*
|
|
6
|
+
* Wire via `createProductDocumentBuilder({ extensions: [taxonomyExtension] })`.
|
|
7
|
+
* Requires the registry to include `productTaxonomyCatalogPolicy` —
|
|
8
|
+
* otherwise the contributed fields are silently dropped by the indexer's
|
|
9
|
+
* field-policy filter.
|
|
10
|
+
*
|
|
11
|
+
* Hierarchy denormalization: Typesense can't recurse, so a product linked
|
|
12
|
+
* to "Hiking" (parent: "Adventure") needs both labels in `categories[]` for
|
|
13
|
+
* the "Adventure" filter to match. The projection walks the parent chain
|
|
14
|
+
* iteratively, filtering inactive ancestors (so an operator-paused parent
|
|
15
|
+
* stops surfacing its still-active children under the parent filter).
|
|
16
|
+
*
|
|
17
|
+
* Localization (#502): when a `product_category_translations` /
|
|
18
|
+
* `product_tag_translations` row exists for the slice's locale, its `name`
|
|
19
|
+
* wins. Otherwise the projection falls back to the canonical
|
|
20
|
+
* `productCategories.name` / `productTags.name` (the legacy single-locale
|
|
21
|
+
* column). This makes the upgrade non-breaking — operators that haven't
|
|
22
|
+
* created translations keep seeing the English label on every locale slice
|
|
23
|
+
* exactly as before.
|
|
24
|
+
*
|
|
25
|
+
* Slugs stay single-locale on `productCategories.slug` (per #502 non-goals
|
|
26
|
+
* — operators want stable URLs that don't shift when translations are
|
|
27
|
+
* edited).
|
|
28
|
+
*/
|
|
29
|
+
import type { ProductProjectionExtension } from "./service-catalog-plane.js";
|
|
30
|
+
interface CategoryRow {
|
|
31
|
+
id: string;
|
|
32
|
+
parentId: string | null;
|
|
33
|
+
name: string;
|
|
34
|
+
slug: string;
|
|
35
|
+
active: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface DirectCategoryLink {
|
|
38
|
+
categoryId: string;
|
|
39
|
+
sortOrder: number;
|
|
40
|
+
}
|
|
41
|
+
interface TagRow {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
interface TaxonomyProjection {
|
|
46
|
+
categoryIds: string[];
|
|
47
|
+
categoryNames: string[];
|
|
48
|
+
categorySlugs: string[];
|
|
49
|
+
primaryCategoryId: string | null;
|
|
50
|
+
primaryCategoryName: string | null;
|
|
51
|
+
primaryCategorySlug: string | null;
|
|
52
|
+
tagIds: string[];
|
|
53
|
+
tagLabels: string[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pure aggregation kernel. `categoryNameByLocale` and `tagNameByLocale`
|
|
57
|
+
* carry slice-locale translations; entries override the canonical name on
|
|
58
|
+
* the row. Missing entries fall back to the canonical name. Slug stays
|
|
59
|
+
* canonical regardless — there's no per-locale slug.
|
|
60
|
+
*/
|
|
61
|
+
declare function buildTaxonomyProjection(directLinks: ReadonlyArray<DirectCategoryLink>, resolvedCategories: ReadonlyMap<string, CategoryRow>, tags: ReadonlyArray<TagRow>, categoryNameByLocale?: ReadonlyMap<string, string>, tagNameByLocale?: ReadonlyMap<string, string>): TaxonomyProjection;
|
|
62
|
+
/**
|
|
63
|
+
* Construct the taxonomy projection extension.
|
|
64
|
+
*
|
|
65
|
+
* Returns a `ProductProjectionExtension` ready to pass to
|
|
66
|
+
* `createProductDocumentBuilder`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createProductTaxonomyProjectionExtension(): ProductProjectionExtension;
|
|
69
|
+
export declare const __test__: {
|
|
70
|
+
buildTaxonomyProjection: typeof buildTaxonomyProjection;
|
|
71
|
+
};
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=service-catalog-plane-taxonomy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-catalog-plane-taxonomy.d.ts","sourceRoot":"","sources":["../src/service-catalog-plane-taxonomy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAaH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AAE5E,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAkED,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AA8DD,UAAU,kBAAkB;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;;GAKG;AACH,iBAAS,uBAAuB,CAC9B,WAAW,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC9C,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,EACpD,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,oBAAoB,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,EAC7D,eAAe,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,GACvD,kBAAkB,CAmEpB;AAED;;;;;GAKG;AACH,wBAAgB,wCAAwC,IAAI,0BAA0B,CA6CrF;AAGD,eAAO,MAAM,QAAQ;;CAEpB,CAAA"}
|