@voyantjs/products 0.20.0 → 0.21.1

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 (70) hide show
  1. package/dist/booking-engine/handler.d.ts +203 -0
  2. package/dist/booking-engine/handler.d.ts.map +1 -0
  3. package/dist/booking-engine/handler.js +330 -0
  4. package/dist/booking-engine/index.d.ts +8 -0
  5. package/dist/booking-engine/index.d.ts.map +1 -0
  6. package/dist/booking-engine/index.js +7 -0
  7. package/dist/catalog-policy.d.ts.map +1 -1
  8. package/dist/catalog-policy.js +15 -1
  9. package/dist/content-shape.d.ts +217 -0
  10. package/dist/content-shape.d.ts.map +1 -0
  11. package/dist/content-shape.js +159 -0
  12. package/dist/draft-shape.d.ts +43 -0
  13. package/dist/draft-shape.d.ts.map +1 -0
  14. package/dist/draft-shape.js +46 -0
  15. package/dist/events.d.ts +37 -0
  16. package/dist/events.d.ts.map +1 -0
  17. package/dist/events.js +32 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/routes-content.d.ts +74 -0
  22. package/dist/routes-content.d.ts.map +1 -0
  23. package/dist/routes-content.js +117 -0
  24. package/dist/routes.d.ts +40 -20
  25. package/dist/routes.d.ts.map +1 -1
  26. package/dist/routes.js +83 -13
  27. package/dist/schema-core.d.ts +240 -1
  28. package/dist/schema-core.d.ts.map +1 -1
  29. package/dist/schema-core.js +49 -0
  30. package/dist/schema-itinerary.d.ts +18 -1
  31. package/dist/schema-itinerary.d.ts.map +1 -1
  32. package/dist/schema-itinerary.js +1 -0
  33. package/dist/schema-settings.d.ts +1 -1
  34. package/dist/schema-sourced-content.d.ts +262 -0
  35. package/dist/schema-sourced-content.d.ts.map +1 -0
  36. package/dist/schema-sourced-content.js +69 -0
  37. package/dist/schema-taxonomy.d.ts +17 -0
  38. package/dist/schema-taxonomy.d.ts.map +1 -1
  39. package/dist/schema-taxonomy.js +13 -0
  40. package/dist/schema.d.ts +1 -0
  41. package/dist/schema.d.ts.map +1 -1
  42. package/dist/schema.js +1 -0
  43. package/dist/service-catalog-plane.d.ts.map +1 -1
  44. package/dist/service-catalog-plane.js +1 -0
  45. package/dist/service-content-owned.d.ts +68 -0
  46. package/dist/service-content-owned.d.ts.map +1 -0
  47. package/dist/service-content-owned.js +224 -0
  48. package/dist/service-content-synthesizer.d.ts +90 -0
  49. package/dist/service-content-synthesizer.d.ts.map +1 -0
  50. package/dist/service-content-synthesizer.js +171 -0
  51. package/dist/service-content.d.ts +106 -0
  52. package/dist/service-content.d.ts.map +1 -0
  53. package/dist/service-content.js +365 -0
  54. package/dist/service.d.ts +76 -22
  55. package/dist/service.d.ts.map +1 -1
  56. package/dist/service.js +4 -0
  57. package/dist/tasks/brochures.d.ts +1 -0
  58. package/dist/tasks/brochures.d.ts.map +1 -1
  59. package/dist/tasks/brochures.js +3 -0
  60. package/dist/validation-catalog.d.ts +4 -4
  61. package/dist/validation-config.d.ts +3 -3
  62. package/dist/validation-content.d.ts +34 -4
  63. package/dist/validation-content.d.ts.map +1 -1
  64. package/dist/validation-content.js +13 -0
  65. package/dist/validation-core.d.ts +53 -3
  66. package/dist/validation-core.d.ts.map +1 -1
  67. package/dist/validation-core.js +16 -0
  68. package/dist/validation-public.d.ts +9 -9
  69. package/dist/validation-shared.d.ts +4 -4
  70. package/package.json +12 -7
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Product content service — `getProductContent` with owned-vs-sourced
3
+ * dispatch, locale-resolved cache reads, SWR refresh, and synthesizer
4
+ * fallback.
5
+ *
6
+ * One entry point per vertical; the catalog plane stays neutral about
7
+ * per-vertical content shapes. Detail routes (operator and storefront)
8
+ * call `getProductContent(db, entityId, scope, options)` and get back a
9
+ * fully-resolved `ContentLocaleResolution<ProductContent>` regardless
10
+ * of whether the row is owned or sourced.
11
+ *
12
+ * Owned rows (entities in the products table without a sourced-entry
13
+ * row) read from the products tables directly — out of scope for this
14
+ * file in v1; callers that need owned reads compose them around this
15
+ * service. Phase D ships sourced + synthesizer; owned dispatch
16
+ * narrows when first sourced template adopts.
17
+ *
18
+ * See `docs/architecture/catalog-sourced-content.md` §3.3, §3.4, §3.6.
19
+ */
20
+ import { createInvalidateOnDrift, fetchOverlaysForEntity, isStale, pickBestCachedLocale, readSourcedEntry, withContentRefreshLock, } from "@voyantjs/catalog";
21
+ import { and, eq } from "drizzle-orm";
22
+ import { mergeOverlaysIntoProductContent, PRODUCTS_CONTENT_SCHEMA_VERSION, productContentSchema, validateProductContent, } from "./content-shape.js";
23
+ import { PRODUCTS_CONTENT_MARKET_ANY, productsSourcedContentTable, } from "./schema-sourced-content.js";
24
+ import { buildOwnedProductContent } from "./service-content-owned.js";
25
+ import { synthesizeProductContent, } from "./service-content-synthesizer.js";
26
+ /** Default TTL when the adapter doesn't pin `fresh_until`. */
27
+ const PRODUCTS_DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24h, per §3.4
28
+ /**
29
+ * Read the rich product content for one entity, resolving locale
30
+ * preference, applying overlays, and refreshing in the background when
31
+ * stale. Returns `null` only when the entity is unknown (no
32
+ * sourced-entry row, no owned row).
33
+ */
34
+ export async function getProductContent(db, entityId, scope, options) {
35
+ const sourcedEntry = await readSourcedEntry(db, "products", entityId);
36
+ if (!sourcedEntry) {
37
+ // Owned-product path. Read from the products module's own tables
38
+ // and project to ProductContent — locale resolution against
39
+ // product_translations + product_option_translations uses the
40
+ // same pickBestCachedLocale scoring the sourced cache reads use.
41
+ // Overlay merge applies the same way it does for sourced rows.
42
+ const owned = await buildOwnedProductContent(db, entityId, {
43
+ preferredLocales: scope.preferredLocales,
44
+ });
45
+ if (!owned)
46
+ return null;
47
+ const overlays = await fetchOverlaysForEntity(db, "products", entityId);
48
+ const merged = mergeOverlaysIntoProductContent(owned.content, overlays.map((o) => ({ field_path: o.field_path, value: o.value })), {
49
+ onOverlayError: options.onOverlayError
50
+ ? (e) => options.onOverlayError({
51
+ field_path: e.overlay.field_path,
52
+ reason: e.reason,
53
+ })
54
+ : undefined,
55
+ });
56
+ return {
57
+ content: merged,
58
+ resolution: {
59
+ candidate: { locale: owned.servedLocale, payload: merged },
60
+ served_locale: owned.servedLocale,
61
+ match_kind: owned.matchKind,
62
+ },
63
+ source: "owned",
64
+ served_stale: false,
65
+ synthesized: false,
66
+ machine_translated: false,
67
+ };
68
+ }
69
+ // Wrap the entry as a ProvenanceReadResult so the synthesizer can
70
+ // consume it without re-reading.
71
+ const provenance = {
72
+ kind: "sourced",
73
+ provenance: {
74
+ source_kind: sourcedEntry.source_kind,
75
+ source_provider: sourcedEntry.source_provider ?? undefined,
76
+ source_connection_id: sourcedEntry.source_connection_id ?? undefined,
77
+ source_ref: sourcedEntry.source_ref ?? undefined,
78
+ source_freshness: sourcedEntry.source_freshness,
79
+ last_sourced_at: sourcedEntry.last_sourced_at ?? undefined,
80
+ },
81
+ entry_id: sourcedEntry.id,
82
+ status: sourcedEntry.status,
83
+ projection: sourcedEntry.projection,
84
+ projection_etag: sourcedEntry.projection_etag,
85
+ projection_seen_at: sourcedEntry.projection_seen_at,
86
+ first_seen_at: sourcedEntry.first_seen_at,
87
+ last_seen_at: sourcedEntry.last_seen_at,
88
+ };
89
+ const adapter = sourcedEntry.source_connection_id
90
+ ? (options.registry.resolveByConnection(sourcedEntry.source_connection_id) ??
91
+ options.registry.byKind(sourcedEntry.source_kind)[0]?.adapter)
92
+ : options.registry.byKind(sourcedEntry.source_kind)[0]?.adapter;
93
+ const adapterCtx = options.buildAdapterContext?.(adapter) ?? {
94
+ connection_id: sourcedEntry.source_connection_id ?? sourcedEntry.source_kind,
95
+ };
96
+ const market = scope.market ?? PRODUCTS_CONTENT_MARKET_ANY;
97
+ const acceptMT = scope.acceptMachineTranslated ?? true;
98
+ if (options.forceFresh && adapter?.getContent) {
99
+ const fresh = await fetchFreshContent(db, adapter, adapterCtx, {
100
+ entity_module: "products",
101
+ entity_id: entityId,
102
+ locale: scope.preferredLocales[0] ?? "en-GB",
103
+ market,
104
+ currency: scope.currency,
105
+ }, options);
106
+ if (fresh) {
107
+ return finalizeFresh(db, entityId, fresh, scope, options);
108
+ }
109
+ }
110
+ // 1. Look up cached candidates across all locales for this entity.
111
+ const cachedRows = await fetchCacheCandidates(db, entityId, market);
112
+ const eligibleRows = acceptMT ? cachedRows : cachedRows.filter((r) => !r.machine_translated);
113
+ const best = pickBestCachedLocale(eligibleRows.map((row) => ({ ...row, locale: row.locale })), scope.preferredLocales);
114
+ const shouldRefreshLegacyAvailability = best
115
+ ? hasLegacyDepartureAvailabilityGap(best.candidate)
116
+ : false;
117
+ if (best && !isStale(best.candidate) && !shouldRefreshLegacyAvailability) {
118
+ return finalizeFromCache(db, entityId, best, "sourced-cache", false, options);
119
+ }
120
+ if (best && (isStale(best.candidate) || shouldRefreshLegacyAvailability)) {
121
+ // SWR for ordinary stale reads. Legacy demo content without
122
+ // departure capacity is refreshed synchronously so operator
123
+ // availability surfaces do not show effectively-unlimited slots.
124
+ if (adapter?.getContent) {
125
+ const refreshRequest = {
126
+ entity_module: "products",
127
+ entity_id: entityId,
128
+ locale: scope.preferredLocales[0] ?? best.candidate.locale,
129
+ market,
130
+ currency: scope.currency,
131
+ };
132
+ if (shouldRefreshLegacyAvailability) {
133
+ const fresh = await fetchFreshContent(db, adapter, adapterCtx, refreshRequest, options);
134
+ if (fresh)
135
+ return finalizeFresh(db, entityId, fresh, scope, options);
136
+ }
137
+ else {
138
+ void scheduleRefresh(db, adapter, adapterCtx, refreshRequest);
139
+ }
140
+ }
141
+ return finalizeFromCache(db, entityId, best, "sourced-cache", true, options);
142
+ }
143
+ // No cache row at all — must produce content somehow.
144
+ if (!adapter?.getContent) {
145
+ // Thin adapter or no adapter registered — synthesize from
146
+ // projection + overlay + plane metadata (§3.6).
147
+ const overlays = await fetchOverlaysForEntity(db, "products", entityId);
148
+ const synthesized = synthesizeProductContent({ locale: scope.preferredLocales[0] ?? "en-GB" }, {
149
+ provenance,
150
+ overlays: overlays.map((o) => ({ field_path: o.field_path, value: o.value })),
151
+ });
152
+ return wrapSynthesized(synthesized, scope, false);
153
+ }
154
+ // Cache miss with a rich adapter — block on the adapter, dedupe
155
+ // across workers via advisory lock, and write through to the cache.
156
+ const fresh = await fetchFreshContent(db, adapter, adapterCtx, {
157
+ entity_module: "products",
158
+ entity_id: entityId,
159
+ locale: scope.preferredLocales[0] ?? "en-GB",
160
+ market,
161
+ currency: scope.currency,
162
+ }, options);
163
+ if (!fresh) {
164
+ // The adapter call could not get the lock AND there's no cached
165
+ // row — fall back to synthesizer rather than blocking forever.
166
+ const overlays = await fetchOverlaysForEntity(db, "products", entityId);
167
+ const synthesized = synthesizeProductContent({ locale: scope.preferredLocales[0] ?? "en-GB" }, {
168
+ provenance,
169
+ overlays: overlays.map((o) => ({ field_path: o.field_path, value: o.value })),
170
+ });
171
+ return wrapSynthesized(synthesized, scope, false);
172
+ }
173
+ return finalizeFresh(db, entityId, fresh, scope, options);
174
+ }
175
+ // ─────────────────────────────────────────────────────────────────────────────
176
+ // Cache candidates
177
+ // ─────────────────────────────────────────────────────────────────────────────
178
+ async function fetchCacheCandidates(db, entityId, market) {
179
+ const rows = await db
180
+ .select()
181
+ .from(productsSourcedContentTable)
182
+ .where(and(eq(productsSourcedContentTable.entity_id, entityId), eq(productsSourcedContentTable.market, market), eq(productsSourcedContentTable.content_schema_version, PRODUCTS_CONTENT_SCHEMA_VERSION)));
183
+ return rows;
184
+ }
185
+ function hasLegacyDepartureAvailabilityGap(row) {
186
+ const validation = validateProductContent(row.payload);
187
+ if (!validation.valid)
188
+ return false;
189
+ return validation.content.departures.some((departure) => departure.capacity == null && departure.remaining == null);
190
+ }
191
+ // ─────────────────────────────────────────────────────────────────────────────
192
+ // Fresh fetch + write-through
193
+ // ─────────────────────────────────────────────────────────────────────────────
194
+ async function fetchFreshContent(db, adapter, ctx, request, _options) {
195
+ const result = await withContentRefreshLock(db, {
196
+ entityModule: request.entity_module,
197
+ entityId: request.entity_id,
198
+ locale: request.locale,
199
+ market: request.market,
200
+ }, async () => {
201
+ const got = await adapter.getContent(ctx, request);
202
+ const validation = validateProductContent(got.content);
203
+ if (!validation.valid) {
204
+ // Surface adapter integration bugs, but don't write to cache.
205
+ throw new Error(`products getContent for ${request.entity_id} failed validation: ${validation.reason}`);
206
+ }
207
+ await writeCacheRow(db, request, got);
208
+ return got;
209
+ });
210
+ return result ?? null;
211
+ }
212
+ function scheduleRefresh(db, adapter, ctx, request) {
213
+ // Fire-and-forget. Errors are swallowed — a failed refresh just
214
+ // leaves the stale row in place; the next read tries again.
215
+ void withContentRefreshLock(db, {
216
+ entityModule: request.entity_module,
217
+ entityId: request.entity_id,
218
+ locale: request.locale,
219
+ market: request.market,
220
+ }, async () => {
221
+ const got = await adapter.getContent(ctx, request);
222
+ const validation = validateProductContent(got.content);
223
+ if (!validation.valid)
224
+ return;
225
+ await writeCacheRow(db, request, got);
226
+ }).catch(() => {
227
+ // intentionally swallow — see comment above
228
+ });
229
+ }
230
+ async function writeCacheRow(db, request, result) {
231
+ const market = request.market ?? PRODUCTS_CONTENT_MARKET_ANY;
232
+ const now = new Date();
233
+ // Date-like fields may arrive as strings when the adapter is an HTTP
234
+ // client (JSON.parse doesn't deserialize ISO timestamps to Date).
235
+ // Coerce at the cache-write boundary so the drizzle timestamp column
236
+ // gets a real Date — `value.toISOString is not a function` otherwise.
237
+ const sourceUpdatedAt = toDateOrNull(result.source_updated_at);
238
+ const freshUntil = toDateOrNull(result.fresh_until) ?? new Date(now.getTime() + PRODUCTS_DEFAULT_TTL_MS);
239
+ await db
240
+ .insert(productsSourcedContentTable)
241
+ .values({
242
+ entity_id: request.entity_id,
243
+ locale: request.locale,
244
+ market,
245
+ payload: result.content,
246
+ content_schema_version: result.content_schema_version,
247
+ returned_locale: result.returned_locale,
248
+ machine_translated: result.machine_translated ?? false,
249
+ source_updated_at: sourceUpdatedAt,
250
+ fetched_at: now,
251
+ fresh_until: freshUntil,
252
+ etag: result.etag ?? null,
253
+ fetch_status: "ok",
254
+ fetch_error: null,
255
+ })
256
+ .onConflictDoUpdate({
257
+ target: [
258
+ productsSourcedContentTable.entity_id,
259
+ productsSourcedContentTable.locale,
260
+ productsSourcedContentTable.market,
261
+ ],
262
+ set: {
263
+ payload: result.content,
264
+ content_schema_version: result.content_schema_version,
265
+ returned_locale: result.returned_locale,
266
+ machine_translated: result.machine_translated ?? false,
267
+ source_updated_at: sourceUpdatedAt,
268
+ fetched_at: now,
269
+ fresh_until: freshUntil,
270
+ etag: result.etag ?? null,
271
+ fetch_status: "ok",
272
+ fetch_error: null,
273
+ },
274
+ });
275
+ }
276
+ function toDateOrNull(value) {
277
+ if (!value)
278
+ return null;
279
+ if (value instanceof Date)
280
+ return value;
281
+ const parsed = new Date(value);
282
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
283
+ }
284
+ // ─────────────────────────────────────────────────────────────────────────────
285
+ // Finalizers — overlay merge + return shape
286
+ // ─────────────────────────────────────────────────────────────────────────────
287
+ async function finalizeFromCache(db, entityId, best, source, servedStale, options) {
288
+ const cachedPayload = best.candidate.payload;
289
+ const validation = validateProductContent(cachedPayload);
290
+ if (!validation.valid) {
291
+ // Schema-version-mismatch case is filtered upstream; if we hit
292
+ // here, the cache row is corrupt for some other reason. Treat as
293
+ // cache miss → caller's next layer (synthesizer) handles.
294
+ throw new Error(`products cache row for ${entityId} (${best.candidate.locale}) failed validation: ${validation.reason}`);
295
+ }
296
+ const cachedContent = validation.content;
297
+ const overlays = await fetchOverlaysForEntity(db, "products", entityId);
298
+ const merged = mergeOverlaysIntoProductContent(cachedContent, overlays.map((o) => ({ field_path: o.field_path, value: o.value })), {
299
+ onOverlayError: options.onOverlayError
300
+ ? (e) => options.onOverlayError({
301
+ field_path: e.overlay.field_path,
302
+ reason: e.reason,
303
+ })
304
+ : undefined,
305
+ });
306
+ return {
307
+ content: merged,
308
+ resolution: {
309
+ candidate: { locale: best.candidate.locale, payload: merged },
310
+ served_locale: best.candidate.returned_locale,
311
+ match_kind: best.match_kind,
312
+ },
313
+ source,
314
+ served_stale: servedStale,
315
+ synthesized: false,
316
+ machine_translated: best.candidate.machine_translated,
317
+ };
318
+ }
319
+ async function finalizeFresh(db, entityId, fresh, scope, options) {
320
+ const cachedContent = productContentSchema.parse(fresh.content);
321
+ const overlays = await fetchOverlaysForEntity(db, "products", entityId);
322
+ const merged = mergeOverlaysIntoProductContent(cachedContent, overlays.map((o) => ({ field_path: o.field_path, value: o.value })), {
323
+ onOverlayError: options.onOverlayError
324
+ ? (e) => options.onOverlayError({
325
+ field_path: e.overlay.field_path,
326
+ reason: e.reason,
327
+ })
328
+ : undefined,
329
+ });
330
+ return {
331
+ content: merged,
332
+ resolution: {
333
+ candidate: { locale: scope.preferredLocales[0] ?? fresh.returned_locale, payload: merged },
334
+ served_locale: fresh.returned_locale,
335
+ match_kind: scope.preferredLocales[0] === fresh.returned_locale ? "exact" : "language_match",
336
+ },
337
+ source: "sourced-fresh",
338
+ served_stale: false,
339
+ synthesized: false,
340
+ machine_translated: fresh.machine_translated ?? false,
341
+ };
342
+ }
343
+ function wrapSynthesized(synthesized, scope, servedStale) {
344
+ return {
345
+ content: synthesized.content,
346
+ resolution: {
347
+ candidate: { locale: synthesized.served_locale, payload: synthesized.content },
348
+ served_locale: synthesized.served_locale,
349
+ match_kind: scope.preferredLocales[0] === synthesized.served_locale ? "exact" : "any",
350
+ },
351
+ source: "synthesized",
352
+ served_stale: servedStale,
353
+ synthesized: true,
354
+ machine_translated: false,
355
+ };
356
+ }
357
+ /**
358
+ * Drift event consumer — sets `fresh_until = now()` on every cache row
359
+ * matching the event's (entity_module, entity_id [, locale [, market]])
360
+ * scope. The next read serves stale + schedules a SWR refresh.
361
+ *
362
+ * Templates subscribe this to the catalog plane's drift-event bus.
363
+ * Per sourced-content §3.4.1.
364
+ */
365
+ export const invalidateProductContentOnDrift = createInvalidateOnDrift(productsSourcedContentTable, { entityModule: "products" });
package/dist/service.d.ts CHANGED
@@ -92,10 +92,13 @@ export declare const productsService: {
92
92
  costAmountCents: number | null;
93
93
  marginPercent: number | null;
94
94
  facilityId: string | null;
95
+ supplierId: string | null;
95
96
  startDate: string | null;
96
97
  endDate: string | null;
97
98
  pax: number | null;
98
99
  productTypeId: string | null;
100
+ taxClassId: string | null;
101
+ customerPaymentPolicy: unknown;
99
102
  tags: string[] | null;
100
103
  createdAt: Date;
101
104
  updatedAt: Date;
@@ -120,10 +123,13 @@ export declare const productsService: {
120
123
  costAmountCents: number | null;
121
124
  marginPercent: number | null;
122
125
  facilityId: string | null;
126
+ supplierId: string | null;
123
127
  startDate: string | null;
124
128
  endDate: string | null;
125
129
  pax: number | null;
126
130
  productTypeId: string | null;
131
+ taxClassId: string | null;
132
+ customerPaymentPolicy: unknown;
127
133
  tags: string[] | null;
128
134
  createdAt: Date;
129
135
  updatedAt: Date;
@@ -144,6 +150,7 @@ export declare const productsService: {
144
150
  activated: boolean;
145
151
  productTypeId: string | null;
146
152
  facilityId: string | null;
153
+ supplierId: string | null;
147
154
  pax: number | null;
148
155
  reservationTimeoutMinutes: number | null;
149
156
  sellAmountCents: number | null;
@@ -151,6 +158,8 @@ export declare const productsService: {
151
158
  costAmountCents: number | null;
152
159
  marginPercent: number | null;
153
160
  tags: string[] | null;
161
+ taxClassId: string | null;
162
+ customerPaymentPolicy: unknown;
154
163
  }>;
155
164
  updateProduct(db: PostgresJsDatabase, id: string, data: UpdateProductInput): Promise<{
156
165
  id: string;
@@ -168,10 +177,13 @@ export declare const productsService: {
168
177
  costAmountCents: number | null;
169
178
  marginPercent: number | null;
170
179
  facilityId: string | null;
180
+ supplierId: string | null;
171
181
  startDate: string | null;
172
182
  endDate: string | null;
173
183
  pax: number | null;
174
184
  productTypeId: string | null;
185
+ taxClassId: string | null;
186
+ customerPaymentPolicy: unknown;
175
187
  tags: string[] | null;
176
188
  createdAt: Date;
177
189
  updatedAt: Date;
@@ -339,7 +351,7 @@ export declare const productsService: {
339
351
  data: {
340
352
  id: string;
341
353
  productId: string;
342
- capability: "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "accommodation" | "transport";
354
+ capability: "accommodation" | "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "transport";
343
355
  enabled: boolean;
344
356
  notes: string | null;
345
357
  createdAt: Date;
@@ -352,7 +364,7 @@ export declare const productsService: {
352
364
  getCapabilityById(db: PostgresJsDatabase, id: string): Promise<{
353
365
  id: string;
354
366
  productId: string;
355
- capability: "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "accommodation" | "transport";
367
+ capability: "accommodation" | "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "transport";
356
368
  enabled: boolean;
357
369
  notes: string | null;
358
370
  createdAt: Date;
@@ -365,12 +377,12 @@ export declare const productsService: {
365
377
  updatedAt: Date;
366
378
  notes: string | null;
367
379
  productId: string;
368
- capability: "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "accommodation" | "transport";
380
+ capability: "accommodation" | "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "transport";
369
381
  } | null>;
370
382
  updateCapability(db: PostgresJsDatabase, id: string, data: UpdateProductCapabilityInput): Promise<{
371
383
  id: string;
372
384
  productId: string;
373
- capability: "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "accommodation" | "transport";
385
+ capability: "accommodation" | "on_request" | "private" | "instant_confirmation" | "pickup_available" | "dropoff_available" | "guided" | "shared" | "digital_ticket" | "voucher_required" | "external_inventory" | "multi_day" | "transport";
374
386
  enabled: boolean;
375
387
  notes: string | null;
376
388
  createdAt: Date;
@@ -556,11 +568,11 @@ export declare const productsService: {
556
568
  updatedAt: Date;
557
569
  productId: string;
558
570
  title: string;
559
- sortOrder: number;
560
- locationType: "start" | "other" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
561
- address: string | null;
562
571
  city: string | null;
572
+ address: string | null;
573
+ sortOrder: number;
563
574
  countryCode: string | null;
575
+ locationType: "start" | "other" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
564
576
  latitude: number | null;
565
577
  longitude: number | null;
566
578
  googlePlaceId: string | null;
@@ -806,7 +818,7 @@ export declare const productsService: {
806
818
  name: string;
807
819
  code: string | null;
808
820
  description: string | null;
809
- unitType: "service" | "other" | "person" | "group" | "room" | "vehicle";
821
+ unitType: "service" | "other" | "group" | "person" | "room" | "vehicle";
810
822
  minQuantity: number | null;
811
823
  maxQuantity: number | null;
812
824
  minAge: number | null;
@@ -829,7 +841,7 @@ export declare const productsService: {
829
841
  name: string;
830
842
  code: string | null;
831
843
  description: string | null;
832
- unitType: "service" | "other" | "person" | "group" | "room" | "vehicle";
844
+ unitType: "service" | "other" | "group" | "person" | "room" | "vehicle";
833
845
  minQuantity: number | null;
834
846
  maxQuantity: number | null;
835
847
  minAge: number | null;
@@ -850,12 +862,12 @@ export declare const productsService: {
850
862
  description: string | null;
851
863
  code: string | null;
852
864
  optionId: string;
865
+ maxAge: number | null;
866
+ minAge: number | null;
853
867
  sortOrder: number;
854
- unitType: "service" | "other" | "person" | "group" | "room" | "vehicle";
868
+ unitType: "service" | "other" | "group" | "person" | "room" | "vehicle";
855
869
  minQuantity: number | null;
856
870
  maxQuantity: number | null;
857
- minAge: number | null;
858
- maxAge: number | null;
859
871
  occupancyMin: number | null;
860
872
  occupancyMax: number | null;
861
873
  isRequired: boolean;
@@ -867,7 +879,7 @@ export declare const productsService: {
867
879
  name: string;
868
880
  code: string | null;
869
881
  description: string | null;
870
- unitType: "service" | "other" | "person" | "group" | "room" | "vehicle";
882
+ unitType: "service" | "other" | "group" | "person" | "room" | "vehicle";
871
883
  minQuantity: number | null;
872
884
  maxQuantity: number | null;
873
885
  minAge: number | null;
@@ -1620,9 +1632,9 @@ export declare const productsService: {
1620
1632
  updatedAt: Date;
1621
1633
  description: string | null;
1622
1634
  title: string | null;
1635
+ location: string | null;
1623
1636
  itineraryId: string;
1624
1637
  dayNumber: number;
1625
- location: string | null;
1626
1638
  } | null | undefined>;
1627
1639
  createItineraryDay(db: PostgresJsDatabase, productId: string, itineraryId: string, data: CreateDayInput): Promise<{
1628
1640
  id: string;
@@ -1630,9 +1642,9 @@ export declare const productsService: {
1630
1642
  updatedAt: Date;
1631
1643
  description: string | null;
1632
1644
  title: string | null;
1645
+ location: string | null;
1633
1646
  itineraryId: string;
1634
1647
  dayNumber: number;
1635
- location: string | null;
1636
1648
  } | null | undefined>;
1637
1649
  updateDay(db: PostgresJsDatabase, dayId: string, data: UpdateDayInput): Promise<{
1638
1650
  id: string;
@@ -1704,7 +1716,7 @@ export declare const productsService: {
1704
1716
  tableName: "product_day_services";
1705
1717
  dataType: "string";
1706
1718
  columnType: "PgEnumColumn";
1707
- data: "other" | "transfer" | "accommodation" | "experience" | "guide" | "meal";
1719
+ data: "other" | "accommodation" | "transfer" | "experience" | "guide" | "meal";
1708
1720
  driverParam: string;
1709
1721
  notNull: true;
1710
1722
  hasDefault: false;
@@ -1750,6 +1762,23 @@ export declare const productsService: {
1750
1762
  identity: undefined;
1751
1763
  generated: undefined;
1752
1764
  }, {}, {}>;
1765
+ countryCode: import("drizzle-orm/pg-core").PgColumn<{
1766
+ name: "country_code";
1767
+ tableName: "product_day_services";
1768
+ dataType: "string";
1769
+ columnType: "PgText";
1770
+ data: string;
1771
+ driverParam: string;
1772
+ notNull: false;
1773
+ hasDefault: false;
1774
+ isPrimaryKey: false;
1775
+ isAutoincrement: false;
1776
+ hasRuntimeDefault: false;
1777
+ enumValues: [string, ...string[]];
1778
+ baseColumn: never;
1779
+ identity: undefined;
1780
+ generated: undefined;
1781
+ }, {}, {}>;
1753
1782
  costCurrency: import("drizzle-orm/pg-core").PgColumn<{
1754
1783
  name: "cost_currency";
1755
1784
  tableName: "product_day_services";
@@ -1856,9 +1885,10 @@ export declare const productsService: {
1856
1885
  id: string;
1857
1886
  dayId: string;
1858
1887
  supplierServiceId: string | null;
1859
- serviceType: "other" | "transfer" | "accommodation" | "experience" | "guide" | "meal";
1888
+ serviceType: "other" | "accommodation" | "transfer" | "experience" | "guide" | "meal";
1860
1889
  name: string;
1861
1890
  description: string | null;
1891
+ countryCode: string | null;
1862
1892
  costCurrency: string;
1863
1893
  costAmountCents: number;
1864
1894
  quantity: number;
@@ -1922,7 +1952,7 @@ export declare const productsService: {
1922
1952
  tableName: "product_day_services";
1923
1953
  dataType: "string";
1924
1954
  columnType: "PgEnumColumn";
1925
- data: "other" | "transfer" | "accommodation" | "experience" | "guide" | "meal";
1955
+ data: "other" | "accommodation" | "transfer" | "experience" | "guide" | "meal";
1926
1956
  driverParam: string;
1927
1957
  notNull: true;
1928
1958
  hasDefault: false;
@@ -1968,6 +1998,23 @@ export declare const productsService: {
1968
1998
  identity: undefined;
1969
1999
  generated: undefined;
1970
2000
  }, {}, {}>;
2001
+ countryCode: import("drizzle-orm/pg-core").PgColumn<{
2002
+ name: "country_code";
2003
+ tableName: "product_day_services";
2004
+ dataType: "string";
2005
+ columnType: "PgText";
2006
+ data: string;
2007
+ driverParam: string;
2008
+ notNull: false;
2009
+ hasDefault: false;
2010
+ isPrimaryKey: false;
2011
+ isAutoincrement: false;
2012
+ hasRuntimeDefault: false;
2013
+ enumValues: [string, ...string[]];
2014
+ baseColumn: never;
2015
+ identity: undefined;
2016
+ generated: undefined;
2017
+ }, {}, {}>;
1971
2018
  costCurrency: import("drizzle-orm/pg-core").PgColumn<{
1972
2019
  name: "cost_currency";
1973
2020
  tableName: "product_day_services";
@@ -2079,19 +2126,21 @@ export declare const productsService: {
2079
2126
  description: string | null;
2080
2127
  supplierServiceId: string | null;
2081
2128
  costAmountCents: number;
2129
+ quantity: number;
2082
2130
  sortOrder: number | null;
2083
2131
  dayId: string;
2084
- serviceType: "other" | "transfer" | "accommodation" | "experience" | "guide" | "meal";
2132
+ serviceType: "other" | "accommodation" | "transfer" | "experience" | "guide" | "meal";
2133
+ countryCode: string | null;
2085
2134
  costCurrency: string;
2086
- quantity: number;
2087
2135
  } | null | undefined>;
2088
2136
  updateDayService(db: PostgresJsDatabase, productId: string, serviceId: string, data: UpdateDayServiceInput): Promise<{
2089
2137
  id: string;
2090
2138
  dayId: string;
2091
2139
  supplierServiceId: string | null;
2092
- serviceType: "other" | "transfer" | "accommodation" | "experience" | "guide" | "meal";
2140
+ serviceType: "other" | "accommodation" | "transfer" | "experience" | "guide" | "meal";
2093
2141
  name: string;
2094
2142
  description: string | null;
2143
+ countryCode: string | null;
2095
2144
  costCurrency: string;
2096
2145
  costAmountCents: number;
2097
2146
  quantity: number;
@@ -2543,8 +2592,8 @@ export declare const productsService: {
2543
2592
  id: string;
2544
2593
  createdAt: Date;
2545
2594
  productId: string;
2546
- authorId: string;
2547
2595
  content: string;
2596
+ authorId: string;
2548
2597
  } | null | undefined>;
2549
2598
  recalculate(db: PostgresJsDatabase, productId: string): Promise<{
2550
2599
  costAmountCents: number;
@@ -2611,6 +2660,7 @@ export declare const productsService: {
2611
2660
  description: string | null;
2612
2661
  sortOrder: number;
2613
2662
  active: boolean;
2663
+ customerPaymentPolicy: unknown;
2614
2664
  metadata: Record<string, unknown> | null;
2615
2665
  createdAt: Date;
2616
2666
  updatedAt: Date;
@@ -2627,6 +2677,7 @@ export declare const productsService: {
2627
2677
  description: string | null;
2628
2678
  sortOrder: number;
2629
2679
  active: boolean;
2680
+ customerPaymentPolicy: unknown;
2630
2681
  metadata: Record<string, unknown> | null;
2631
2682
  createdAt: Date;
2632
2683
  updatedAt: Date;
@@ -2640,6 +2691,7 @@ export declare const productsService: {
2640
2691
  slug: string;
2641
2692
  description: string | null;
2642
2693
  active: boolean;
2694
+ customerPaymentPolicy: unknown;
2643
2695
  sortOrder: number;
2644
2696
  parentId: string | null;
2645
2697
  } | undefined>;
@@ -2651,6 +2703,7 @@ export declare const productsService: {
2651
2703
  description: string | null;
2652
2704
  sortOrder: number;
2653
2705
  active: boolean;
2706
+ customerPaymentPolicy: unknown;
2654
2707
  metadata: Record<string, unknown> | null;
2655
2708
  createdAt: Date;
2656
2709
  updatedAt: Date;
@@ -2708,6 +2761,7 @@ export declare const productsService: {
2708
2761
  description: string | null;
2709
2762
  sortOrder: number;
2710
2763
  active: boolean;
2764
+ customerPaymentPolicy: unknown;
2711
2765
  metadata: Record<string, unknown> | null;
2712
2766
  createdAt: Date;
2713
2767
  updatedAt: Date;