@voyantjs/bookings 0.9.0 → 0.10.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.
Files changed (47) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -0
  4. package/dist/markets-ref.d.ts +151 -0
  5. package/dist/markets-ref.d.ts.map +1 -0
  6. package/dist/markets-ref.js +19 -0
  7. package/dist/pii-redaction.d.ts +89 -0
  8. package/dist/pii-redaction.d.ts.map +1 -0
  9. package/dist/pii-redaction.js +120 -0
  10. package/dist/pii.d.ts +1 -0
  11. package/dist/pii.d.ts.map +1 -1
  12. package/dist/pii.js +20 -1
  13. package/dist/routes-groups.d.ts +3 -2
  14. package/dist/routes-groups.d.ts.map +1 -1
  15. package/dist/routes-public.d.ts +11 -13
  16. package/dist/routes-public.d.ts.map +1 -1
  17. package/dist/routes-public.js +3 -3
  18. package/dist/routes.d.ts +16 -8
  19. package/dist/routes.d.ts.map +1 -1
  20. package/dist/routes.js +57 -9
  21. package/dist/schema/travel-details.d.ts +37 -0
  22. package/dist/schema/travel-details.d.ts.map +1 -1
  23. package/dist/schema/travel-details.js +6 -0
  24. package/dist/schema-core.d.ts +17 -17
  25. package/dist/schema-core.d.ts.map +1 -1
  26. package/dist/schema-core.js +8 -2
  27. package/dist/schema-items.d.ts.map +1 -1
  28. package/dist/schema-items.js +6 -1
  29. package/dist/service-public.d.ts +0 -6
  30. package/dist/service-public.d.ts.map +1 -1
  31. package/dist/service-public.js +0 -4
  32. package/dist/service.d.ts +55 -46
  33. package/dist/service.d.ts.map +1 -1
  34. package/dist/service.js +288 -89
  35. package/dist/state-machine.d.ts +29 -0
  36. package/dist/state-machine.d.ts.map +1 -0
  37. package/dist/state-machine.js +39 -0
  38. package/dist/validation-public.d.ts +0 -6
  39. package/dist/validation-public.d.ts.map +1 -1
  40. package/dist/validation-public.js +0 -2
  41. package/dist/validation.d.ts +0 -4
  42. package/dist/validation.d.ts.map +1 -1
  43. package/dist/validation.js +0 -2
  44. package/dist/workflows/refund-booking.d.ts +87 -0
  45. package/dist/workflows/refund-booking.d.ts.map +1 -0
  46. package/dist/workflows/refund-booking.js +210 -0
  47. package/package.json +7 -6
package/dist/index.d.ts CHANGED
@@ -2,9 +2,11 @@ import type { LinkableDefinition, Module } from "@voyantjs/core";
2
2
  import type { HonoModule } from "@voyantjs/hono/module";
3
3
  export { bookingsSupplierExtension } from "./extensions/suppliers.js";
4
4
  export { type BookingPiiAuditEvent, type BookingPiiService, type BookingPiiServiceOptions, createBookingPiiService, type UpsertBookingTravelerTravelDetailInput, } from "./pii.js";
5
+ export { type PiiAccessContext, redactBookingContact, redactEmail, redactPhone, redactString, redactTravelerIdentity, shouldRevealBookingPii, } from "./pii-redaction.js";
5
6
  export type { ConvertProductData } from "./service.js";
6
7
  export { bookingsService } from "./service.js";
7
8
  export { type AddBookingGroupMemberInput, type BookingGroupListQuery, type BookingGroupMemberWithBooking, bookingGroupsService, type CreateBookingGroupInput, listGroupBookingTravelers, type UpdateBookingGroupInput, } from "./service-groups.js";
9
+ export { BOOKING_TRANSITIONS, type BookingStatus, type BookingStatusPatch, BookingTransitionError, canTransitionBooking, transitionBooking, } from "./state-machine.js";
8
10
  export { type ExpireStaleBookingHoldsInput, type ExpireStaleBookingHoldsResult, expireStaleBookingHolds, } from "./tasks/index.js";
9
11
  export declare const bookingLinkable: LinkableDefinition;
10
12
  export declare const bookingsLinkable: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3,8 +3,10 @@ import { bookingRoutes } from "./routes.js";
3
3
  import { publicBookingRoutes } from "./routes-public.js";
4
4
  export { bookingsSupplierExtension } from "./extensions/suppliers.js";
