@voyantjs/extras 0.20.0 → 0.21.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.
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Extras content service — `getExtraContent` with locale-resolved
3
+ * cache reads, SWR refresh, and synthesizer fallback.
4
+ *
5
+ * Mirrors `service-content.ts` in the products / cruises / hospitality
6
+ * / charters packages but extras-shaped. The extras content aggregate
7
+ * (§3.2 / §3.6) is `{ extra, options[], media[], policies[] }` — one
8
+ * payload returned by a single getContent. Pricing stays out (volatile-
9
+ * live, flows through `liveResolve`).
10
+ *
11
+ * Extras don't appear in the search index (per the vertical's catalog-
12
+ * policy.ts), but sourced extras still need rich content for the
13
+ * booking-flow's add-on selection UI. The cache layer covers exactly
14
+ * that surface.
15
+ */
16
+ import { createInvalidateOnDrift, fetchOverlaysForEntity, isStale, pickBestCachedLocale, readSourcedEntry, withContentRefreshLock, } from "@voyantjs/catalog";
17
+ import { and, eq } from "drizzle-orm";
18
+ import { EXTRAS_CONTENT_SCHEMA_VERSION, extraContentSchema, mergeOverlaysIntoExtraContent, validateExtraContent, } from "./content-shape.js";
19
+ import { EXTRAS_CONTENT_MARKET_ANY, extrasSourcedContentTable, } from "./schema-sourced-content.js";
20
+ import { synthesizeExtraContent, } from "./service-content-synthesizer.js";
21
+ /** Extras cache TTL is 24h — same as products. */
22
+ const EXTRAS_DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
23
+ export async function getExtraContent(db, entityId, scope, options) {
24
+ const sourcedEntry = await readSourcedEntry(db, "extras", entityId);
25
+ if (!sourcedEntry)
26
+ return null;
27
+ const provenance = {
28
+ kind: "sourced",
29
+ provenance: {
30
+ source_kind: sourcedEntry.source_kind,
31
+ source_provider: sourcedEntry.source_provider ?? undefined,
32
+ source_connection_id: sourcedEntry.source_connection_id ?? undefined,
33
+ source_ref: sourcedEntry.source_ref ?? undefined,
34
+ source_freshness: sourcedEntry.source_freshness,
35
+ last_sourced_at: sourcedEntry.last_sourced_at ?? undefined,
36
+ },
37
+ entry_id: sourcedEntry.id,
38
+ status: sourcedEntry.status,
39
+ projection: sourcedEntry.projection,
40
+ projection_etag: sourcedEntry.projection_etag,
41
+ projection_seen_at: sourcedEntry.projection_seen_at,
42
+ first_seen_at: sourcedEntry.first_seen_at,
43
+ last_seen_at: sourcedEntry.last_seen_at,
44
+ };
45
+ const adapter = sourcedEntry.source_connection_id
46
+ ? (options.registry.resolveByConnection(sourcedEntry.source_connection_id) ??
47
+ options.registry.byKind(sourcedEntry.source_kind)[0]?.adapter)
48
+ : options.registry.byKind(sourcedEntry.source_kind)[0]?.adapter;
49
+ const adapterCtx = options.buildAdapterContext?.(adapter) ?? {
50
+ connection_id: sourcedEntry.source_connection_id ?? sourcedEntry.source_kind,
51
+ };
52
+ const market = scope.market ?? EXTRAS_CONTENT_MARKET_ANY;
53
+ const acceptMT = scope.acceptMachineTranslated ?? true;
54
+ const cachedRows = await fetchCacheCandidates(db, entityId, market);
55
+ const eligibleRows = acceptMT ? cachedRows : cachedRows.filter((r) => !r.machine_translated);
56
+ const best = pickBestCachedLocale(eligibleRows.map((row) => ({ ...row, locale: row.locale })), scope.preferredLocales);
57
+ if (best && !isStale(best.candidate)) {
58
+ return finalizeFromCache(db, entityId, best, false, options);
59
+ }
60
+ if (best && isStale(best.candidate)) {
61
+ if (adapter?.getContent) {
62
+ void scheduleRefresh(db, adapter, adapterCtx, {
63
+ entity_module: "extras",
64
+ entity_id: entityId,
65
+ locale: scope.preferredLocales[0] ?? best.candidate.locale,
66
+ market,
67
+ currency: scope.currency,
68
+ });
69
+ }
70
+ return finalizeFromCache(db, entityId, best, true, options);
71
+ }
72
+ if (!adapter?.getContent) {
73
+ const overlays = await fetchOverlaysForEntity(db, "extras", entityId);
74
+ const synthesized = synthesizeExtraContent({ locale: scope.preferredLocales[0] ?? "en-GB" }, {
75
+ provenance,
76
+ overlays: overlays.map((o) => ({ field_path: o.field_path, value: o.value })),
77
+ });
78
+ return wrapSynthesized(synthesized, scope, false);
79
+ }
80
+ const fresh = await fetchFreshContent(db, adapter, adapterCtx, {
81
+ entity_module: "extras",
82
+ entity_id: entityId,
83
+ locale: scope.preferredLocales[0] ?? "en-GB",
84
+ market,
85
+ currency: scope.currency,
86
+ });
87
+ if (!fresh) {
88
+ const overlays = await fetchOverlaysForEntity(db, "extras", entityId);
89
+ const synthesized = synthesizeExtraContent({ locale: scope.preferredLocales[0] ?? "en-GB" }, {
90
+ provenance,
91
+ overlays: overlays.map((o) => ({ field_path: o.field_path, value: o.value })),
92
+ });
93
+ return wrapSynthesized(synthesized, scope, false);
94
+ }
95
+ return finalizeFresh(db, entityId, fresh, scope, options);
96
+ }
97
+ async function fetchCacheCandidates(db, entityId, market) {
98
+ return db
99
+ .select()
100
+ .from(extrasSourcedContentTable)
101
+ .where(and(eq(extrasSourcedContentTable.entity_id, entityId), eq(extrasSourcedContentTable.market, market), eq(extrasSourcedContentTable.content_schema_version, EXTRAS_CONTENT_SCHEMA_VERSION)));
102
+ }
103
+ async function fetchFreshContent(db, adapter, ctx, request) {
104
+ const result = await withContentRefreshLock(db, {
105
+ entityModule: request.entity_module,
106
+ entityId: request.entity_id,
107
+ locale: request.locale,
108
+ market: request.market,
109
+ }, async () => {
110
+ const got = await adapter.getContent(ctx, request);
111
+ const validation = validateExtraContent(got.content);
112
+ if (!validation.valid) {
113
+ throw new Error(`extras getContent for ${request.entity_id} failed validation: ${validation.reason}`);
114
+ }
115
+ await writeCacheRow(db, request, got);
116
+ return got;
117
+ });
118
+ return result ?? null;
119
+ }
120
+ function scheduleRefresh(db, adapter, ctx, request) {
121
+ void withContentRefreshLock(db, {
122
+ entityModule: request.entity_module,
123
+ entityId: request.entity_id,
124
+ locale: request.locale,
125
+ market: request.market,
126
+ }, async () => {
127
+ const got = await adapter.getContent(ctx, request);
128
+ const validation = validateExtraContent(got.content);
129
+ if (!validation.valid)
130
+ return;
131
+ await writeCacheRow(db, request, got);
132
+ }).catch(() => {
133
+ // intentional swallow — see §3.4 SWR refresh contract
134
+ });
135
+ }
136
+ async function writeCacheRow(db, request, result) {
137
+ const market = request.market ?? EXTRAS_CONTENT_MARKET_ANY;
138
+ const now = new Date();
139
+ // Coerce JSON-string dates to Date — see products writeCacheRow.
140
+ const sourceUpdatedAt = toDateOrNull(result.source_updated_at);
141
+ const freshUntil = toDateOrNull(result.fresh_until) ?? new Date(now.getTime() + EXTRAS_DEFAULT_TTL_MS);
142
+ await db
143
+ .insert(extrasSourcedContentTable)
144
+ .values({
145
+ entity_id: request.entity_id,
146
+ locale: request.locale,
147
+ market,
148
+ payload: result.content,
149
+ content_schema_version: result.content_schema_version,
150
+ returned_locale: result.returned_locale,
151
+ machine_translated: result.machine_translated ?? false,
152
+ source_updated_at: sourceUpdatedAt,
153
+ fetched_at: now,
154
+ fresh_until: freshUntil,
155
+ etag: result.etag ?? null,
156
+ fetch_status: "ok",
157
+ fetch_error: null,
158
+ })
159
+ .onConflictDoUpdate({
160
+ target: [
161
+ extrasSourcedContentTable.entity_id,
162
+ extrasSourcedContentTable.locale,
163
+ extrasSourcedContentTable.market,
164
+ ],
165
+ set: {
166
+ payload: result.content,
167
+ content_schema_version: result.content_schema_version,
168
+ returned_locale: result.returned_locale,
169
+ machine_translated: result.machine_translated ?? false,
170
+ source_updated_at: sourceUpdatedAt,
171
+ fetched_at: now,
172
+ fresh_until: freshUntil,
173
+ etag: result.etag ?? null,
174
+ fetch_status: "ok",
175
+ fetch_error: null,
176
+ },
177
+ });
178
+ }
179
+ async function finalizeFromCache(db, entityId, best, servedStale, options) {
180
+ const validation = validateExtraContent(best.candidate.payload);
181
+ if (!validation.valid) {
182
+ throw new Error(`extras cache row for ${entityId} (${best.candidate.locale}) failed validation: ${validation.reason}`);
183
+ }
184
+ const overlays = await fetchOverlaysForEntity(db, "extras", entityId);
185
+ const merged = mergeOverlaysIntoExtraContent(validation.content, overlays.map((o) => ({ field_path: o.field_path, value: o.value })), {
186
+ onOverlayError: options.onOverlayError
187
+ ? (e) => options.onOverlayError({
188
+ field_path: e.overlay.field_path,
189
+ reason: e.reason,
190
+ })
191
+ : undefined,
192
+ });
193
+ return {
194
+ content: merged,
195
+ resolution: {
196
+ candidate: { locale: best.candidate.locale, payload: merged },
197
+ served_locale: best.candidate.returned_locale,
198
+ match_kind: best.match_kind,
199
+ },
200
+ source: "sourced-cache",
201
+ served_stale: servedStale,
202
+ synthesized: false,
203
+ machine_translated: best.candidate.machine_translated,
204
+ };
205
+ }
206
+ async function finalizeFresh(db, entityId, fresh, scope, options) {
207
+ const cachedContent = extraContentSchema.parse(fresh.content);
208
+ const overlays = await fetchOverlaysForEntity(db, "extras", entityId);
209
+ const merged = mergeOverlaysIntoExtraContent(cachedContent, overlays.map((o) => ({ field_path: o.field_path, value: o.value })), {
210
+ onOverlayError: options.onOverlayError
211
+ ? (e) => options.onOverlayError({
212
+ field_path: e.overlay.field_path,
213
+ reason: e.reason,
214
+ })
215
+ : undefined,
216
+ });
217
+ return {
218
+ content: merged,
219
+ resolution: {
220
+ candidate: { locale: scope.preferredLocales[0] ?? fresh.returned_locale, payload: merged },
221
+ served_locale: fresh.returned_locale,
222
+ match_kind: scope.preferredLocales[0] === fresh.returned_locale ? "exact" : "language_match",
223
+ },
224
+ source: "sourced-fresh",
225
+ served_stale: false,
226
+ synthesized: false,
227
+ machine_translated: fresh.machine_translated ?? false,
228
+ };
229
+ }
230
+ function wrapSynthesized(synthesized, scope, servedStale) {
231
+ return {
232
+ content: synthesized.content,
233
+ resolution: {
234
+ candidate: { locale: synthesized.served_locale, payload: synthesized.content },
235
+ served_locale: synthesized.served_locale,
236
+ match_kind: scope.preferredLocales[0] === synthesized.served_locale ? "exact" : "any",
237
+ },
238
+ source: "synthesized",
239
+ served_stale: servedStale,
240
+ synthesized: true,
241
+ machine_translated: false,
242
+ };
243
+ }
244
+ /** Drift event consumer for the extras content cache. Per §3.4.1. */
245
+ export const invalidateExtraContentOnDrift = createInvalidateOnDrift(extrasSourcedContentTable, { entityModule: "extras" });
246
+ function toDateOrNull(value) {
247
+ if (!value)
248
+ return null;
249
+ if (value instanceof Date)
250
+ return value;
251
+ const parsed = new Date(value);
252
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
253
+ }
package/dist/service.d.ts CHANGED
@@ -15,10 +15,11 @@ export declare const extrasService: {
15
15
  data: {
16
16
  id: string;
17
17
  productId: string;
18
+ supplierId: string | null;
18
19
  code: string | null;
19
20
  name: string;
20
21
  description: string | null;
21
- selectionType: "optional" | "required" | "default_selected" | "unavailable";
22
+ selectionType: "unavailable" | "optional" | "default_selected" | "required";
22
23
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
23
24
  pricedPerPerson: boolean;
24
25
  minQuantity: number | null;
@@ -37,10 +38,11 @@ export declare const extrasService: {
37
38
  getProductExtraById(db: PostgresJsDatabase, id: string): Promise<{
38
39
  id: string;
39
40
  productId: string;
41
+ supplierId: string | null;
40
42
  code: string | null;
41
43
  name: string;
42
44
  description: string | null;
43
- selectionType: "optional" | "required" | "default_selected" | "unavailable";
45
+ selectionType: "unavailable" | "optional" | "default_selected" | "required";
44
46
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
45
47
  pricedPerPerson: boolean;
46
48
  minQuantity: number | null;
@@ -58,9 +60,10 @@ export declare const extrasService: {
58
60
  createdAt: Date;
59
61
  updatedAt: Date;
60
62
  productId: string;
63
+ supplierId: string | null;
61
64
  name: string;
62
65
  description: string | null;
63
- selectionType: "optional" | "required" | "default_selected" | "unavailable";
66
+ selectionType: "unavailable" | "optional" | "default_selected" | "required";
64
67
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
65
68
  pricedPerPerson: boolean;
66
69
  minQuantity: number | null;
@@ -73,10 +76,11 @@ export declare const extrasService: {
73
76
  updateProductExtra(db: PostgresJsDatabase, id: string, data: UpdateProductExtraInput): Promise<{
74
77
  id: string;
75
78
  productId: string;
79
+ supplierId: string | null;
76
80
  code: string | null;
77
81
  name: string;
78
82
  description: string | null;
79
- selectionType: "optional" | "required" | "default_selected" | "unavailable";
83
+ selectionType: "unavailable" | "optional" | "default_selected" | "required";
80
84
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
81
85
  pricedPerPerson: boolean;
82
86
  minQuantity: number | null;
@@ -96,7 +100,7 @@ export declare const extrasService: {
96
100
  id: string;
97
101
  optionId: string;
98
102
  productExtraId: string;
99
- selectionType: "optional" | "required" | "default_selected" | "unavailable" | null;
103
+ selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
100
104
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
101
105
  pricedPerPerson: boolean | null;
102
106
  minQuantity: number | null;
@@ -118,7 +122,7 @@ export declare const extrasService: {
118
122
  id: string;
119
123
  optionId: string;
120
124
  productExtraId: string;
121
- selectionType: "optional" | "required" | "default_selected" | "unavailable" | null;
125
+ selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
122
126
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
123
127
  pricedPerPerson: boolean | null;
124
128
  minQuantity: number | null;
@@ -136,7 +140,7 @@ export declare const extrasService: {
136
140
  id: string;
137
141
  createdAt: Date;
138
142
  updatedAt: Date;
139
- selectionType: "optional" | "required" | "default_selected" | "unavailable" | null;
143
+ selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
140
144
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
141
145
  pricedPerPerson: boolean | null;
142
146
  minQuantity: number | null;
@@ -154,7 +158,7 @@ export declare const extrasService: {
154
158
  id: string;
155
159
  optionId: string;
156
160
  productExtraId: string;
157
- selectionType: "optional" | "required" | "default_selected" | "unavailable" | null;
161
+ selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
158
162
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
159
163
  pricedPerPerson: boolean | null;
160
164
  minQuantity: number | null;
@@ -179,7 +183,7 @@ export declare const extrasService: {
179
183
  optionExtraConfigId: string | null;
180
184
  name: string;
181
185
  description: string | null;
182
- status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled";
186
+ status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
183
187
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
184
188
  pricedPerPerson: boolean;
185
189
  quantity: number;
@@ -205,7 +209,7 @@ export declare const extrasService: {
205
209
  optionExtraConfigId: string | null;
206
210
  name: string;
207
211
  description: string | null;
208
- status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled";
212
+ status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
209
213
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
210
214
  pricedPerPerson: boolean;
211
215
  quantity: number;
@@ -228,13 +232,13 @@ export declare const extrasService: {
228
232
  description: string | null;
229
233
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
230
234
  pricedPerPerson: boolean;
235
+ status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
231
236
  metadata: Record<string, unknown> | null;
232
- status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled";
233
- notes: string | null;
234
237
  bookingId: string;
238
+ quantity: number;
239
+ notes: string | null;
235
240
  productExtraId: string | null;
236
241
  optionExtraConfigId: string | null;
237
- quantity: number;
238
242
  sellCurrency: string;
239
243
  unitSellAmountCents: number | null;
240
244
  totalSellAmountCents: number | null;
@@ -249,7 +253,7 @@ export declare const extrasService: {
249
253
  optionExtraConfigId: string | null;
250
254
  name: string;
251
255
  description: string | null;
252
- status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled";
256
+ status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
253
257
  pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
254
258
  pricedPerPerson: boolean;
255
259
  quantity: number;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,KAAK,EACV,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACxB,gCAAgC,EAChC,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,iBAAiB,CAAA;AAExB,KAAK,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AACxE,KAAK,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAClF,KAAK,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AACxE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACjF,KAAK,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACjF,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAYvE,eAAO,MAAM,aAAa;0BACI,kBAAkB,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;4BAuB9C,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;2BAK/B,kBAAkB,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;2BAKjD,kBAAkB,MAAM,MAAM,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;2BAS7D,kBAAkB,MAAM,MAAM;;;+BAQ1B,kBAAkB,SAAS,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;iCAqBnD,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;gCAS/B,kBAAkB,QAAQ,4BAA4B;;;;;;;;;;;;;;;;;;gCAMlF,kBAAkB,MAClB,MAAM,QACJ,4BAA4B;;;;;;;;;;;;;;;;;;gCAUF,kBAAkB,MAAM,MAAM;;;0BAQpC,kBAAkB,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuB9C,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;2BAK/B,kBAAkB,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;;;;;2BAKjD,kBAAkB,MAAM,MAAM,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;;;;;2BAS7D,kBAAkB,MAAM,MAAM;;;CAO5D,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,KAAK,EACV,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACxB,gCAAgC,EAChC,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,iBAAiB,CAAA;AAExB,KAAK,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AACxE,KAAK,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAClF,KAAK,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AACxE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACjF,KAAK,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACjF,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAYvE,eAAO,MAAM,aAAa;0BACI,kBAAkB,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;4BAwB9C,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;2BAK/B,kBAAkB,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;;2BAKjD,kBAAkB,MAAM,MAAM,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;;2BAS7D,kBAAkB,MAAM,MAAM;;;+BAQ1B,kBAAkB,SAAS,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;iCAqBnD,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;gCAS/B,kBAAkB,QAAQ,4BAA4B;;;;;;;;;;;;;;;;;;gCAMlF,kBAAkB,MAClB,MAAM,QACJ,4BAA4B;;;;;;;;;;;;;;;;;;gCAUF,kBAAkB,MAAM,MAAM;;;0BAQpC,kBAAkB,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuB9C,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;2BAK/B,kBAAkB,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;;;;;2BAKjD,kBAAkB,MAAM,MAAM,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;;;;;2BAS7D,kBAAkB,MAAM,MAAM;;;CAO5D,CAAA"}
package/dist/service.js CHANGED
@@ -9,6 +9,8 @@ export const extrasService = {
9
9
  const conditions = [];
10
10
  if (query.productId)
11
11
  conditions.push(eq(productExtras.productId, query.productId));
12
+ if (query.supplierId)
13
+ conditions.push(eq(productExtras.supplierId, query.supplierId));
12
14
  if (query.active !== undefined)
13
15
  conditions.push(eq(productExtras.active, query.active));
14
16
  if (query.search) {
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
2
  export declare const extraSelectionTypeSchema: z.ZodEnum<{
3
+ unavailable: "unavailable";
3
4
  optional: "optional";
4
- required: "required";
5
5
  default_selected: "default_selected";
6
- unavailable: "unavailable";
6
+ required: "required";
7
7
  }>;
8
8
  export declare const extraPricingModeSchema: z.ZodEnum<{
9
9
  included: "included";
@@ -14,22 +14,23 @@ export declare const extraPricingModeSchema: z.ZodEnum<{
14
14
  free: "free";
15
15
  }>;
16
16
  export declare const bookingExtraStatusSchema: z.ZodEnum<{
17
- draft: "draft";
18
- selected: "selected";
19
17
  confirmed: "confirmed";
20
18
  cancelled: "cancelled";
19
+ draft: "draft";
20
+ selected: "selected";
21
21
  fulfilled: "fulfilled";
22
22
  }>;
23
23
  export declare const productExtraCoreSchema: z.ZodObject<{
24
24
  productId: z.ZodString;
25
+ supplierId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
25
26
  code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
26
27
  name: z.ZodString;
27
28
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
29
  selectionType: z.ZodDefault<z.ZodEnum<{
30
+ unavailable: "unavailable";
29
31
  optional: "optional";
30
- required: "required";
31
32
  default_selected: "default_selected";
32
- unavailable: "unavailable";
33
+ required: "required";
33
34
  }>>;
34
35
  pricingMode: z.ZodDefault<z.ZodEnum<{
35
36
  included: "included";
@@ -49,14 +50,15 @@ export declare const productExtraCoreSchema: z.ZodObject<{
49
50
  }, z.core.$strip>;
50
51
  export declare const insertProductExtraSchema: z.ZodObject<{
51
52
  productId: z.ZodString;
53
+ supplierId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
52
54
  code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
53
55
  name: z.ZodString;
54
56
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
55
57
  selectionType: z.ZodDefault<z.ZodEnum<{
58
+ unavailable: "unavailable";
56
59
  optional: "optional";
57
- required: "required";
58
60
  default_selected: "default_selected";
59
- unavailable: "unavailable";
61
+ required: "required";
60
62
  }>>;
61
63
  pricingMode: z.ZodDefault<z.ZodEnum<{
62
64
  included: "included";
@@ -76,14 +78,15 @@ export declare const insertProductExtraSchema: z.ZodObject<{
76
78
  }, z.core.$strip>;
77
79
  export declare const updateProductExtraSchema: z.ZodObject<{
78
80
  productId: z.ZodOptional<z.ZodString>;
81
+ supplierId: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
79
82
  code: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
80
83
  name: z.ZodOptional<z.ZodString>;
81
84
  description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
82
85
  selectionType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
86
+ unavailable: "unavailable";
83
87
  optional: "optional";
84
- required: "required";
85
88
  default_selected: "default_selected";
86
- unavailable: "unavailable";
89
+ required: "required";
87
90
  }>>>;
88
91
  pricingMode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
89
92
  included: "included";
@@ -105,6 +108,7 @@ export declare const productExtraListQuerySchema: z.ZodObject<{
105
108
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
106
109
  offset: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
107
110
  productId: z.ZodOptional<z.ZodString>;
111
+ supplierId: z.ZodOptional<z.ZodString>;
108
112
  active: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
109
113
  0: "0";
110
114
  1: "1";
@@ -117,10 +121,10 @@ export declare const optionExtraConfigCoreSchema: z.ZodObject<{
117
121
  optionId: z.ZodString;
118
122
  productExtraId: z.ZodString;
119
123
  selectionType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
124
+ unavailable: "unavailable";
120
125
  optional: "optional";
121
- required: "required";
122
126
  default_selected: "default_selected";
123
- unavailable: "unavailable";
127
+ required: "required";
124
128
  }>>>;
125
129
  pricingMode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
126
130
  included: "included";
@@ -144,10 +148,10 @@ export declare const insertOptionExtraConfigSchema: z.ZodObject<{
144
148
  optionId: z.ZodString;
145
149
  productExtraId: z.ZodString;
146
150
  selectionType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
151
+ unavailable: "unavailable";
147
152
  optional: "optional";
148
- required: "required";
149
153
  default_selected: "default_selected";
150
- unavailable: "unavailable";
154
+ required: "required";
151
155
  }>>>;
152
156
  pricingMode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
153
157
  included: "included";
@@ -171,10 +175,10 @@ export declare const updateOptionExtraConfigSchema: z.ZodObject<{
171
175
  optionId: z.ZodOptional<z.ZodString>;
172
176
  productExtraId: z.ZodOptional<z.ZodString>;
173
177
  selectionType: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
178
+ unavailable: "unavailable";
174
179
  optional: "optional";
175
- required: "required";
176
180
  default_selected: "default_selected";
177
- unavailable: "unavailable";
181
+ required: "required";
178
182
  }>>>>;
179
183
  pricingMode: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
180
184
  included: "included";
@@ -213,10 +217,10 @@ export declare const bookingExtraCoreSchema: z.ZodObject<{
213
217
  name: z.ZodString;
214
218
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
215
219
  status: z.ZodDefault<z.ZodEnum<{
216
- draft: "draft";
217
- selected: "selected";
218
220
  confirmed: "confirmed";
219
221
  cancelled: "cancelled";
222
+ draft: "draft";
223
+ selected: "selected";
220
224
  fulfilled: "fulfilled";
221
225
  }>>;
222
226
  pricingMode: z.ZodDefault<z.ZodEnum<{
@@ -245,10 +249,10 @@ export declare const insertBookingExtraSchema: z.ZodObject<{
245
249
  name: z.ZodString;
246
250
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
247
251
  status: z.ZodDefault<z.ZodEnum<{
248
- draft: "draft";
249
- selected: "selected";
250
252
  confirmed: "confirmed";
251
253
  cancelled: "cancelled";
254
+ draft: "draft";
255
+ selected: "selected";
252
256
  fulfilled: "fulfilled";
253
257
  }>>;
254
258
  pricingMode: z.ZodDefault<z.ZodEnum<{
@@ -277,10 +281,10 @@ export declare const updateBookingExtraSchema: z.ZodObject<{
277
281
  name: z.ZodOptional<z.ZodString>;
278
282
  description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
279
283
  status: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
280
- draft: "draft";
281
- selected: "selected";
282
284
  confirmed: "confirmed";
283
285
  cancelled: "cancelled";
286
+ draft: "draft";
287
+ selected: "selected";
284
288
  fulfilled: "fulfilled";
285
289
  }>>>;
286
290
  pricingMode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
@@ -309,10 +313,10 @@ export declare const bookingExtraListQuerySchema: z.ZodObject<{
309
313
  productExtraId: z.ZodOptional<z.ZodString>;
310
314
  optionExtraConfigId: z.ZodOptional<z.ZodString>;
311
315
  status: z.ZodOptional<z.ZodEnum<{
312
- draft: "draft";
313
- selected: "selected";
314
316
  confirmed: "confirmed";
315
317
  cancelled: "cancelled";
318
+ draft: "draft";
319
+ selected: "selected";
316
320
  fulfilled: "fulfilled";
317
321
  }>>;
318
322
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,eAAO,MAAM,wBAAwB;;;;;EAKnC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;EAOjC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;EAMnC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;iBAcjC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyB,CAAA;AAC9D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAA;AACxE,eAAO,MAAM,2BAA2B;;;;;;;;;;;iBAItC,CAAA;AAEF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;iBActC,CAAA;AAEF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA8B,CAAA;AACxE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAwC,CAAA;AAClF,eAAO,MAAM,gCAAgC;;;;;;;;;;;iBAI3C,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBjC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyB,CAAA;AAC9D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAA;AACxE,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;iBAKtC,CAAA"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,eAAO,MAAM,wBAAwB;;;;;EAKnC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;EAOjC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;EAMnC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAejC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyB,CAAA;AAC9D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAA;AACxE,eAAO,MAAM,2BAA2B;;;;;;;;;;;;iBAKtC,CAAA;AAEF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;iBActC,CAAA;AAEF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA8B,CAAA;AACxE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAwC,CAAA;AAClF,eAAO,MAAM,gCAAgC;;;;;;;;;;;iBAI3C,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBjC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyB,CAAA;AAC9D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAA;AACxE,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;iBAKtC,CAAA"}
@@ -28,6 +28,7 @@ export const bookingExtraStatusSchema = z.enum([
28
28
  ]);
29
29
  export const productExtraCoreSchema = z.object({
30
30
  productId: z.string(),
31
+ supplierId: z.string().nullable().optional(),
31
32
  code: z.string().max(100).nullable().optional(),
32
33
  name: z.string().min(1).max(255),
33
34
  description: z.string().nullable().optional(),
@@ -45,6 +46,7 @@ export const insertProductExtraSchema = productExtraCoreSchema;
45
46
  export const updateProductExtraSchema = productExtraCoreSchema.partial();
46
47
  export const productExtraListQuerySchema = paginationSchema.extend({
47
48
  productId: z.string().optional(),
49
+ supplierId: z.string().optional(),
48
50
  active: booleanQueryParam.optional(),
49
51
  search: z.string().optional(),
50
52
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/extras",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -29,15 +29,15 @@
29
29
  "drizzle-orm": "^0.45.2",
30
30
  "hono": "^4.12.10",
31
31
  "zod": "^4.3.6",
32
- "@voyantjs/core": "0.20.0",
33
- "@voyantjs/db": "0.20.0",
34
- "@voyantjs/hono": "0.20.0",
35
- "@voyantjs/catalog": "0.20.0"
32
+ "@voyantjs/core": "0.21.0",
33
+ "@voyantjs/db": "0.21.0",
34
+ "@voyantjs/hono": "0.21.0",
35
+ "@voyantjs/catalog": "0.21.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "typescript": "^6.0.2",
39
- "@voyantjs/bookings": "0.20.0",
40
- "@voyantjs/products": "0.20.0",
39
+ "@voyantjs/bookings": "0.21.0",
40
+ "@voyantjs/products": "0.21.0",
41
41
  "@voyantjs/voyant-typescript-config": "0.1.0"
42
42
  },
43
43
  "files": [