@voyantjs/cruises-contracts 0.90.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/README.md +31 -0
- package/dist/cabin-features.d.ts +16 -0
- package/dist/cabin-features.d.ts.map +1 -0
- package/dist/cabin-features.js +44 -0
- package/dist/content-shape.d.ts +252 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +115 -0
- package/dist/content-shape.test.d.ts +2 -0
- package/dist/content-shape.test.d.ts.map +1 -0
- package/dist/content-shape.test.js +78 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @voyantjs/cruises-contracts
|
|
2
|
+
|
|
3
|
+
Pure cruise content contracts for adapter implementers and external consumers
|
|
4
|
+
that need to validate `cruises/v1` rich content payloads without installing the
|
|
5
|
+
full cruises runtime package.
|
|
6
|
+
|
|
7
|
+
Use this package for `CRUISES_CONTENT_SCHEMA_VERSION`, `cruiseContentSchema`,
|
|
8
|
+
`CruiseContent`, nested content types, `validateCruiseContent`, and the cabin
|
|
9
|
+
facet vocabularies (`CABIN_BED_CONFIGURATIONS`, `CABIN_ACCESSIBILITY_FEATURES`,
|
|
10
|
+
`CABIN_VIEW_TYPES`, exported from `@voyantjs/cruises-contracts/cabin-features`).
|
|
11
|
+
Use `@voyantjs/cruises` when you also need Drizzle schema, routes, services,
|
|
12
|
+
booking integration, adapter registry helpers, or runtime content resolution.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @voyantjs/cruises-contracts zod
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import {
|
|
24
|
+
CRUISES_CONTENT_SCHEMA_VERSION,
|
|
25
|
+
cruiseContentSchema,
|
|
26
|
+
type CruiseContent,
|
|
27
|
+
} from "@voyantjs/cruises-contracts"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Existing `@voyantjs/cruises/content-shape` imports remain available for
|
|
31
|
+
applications that already depend on the full runtime package.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cabin facet vocabularies for the cruises content contract.
|
|
3
|
+
*
|
|
4
|
+
* Pure, dependency-free enumerations shared between the rich content
|
|
5
|
+
* schema (`cabin_categories[].{bed_configurations,accessibility_features,
|
|
6
|
+
* view_type}`) and the runtime cruises catalog facets. They live in the
|
|
7
|
+
* contracts package so external adapter authors can emit and validate
|
|
8
|
+
* cabin facets without pulling in the cruises runtime.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CABIN_BED_CONFIGURATIONS: readonly ["single", "twin", "double", "queen", "king", "convertible_twins", "sofa_bed", "pullman", "bunk", "murphy"];
|
|
11
|
+
export type CabinBedConfiguration = (typeof CABIN_BED_CONFIGURATIONS)[number];
|
|
12
|
+
export declare const CABIN_ACCESSIBILITY_FEATURES: readonly ["wheelchair_accessible", "step_free_access", "roll_in_shower", "grab_bars", "visual_alarm", "hearing_loop", "accessible_balcony", "accessible_bathroom"];
|
|
13
|
+
export type CabinAccessibilityFeature = (typeof CABIN_ACCESSIBILITY_FEATURES)[number];
|
|
14
|
+
export declare const CABIN_VIEW_TYPES: readonly ["none", "interior", "virtual", "porthole", "window", "oceanview", "river_view", "balcony", "french_balcony", "promenade", "obstructed"];
|
|
15
|
+
export type CabinViewType = (typeof CABIN_VIEW_TYPES)[number];
|
|
16
|
+
//# sourceMappingURL=cabin-features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cabin-features.d.ts","sourceRoot":"","sources":["../src/cabin-features.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,wBAAwB,sHAW3B,CAAA;AAEV,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7E,eAAO,MAAM,4BAA4B,oKAS/B,CAAA;AAEV,MAAM,MAAM,yBAAyB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAA;AAErF,eAAO,MAAM,gBAAgB,mJAYnB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cabin facet vocabularies for the cruises content contract.
|
|
3
|
+
*
|
|
4
|
+
* Pure, dependency-free enumerations shared between the rich content
|
|
5
|
+
* schema (`cabin_categories[].{bed_configurations,accessibility_features,
|
|
6
|
+
* view_type}`) and the runtime cruises catalog facets. They live in the
|
|
7
|
+
* contracts package so external adapter authors can emit and validate
|
|
8
|
+
* cabin facets without pulling in the cruises runtime.
|
|
9
|
+
*/
|
|
10
|
+
export const CABIN_BED_CONFIGURATIONS = [
|
|
11
|
+
"single",
|
|
12
|
+
"twin",
|
|
13
|
+
"double",
|
|
14
|
+
"queen",
|
|
15
|
+
"king",
|
|
16
|
+
"convertible_twins",
|
|
17
|
+
"sofa_bed",
|
|
18
|
+
"pullman",
|
|
19
|
+
"bunk",
|
|
20
|
+
"murphy",
|
|
21
|
+
];
|
|
22
|
+
export const CABIN_ACCESSIBILITY_FEATURES = [
|
|
23
|
+
"wheelchair_accessible",
|
|
24
|
+
"step_free_access",
|
|
25
|
+
"roll_in_shower",
|
|
26
|
+
"grab_bars",
|
|
27
|
+
"visual_alarm",
|
|
28
|
+
"hearing_loop",
|
|
29
|
+
"accessible_balcony",
|
|
30
|
+
"accessible_bathroom",
|
|
31
|
+
];
|
|
32
|
+
export const CABIN_VIEW_TYPES = [
|
|
33
|
+
"none",
|
|
34
|
+
"interior",
|
|
35
|
+
"virtual",
|
|
36
|
+
"porthole",
|
|
37
|
+
"window",
|
|
38
|
+
"oceanview",
|
|
39
|
+
"river_view",
|
|
40
|
+
"balcony",
|
|
41
|
+
"french_balcony",
|
|
42
|
+
"promenade",
|
|
43
|
+
"obstructed",
|
|
44
|
+
];
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cruises content shape — the rich detail-page content shape returned
|
|
3
|
+
* by `getContent` for sourced cruises.
|
|
4
|
+
*
|
|
5
|
+
* The doc's "cruise content aggregate" (§E in the migration plan +
|
|
6
|
+
* §3.2): `{ cruise, ship, sailings[], cabinCategories[], itineraryStops[],
|
|
7
|
+
* policies[] }` is one content payload returned by a single getContent.
|
|
8
|
+
* The cruise adapter's existing internal multi-call composition
|
|
9
|
+
* (`fetchCruise / fetchSailing / fetchShip / fetchItinerary`) flattens
|
|
10
|
+
* to one `GetContentResult.content` blob; the public adapter contract
|
|
11
|
+
* gets one method, not five.
|
|
12
|
+
*
|
|
13
|
+
* Full pricing stays out — it's volatile and continues to flow through
|
|
14
|
+
* `liveResolve`. The content blob carries structural cabin categories and
|
|
15
|
+
* itinerary stops, plus optional per-sailing browse price summaries as
|
|
16
|
+
* integer minor units paired with currency.
|
|
17
|
+
*
|
|
18
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §E.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
export declare const CRUISES_CONTENT_SCHEMA_VERSION = "cruises/v1";
|
|
22
|
+
export declare const cruiseSummarySchema: z.ZodObject<{
|
|
23
|
+
id: z.ZodString;
|
|
24
|
+
name: z.ZodString;
|
|
25
|
+
status: z.ZodOptional<z.ZodString>;
|
|
26
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
27
|
+
cruise_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
29
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
30
|
+
cruise_line: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
duration_nights: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
32
|
+
embarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
disembarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
export declare const cruiseShipSchema: z.ZodObject<{
|
|
36
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
37
|
+
name: z.ZodString;
|
|
38
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
40
|
+
decks: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
41
|
+
year_built: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export declare const cruiseItineraryStopSchema: z.ZodObject<{
|
|
44
|
+
day_number: z.ZodNumber;
|
|
45
|
+
date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
|
+
port_name: z.ZodString;
|
|
47
|
+
arrival_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
departure_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
49
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
50
|
+
is_at_sea: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export declare const cruiseSailingSchema: z.ZodObject<{
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
source_ref: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
55
|
+
start_date: z.ZodString;
|
|
56
|
+
end_date: z.ZodString;
|
|
57
|
+
duration_nights: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
58
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
embarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
|
+
disembarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
61
|
+
itinerary_stops: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
62
|
+
day_number: z.ZodNumber;
|
|
63
|
+
date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
64
|
+
port_name: z.ZodString;
|
|
65
|
+
arrival_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
66
|
+
departure_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
67
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
68
|
+
is_at_sea: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
69
|
+
}, z.core.$strip>>>;
|
|
70
|
+
lowest_price_cents: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
71
|
+
currency: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
export declare const cruiseCabinCategorySchema: z.ZodObject<{
|
|
74
|
+
id: z.ZodString;
|
|
75
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
76
|
+
name: z.ZodString;
|
|
77
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
78
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
+
capacity_min: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
80
|
+
capacity_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
81
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
82
|
+
feature_codes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
83
|
+
bed_configurations: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
84
|
+
single: "single";
|
|
85
|
+
twin: "twin";
|
|
86
|
+
double: "double";
|
|
87
|
+
queen: "queen";
|
|
88
|
+
king: "king";
|
|
89
|
+
convertible_twins: "convertible_twins";
|
|
90
|
+
sofa_bed: "sofa_bed";
|
|
91
|
+
pullman: "pullman";
|
|
92
|
+
bunk: "bunk";
|
|
93
|
+
murphy: "murphy";
|
|
94
|
+
}>>>;
|
|
95
|
+
accessibility_features: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
96
|
+
wheelchair_accessible: "wheelchair_accessible";
|
|
97
|
+
step_free_access: "step_free_access";
|
|
98
|
+
roll_in_shower: "roll_in_shower";
|
|
99
|
+
grab_bars: "grab_bars";
|
|
100
|
+
visual_alarm: "visual_alarm";
|
|
101
|
+
hearing_loop: "hearing_loop";
|
|
102
|
+
accessible_balcony: "accessible_balcony";
|
|
103
|
+
accessible_bathroom: "accessible_bathroom";
|
|
104
|
+
}>>>;
|
|
105
|
+
view_type: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
|
|
106
|
+
none: "none";
|
|
107
|
+
interior: "interior";
|
|
108
|
+
virtual: "virtual";
|
|
109
|
+
porthole: "porthole";
|
|
110
|
+
window: "window";
|
|
111
|
+
oceanview: "oceanview";
|
|
112
|
+
river_view: "river_view";
|
|
113
|
+
balcony: "balcony";
|
|
114
|
+
french_balcony: "french_balcony";
|
|
115
|
+
promenade: "promenade";
|
|
116
|
+
obstructed: "obstructed";
|
|
117
|
+
}>>>;
|
|
118
|
+
}, z.core.$strip>;
|
|
119
|
+
export declare const cruisePolicySchema: z.ZodObject<{
|
|
120
|
+
kind: z.ZodEnum<{
|
|
121
|
+
cancellation: "cancellation";
|
|
122
|
+
payment: "payment";
|
|
123
|
+
supplier_notes: "supplier_notes";
|
|
124
|
+
requirements: "requirements";
|
|
125
|
+
}>;
|
|
126
|
+
body: z.ZodString;
|
|
127
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
128
|
+
}, z.core.$strip>;
|
|
129
|
+
export declare const cruiseContentSchema: z.ZodObject<{
|
|
130
|
+
cruise: z.ZodObject<{
|
|
131
|
+
id: z.ZodString;
|
|
132
|
+
name: z.ZodString;
|
|
133
|
+
status: z.ZodOptional<z.ZodString>;
|
|
134
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
135
|
+
cruise_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
136
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
137
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
138
|
+
cruise_line: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
139
|
+
duration_nights: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
140
|
+
embarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
141
|
+
disembarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
142
|
+
}, z.core.$strip>;
|
|
143
|
+
ship: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
144
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
145
|
+
name: z.ZodString;
|
|
146
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
147
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
148
|
+
decks: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
149
|
+
year_built: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
150
|
+
}, z.core.$strip>>>;
|
|
151
|
+
sailings: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
152
|
+
id: z.ZodString;
|
|
153
|
+
source_ref: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
154
|
+
start_date: z.ZodString;
|
|
155
|
+
end_date: z.ZodString;
|
|
156
|
+
duration_nights: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
157
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
158
|
+
embarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
159
|
+
disembarkation_port: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
160
|
+
itinerary_stops: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
161
|
+
day_number: z.ZodNumber;
|
|
162
|
+
date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
163
|
+
port_name: z.ZodString;
|
|
164
|
+
arrival_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
165
|
+
departure_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
166
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
167
|
+
is_at_sea: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
168
|
+
}, z.core.$strip>>>;
|
|
169
|
+
lowest_price_cents: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
170
|
+
currency: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
171
|
+
}, z.core.$strip>>>;
|
|
172
|
+
cabin_categories: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
173
|
+
id: z.ZodString;
|
|
174
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
175
|
+
name: z.ZodString;
|
|
176
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
177
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
178
|
+
capacity_min: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
179
|
+
capacity_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
180
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
181
|
+
feature_codes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
182
|
+
bed_configurations: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
183
|
+
single: "single";
|
|
184
|
+
twin: "twin";
|
|
185
|
+
double: "double";
|
|
186
|
+
queen: "queen";
|
|
187
|
+
king: "king";
|
|
188
|
+
convertible_twins: "convertible_twins";
|
|
189
|
+
sofa_bed: "sofa_bed";
|
|
190
|
+
pullman: "pullman";
|
|
191
|
+
bunk: "bunk";
|
|
192
|
+
murphy: "murphy";
|
|
193
|
+
}>>>;
|
|
194
|
+
accessibility_features: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
195
|
+
wheelchair_accessible: "wheelchair_accessible";
|
|
196
|
+
step_free_access: "step_free_access";
|
|
197
|
+
roll_in_shower: "roll_in_shower";
|
|
198
|
+
grab_bars: "grab_bars";
|
|
199
|
+
visual_alarm: "visual_alarm";
|
|
200
|
+
hearing_loop: "hearing_loop";
|
|
201
|
+
accessible_balcony: "accessible_balcony";
|
|
202
|
+
accessible_bathroom: "accessible_bathroom";
|
|
203
|
+
}>>>;
|
|
204
|
+
view_type: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
|
|
205
|
+
none: "none";
|
|
206
|
+
interior: "interior";
|
|
207
|
+
virtual: "virtual";
|
|
208
|
+
porthole: "porthole";
|
|
209
|
+
window: "window";
|
|
210
|
+
oceanview: "oceanview";
|
|
211
|
+
river_view: "river_view";
|
|
212
|
+
balcony: "balcony";
|
|
213
|
+
french_balcony: "french_balcony";
|
|
214
|
+
promenade: "promenade";
|
|
215
|
+
obstructed: "obstructed";
|
|
216
|
+
}>>>;
|
|
217
|
+
}, z.core.$strip>>>;
|
|
218
|
+
itinerary_stops: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
219
|
+
day_number: z.ZodNumber;
|
|
220
|
+
date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
221
|
+
port_name: z.ZodString;
|
|
222
|
+
arrival_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
223
|
+
departure_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
224
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
225
|
+
is_at_sea: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
226
|
+
}, z.core.$strip>>>;
|
|
227
|
+
policies: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
228
|
+
kind: z.ZodEnum<{
|
|
229
|
+
cancellation: "cancellation";
|
|
230
|
+
payment: "payment";
|
|
231
|
+
supplier_notes: "supplier_notes";
|
|
232
|
+
requirements: "requirements";
|
|
233
|
+
}>;
|
|
234
|
+
body: z.ZodString;
|
|
235
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
236
|
+
}, z.core.$strip>>>;
|
|
237
|
+
}, z.core.$strip>;
|
|
238
|
+
export type CruiseContent = z.infer<typeof cruiseContentSchema>;
|
|
239
|
+
export type CruiseSummary = z.infer<typeof cruiseSummarySchema>;
|
|
240
|
+
export type CruiseShip = z.infer<typeof cruiseShipSchema>;
|
|
241
|
+
export type CruiseSailing = z.infer<typeof cruiseSailingSchema>;
|
|
242
|
+
export type CruiseCabinCategory = z.infer<typeof cruiseCabinCategorySchema>;
|
|
243
|
+
export type CruiseItineraryStop = z.infer<typeof cruiseItineraryStopSchema>;
|
|
244
|
+
export type CruisePolicy = z.infer<typeof cruisePolicySchema>;
|
|
245
|
+
export declare function validateCruiseContent(payload: unknown): {
|
|
246
|
+
valid: true;
|
|
247
|
+
content: CruiseContent;
|
|
248
|
+
} | {
|
|
249
|
+
valid: false;
|
|
250
|
+
reason: string;
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=content-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-shape.d.ts","sourceRoot":"","sources":["../src/content-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAQvB,eAAO,MAAM,8BAA8B,eAAe,CAAA;AAE1D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAY9B,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;iBAO3B,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;iBAQpC,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;iBAuB5B,CAAA;AAEJ,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAapC,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;iBAI7B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO9B,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC/D,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC/D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC/D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAU5E"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cruises content shape — the rich detail-page content shape returned
|
|
3
|
+
* by `getContent` for sourced cruises.
|
|
4
|
+
*
|
|
5
|
+
* The doc's "cruise content aggregate" (§E in the migration plan +
|
|
6
|
+
* §3.2): `{ cruise, ship, sailings[], cabinCategories[], itineraryStops[],
|
|
7
|
+
* policies[] }` is one content payload returned by a single getContent.
|
|
8
|
+
* The cruise adapter's existing internal multi-call composition
|
|
9
|
+
* (`fetchCruise / fetchSailing / fetchShip / fetchItinerary`) flattens
|
|
10
|
+
* to one `GetContentResult.content` blob; the public adapter contract
|
|
11
|
+
* gets one method, not five.
|
|
12
|
+
*
|
|
13
|
+
* Full pricing stays out — it's volatile and continues to flow through
|
|
14
|
+
* `liveResolve`. The content blob carries structural cabin categories and
|
|
15
|
+
* itinerary stops, plus optional per-sailing browse price summaries as
|
|
16
|
+
* integer minor units paired with currency.
|
|
17
|
+
*
|
|
18
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §E.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { CABIN_ACCESSIBILITY_FEATURES, CABIN_BED_CONFIGURATIONS, CABIN_VIEW_TYPES, } from "./cabin-features.js";
|
|
22
|
+
export const CRUISES_CONTENT_SCHEMA_VERSION = "cruises/v1";
|
|
23
|
+
export const cruiseSummarySchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
name: z.string(),
|
|
26
|
+
status: z.string().optional(),
|
|
27
|
+
description: z.string().nullable().optional(),
|
|
28
|
+
cruise_type: z.string().nullable().optional(),
|
|
29
|
+
hero_image_url: z.string().nullable().optional(),
|
|
30
|
+
highlights: z.array(z.string()).optional(),
|
|
31
|
+
cruise_line: z.string().nullable().optional(),
|
|
32
|
+
duration_nights: z.number().int().nonnegative().nullable().optional(),
|
|
33
|
+
embarkation_port: z.string().nullable().optional(),
|
|
34
|
+
disembarkation_port: z.string().nullable().optional(),
|
|
35
|
+
});
|
|
36
|
+
export const cruiseShipSchema = z.object({
|
|
37
|
+
id: z.string().nullable().optional(),
|
|
38
|
+
name: z.string(),
|
|
39
|
+
description: z.string().nullable().optional(),
|
|
40
|
+
capacity: z.number().int().nonnegative().nullable().optional(),
|
|
41
|
+
decks: z.number().int().nonnegative().nullable().optional(),
|
|
42
|
+
year_built: z.number().int().nonnegative().nullable().optional(),
|
|
43
|
+
});
|
|
44
|
+
export const cruiseItineraryStopSchema = z.object({
|
|
45
|
+
day_number: z.number().int().positive(),
|
|
46
|
+
date: z.string().nullable().optional(),
|
|
47
|
+
port_name: z.string(),
|
|
48
|
+
arrival_time: z.string().nullable().optional(),
|
|
49
|
+
departure_time: z.string().nullable().optional(),
|
|
50
|
+
description: z.string().nullable().optional(),
|
|
51
|
+
is_at_sea: z.boolean().optional().default(false),
|
|
52
|
+
});
|
|
53
|
+
export const cruiseSailingSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
id: z.string(),
|
|
56
|
+
source_ref: z.string().nullable().optional(),
|
|
57
|
+
start_date: z.string(),
|
|
58
|
+
end_date: z.string(),
|
|
59
|
+
duration_nights: z.number().int().nonnegative().nullable().optional(),
|
|
60
|
+
status: z.string().nullable().optional(),
|
|
61
|
+
embarkation_port: z.string().nullable().optional(),
|
|
62
|
+
disembarkation_port: z.string().nullable().optional(),
|
|
63
|
+
itinerary_stops: z.array(cruiseItineraryStopSchema).default([]),
|
|
64
|
+
lowest_price_cents: z.number().int().nonnegative().nullable().default(null),
|
|
65
|
+
currency: z.string().min(1).nullable().default(null),
|
|
66
|
+
})
|
|
67
|
+
.superRefine((sailing, ctx) => {
|
|
68
|
+
const hasLowestPrice = sailing.lowest_price_cents !== null;
|
|
69
|
+
const hasCurrency = sailing.currency !== null;
|
|
70
|
+
if (hasLowestPrice === hasCurrency)
|
|
71
|
+
return;
|
|
72
|
+
ctx.addIssue({
|
|
73
|
+
code: "custom",
|
|
74
|
+
path: hasLowestPrice ? ["currency"] : ["lowest_price_cents"],
|
|
75
|
+
message: "lowest_price_cents and currency must both be present or both be null",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
export const cruiseCabinCategorySchema = z.object({
|
|
79
|
+
id: z.string(),
|
|
80
|
+
code: z.string().nullable().optional(),
|
|
81
|
+
name: z.string(),
|
|
82
|
+
description: z.string().nullable().optional(),
|
|
83
|
+
type: z.string().nullable().optional(), // inside, outside, balcony, suite
|
|
84
|
+
capacity_min: z.number().int().nonnegative().nullable().optional(),
|
|
85
|
+
capacity_max: z.number().int().nonnegative().nullable().optional(),
|
|
86
|
+
inclusions: z.array(z.string()).optional().default([]),
|
|
87
|
+
feature_codes: z.array(z.string()).default([]),
|
|
88
|
+
bed_configurations: z.array(z.enum(CABIN_BED_CONFIGURATIONS)).default([]),
|
|
89
|
+
accessibility_features: z.array(z.enum(CABIN_ACCESSIBILITY_FEATURES)).default([]),
|
|
90
|
+
view_type: z.enum(CABIN_VIEW_TYPES).nullable().default(null),
|
|
91
|
+
});
|
|
92
|
+
export const cruisePolicySchema = z.object({
|
|
93
|
+
kind: z.enum(["cancellation", "payment", "supplier_notes", "requirements"]),
|
|
94
|
+
body: z.string(),
|
|
95
|
+
rules: z.unknown().optional(),
|
|
96
|
+
});
|
|
97
|
+
export const cruiseContentSchema = z.object({
|
|
98
|
+
cruise: cruiseSummarySchema,
|
|
99
|
+
ship: cruiseShipSchema.nullable().optional(),
|
|
100
|
+
sailings: z.array(cruiseSailingSchema).default([]),
|
|
101
|
+
cabin_categories: z.array(cruiseCabinCategorySchema).default([]),
|
|
102
|
+
itinerary_stops: z.array(cruiseItineraryStopSchema).default([]),
|
|
103
|
+
policies: z.array(cruisePolicySchema).default([]),
|
|
104
|
+
});
|
|
105
|
+
export function validateCruiseContent(payload) {
|
|
106
|
+
const result = cruiseContentSchema.safeParse(payload);
|
|
107
|
+
if (result.success) {
|
|
108
|
+
return { valid: true, content: result.data };
|
|
109
|
+
}
|
|
110
|
+
const issue = result.error.issues[0];
|
|
111
|
+
return {
|
|
112
|
+
valid: false,
|
|
113
|
+
reason: issue ? `${issue.path.join(".")}: ${issue.message}` : "validation failed",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-shape.test.d.ts","sourceRoot":"","sources":["../src/content-shape.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { CRUISES_CONTENT_SCHEMA_VERSION, cruiseContentSchema, validateCruiseContent, } from "./index.js";
|
|
3
|
+
describe("@voyantjs/cruises-contracts content shape", () => {
|
|
4
|
+
it("validates the cruises/v1 rich content payload", () => {
|
|
5
|
+
const content = cruiseContentSchema.parse({
|
|
6
|
+
cruise: {
|
|
7
|
+
id: "cru_123",
|
|
8
|
+
name: "Danube Highlights",
|
|
9
|
+
},
|
|
10
|
+
sailings: [
|
|
11
|
+
{
|
|
12
|
+
id: "crsl_123",
|
|
13
|
+
start_date: "2026-07-01",
|
|
14
|
+
end_date: "2026-07-08",
|
|
15
|
+
lowest_price_cents: 120000,
|
|
16
|
+
currency: "EUR",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
expect(CRUISES_CONTENT_SCHEMA_VERSION).toBe("cruises/v1");
|
|
21
|
+
expect(validateCruiseContent(content)).toMatchObject({ valid: true });
|
|
22
|
+
expect(content.sailings[0]?.itinerary_stops).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
it("carries structured cabin feature facets", () => {
|
|
25
|
+
const content = cruiseContentSchema.parse({
|
|
26
|
+
cruise: { id: "cru_123", name: "Danube Highlights" },
|
|
27
|
+
cabin_categories: [
|
|
28
|
+
{ id: "cab_inside", name: "Inside", type: "inside", view_type: "interior" },
|
|
29
|
+
{
|
|
30
|
+
id: "cab_balcony",
|
|
31
|
+
name: "Balcony",
|
|
32
|
+
type: "balcony",
|
|
33
|
+
feature_codes: ["minibar"],
|
|
34
|
+
bed_configurations: ["king", "convertible_twins"],
|
|
35
|
+
accessibility_features: ["step_free_access"],
|
|
36
|
+
view_type: "balcony",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
expect(content.cabin_categories[1]?.feature_codes).toEqual(["minibar"]);
|
|
41
|
+
expect(content.cabin_categories[1]?.bed_configurations).toEqual(["king", "convertible_twins"]);
|
|
42
|
+
expect(content.cabin_categories[1]?.accessibility_features).toEqual(["step_free_access"]);
|
|
43
|
+
expect(content.cabin_categories[1]?.view_type).toBe("balcony");
|
|
44
|
+
});
|
|
45
|
+
it("defaults cabin feature facets when omitted", () => {
|
|
46
|
+
const content = cruiseContentSchema.parse({
|
|
47
|
+
cruise: { id: "cru_123", name: "Danube Highlights" },
|
|
48
|
+
cabin_categories: [{ id: "cab_inside", name: "Inside" }],
|
|
49
|
+
});
|
|
50
|
+
expect(content.cabin_categories[0]?.feature_codes).toEqual([]);
|
|
51
|
+
expect(content.cabin_categories[0]?.bed_configurations).toEqual([]);
|
|
52
|
+
expect(content.cabin_categories[0]?.accessibility_features).toEqual([]);
|
|
53
|
+
expect(content.cabin_categories[0]?.view_type).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
it("rejects unknown cabin facet enum values", () => {
|
|
56
|
+
expect(validateCruiseContent({
|
|
57
|
+
cruise: { id: "cru_123", name: "Danube Highlights" },
|
|
58
|
+
cabin_categories: [{ id: "cab_x", name: "X", view_type: "spaceship" }],
|
|
59
|
+
})).toMatchObject({ valid: false });
|
|
60
|
+
});
|
|
61
|
+
it("rejects partial browse price hints", () => {
|
|
62
|
+
const result = validateCruiseContent({
|
|
63
|
+
cruise: {
|
|
64
|
+
id: "cru_123",
|
|
65
|
+
name: "Danube Highlights",
|
|
66
|
+
},
|
|
67
|
+
sailings: [
|
|
68
|
+
{
|
|
69
|
+
id: "crsl_123",
|
|
70
|
+
start_date: "2026-07-01",
|
|
71
|
+
end_date: "2026-07-08",
|
|
72
|
+
lowest_price_cents: 120000,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
expect(result).toMatchObject({ valid: false });
|
|
77
|
+
});
|
|
78
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA"}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voyantjs/cruises-contracts",
|
|
3
|
+
"version": "0.90.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./cabin-features": "./src/cabin-features.ts",
|
|
9
|
+
"./content-shape": "./src/content-shape.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"lint": "biome check src/",
|
|
14
|
+
"test": "vitest run --passWithNoTests",
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
17
|
+
"prepack": "pnpm run build"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./cabin-features": {
|
|
31
|
+
"types": "./dist/cabin-features.d.ts",
|
|
32
|
+
"import": "./dist/cabin-features.js",
|
|
33
|
+
"default": "./dist/cabin-features.js"
|
|
34
|
+
},
|
|
35
|
+
"./content-shape": {
|
|
36
|
+
"types": "./dist/content-shape.d.ts",
|
|
37
|
+
"import": "./dist/content-shape.js",
|
|
38
|
+
"default": "./dist/content-shape.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"main": "./dist/index.js",
|
|
42
|
+
"types": "./dist/index.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"zod": "^4.3.6"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@voyantjs/voyant-typescript-config": "workspace:*",
|
|
49
|
+
"typescript": "^6.0.2",
|
|
50
|
+
"vitest": "^4.1.2"
|
|
51
|
+
},
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/voyantjs/voyant.git",
|
|
55
|
+
"directory": "packages/cruises-contracts"
|
|
56
|
+
}
|
|
57
|
+
}
|