5
5
  export { createBookingPiiService, } from "./pii.js";
6
+ export { redactBookingContact, redactEmail, redactPhone, redactString, redactTravelerIdentity, shouldRevealBookingPii, } from "./pii-redaction.js";
6
7
  export { bookingsService } from "./service.js";
7
8
  export { bookingGroupsService, listGroupBookingTravelers, } from "./service-groups.js";
9
+ export { BOOKING_TRANSITIONS, BookingTransitionError, canTransitionBooking, transitionBooking, } from "./state-machine.js";
8
10
  export { expireStaleBookingHolds, } from "./tasks/index.js";
9
11
  export const bookingLinkable = {
10
12
  module: "bookings",
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Local reference to `markets.exchange_rates`. Bookings reads this
3
+ * table to convert per-item totals into the booking's base currency,
4
+ * but doesn't pull the markets package as a hard dep — the FK rule
5
+ * (intra-domain FKs OK, cross-domain MUST use plain text + links)
6
+ * means we mirror the columns we need with a `Ref`.
7
+ */
8
+ export declare const exchangeRatesRef: import("drizzle-orm/pg-core").PgTableWithColumns<{
9
+ name: "exchange_rates";
10
+ schema: undefined;
11
+ columns: {
12
+ id: import("drizzle-orm/pg-core").PgColumn<{
13
+ name: string;
14
+ tableName: "exchange_rates";
15
+ dataType: "string";
16
+ columnType: "PgText";
17
+ data: string;
18
+ driverParam: string;
19
+ notNull: true;
20
+ hasDefault: true;
21
+ isPrimaryKey: true;
22
+ isAutoincrement: false;
23
+ hasRuntimeDefault: true;
24
+ enumValues: [string, ...string[]];
25
+ baseColumn: never;
26
+ identity: undefined;
27
+ generated: undefined;
28
+ }, {}, {}>;
29
+ fxRateSetId: import("drizzle-orm/pg-core").PgColumn<{
30
+ name: string;
31
+ tableName: "exchange_rates";
32
+ dataType: "string";
33
+ columnType: "PgText";
34
+ data: string;
35
+ driverParam: string;
36
+ notNull: true;
37
+ hasDefault: false;
38
+ isPrimaryKey: false;
39
+ isAutoincrement: false;
40
+ hasRuntimeDefault: false;
41
+ enumValues: [string, ...string[]];
42
+ baseColumn: never;
43
+ identity: undefined;
44
+ generated: undefined;
45
+ }, {}, {}>;
46
+ baseCurrency: import("drizzle-orm/pg-core").PgColumn<{
47
+ name: "base_currency";
48
+ tableName: "exchange_rates";
49
+ dataType: "string";
50
+ columnType: "PgText";
51
+ data: string;
52
+ driverParam: string;
53
+ notNull: true;
54
+ hasDefault: false;
55
+ isPrimaryKey: false;
56
+ isAutoincrement: false;
57
+ hasRuntimeDefault: false;
58
+ enumValues: [string, ...string[]];
59
+ baseColumn: never;
60
+ identity: undefined;
61
+ generated: undefined;
62
+ }, {}, {}>;
63
+ quoteCurrency: import("drizzle-orm/pg-core").PgColumn<{
64
+ name: "quote_currency";
65
+ tableName: "exchange_rates";
66
+ dataType: "string";
67
+ columnType: "PgText";
68
+ data: string;
69
+ driverParam: string;
70
+ notNull: true;
71
+ hasDefault: false;
72
+ isPrimaryKey: false;
73
+ isAutoincrement: false;
74
+ hasRuntimeDefault: false;
75
+ enumValues: [string, ...string[]];
76
+ baseColumn: never;
77
+ identity: undefined;
78
+ generated: undefined;
79
+ }, {}, {}>;
80
+ rateDecimal: import("drizzle-orm/pg-core").PgColumn<{
81
+ name: "rate_decimal";
82
+ tableName: "exchange_rates";
83
+ dataType: "string";
84
+ columnType: "PgNumeric";
85
+ data: string;
86
+ driverParam: string;
87
+ notNull: true;
88
+ hasDefault: false;
89
+ isPrimaryKey: false;
90
+ isAutoincrement: false;
91
+ hasRuntimeDefault: false;
92
+ enumValues: undefined;
93
+ baseColumn: never;
94
+ identity: undefined;
95
+ generated: undefined;
96
+ }, {}, {}>;
97
+ inverseRateDecimal: import("drizzle-orm/pg-core").PgColumn<{
98
+ name: "inverse_rate_decimal";
99
+ tableName: "exchange_rates";
100
+ dataType: "string";
101
+ columnType: "PgNumeric";
102
+ data: string;
103
+ driverParam: string;
104
+ notNull: false;
105
+ hasDefault: false;
106
+ isPrimaryKey: false;
107
+ isAutoincrement: false;
108
+ hasRuntimeDefault: false;
109
+ enumValues: undefined;
110
+ baseColumn: never;
111
+ identity: undefined;
112
+ generated: undefined;
113
+ }, {}, {}>;
114
+ observedAt: import("drizzle-orm/pg-core").PgColumn<{
115
+ name: "observed_at";
116
+ tableName: "exchange_rates";
117
+ dataType: "date";
118
+ columnType: "PgTimestamp";
119
+ data: Date;
120
+ driverParam: string;
121
+ notNull: false;
122
+ hasDefault: false;
123
+ isPrimaryKey: false;
124
+ isAutoincrement: false;
125
+ hasRuntimeDefault: false;
126
+ enumValues: undefined;
127
+ baseColumn: never;
128
+ identity: undefined;
129
+ generated: undefined;
130
+ }, {}, {}>;
131
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
132
+ name: "created_at";
133
+ tableName: "exchange_rates";
134
+ dataType: "date";
135
+ columnType: "PgTimestamp";
136
+ data: Date;
137
+ driverParam: string;
138
+ notNull: true;
139
+ hasDefault: false;
140
+ isPrimaryKey: false;
141
+ isAutoincrement: false;
142
+ hasRuntimeDefault: false;
143
+ enumValues: undefined;
144
+ baseColumn: never;
145
+ identity: undefined;
146
+ generated: undefined;
147
+ }, {}, {}>;
148
+ };
149
+ dialect: "pg";
150
+ }>;
151
+ //# sourceMappingURL=markets-ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markets-ref.d.ts","sourceRoot":"","sources":["../src/markets-ref.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS3B,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { typeId, typeIdRef } from "@voyantjs/db/lib/typeid-column";
2
+ import { numeric, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
+ /**
4
+ * Local reference to `markets.exchange_rates`. Bookings reads this
5
+ * table to convert per-item totals into the booking's base currency,
6
+ * but doesn't pull the markets package as a hard dep — the FK rule
7
+ * (intra-domain FKs OK, cross-domain MUST use plain text + links)
8
+ * means we mirror the columns we need with a `Ref`.
9
+ */
10
+ export const exchangeRatesRef = pgTable("exchange_rates", {
11
+ id: typeId("exchange_rates").primaryKey(),
12
+ fxRateSetId: typeIdRef("fx_rate_set_id").notNull(),
13
+ baseCurrency: text("base_currency").notNull(),
14
+ quoteCurrency: text("quote_currency").notNull(),
15
+ rateDecimal: numeric("rate_decimal", { precision: 18, scale: 8 }).notNull(),
16
+ inverseRateDecimal: numeric("inverse_rate_decimal", { precision: 18, scale: 8 }),
17
+ observedAt: timestamp("observed_at", { withTimezone: true }),
18
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
19
+ });
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Booking PII redaction.
3
+ *
4
+ * Booking rows and traveler rows store contact identifiers (name, email,
5
+ * phone) as plaintext columns so the operator UI can search and sort. The
6
+ * `bookings-pii:*` scope (or `*` superuser scope) gates the right to see
7
+ * those identifiers verbatim. Callers without that scope receive a redacted
8
+ * shape that retains enough signal for operational triage but not enough to
9
+ * exfiltrate a full contact list.
10
+ *
11
+ * **Scope of this module:** route-layer redaction at the API boundary.
12
+ * Internal callers (cron jobs, workflows, in-process tasks) bypass redaction
13
+ * because they need the full record to do their job.
14
+ *
15
+ * **What this does NOT do:** encrypt the columns at rest. That is a
16
+ * follow-up requiring a schema migration + a search-tokenisation strategy
17
+ * (see issue #283 follow-up). For now plaintext-on-disk + redact-in-flight
18
+ * is the documented posture.
19
+ */
20
+ export interface PiiAccessContext {
21
+ actor?: string | null;
22
+ scopes?: string[] | null;
23
+ callerType?: string | null;
24
+ isInternalRequest?: boolean;
25
+ }
26
+ /**
27
+ * Returns true when the caller has earned the right to see PII in the clear.
28
+ *
29
+ * Internal requests (server-to-server inside the trust boundary) and API
30
+ * keys with explicit `bookings-pii:read` scope (or superuser `*`) reveal.
31
+ * Plain staff sessions WITHOUT the scope do NOT reveal — staff are
32
+ * authorised to see *some* booking data but not necessarily the contact
33
+ * identifiers. This makes "give the new agent a read-only role" safe by
34
+ * default; granting `bookings-pii:read` is an explicit decision.
35
+ */
36
+ export declare function shouldRevealBookingPii(ctx: PiiAccessContext): boolean;
37
+ /**
38
+ * Mask the local-part of an email, preserving the domain so the operator
39
+ * can tell at a glance which provider is involved.
40
+ *
41
+ * `alice@example.com` → `a***e@example.com`
42
+ * `bo@example.com` → `**@example.com`
43
+ */
44
+ export declare function redactEmail(email: string | null | undefined): string | null;
45
+ /**
46
+ * Mask all but the last four digits of a phone number, dropping
47
+ * non-digit characters from the masked region so the result is recognisable
48
+ * as a phone fragment.
49
+ *
50
+ * `+40 712 345 678` → `***5678`
51
+ */
52
+ export declare function redactPhone(phone: string | null | undefined): string | null;
53
+ /**
54
+ * Masks an arbitrary identifier like a postal address or city to a single-
55
+ * char marker. We keep the field present so client schemas don't break,
56
+ * but the value is effectively absent.
57
+ */
58
+ export declare function redactString(value: string | null | undefined): string | null;
59
+ /**
60
+ * Booking row contact-PII redaction. Returns a shallow copy with the
61
+ * `contact*` columns masked. Caller is responsible for not running this on
62
+ * already-redacted rows.
63
+ */
64
+ export declare function redactBookingContact<T extends {
65
+ contactFirstName?: string | null;
66
+ contactLastName?: string | null;
67
+ contactEmail?: string | null;
68
+ contactPhone?: string | null;
69
+ contactAddressLine1?: string | null;
70
+ contactPostalCode?: string | null;
71
+ }>(row: T): T;
72
+ /**
73
+ * Traveler row redaction. Same shape as `redactBookingContact` but for
74
+ * traveler identity columns.
75
+ *
76
+ * `accessibilityNeeds` is intentionally NOT in the redacted set — it was
77
+ * moved to the encrypted `bookingTravelerTravelDetails` table (#283) and
78
+ * is only ever returned through `createBookingPiiService` after
79
+ * decryption + audit. It's never present on this row shape.
80
+ */
81
+ export declare function redactTravelerIdentity<T extends {
82
+ firstName?: string | null;
83
+ lastName?: string | null;
84
+ email?: string | null;
85
+ phone?: string | null;
86
+ specialRequests?: string | null;
87
+ notes?: string | null;
88
+ }>(row: T): T;
89
+ //# sourceMappingURL=pii-redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii-redaction.d.ts","sourceRoot":"","sources":["../src/pii-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAQrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQ3E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK3E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,SAAS;IACR,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,EACD,GAAG,EAAE,CAAC,GAAG,CAAC,CAUX;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,SAAS;IACR,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,EACD,GAAG,EAAE,CAAC,GAAG,CAAC,CAUX"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Booking PII redaction.
3
+ *
4
+ * Booking rows and traveler rows store contact identifiers (name, email,
5
+ * phone) as plaintext columns so the operator UI can search and sort. The
6
+ * `bookings-pii:*` scope (or `*` superuser scope) gates the right to see
7
+ * those identifiers verbatim. Callers without that scope receive a redacted
8
+ * shape that retains enough signal for operational triage but not enough to
9
+ * exfiltrate a full contact list.
10
+ *
11
+ * **Scope of this module:** route-layer redaction at the API boundary.
12
+ * Internal callers (cron jobs, workflows, in-process tasks) bypass redaction
13
+ * because they need the full record to do their job.
14
+ *
15
+ * **What this does NOT do:** encrypt the columns at rest. That is a
16
+ * follow-up requiring a schema migration + a search-tokenisation strategy
17
+ * (see issue #283 follow-up). For now plaintext-on-disk + redact-in-flight
18
+ * is the documented posture.
19
+ */
20
+ const PII_SCOPE_ANY = "bookings-pii:*";
21
+ const PII_SCOPE_READ = "bookings-pii:read";
22
+ const SUPERUSER_SCOPE = "*";
23
+ /**
24
+ * Returns true when the caller has earned the right to see PII in the clear.
25
+ *
26
+ * Internal requests (server-to-server inside the trust boundary) and API
27
+ * keys with explicit `bookings-pii:read` scope (or superuser `*`) reveal.
28
+ * Plain staff sessions WITHOUT the scope do NOT reveal — staff are
29
+ * authorised to see *some* booking data but not necessarily the contact
30
+ * identifiers. This makes "give the new agent a read-only role" safe by
31
+ * default; granting `bookings-pii:read` is an explicit decision.
32
+ */
33
+ export function shouldRevealBookingPii(ctx) {
34
+ if (ctx.isInternalRequest)
35
+ return true;
36
+ const scopes = ctx.scopes ?? [];
37
+ return (scopes.includes(SUPERUSER_SCOPE) ||
38
+ scopes.includes(PII_SCOPE_ANY) ||
39
+ scopes.includes(PII_SCOPE_READ));
40
+ }
41
+ /**
42
+ * Mask the local-part of an email, preserving the domain so the operator
43
+ * can tell at a glance which provider is involved.
44
+ *
45
+ * `alice@example.com` → `a***e@example.com`
46
+ * `bo@example.com` → `**@example.com`
47
+ */
48
+ export function redactEmail(email) {
49
+ if (email == null)
50
+ return email ?? null;
51
+ const at = email.lastIndexOf("@");
52
+ if (at < 1)
53
+ return "***";
54
+ const local = email.slice(0, at);
55
+ const domain = email.slice(at);
56
+ if (local.length <= 2)
57
+ return `${"*".repeat(local.length)}${domain}`;
58
+ return `${local[0]}***${local[local.length - 1]}${domain}`;
59
+ }
60
+ /**
61
+ * Mask all but the last four digits of a phone number, dropping
62
+ * non-digit characters from the masked region so the result is recognisable
63
+ * as a phone fragment.
64
+ *
65
+ * `+40 712 345 678` → `***5678`
66
+ */
67
+ export function redactPhone(phone) {
68
+ if (phone == null)
69
+ return phone ?? null;
70
+ const digits = phone.replace(/\D/g, "");
71
+ if (digits.length <= 4)
72
+ return "***";
73
+ return `***${digits.slice(-4)}`;
74
+ }
75
+ /**
76
+ * Masks an arbitrary identifier like a postal address or city to a single-
77
+ * char marker. We keep the field present so client schemas don't break,
78
+ * but the value is effectively absent.
79
+ */
80
+ export function redactString(value) {
81
+ if (value == null)
82
+ return value ?? null;
83
+ return value.length === 0 ? value : "***";
84
+ }
85
+ /**
86
+ * Booking row contact-PII redaction. Returns a shallow copy with the
87
+ * `contact*` columns masked. Caller is responsible for not running this on
88
+ * already-redacted rows.
89
+ */
90
+ export function redactBookingContact(row) {
91
+ return {
92
+ ...row,
93
+ contactFirstName: redactString(row.contactFirstName),
94
+ contactLastName: redactString(row.contactLastName),
95
+ contactEmail: redactEmail(row.contactEmail),
96
+ contactPhone: redactPhone(row.contactPhone),
97
+ contactAddressLine1: redactString(row.contactAddressLine1),
98
+ contactPostalCode: redactString(row.contactPostalCode),
99
+ };
100
+ }
101
+ /**
102
+ * Traveler row redaction. Same shape as `redactBookingContact` but for
103
+ * traveler identity columns.
104
+ *
105
+ * `accessibilityNeeds` is intentionally NOT in the redacted set — it was
106
+ * moved to the encrypted `bookingTravelerTravelDetails` table (#283) and
107
+ * is only ever returned through `createBookingPiiService` after
108
+ * decryption + audit. It's never present on this row shape.
109
+ */
110
+ export function redactTravelerIdentity(row) {
111
+ return {
112
+ ...row,
113
+ firstName: redactString(row.firstName),
114
+ lastName: redactString(row.lastName),
115
+ email: redactEmail(row.email),
116
+ phone: redactPhone(row.phone),
117
+ specialRequests: redactString(row.specialRequests),
118
+ notes: redactString(row.notes),
119
+ };
120
+ }
package/dist/pii.d.ts CHANGED
@@ -7,6 +7,7 @@ export interface UpsertBookingTravelerTravelDetailInput {
7
7
  passportExpiry?: string | null;
8
8
  dateOfBirth?: string | null;
9
9
  dietaryRequirements?: string | null;
10
+ accessibilityNeeds?: string | null;
10
11
  isLeadTraveler?: boolean | null;
11
12
  }
12
13
  export interface BookingPiiAuditEvent {
package/dist/pii.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pii.d.ts","sourceRoot":"","sources":["../src/pii.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAG1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAIL,KAAK,oCAAoC,EAC1C,MAAM,4BAA4B,CAAA;AAGnC,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,WAAW,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAED,MAAM,WAAW,iBAAiB;IAChC,wBAAwB,CACtB,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,oCAAoC,GAAG,IAAI,CAAC,CAAA;IACvD,2BAA2B,CACzB,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,sCAAsC,EAC7C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,oCAAoC,GAAG,IAAI,CAAC,CAAA;IACvD,2BAA2B,CACzB,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;CAC1C;AAqGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAuH5F"}
1
+ {"version":3,"file":"pii.d.ts","sourceRoot":"","sources":["../src/pii.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAG1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAKL,KAAK,oCAAoC,EAC1C,MAAM,4BAA4B,CAAA;AAGnC,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,WAAW,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAED,MAAM,WAAW,iBAAiB;IAChC,wBAAwB,CACtB,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,oCAAoC,GAAG,IAAI,CAAC,CAAA;IACvD,2BAA2B,CACzB,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,sCAAsC,EAC7C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,oCAAoC,GAAG,IAAI,CAAC,CAAA;IACvD,2BAA2B,CACzB,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;CAC1C;AA4HD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAqI5F"}
package/dist/pii.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { decryptOptionalJsonEnvelope, encryptOptionalJsonEnvelope } from "@voyantjs/utils";
2
2
  import { eq } from "drizzle-orm";
3
- import { bookingTravelerDietarySchema, bookingTravelerIdentitySchema, bookingTravelerTravelDetails, } from "./schema/travel-details.js";
3
+ import { bookingTravelerAccessibilitySchema, bookingTravelerDietarySchema, bookingTravelerIdentitySchema, bookingTravelerTravelDetails, } from "./schema/travel-details.js";
4
4
  import { bookingTravelers } from "./schema.js";
5
5
  function buildIdentityPayload(input) {
6
6
  const payload = bookingTravelerIdentitySchema.parse({
@@ -26,6 +26,15 @@ function buildDietaryPayload(input) {
26
26
  }
27
27
  return payload;
28
28
  }
29
+ function buildAccessibilityPayload(input) {
30
+ const payload = bookingTravelerAccessibilitySchema.parse({
31
+ accessibilityNeeds: input.accessibilityNeeds ?? null,
32
+ });
33
+ if (!payload.accessibilityNeeds) {
34
+ return null;
35
+ }
36
+ return payload;
37
+ }
29
38
  async function loadExistingTravelDetails(db, travelerId, options, keyRef) {
30
39
  const [row] = await db
31
40
  .select()
@@ -37,12 +46,14 @@ async function loadExistingTravelDetails(db, travelerId, options, keyRef) {
37
46
  }
38
47
  const identity = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.identityEncrypted, bookingTravelerIdentitySchema);
39
48
  const dietary = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.dietaryEncrypted, bookingTravelerDietarySchema);
49
+ const accessibility = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.accessibilityEncrypted, bookingTravelerAccessibilitySchema);
40
50
  return {
41
51
  nationality: identity?.nationality ?? null,
42
52
  passportNumber: identity?.passportNumber ?? null,
43
53
  passportExpiry: identity?.passportExpiry ?? null,
44
54
  dateOfBirth: identity?.dateOfBirth ?? null,
45
55
  dietaryRequirements: dietary?.dietaryRequirements ?? null,
56
+ accessibilityNeeds: accessibility?.accessibilityNeeds ?? null,
46
57
  isLeadTraveler: row.isLeadTraveler,
47
58
  };
48
59
  }
@@ -59,6 +70,9 @@ function mergeTravelDetailInput(existing, input) {
59
70
  dietaryRequirements: input.dietaryRequirements === undefined
60
71
  ? (existing?.dietaryRequirements ?? null)
61
72
  : input.dietaryRequirements,
73
+ accessibilityNeeds: input.accessibilityNeeds === undefined
74
+ ? (existing?.accessibilityNeeds ?? null)
75
+ : input.accessibilityNeeds,
62
76
  isLeadTraveler: input.isLeadTraveler === undefined
63
77
  ? (existing?.isLeadTraveler ?? false)
64
78
  : input.isLeadTraveler,
@@ -78,6 +92,7 @@ export function createBookingPiiService(options) {
78
92
  }
79
93
  const identity = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.identityEncrypted, bookingTravelerIdentitySchema);
80
94
  const dietary = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.dietaryEncrypted, bookingTravelerDietarySchema);
95
+ const accessibility = await decryptOptionalJsonEnvelope(options.kms, keyRef, row.accessibilityEncrypted, bookingTravelerAccessibilitySchema);
81
96
  await options.onAudit?.({ action: "decrypt", travelerId, actorId });
82
97
  return {
83
98
  travelerId: row.travelerId,
@@ -86,6 +101,7 @@ export function createBookingPiiService(options) {
86
101
  passportExpiry: identity?.passportExpiry ?? null,
87
102
  dateOfBirth: identity?.dateOfBirth ?? null,
88
103
  dietaryRequirements: dietary?.dietaryRequirements ?? null,
104
+ accessibilityNeeds: accessibility?.accessibilityNeeds ?? null,
89
105
  isLeadTraveler: row.isLeadTraveler,
90
106
  createdAt: row.createdAt,
91
107
  updatedAt: row.updatedAt,
@@ -104,6 +120,7 @@ export function createBookingPiiService(options) {
104
120
  const mergedInput = mergeTravelDetailInput(existing, input);
105
121
  const identityEncrypted = await encryptOptionalJsonEnvelope(options.kms, keyRef, buildIdentityPayload(mergedInput));
106
122
  const dietaryEncrypted = await encryptOptionalJsonEnvelope(options.kms, keyRef, buildDietaryPayload(mergedInput));
123
+ const accessibilityEncrypted = await encryptOptionalJsonEnvelope(options.kms, keyRef, buildAccessibilityPayload(mergedInput));
107
124
  const now = new Date();
108
125
  await db
109
126
  .insert(bookingTravelerTravelDetails)
@@ -111,6 +128,7 @@ export function createBookingPiiService(options) {
111
128
  travelerId,
112
129
  identityEncrypted,
113
130
  dietaryEncrypted,
131
+ accessibilityEncrypted,
114
132
  isLeadTraveler: mergedInput.isLeadTraveler ?? false,
115
133
  updatedAt: now,
116
134
  })
@@ -119,6 +137,7 @@ export function createBookingPiiService(options) {
119
137
  set: {
120
138
  identityEncrypted,
121
139
  dietaryEncrypted,
140
+ accessibilityEncrypted,
122
141
  isLeadTraveler: mergedInput.isLeadTraveler ?? false,
123
142
  updatedAt: now,
124
143
  },
@@ -107,6 +107,7 @@ export declare const bookingGroupRoutes: import("hono/hono-base").HonoBase<Env,
107
107
  contactPostalCode: string | null;
108
108
  sellCurrency: string;
109
109
  baseCurrency: string | null;
110
+ fxRateSetId: string | null;
110
111
  sellAmountCents: number | null;
111
112
  baseSellAmountCents: number | null;
112
113
  costAmountCents: number | null;
@@ -243,6 +244,7 @@ export declare const bookingGroupRoutes: import("hono/hono-base").HonoBase<Env,
243
244
  contactPostalCode: string | null;
244
245
  sellCurrency: string;
245
246
  baseCurrency: string | null;
247
+ fxRateSetId: string | null;
246
248
  sellAmountCents: number | null;
247
249
  baseSellAmountCents: number | null;
248
250
  costAmountCents: number | null;
@@ -353,13 +355,12 @@ export declare const bookingGroupRoutes: import("hono/hono-base").HonoBase<Env,
353
355
  email: string | null;
354
356
  firstName: string;
355
357
  lastName: string;
356
- personId: string | null;
357
358
  bookingId: string;
359
+ personId: string | null;
358
360
  participantType: "other" | "traveler" | "occupant";
359
361
  travelerCategory: "other" | "adult" | "child" | "infant" | "senior" | null;
360
362
  phone: string | null;
361
363
  preferredLanguage: string | null;
362
- accessibilityNeeds: string | null;
363
364
  specialRequests: string | null;
364
365
  isPrimary: boolean;
365
366
  }[];
@@ -1 +1 @@
1
- {"version":3,"file":"routes-groups.d.ts","sourceRoot":"","sources":["../src/routes-groups.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAQ1D,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;QAChE,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA0E3B,CAAA;AAEJ,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAA"}
1
+ {"version":3,"file":"routes-groups.d.ts","sourceRoot":"","sources":["../src/routes-groups.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAQ1D,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;QAChE,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA0E3B,CAAA;AAEJ,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAA"}
@@ -27,17 +27,6 @@ export declare const publicBookingRoutes: import("hono/hono-base").HonoBase<Env,
27
27
  } & {
28
28
  "/sessions/:sessionId": {
29
29
  $get: {
30
- input: {
31
- param: {
32
- sessionId: string;
33
- };
34
- };
35
- output: {
36
- error: string;
37
- };
38
- outputFormat: "json";
39
- status: 404;
40
- } | {
41
30
  input: {
42
31
  param: {
43
32
  sessionId: string;
@@ -69,7 +58,6 @@ export declare const publicBookingRoutes: import("hono/hono-base").HonoBase<Env,
69
58
  email: string | null;
70
59
  phone: string | null;
71
60
  preferredLanguage: string | null;
72
- accessibilityNeeds: string | null;
73
61
  specialRequests: string | null;
74
62
  isPrimary: boolean;
75
63
  notes: string | null;
@@ -140,6 +128,17 @@ export declare const publicBookingRoutes: import("hono/hono-base").HonoBase<Env,
140
128
  };
141
129
  outputFormat: "json";
142
130
  status: import("hono/utils/http-status").ContentfulStatusCode;
131
+ } | {
132
+ input: {
133
+ param: {
134
+ sessionId: string;
135
+ };
136
+ };
137
+ output: {
138
+ error: string;
139
+ };
140
+ outputFormat: "json";
141
+ status: 404;
143
142
  };
144
143
  };
145
144
  } & {
@@ -344,7 +343,6 @@ export declare const publicBookingRoutes: import("hono/hono-base").HonoBase<Env,
344
343
  email: string | null;
345
344
  phone: string | null;
346
345
  preferredLanguage: string | null;
347
- accessibilityNeeds: string | null;
348
346
  specialRequests: string | null;
349
347
  isPrimary: boolean;
350
348
  notes: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,oBAAoB,CAAA;AAsCvD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAmI5B,CAAA;AAEJ,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAA"}
1
+ {"version":3,"file":"routes-public.d.ts","sourceRoot":"","sources":["../src/routes-public.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,oBAAoB,CAAA;AAsCvD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAuI5B,CAAA;AAEJ,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAA"}
@@ -1,4 +1,4 @@
1
- import { parseJsonBody, parseQuery } from "@voyantjs/hono";
1
+ import { idempotencyKey, parseJsonBody, parseQuery } from "@voyantjs/hono";
2
2
  import { Hono } from "hono";
3
3
  import { notFound } from "./routes-shared.js";
4
4
  import { publicBookingsService } from "./service-public.js";
@@ -27,7 +27,7 @@ function sessionConflictError(status) {
27
27
  }
28
28
  }
29
29
  export const publicBookingRoutes = new Hono()
30
- .post("/sessions", async (c) => {
30
+ .post("/sessions", idempotencyKey({ scope: "POST /v1/public/bookings/sessions" }), async (c) => {
31
31
  const result = await publicBookingsService.createSession(c.get("db"), await parseJsonBody(c, publicCreateBookingSessionSchema), c.get("userId"));
32
32
  if (result.status === "slot_not_found") {
33
33
  return notFound(c, "Availability slot not found");
@@ -80,7 +80,7 @@ export const publicBookingRoutes = new Hono()
80
80
  },
81
81
  });
82
82
  })
83
- .post("/sessions/:sessionId/confirm", async (c) => {
83
+ .post("/sessions/:sessionId/confirm", idempotencyKey({ scope: "POST /v1/public/bookings/sessions/confirm" }), async (c) => {
84
84
  const result = await publicBookingsService.confirmSession(c.get("db"), c.req.param("sessionId"), await parseJsonBody(c, publicBookingSessionMutationSchema), c.get("userId"));
85
85
  if (result.status === "not_found") {
86
86
  return notFound(c, "Booking session not found");