@voyant-travel/cruises 0.118.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +50 -0
- package/dist/adapters/connect-compat.d.ts +20 -0
- package/dist/adapters/connect-compat.d.ts.map +1 -0
- package/dist/adapters/connect-compat.js +71 -0
- package/dist/adapters/contract-fixture.d.ts +32 -0
- package/dist/adapters/contract-fixture.d.ts.map +1 -0
- package/dist/adapters/contract-fixture.js +152 -0
- package/dist/adapters/index.d.ts +331 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +16 -0
- package/dist/adapters/memoize.d.ts +28 -0
- package/dist/adapters/memoize.d.ts.map +1 -0
- package/dist/adapters/memoize.js +131 -0
- package/dist/adapters/mock.d.ts +44 -0
- package/dist/adapters/mock.d.ts.map +1 -0
- package/dist/adapters/mock.js +192 -0
- package/dist/adapters/registry.d.ts +26 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +42 -0
- package/dist/adapters/source-adapter-shim.d.ts +80 -0
- package/dist/adapters/source-adapter-shim.d.ts.map +1 -0
- package/dist/adapters/source-adapter-shim.js +390 -0
- package/dist/booking-engine/handler.d.ts +108 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +225 -0
- package/dist/booking-engine/index.d.ts +9 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +8 -0
- package/dist/booking-extension.d.ts +1179 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +342 -0
- package/dist/cabin-features.d.ts +8 -0
- package/dist/cabin-features.d.ts.map +1 -0
- package/dist/cabin-features.js +7 -0
- package/dist/catalog-policy-cabins.d.ts +18 -0
- package/dist/catalog-policy-cabins.d.ts.map +1 -0
- package/dist/catalog-policy-cabins.js +96 -0
- package/dist/catalog-policy-core.d.ts +3 -0
- package/dist/catalog-policy-core.d.ts.map +1 -0
- package/dist/catalog-policy-core.js +247 -0
- package/dist/catalog-policy-structure.d.ts +3 -0
- package/dist/catalog-policy-structure.d.ts.map +1 -0
- package/dist/catalog-policy-structure.js +387 -0
- package/dist/catalog-policy.d.ts +15 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +19 -0
- package/dist/content-shape.d.ts +5 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +13 -0
- package/dist/draft-shape.d.ts +59 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +98 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +21 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/lib/key.d.ts +41 -0
- package/dist/lib/key.d.ts.map +1 -0
- package/dist/lib/key.js +100 -0
- package/dist/routes-booking-payloads.d.ts +133 -0
- package/dist/routes-booking-payloads.d.ts.map +1 -0
- package/dist/routes-booking-payloads.js +142 -0
- package/dist/routes-content.d.ts +53 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +158 -0
- package/dist/routes-core.d.ts +4 -0
- package/dist/routes-core.d.ts.map +1 -0
- package/dist/routes-core.js +68 -0
- package/dist/routes-detail.d.ts +4 -0
- package/dist/routes-detail.d.ts.map +1 -0
- package/dist/routes-detail.js +261 -0
- package/dist/routes-env.d.ts +13 -0
- package/dist/routes-env.d.ts.map +1 -0
- package/dist/routes-env.js +1 -0
- package/dist/routes-keying.d.ts +28 -0
- package/dist/routes-keying.d.ts.map +1 -0
- package/dist/routes-keying.js +70 -0
- package/dist/routes-public.d.ts +911 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +252 -0
- package/dist/routes-sailings-prices.d.ts +4 -0
- package/dist/routes-sailings-prices.d.ts.map +1 -0
- package/dist/routes-sailings-prices.js +278 -0
- package/dist/routes-search-index.d.ts +4 -0
- package/dist/routes-search-index.d.ts.map +1 -0
- package/dist/routes-search-index.js +25 -0
- package/dist/routes-ships.d.ts +4 -0
- package/dist/routes-ships.d.ts.map +1 -0
- package/dist/routes-ships.js +147 -0
- package/dist/routes-voyage-groups.d.ts +4 -0
- package/dist/routes-voyage-groups.d.ts.map +1 -0
- package/dist/routes-voyage-groups.js +85 -0
- package/dist/routes.d.ts +5 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +14 -0
- package/dist/schema-cabins.d.ts +1098 -0
- package/dist/schema-cabins.d.ts.map +1 -0
- package/dist/schema-cabins.js +105 -0
- package/dist/schema-content.d.ts +577 -0
- package/dist/schema-content.d.ts.map +1 -0
- package/dist/schema-content.js +63 -0
- package/dist/schema-core.d.ts +1790 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +171 -0
- package/dist/schema-itinerary.d.ts +556 -0
- package/dist/schema-itinerary.d.ts.map +1 -0
- package/dist/schema-itinerary.js +50 -0
- package/dist/schema-pricing.d.ts +633 -0
- package/dist/schema-pricing.d.ts.map +1 -0
- package/dist/schema-pricing.js +73 -0
- package/dist/schema-search.d.ts +611 -0
- package/dist/schema-search.d.ts.map +1 -0
- package/dist/schema-search.js +64 -0
- package/dist/schema-shared.d.ts +23 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +107 -0
- package/dist/schema-sourced-content.d.ts +247 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +38 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +9 -0
- package/dist/service-booking-helpers.d.ts +12 -0
- package/dist/service-booking-helpers.d.ts.map +1 -0
- package/dist/service-booking-helpers.js +94 -0
- package/dist/service-booking-types.d.ts +101 -0
- package/dist/service-booking-types.d.ts.map +1 -0
- package/dist/service-booking-types.js +1 -0
- package/dist/service-bookings.d.ts +46 -0
- package/dist/service-bookings.d.ts.map +1 -0
- package/dist/service-bookings.js +420 -0
- package/dist/service-catalog-plane-cabins.d.ts +24 -0
- package/dist/service-catalog-plane-cabins.d.ts.map +1 -0
- package/dist/service-catalog-plane-cabins.js +90 -0
- package/dist/service-catalog-plane.d.ts +74 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +194 -0
- package/dist/service-content-synthesizer.d.ts +42 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +144 -0
- package/dist/service-content.d.ts +74 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +315 -0
- package/dist/service-core.d.ts +134 -0
- package/dist/service-core.d.ts.map +1 -0
- package/dist/service-core.js +257 -0
- package/dist/service-detach.d.ts +18 -0
- package/dist/service-detach.d.ts.map +1 -0
- package/dist/service-detach.js +199 -0
- package/dist/service-enrichment.d.ts +11 -0
- package/dist/service-enrichment.d.ts.map +1 -0
- package/dist/service-enrichment.js +47 -0
- package/dist/service-external-refresh.d.ts +39 -0
- package/dist/service-external-refresh.d.ts.map +1 -0
- package/dist/service-external-refresh.js +47 -0
- package/dist/service-itinerary.d.ts +22 -0
- package/dist/service-itinerary.d.ts.map +1 -0
- package/dist/service-itinerary.js +34 -0
- package/dist/service-prices.d.ts +46 -0
- package/dist/service-prices.d.ts.map +1 -0
- package/dist/service-prices.js +89 -0
- package/dist/service-pricing.d.ts +97 -0
- package/dist/service-pricing.d.ts.map +1 -0
- package/dist/service-pricing.js +198 -0
- package/dist/service-sailings.d.ts +48 -0
- package/dist/service-sailings.d.ts.map +1 -0
- package/dist/service-sailings.js +145 -0
- package/dist/service-search-types.d.ts +54 -0
- package/dist/service-search-types.d.ts.map +1 -0
- package/dist/service-search-types.js +1 -0
- package/dist/service-search.d.ts +65 -0
- package/dist/service-search.d.ts.map +1 -0
- package/dist/service-search.js +467 -0
- package/dist/service-shared.d.ts +22 -0
- package/dist/service-shared.d.ts.map +1 -0
- package/dist/service-shared.js +22 -0
- package/dist/service-ships.d.ts +47 -0
- package/dist/service-ships.d.ts.map +1 -0
- package/dist/service-ships.js +156 -0
- package/dist/service.d.ts +255 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +12 -0
- package/dist/validation-cabins.d.ts +267 -0
- package/dist/validation-cabins.d.ts.map +1 -0
- package/dist/validation-cabins.js +77 -0
- package/dist/validation-content.d.ts +123 -0
- package/dist/validation-content.d.ts.map +1 -0
- package/dist/validation-content.js +40 -0
- package/dist/validation-core.d.ts +393 -0
- package/dist/validation-core.d.ts.map +1 -0
- package/dist/validation-core.js +162 -0
- package/dist/validation-itinerary.d.ts +123 -0
- package/dist/validation-itinerary.d.ts.map +1 -0
- package/dist/validation-itinerary.js +47 -0
- package/dist/validation-pricing.d.ts +137 -0
- package/dist/validation-pricing.d.ts.map +1 -0
- package/dist/validation-pricing.js +49 -0
- package/dist/validation-search.d.ts +118 -0
- package/dist/validation-search.d.ts.map +1 -0
- package/dist/validation-search.js +60 -0
- package/dist/validation-shared.d.ts +123 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +103 -0
- package/dist/validation.d.ts +8 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +7 -0
- package/package.json +146 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search-index service for mixed local/external cruise browse rows.
|
|
3
|
+
* `cruise_search_index` is optional; storefront deployments populate it
|
|
4
|
+
* from local projection hooks and adapter `searchProjection()` streams.
|
|
5
|
+
*/
|
|
6
|
+
import { and, asc, eq, gte, ilike, lte, notInArray, or, sql } from "drizzle-orm";
|
|
7
|
+
import { listCruiseAdapters } from "./adapters/registry.js";
|
|
8
|
+
import { cruiseShips } from "./schema-cabins.js";
|
|
9
|
+
import { cruiseSailings, cruises } from "./schema-core.js";
|
|
10
|
+
import { cruisePrices } from "./schema-pricing.js";
|
|
11
|
+
import { cruiseSearchIndex, } from "./schema-search.js";
|
|
12
|
+
export const cruisesSearchService = {
|
|
13
|
+
// ---------- queries ----------
|
|
14
|
+
async query(db, query) {
|
|
15
|
+
const conditions = [];
|
|
16
|
+
if (query.cruiseType)
|
|
17
|
+
conditions.push(eq(cruiseSearchIndex.cruiseType, query.cruiseType));
|
|
18
|
+
if (query.source)
|
|
19
|
+
conditions.push(eq(cruiseSearchIndex.source, query.source));
|
|
20
|
+
if (query.region) {
|
|
21
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
22
|
+
conditions.push(sql `${cruiseSearchIndex.regions} @> ${JSON.stringify([query.region])}::jsonb`);
|
|
23
|
+
}
|
|
24
|
+
if (query.regionId) {
|
|
25
|
+
conditions.push(
|
|
26
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
27
|
+
sql `${cruiseSearchIndex.regionIds} @> ${JSON.stringify([query.regionId])}::jsonb`);
|
|
28
|
+
}
|
|
29
|
+
if (query.waterwayId) {
|
|
30
|
+
conditions.push(
|
|
31
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
32
|
+
sql `${cruiseSearchIndex.waterwayIds} @> ${JSON.stringify([query.waterwayId])}::jsonb`);
|
|
33
|
+
}
|
|
34
|
+
if (query.portId) {
|
|
35
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
36
|
+
conditions.push(sql `${cruiseSearchIndex.portIds} @> ${JSON.stringify([query.portId])}::jsonb`);
|
|
37
|
+
}
|
|
38
|
+
if (query.countryIso) {
|
|
39
|
+
conditions.push(
|
|
40
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
41
|
+
sql `${cruiseSearchIndex.countryIso} @> ${JSON.stringify([query.countryIso])}::jsonb`);
|
|
42
|
+
}
|
|
43
|
+
if (query.theme) {
|
|
44
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
45
|
+
conditions.push(sql `${cruiseSearchIndex.themes} @> ${JSON.stringify([query.theme])}::jsonb`);
|
|
46
|
+
}
|
|
47
|
+
if (query.dateFrom)
|
|
48
|
+
conditions.push(gte(cruiseSearchIndex.earliestDeparture, query.dateFrom));
|
|
49
|
+
if (query.dateTo)
|
|
50
|
+
conditions.push(lte(cruiseSearchIndex.latestDeparture, query.dateTo));
|
|
51
|
+
if (query.priceMaxCents !== undefined) {
|
|
52
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
53
|
+
conditions.push(sql `${cruiseSearchIndex.lowestPriceCents} <= ${query.priceMaxCents}`);
|
|
54
|
+
}
|
|
55
|
+
if (query.embarkPortCanonicalPlaceId) {
|
|
56
|
+
conditions.push(eq(cruiseSearchIndex.embarkPortCanonicalPlaceId, query.embarkPortCanonicalPlaceId));
|
|
57
|
+
}
|
|
58
|
+
if (query.disembarkPortCanonicalPlaceId) {
|
|
59
|
+
conditions.push(eq(cruiseSearchIndex.disembarkPortCanonicalPlaceId, query.disembarkPortCanonicalPlaceId));
|
|
60
|
+
}
|
|
61
|
+
if (query.portCanonicalPlaceId) {
|
|
62
|
+
const portClause = or(eq(cruiseSearchIndex.embarkPortCanonicalPlaceId, query.portCanonicalPlaceId), eq(cruiseSearchIndex.disembarkPortCanonicalPlaceId, query.portCanonicalPlaceId));
|
|
63
|
+
if (portClause)
|
|
64
|
+
conditions.push(portClause);
|
|
65
|
+
}
|
|
66
|
+
if (query.search) {
|
|
67
|
+
const term = `%${query.search}%`;
|
|
68
|
+
const searchClause = or(ilike(cruiseSearchIndex.name, term), ilike(cruiseSearchIndex.lineName, term), ilike(cruiseSearchIndex.shipName, term));
|
|
69
|
+
if (searchClause)
|
|
70
|
+
conditions.push(searchClause);
|
|
71
|
+
}
|
|
72
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
73
|
+
const [rows, totalRows] = await Promise.all([
|
|
74
|
+
db
|
|
75
|
+
.select()
|
|
76
|
+
.from(cruiseSearchIndex)
|
|
77
|
+
.where(where)
|
|
78
|
+
.orderBy(asc(cruiseSearchIndex.earliestDeparture),
|
|
79
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
80
|
+
asc(sql `${cruiseSearchIndex.lowestPriceCents} NULLS LAST`), asc(cruiseSearchIndex.name))
|
|
81
|
+
.limit(query.limit)
|
|
82
|
+
.offset(query.offset),
|
|
83
|
+
db.select({ value: sql `count(*)::int` }).from(cruiseSearchIndex).where(where),
|
|
84
|
+
]);
|
|
85
|
+
return {
|
|
86
|
+
data: rows,
|
|
87
|
+
total: totalRows[0]?.value ?? 0,
|
|
88
|
+
limit: query.limit,
|
|
89
|
+
offset: query.offset,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
async getBySlug(db, slug) {
|
|
93
|
+
const [row] = await db
|
|
94
|
+
.select()
|
|
95
|
+
.from(cruiseSearchIndex)
|
|
96
|
+
.where(eq(cruiseSearchIndex.slug, slug))
|
|
97
|
+
.limit(1);
|
|
98
|
+
return row ?? null;
|
|
99
|
+
},
|
|
100
|
+
// ---------- writes ----------
|
|
101
|
+
async upsertEntry(db, entry) {
|
|
102
|
+
const payload = {
|
|
103
|
+
source: entry.source,
|
|
104
|
+
sourceProvider: entry.sourceProvider ?? null,
|
|
105
|
+
sourceRef: entry.sourceRef ?? null,
|
|
106
|
+
localCruiseId: entry.localCruiseId ?? null,
|
|
107
|
+
slug: entry.slug,
|
|
108
|
+
name: entry.name,
|
|
109
|
+
cruiseType: entry.cruiseType,
|
|
110
|
+
lineName: entry.lineName,
|
|
111
|
+
shipName: entry.shipName,
|
|
112
|
+
nights: entry.nights,
|
|
113
|
+
embarkPortName: entry.embarkPortName ?? null,
|
|
114
|
+
embarkPortCanonicalPlaceId: entry.embarkPortCanonicalPlaceId ?? null,
|
|
115
|
+
disembarkPortName: entry.disembarkPortName ?? null,
|
|
116
|
+
disembarkPortCanonicalPlaceId: entry.disembarkPortCanonicalPlaceId ?? null,
|
|
117
|
+
regionIds: entry.regionIds ?? [],
|
|
118
|
+
waterwayIds: entry.waterwayIds ?? [],
|
|
119
|
+
portIds: entry.portIds ?? [],
|
|
120
|
+
countryIso: entry.countryIso ?? [],
|
|
121
|
+
regions: entry.regions ?? [],
|
|
122
|
+
waterways: entry.waterways ?? [],
|
|
123
|
+
ports: entry.ports ?? [],
|
|
124
|
+
countries: entry.countries ?? [],
|
|
125
|
+
themes: entry.themes ?? [],
|
|
126
|
+
earliestDeparture: entry.earliestDeparture ?? null,
|
|
127
|
+
latestDeparture: entry.latestDeparture ?? null,
|
|
128
|
+
departureCount: entry.departureCount ?? null,
|
|
129
|
+
lowestPriceCents: entry.lowestPriceCents ?? null,
|
|
130
|
+
lowestPriceCurrency: entry.lowestPriceCurrency ?? null,
|
|
131
|
+
salesStatus: entry.salesStatus ?? null,
|
|
132
|
+
heroImageUrl: entry.heroImageUrl ?? null,
|
|
133
|
+
refreshedAt: new Date(),
|
|
134
|
+
};
|
|
135
|
+
const existing = await findExisting(db, entry);
|
|
136
|
+
if (existing) {
|
|
137
|
+
const [row] = await db
|
|
138
|
+
.update(cruiseSearchIndex)
|
|
139
|
+
.set({ ...payload, updatedAt: new Date() })
|
|
140
|
+
.where(eq(cruiseSearchIndex.id, existing.id))
|
|
141
|
+
.returning();
|
|
142
|
+
if (!row)
|
|
143
|
+
throw new Error("Failed to update search index entry");
|
|
144
|
+
return row;
|
|
145
|
+
}
|
|
146
|
+
const [row] = await db.insert(cruiseSearchIndex).values(payload).returning();
|
|
147
|
+
if (!row)
|
|
148
|
+
throw new Error("Failed to insert search index entry");
|
|
149
|
+
return row;
|
|
150
|
+
},
|
|
151
|
+
async bulkUpsert(db, entries) {
|
|
152
|
+
let upserted = 0;
|
|
153
|
+
// Run in a transaction so a partial run can roll back. Adapters typically
|
|
154
|
+
// call this in chunks; the chunk size is the adapter's choice.
|
|
155
|
+
await db.transaction(async (tx) => {
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
await this.upsertEntry(tx, entry);
|
|
158
|
+
upserted++;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return { upserted };
|
|
162
|
+
},
|
|
163
|
+
async removeEntry(db, id) {
|
|
164
|
+
const result = await db
|
|
165
|
+
.delete(cruiseSearchIndex)
|
|
166
|
+
.where(eq(cruiseSearchIndex.id, id))
|
|
167
|
+
.returning({ id: cruiseSearchIndex.id });
|
|
168
|
+
return result.length > 0;
|
|
169
|
+
},
|
|
170
|
+
async removeBySource(db, sourceProvider) {
|
|
171
|
+
const result = await db
|
|
172
|
+
.delete(cruiseSearchIndex)
|
|
173
|
+
.where(and(eq(cruiseSearchIndex.source, "external"), eq(cruiseSearchIndex.sourceProvider, sourceProvider)))
|
|
174
|
+
.returning({ id: cruiseSearchIndex.id });
|
|
175
|
+
return { removed: result.length };
|
|
176
|
+
},
|
|
177
|
+
async removeExternalByIdsExcept(db, sourceProvider, keepIds, sourceConnectionId) {
|
|
178
|
+
const conditions = [
|
|
179
|
+
eq(cruiseSearchIndex.source, "external"),
|
|
180
|
+
eq(cruiseSearchIndex.sourceProvider, sourceProvider),
|
|
181
|
+
sourceConnectionId == null
|
|
182
|
+
? // agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
183
|
+
sql `coalesce(${cruiseSearchIndex.sourceRef}->>'connectionId', '') = ''`
|
|
184
|
+
: // agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
185
|
+
sql `${cruiseSearchIndex.sourceRef}->>'connectionId' = ${sourceConnectionId}`,
|
|
186
|
+
];
|
|
187
|
+
if (keepIds.length > 0) {
|
|
188
|
+
conditions.push(notInArray(cruiseSearchIndex.id, [...keepIds]));
|
|
189
|
+
}
|
|
190
|
+
const result = await db
|
|
191
|
+
.delete(cruiseSearchIndex)
|
|
192
|
+
.where(and(...conditions))
|
|
193
|
+
.returning({ id: cruiseSearchIndex.id });
|
|
194
|
+
return { removed: result.length };
|
|
195
|
+
},
|
|
196
|
+
async listExternalConnectionIds(db, sourceProvider) {
|
|
197
|
+
const connectionId = sql `nullif(${cruiseSearchIndex.sourceRef}->>'connectionId', '')`;
|
|
198
|
+
const rows = await db
|
|
199
|
+
.select({ connectionId })
|
|
200
|
+
.from(cruiseSearchIndex)
|
|
201
|
+
.where(and(eq(cruiseSearchIndex.source, "external"), eq(cruiseSearchIndex.sourceProvider, sourceProvider)))
|
|
202
|
+
.groupBy(connectionId);
|
|
203
|
+
return rows.map((row) => row.connectionId);
|
|
204
|
+
},
|
|
205
|
+
// ---------- projection from local cruises ----------
|
|
206
|
+
/**
|
|
207
|
+
* Re-project a single local cruise into the search index. Called from the
|
|
208
|
+
* cruisesService mutation hooks so the index stays fresh without a separate
|
|
209
|
+
* scheduled job. Computes the lowest available price across the cruise's
|
|
210
|
+
* sailings and the earliest/latest departure dates.
|
|
211
|
+
*
|
|
212
|
+
* If the cruise's status is 'archived' the entry is removed instead — archived
|
|
213
|
+
* cruises shouldn't appear on the storefront.
|
|
214
|
+
*/
|
|
215
|
+
async projectLocalCruise(db, cruiseId) {
|
|
216
|
+
const [cruise] = await db.select().from(cruises).where(eq(cruises.id, cruiseId)).limit(1);
|
|
217
|
+
if (!cruise) {
|
|
218
|
+
// Cruise was deleted — drop any matching index row.
|
|
219
|
+
await db.delete(cruiseSearchIndex).where(eq(cruiseSearchIndex.localCruiseId, cruiseId));
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (cruise.status === "archived") {
|
|
223
|
+
await db.delete(cruiseSearchIndex).where(eq(cruiseSearchIndex.localCruiseId, cruiseId));
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const entry = await buildLocalEntry(db, cruise);
|
|
227
|
+
if (!entry)
|
|
228
|
+
return null;
|
|
229
|
+
return this.upsertEntry(db, entry);
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* Drop and rebuild every local cruise entry. Useful after schema changes
|
|
233
|
+
* or operator-triggered "rebuild storefront index" actions.
|
|
234
|
+
*/
|
|
235
|
+
async rebuildLocal(db) {
|
|
236
|
+
// Remove all local entries first so deleted cruises don't linger.
|
|
237
|
+
await db.delete(cruiseSearchIndex).where(eq(cruiseSearchIndex.source, "local"));
|
|
238
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
239
|
+
const allCruises = await db.select().from(cruises).where(sql `${cruises.status} <> 'archived'`);
|
|
240
|
+
let upserted = 0;
|
|
241
|
+
for (const cruise of allCruises) {
|
|
242
|
+
const entry = await buildLocalEntry(db, cruise);
|
|
243
|
+
if (!entry)
|
|
244
|
+
continue;
|
|
245
|
+
await this.upsertEntry(db, entry);
|
|
246
|
+
upserted++;
|
|
247
|
+
}
|
|
248
|
+
return { upserted };
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* Drain `searchProjection()` from a single adapter and bulk-upsert. Useful
|
|
252
|
+
* for ad-hoc "refresh from upstream" actions; production deployments
|
|
253
|
+
* typically have the adapter push deltas continuously instead.
|
|
254
|
+
*/
|
|
255
|
+
async rebuildExternalForAdapter(db, adapter) {
|
|
256
|
+
const result = await this.refreshExternalForAdapter(db, adapter);
|
|
257
|
+
return { upserted: result.upserted };
|
|
258
|
+
},
|
|
259
|
+
/**
|
|
260
|
+
* Drain `searchProjection()` from a single adapter and reconcile the local
|
|
261
|
+
* external search-index rows for that provider. Existing rows stay intact
|
|
262
|
+
* until the adapter stream completes; only then are missing rows removed.
|
|
263
|
+
*/
|
|
264
|
+
async refreshExternalForAdapter(db, adapter) {
|
|
265
|
+
let upserted = 0;
|
|
266
|
+
const keptIdsByConnection = new Map();
|
|
267
|
+
const pruneConnectionIds = new Set(await this.listExternalConnectionIds(db, adapter.name));
|
|
268
|
+
for await (const entry of adapter.searchProjection()) {
|
|
269
|
+
const row = await this.upsertEntry(db, {
|
|
270
|
+
source: "external",
|
|
271
|
+
sourceProvider: adapter.name,
|
|
272
|
+
sourceRef: entry.sourceRef,
|
|
273
|
+
slug: entry.slug,
|
|
274
|
+
name: entry.name,
|
|
275
|
+
cruiseType: entry.cruiseType,
|
|
276
|
+
lineName: entry.lineName,
|
|
277
|
+
shipName: entry.shipName,
|
|
278
|
+
nights: entry.nights,
|
|
279
|
+
embarkPortName: entry.embarkPortName ?? null,
|
|
280
|
+
embarkPortCanonicalPlaceId: entry.embarkPortCanonicalPlaceId ?? null,
|
|
281
|
+
disembarkPortName: entry.disembarkPortName ?? null,
|
|
282
|
+
disembarkPortCanonicalPlaceId: entry.disembarkPortCanonicalPlaceId ?? null,
|
|
283
|
+
regionIds: entry.regionIds ?? [],
|
|
284
|
+
waterwayIds: entry.waterwayIds ?? [],
|
|
285
|
+
portIds: entry.portIds ?? [],
|
|
286
|
+
countryIso: entry.countryIso ?? [],
|
|
287
|
+
regions: entry.regions ?? [],
|
|
288
|
+
waterways: entry.waterways ?? [],
|
|
289
|
+
ports: entry.ports ?? [],
|
|
290
|
+
countries: entry.countries ?? [],
|
|
291
|
+
themes: entry.themes ?? [],
|
|
292
|
+
earliestDeparture: entry.earliestDeparture ?? null,
|
|
293
|
+
latestDeparture: entry.latestDeparture ?? null,
|
|
294
|
+
departureCount: entry.departureCount ?? null,
|
|
295
|
+
lowestPriceCents: entry.lowestPriceCents ?? null,
|
|
296
|
+
lowestPriceCurrency: entry.lowestPriceCurrency ?? null,
|
|
297
|
+
salesStatus: entry.salesStatus ?? null,
|
|
298
|
+
heroImageUrl: entry.heroImageUrl ?? null,
|
|
299
|
+
});
|
|
300
|
+
const connectionId = sourceRefConnectionId(entry.sourceRef);
|
|
301
|
+
const keptIds = keptIdsByConnection.get(connectionId) ?? [];
|
|
302
|
+
keptIds.push(row.id);
|
|
303
|
+
keptIdsByConnection.set(connectionId, keptIds);
|
|
304
|
+
pruneConnectionIds.add(connectionId);
|
|
305
|
+
upserted++;
|
|
306
|
+
}
|
|
307
|
+
let removed = 0;
|
|
308
|
+
for (const connectionId of pruneConnectionIds) {
|
|
309
|
+
const keptIds = keptIdsByConnection.get(connectionId) ?? [];
|
|
310
|
+
const result = await this.removeExternalByIdsExcept(db, adapter.name, keptIds, connectionId);
|
|
311
|
+
removed += result.removed;
|
|
312
|
+
}
|
|
313
|
+
return { upserted, removed };
|
|
314
|
+
},
|
|
315
|
+
/**
|
|
316
|
+
* Full rebuild — local cruises + every registered adapter.
|
|
317
|
+
* Per-adapter errors are collected so one bad adapter doesn't block the rest.
|
|
318
|
+
*/
|
|
319
|
+
async rebuildAll(db) {
|
|
320
|
+
const localResult = await this.rebuildLocal(db);
|
|
321
|
+
const externalErrors = [];
|
|
322
|
+
let externalUpserted = 0;
|
|
323
|
+
let externalRemoved = 0;
|
|
324
|
+
for (const adapter of listCruiseAdapters()) {
|
|
325
|
+
try {
|
|
326
|
+
const result = await this.refreshExternalForAdapter(db, adapter);
|
|
327
|
+
externalUpserted += result.upserted;
|
|
328
|
+
externalRemoved += result.removed;
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
externalErrors.push({ adapter: adapter.name, error: err.message });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
localUpserted: localResult.upserted,
|
|
336
|
+
externalUpserted,
|
|
337
|
+
externalRemoved,
|
|
338
|
+
externalErrors,
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
// ---------- helpers ----------
|
|
343
|
+
async function findExisting(db, entry) {
|
|
344
|
+
if (entry.source === "local" && entry.localCruiseId) {
|
|
345
|
+
const [row] = await db
|
|
346
|
+
.select()
|
|
347
|
+
.from(cruiseSearchIndex)
|
|
348
|
+
.where(eq(cruiseSearchIndex.localCruiseId, entry.localCruiseId))
|
|
349
|
+
.limit(1);
|
|
350
|
+
if (row)
|
|
351
|
+
return row;
|
|
352
|
+
}
|
|
353
|
+
if (entry.source === "external" && entry.sourceProvider && entry.sourceRef) {
|
|
354
|
+
const [row] = await db
|
|
355
|
+
.select()
|
|
356
|
+
.from(cruiseSearchIndex)
|
|
357
|
+
.where(and(eq(cruiseSearchIndex.source, "external"), eq(cruiseSearchIndex.sourceProvider, entry.sourceProvider),
|
|
358
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
359
|
+
sql `${cruiseSearchIndex.sourceRef} = ${sourceRefIdentityJson(entry.sourceRef)}::jsonb`))
|
|
360
|
+
.limit(1);
|
|
361
|
+
if (row)
|
|
362
|
+
return row;
|
|
363
|
+
}
|
|
364
|
+
// Fallback: match by slug (slug is unique across the index).
|
|
365
|
+
const [bySlug] = await db
|
|
366
|
+
.select()
|
|
367
|
+
.from(cruiseSearchIndex)
|
|
368
|
+
.where(eq(cruiseSearchIndex.slug, entry.slug))
|
|
369
|
+
.limit(1);
|
|
370
|
+
return bySlug ?? null;
|
|
371
|
+
}
|
|
372
|
+
export function sourceRefIdentityJson(sourceRef) {
|
|
373
|
+
return JSON.stringify(sortValue(sourceRef));
|
|
374
|
+
}
|
|
375
|
+
function sourceRefConnectionId(sourceRef) {
|
|
376
|
+
return typeof sourceRef.connectionId === "string" ? sourceRef.connectionId : null;
|
|
377
|
+
}
|
|
378
|
+
function moneyStringToCents(value) {
|
|
379
|
+
if (!value)
|
|
380
|
+
return null;
|
|
381
|
+
const major = Number.parseFloat(value);
|
|
382
|
+
if (!Number.isFinite(major))
|
|
383
|
+
return null;
|
|
384
|
+
return Math.round(major * 100);
|
|
385
|
+
}
|
|
386
|
+
function sortValue(value) {
|
|
387
|
+
if (Array.isArray(value))
|
|
388
|
+
return value.map(sortValue);
|
|
389
|
+
if (!value || typeof value !== "object")
|
|
390
|
+
return value;
|
|
391
|
+
const out = {};
|
|
392
|
+
for (const key of Object.keys(value).sort()) {
|
|
393
|
+
out[key] = sortValue(value[key]);
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
async function buildLocalEntry(db, cruise) {
|
|
398
|
+
// Resolve ship name. Falls back to "—" when no default ship is set; storefront
|
|
399
|
+
// can hide rows without a ship if it cares, but most local cruises have one.
|
|
400
|
+
let shipName = "—";
|
|
401
|
+
if (cruise.defaultShipId) {
|
|
402
|
+
const [ship] = await db
|
|
403
|
+
.select({ name: cruiseShips.name })
|
|
404
|
+
.from(cruiseShips)
|
|
405
|
+
.where(eq(cruiseShips.id, cruise.defaultShipId))
|
|
406
|
+
.limit(1);
|
|
407
|
+
if (ship)
|
|
408
|
+
shipName = ship.name;
|
|
409
|
+
}
|
|
410
|
+
// Aggregate over sailings + prices in two parallel queries.
|
|
411
|
+
const [dateAgg] = await db
|
|
412
|
+
.select({
|
|
413
|
+
earliest: sql `MIN(${cruiseSailings.departureDate})`,
|
|
414
|
+
latest: sql `MAX(${cruiseSailings.departureDate})`,
|
|
415
|
+
count: sql `COUNT(*)::int`,
|
|
416
|
+
})
|
|
417
|
+
.from(cruiseSailings)
|
|
418
|
+
.where(eq(cruiseSailings.cruiseId, cruise.id));
|
|
419
|
+
const [priceAgg] = await db
|
|
420
|
+
.select({
|
|
421
|
+
lowestCents: sql `MIN(ROUND(${cruisePrices.pricePerPerson}::numeric * 100))::int`,
|
|
422
|
+
currency: sql `(ARRAY_AGG(${cruisePrices.currency} ORDER BY ${cruisePrices.pricePerPerson}::numeric ASC))[1]`,
|
|
423
|
+
})
|
|
424
|
+
.from(cruisePrices)
|
|
425
|
+
.innerJoin(cruiseSailings, eq(cruisePrices.sailingId, cruiseSailings.id))
|
|
426
|
+
.where(
|
|
427
|
+
// agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
428
|
+
and(eq(cruiseSailings.cruiseId, cruise.id), sql `${cruisePrices.availability} <> 'sold_out'`));
|
|
429
|
+
// Sales status is a coarse roll-up: if any sailing is open, the cruise is open.
|
|
430
|
+
const [salesAgg] = await db
|
|
431
|
+
.select({
|
|
432
|
+
hasOpen: sql `bool_or(${cruiseSailings.salesStatus} = 'open')`,
|
|
433
|
+
})
|
|
434
|
+
.from(cruiseSailings)
|
|
435
|
+
.where(eq(cruiseSailings.cruiseId, cruise.id));
|
|
436
|
+
const salesStatus = salesAgg?.hasOpen ? "open" : "closed";
|
|
437
|
+
return {
|
|
438
|
+
source: "local",
|
|
439
|
+
sourceProvider: null,
|
|
440
|
+
sourceRef: null,
|
|
441
|
+
localCruiseId: cruise.id,
|
|
442
|
+
slug: cruise.slug,
|
|
443
|
+
name: cruise.name,
|
|
444
|
+
cruiseType: cruise.cruiseType,
|
|
445
|
+
lineName: cruise.lineSupplierId ?? "—",
|
|
446
|
+
shipName,
|
|
447
|
+
nights: cruise.nights,
|
|
448
|
+
embarkPortCanonicalPlaceId: cruise.embarkPortCanonicalPlaceId ?? null,
|
|
449
|
+
disembarkPortCanonicalPlaceId: cruise.disembarkPortCanonicalPlaceId ?? null,
|
|
450
|
+
regionIds: cruise.regionIds ?? [],
|
|
451
|
+
waterwayIds: cruise.waterwayIds ?? [],
|
|
452
|
+
portIds: cruise.portIds ?? [],
|
|
453
|
+
countryIso: cruise.countryIso ?? [],
|
|
454
|
+
regions: cruise.regions ?? [],
|
|
455
|
+
waterways: cruise.waterways ?? [],
|
|
456
|
+
ports: cruise.ports ?? [],
|
|
457
|
+
countries: cruise.countries ?? [],
|
|
458
|
+
themes: cruise.themes ?? [],
|
|
459
|
+
earliestDeparture: dateAgg?.earliest ?? null,
|
|
460
|
+
latestDeparture: dateAgg?.latest ?? null,
|
|
461
|
+
departureCount: dateAgg?.count ?? null,
|
|
462
|
+
lowestPriceCents: priceAgg?.lowestCents ?? moneyStringToCents(cruise.lowestPriceCached ?? null) ?? null,
|
|
463
|
+
lowestPriceCurrency: priceAgg?.currency ?? cruise.lowestPriceCurrencyCached ?? null,
|
|
464
|
+
salesStatus,
|
|
465
|
+
heroImageUrl: cruise.heroImageUrl ?? null,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EventBus } from "@voyant-travel/core";
|
|
2
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
+
export declare const setUpdated: {
|
|
4
|
+
updatedAt: Date;
|
|
5
|
+
};
|
|
6
|
+
export declare function paginate(query: {
|
|
7
|
+
limit: number;
|
|
8
|
+
offset: number;
|
|
9
|
+
}): {
|
|
10
|
+
limit: number;
|
|
11
|
+
offset: number;
|
|
12
|
+
};
|
|
13
|
+
export interface CruiseMutationRuntime {
|
|
14
|
+
eventBus?: EventBus;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Re-project a cruise into cruise_search_index after a mutation. Errors are
|
|
18
|
+
* swallowed and logged; the search index is best-effort and never blocks the
|
|
19
|
+
* underlying mutation.
|
|
20
|
+
*/
|
|
21
|
+
export declare function reprojectIfPossible(db: PostgresJsDatabase, cruiseId: string | null): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=service-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,eAAO,MAAM,UAAU;;CAA4B,CAAA;AAEnD,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;EAEhE;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,kBAAkB,EACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const setUpdated = { updatedAt: new Date() };
|
|
2
|
+
export function paginate(query) {
|
|
3
|
+
return { limit: query.limit, offset: query.offset };
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Re-project a cruise into cruise_search_index after a mutation. Errors are
|
|
7
|
+
* swallowed and logged; the search index is best-effort and never blocks the
|
|
8
|
+
* underlying mutation.
|
|
9
|
+
*/
|
|
10
|
+
export async function reprojectIfPossible(db, cruiseId) {
|
|
11
|
+
if (!cruiseId)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
const { cruisesSearchService } = await import("./service-search.js");
|
|
15
|
+
await cruisesSearchService.projectLocalCruise(db, cruiseId);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
// Don't crash the caller. Operators can run the search-index rebuild route to repair drift.
|
|
19
|
+
// eslint-disable-next-line no-console -- owner: cruises; existing suppression is intentional pending typed cleanup.
|
|
20
|
+
console.warn(`[cruises] search-index projection failed for ${cruiseId}:`, err);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { CruiseCabin, CruiseCabinCategory, CruiseDeck, CruiseShip } from "./schema-cabins.js";
|
|
3
|
+
import type { InsertCabin, InsertCabinCategory, InsertDeck, InsertShip, ShipListQuery, UpdateCabin, UpdateCabinCategory, UpdateDeck, UpdateShip } from "./validation-cabins.js";
|
|
4
|
+
export declare const cruiseShipService: {
|
|
5
|
+
listShips(db: PostgresJsDatabase, query: ShipListQuery): Promise<{
|
|
6
|
+
data: {
|
|
7
|
+
id: string;
|
|
8
|
+
lineSupplierId: string | null;
|
|
9
|
+
name: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
shipType: "ocean" | "river" | "expedition" | "coastal" | "yacht" | "sailing";
|
|
12
|
+
capacityGuests: number | null;
|
|
13
|
+
capacityCrew: number | null;
|
|
14
|
+
cabinCount: number | null;
|
|
15
|
+
deckCount: number | null;
|
|
16
|
+
lengthMeters: string | null;
|
|
17
|
+
cruisingSpeedKnots: string | null;
|
|
18
|
+
yearBuilt: number | null;
|
|
19
|
+
yearRefurbished: number | null;
|
|
20
|
+
imo: string | null;
|
|
21
|
+
description: string | null;
|
|
22
|
+
deckPlanUrl: string | null;
|
|
23
|
+
gallery: string[] | null;
|
|
24
|
+
amenities: Record<string, unknown> | null;
|
|
25
|
+
externalRefs: Record<string, string> | null;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
updatedAt: Date;
|
|
29
|
+
}[];
|
|
30
|
+
total: number;
|
|
31
|
+
limit: number;
|
|
32
|
+
offset: number;
|
|
33
|
+
}>;
|
|
34
|
+
getShipById(db: PostgresJsDatabase, id: string): Promise<CruiseShip | null>;
|
|
35
|
+
createShip(db: PostgresJsDatabase, data: InsertShip): Promise<CruiseShip>;
|
|
36
|
+
updateShip(db: PostgresJsDatabase, id: string, data: UpdateShip): Promise<CruiseShip | null>;
|
|
37
|
+
listShipDecks(db: PostgresJsDatabase, shipId: string): Promise<CruiseDeck[]>;
|
|
38
|
+
upsertDeck(db: PostgresJsDatabase, data: InsertDeck): Promise<CruiseDeck>;
|
|
39
|
+
updateDeck(db: PostgresJsDatabase, id: string, data: UpdateDeck): Promise<CruiseDeck | null>;
|
|
40
|
+
listShipCabinCategories(db: PostgresJsDatabase, shipId: string): Promise<CruiseCabinCategory[]>;
|
|
41
|
+
upsertCabinCategory(db: PostgresJsDatabase, data: InsertCabinCategory): Promise<CruiseCabinCategory>;
|
|
42
|
+
updateCabinCategory(db: PostgresJsDatabase, id: string, data: UpdateCabinCategory): Promise<CruiseCabinCategory | null>;
|
|
43
|
+
listCabinsByCategory(db: PostgresJsDatabase, categoryId: string): Promise<CruiseCabin[]>;
|
|
44
|
+
upsertCabin(db: PostgresJsDatabase, data: InsertCabin): Promise<CruiseCabin>;
|
|
45
|
+
updateCabin(db: PostgresJsDatabase, id: string, data: UpdateCabin): Promise<CruiseCabin | null>;
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=service-ships.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-ships.d.ts","sourceRoot":"","sources":["../src/service-ships.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAGlG,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,UAAU,EACV,UAAU,EACV,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,UAAU,EACV,UAAU,EACX,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,iBAAiB;kBACR,kBAAkB,SAAS,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAuBtC,kBAAkB,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;mBAK5D,kBAAkB,QAAQ,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;mBAOzE,kBAAkB,MAClB,MAAM,QACJ,UAAU,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;sBASL,kBAAkB,UAAU,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;mBAQ7D,kBAAkB,QAAQ,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;mBAqBzE,kBAAkB,MAClB,MAAM,QACJ,UAAU,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;gCAUvB,kBAAkB,UACd,MAAM,GACb,OAAO,CAAC,mBAAmB,EAAE,CAAC;4BAS3B,kBAAkB,QAChB,mBAAmB,GACxB,OAAO,CAAC,mBAAmB,CAAC;4BA0BzB,kBAAkB,MAClB,MAAM,QACJ,mBAAmB,GACxB,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;6BASP,kBAAkB,cAAc,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;oBAQxE,kBAAkB,QAAQ,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;oBA0B5E,kBAAkB,MAClB,MAAM,QACJ,WAAW,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;CAU/B,CAAA"}
|