@voyant-travel/distribution 0.109.8

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 (168) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +42 -0
  3. package/dist/booking-extension.d.ts +168 -0
  4. package/dist/booking-extension.d.ts.map +1 -0
  5. package/dist/booking-extension.js +102 -0
  6. package/dist/channel-push/admin-routes.d.ts +31 -0
  7. package/dist/channel-push/admin-routes.d.ts.map +1 -0
  8. package/dist/channel-push/admin-routes.js +165 -0
  9. package/dist/channel-push/availability-push.d.ts +76 -0
  10. package/dist/channel-push/availability-push.d.ts.map +1 -0
  11. package/dist/channel-push/availability-push.js +236 -0
  12. package/dist/channel-push/booking-push-helpers.d.ts +36 -0
  13. package/dist/channel-push/booking-push-helpers.d.ts.map +1 -0
  14. package/dist/channel-push/booking-push-helpers.js +169 -0
  15. package/dist/channel-push/booking-push.d.ts +108 -0
  16. package/dist/channel-push/booking-push.d.ts.map +1 -0
  17. package/dist/channel-push/booking-push.js +335 -0
  18. package/dist/channel-push/boundary-sql.d.ts +23 -0
  19. package/dist/channel-push/boundary-sql.d.ts.map +1 -0
  20. package/dist/channel-push/boundary-sql.js +75 -0
  21. package/dist/channel-push/content-push.d.ts +60 -0
  22. package/dist/channel-push/content-push.d.ts.map +1 -0
  23. package/dist/channel-push/content-push.js +252 -0
  24. package/dist/channel-push/index.d.ts +15 -0
  25. package/dist/channel-push/index.d.ts.map +1 -0
  26. package/dist/channel-push/index.js +18 -0
  27. package/dist/channel-push/plugin.d.ts +18 -0
  28. package/dist/channel-push/plugin.d.ts.map +1 -0
  29. package/dist/channel-push/plugin.js +21 -0
  30. package/dist/channel-push/reconciler.d.ts +85 -0
  31. package/dist/channel-push/reconciler.d.ts.map +1 -0
  32. package/dist/channel-push/reconciler.js +179 -0
  33. package/dist/channel-push/subscriber.d.ts +40 -0
  34. package/dist/channel-push/subscriber.d.ts.map +1 -0
  35. package/dist/channel-push/subscriber.js +199 -0
  36. package/dist/channel-push/types.d.ts +43 -0
  37. package/dist/channel-push/types.d.ts.map +1 -0
  38. package/dist/channel-push/types.js +32 -0
  39. package/dist/channel-push/workflows.d.ts +56 -0
  40. package/dist/channel-push/workflows.d.ts.map +1 -0
  41. package/dist/channel-push/workflows.js +100 -0
  42. package/dist/external-refs/index.d.ts +11 -0
  43. package/dist/external-refs/index.d.ts.map +1 -0
  44. package/dist/external-refs/index.js +12 -0
  45. package/dist/external-refs/routes.d.ts +253 -0
  46. package/dist/external-refs/routes.d.ts.map +1 -0
  47. package/dist/external-refs/routes.js +52 -0
  48. package/dist/external-refs/schema.d.ts +251 -0
  49. package/dist/external-refs/schema.d.ts.map +1 -0
  50. package/dist/external-refs/schema.js +32 -0
  51. package/dist/external-refs/service.d.ts +82 -0
  52. package/dist/external-refs/service.d.ts.map +1 -0
  53. package/dist/external-refs/service.js +112 -0
  54. package/dist/external-refs/validation.d.ts +91 -0
  55. package/dist/external-refs/validation.d.ts.map +1 -0
  56. package/dist/external-refs/validation.js +40 -0
  57. package/dist/index.d.ts +21 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +20 -0
  60. package/dist/interface-types.d.ts +128 -0
  61. package/dist/interface-types.d.ts.map +1 -0
  62. package/dist/interface-types.js +1 -0
  63. package/dist/interface.d.ts +10 -0
  64. package/dist/interface.d.ts.map +1 -0
  65. package/dist/interface.js +286 -0
  66. package/dist/rate-limit.d.ts +69 -0
  67. package/dist/rate-limit.d.ts.map +1 -0
  68. package/dist/rate-limit.js +135 -0
  69. package/dist/routes/batch.d.ts +200 -0
  70. package/dist/routes/batch.d.ts.map +1 -0
  71. package/dist/routes/batch.js +52 -0
  72. package/dist/routes/env.d.ts +8 -0
  73. package/dist/routes/env.d.ts.map +1 -0
  74. package/dist/routes/env.js +1 -0
  75. package/dist/routes/inventory.d.ts +604 -0
  76. package/dist/routes/inventory.d.ts.map +1 -0
  77. package/dist/routes/inventory.js +138 -0
  78. package/dist/routes/settlements.d.ts +1649 -0
  79. package/dist/routes/settlements.d.ts.map +1 -0
  80. package/dist/routes/settlements.js +265 -0
  81. package/dist/routes.d.ts +3909 -0
  82. package/dist/routes.d.ts.map +1 -0
  83. package/dist/routes.js +323 -0
  84. package/dist/schema-automation.d.ts +680 -0
  85. package/dist/schema-automation.d.ts.map +1 -0
  86. package/dist/schema-automation.js +76 -0
  87. package/dist/schema-core.d.ts +1674 -0
  88. package/dist/schema-core.d.ts.map +1 -0
  89. package/dist/schema-core.js +227 -0
  90. package/dist/schema-finance.d.ts +1372 -0
  91. package/dist/schema-finance.d.ts.map +1 -0
  92. package/dist/schema-finance.js +153 -0
  93. package/dist/schema-inventory.d.ts +855 -0
  94. package/dist/schema-inventory.d.ts.map +1 -0
  95. package/dist/schema-inventory.js +102 -0
  96. package/dist/schema-push-intents.d.ts +387 -0
  97. package/dist/schema-push-intents.d.ts.map +1 -0
  98. package/dist/schema-push-intents.js +77 -0
  99. package/dist/schema-relations.d.ts +95 -0
  100. package/dist/schema-relations.d.ts.map +1 -0
  101. package/dist/schema-relations.js +196 -0
  102. package/dist/schema-shared.d.ts +24 -0
  103. package/dist/schema-shared.d.ts.map +1 -0
  104. package/dist/schema-shared.js +123 -0
  105. package/dist/schema.d.ts +9 -0
  106. package/dist/schema.d.ts.map +1 -0
  107. package/dist/schema.js +8 -0
  108. package/dist/service/channels.d.ts +167 -0
  109. package/dist/service/channels.d.ts.map +1 -0
  110. package/dist/service/channels.js +305 -0
  111. package/dist/service/commercial.d.ts +385 -0
  112. package/dist/service/commercial.d.ts.map +1 -0
  113. package/dist/service/commercial.js +248 -0
  114. package/dist/service/helpers.d.ts +10 -0
  115. package/dist/service/helpers.d.ts.map +1 -0
  116. package/dist/service/helpers.js +7 -0
  117. package/dist/service/inventory.d.ts +193 -0
  118. package/dist/service/inventory.d.ts.map +1 -0
  119. package/dist/service/inventory.js +154 -0
  120. package/dist/service/settlement-policies.d.ts +325 -0
  121. package/dist/service/settlement-policies.d.ts.map +1 -0
  122. package/dist/service/settlement-policies.js +272 -0
  123. package/dist/service/settlements.d.ts +357 -0
  124. package/dist/service/settlements.d.ts.map +1 -0
  125. package/dist/service/settlements.js +319 -0
  126. package/dist/service/types.d.ts +60 -0
  127. package/dist/service/types.d.ts.map +1 -0
  128. package/dist/service/types.js +1 -0
  129. package/dist/service.d.ts +1418 -0
  130. package/dist/service.d.ts.map +1 -0
  131. package/dist/service.js +17 -0
  132. package/dist/suppliers/index.d.ts +15 -0
  133. package/dist/suppliers/index.d.ts.map +1 -0
  134. package/dist/suppliers/index.js +23 -0
  135. package/dist/suppliers/routes.d.ts +1202 -0
  136. package/dist/suppliers/routes.d.ts.map +1 -0
  137. package/dist/suppliers/routes.js +290 -0
  138. package/dist/suppliers/schema.d.ts +1272 -0
  139. package/dist/suppliers/schema.d.ts.map +1 -0
  140. package/dist/suppliers/schema.js +219 -0
  141. package/dist/suppliers/service-aggregates.d.ts +23 -0
  142. package/dist/suppliers/service-aggregates.d.ts.map +1 -0
  143. package/dist/suppliers/service-aggregates.js +51 -0
  144. package/dist/suppliers/service-core.d.ts +89 -0
  145. package/dist/suppliers/service-core.d.ts.map +1 -0
  146. package/dist/suppliers/service-core.js +164 -0
  147. package/dist/suppliers/service-identity.d.ts +162 -0
  148. package/dist/suppliers/service-identity.d.ts.map +1 -0
  149. package/dist/suppliers/service-identity.js +101 -0
  150. package/dist/suppliers/service-operations.d.ts +1500 -0
  151. package/dist/suppliers/service-operations.d.ts.map +1 -0
  152. package/dist/suppliers/service-operations.js +157 -0
  153. package/dist/suppliers/service-shared.d.ts +45 -0
  154. package/dist/suppliers/service-shared.d.ts.map +1 -0
  155. package/dist/suppliers/service-shared.js +294 -0
  156. package/dist/suppliers/service.d.ts +41 -0
  157. package/dist/suppliers/service.d.ts.map +1 -0
  158. package/dist/suppliers/service.js +40 -0
  159. package/dist/suppliers/validation.d.ts +2 -0
  160. package/dist/suppliers/validation.d.ts.map +1 -0
  161. package/dist/suppliers/validation.js +1 -0
  162. package/dist/validation.d.ts +1371 -0
  163. package/dist/validation.d.ts.map +1 -0
  164. package/dist/validation.js +445 -0
  165. package/dist/webhook-deliveries.d.ts +86 -0
  166. package/dist/webhook-deliveries.d.ts.map +1 -0
  167. package/dist/webhook-deliveries.js +296 -0
  168. package/package.json +71 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-core.d.ts","sourceRoot":"","sources":["../src/schema-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,227 @@
1
+ import { typeId, typeIdRef } from "@voyant-travel/db/lib/typeid-column";
2
+ import { sql } from "drizzle-orm";
3
+ import { boolean, date, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, } from "drizzle-orm/pg-core";
4
+ import { channelCommissionScopeEnum, channelCommissionTypeEnum, channelContractStatusEnum, channelKindEnum, channelStatusEnum, channelWebhookStatusEnum, distributionCancellationOwnerEnum, distributionPaymentOwnerEnum, } from "./schema-shared.js";
5
+ import { suppliers } from "./suppliers/schema.js";
6
+ export const channels = pgTable("channels", {
7
+ id: typeId("channels"),
8
+ name: text("name").notNull(),
9
+ description: text("description"),
10
+ kind: channelKindEnum("kind").notNull(),
11
+ status: channelStatusEnum("status").notNull().default("active"),
12
+ metadata: jsonb("metadata").$type(),
13
+ // ── Channel push: per-channel rate-limit defaults ───────────────
14
+ // Per channel-push-architecture §14.1. Contract-level rules in
15
+ // `channel_contracts` override these for specific commercial deals.
16
+ /** Sustained requests per second the channel allows (operator estimate). */
17
+ rateLimitRps: integer("rate_limit_rps"),
18
+ /** Max tokens in the bucket — controls burst capacity. */
19
+ rateLimitBurst: integer("rate_limit_burst"),
20
+ /**
21
+ * Per-priority reserve thresholds. Example:
22
+ * { "booking": 0, "availability": 0.3, "content": 0.7 }
23
+ * Reads as: bookings dispatch with any tokens; availability when
24
+ * bucket ≥ 30% full; content when ≥ 70% full. Bookings always
25
+ * pre-empt availability/content within one shared upstream budget.
26
+ */
27
+ rateLimitPriorityGates: jsonb("rate_limit_priority_gates").$type(),
28
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
29
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
30
+ }, (table) => [
31
+ index("idx_channels_created").on(table.createdAt),
32
+ index("idx_channels_kind_created").on(table.kind, table.createdAt),
33
+ index("idx_channels_status_created").on(table.status, table.createdAt),
34
+ ]);
35
+ export const channelContactProjections = pgTable("channel_contact_projections", {
36
+ channelId: typeIdRef("channel_id")
37
+ .primaryKey()
38
+ .references(() => channels.id, { onDelete: "cascade" }),
39
+ websiteContactPointId: text("website_contact_point_id"),
40
+ primaryNamedContactId: text("primary_named_contact_id"),
41
+ website: text("website"),
42
+ contactName: text("contact_name"),
43
+ contactEmail: text("contact_email"),
44
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
45
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
46
+ });
47
+ export const channelContracts = pgTable("channel_contracts", {
48
+ id: typeId("channel_contracts"),
49
+ channelId: typeIdRef("channel_id")
50
+ .notNull()
51
+ .references(() => channels.id, { onDelete: "cascade" }),
52
+ supplierId: typeIdRef("supplier_id").references(() => suppliers.id, { onDelete: "set null" }),
53
+ status: channelContractStatusEnum("status").notNull().default("draft"),
54
+ startsAt: date("starts_at").notNull(),
55
+ endsAt: date("ends_at"),
56
+ paymentOwner: distributionPaymentOwnerEnum("payment_owner").notNull().default("operator"),
57
+ cancellationOwner: distributionCancellationOwnerEnum("cancellation_owner")
58
+ .notNull()
59
+ .default("operator"),
60
+ settlementTerms: text("settlement_terms"),
61
+ notes: text("notes"),
62
+ // ── Channel push: per-contract rate-limit overrides ─────────────
63
+ // Per §14.1 — overrides the `channels.*` defaults for this specific
64
+ // supplier relationship (e.g. an enterprise contract gets a higher
65
+ // burst than the public default).
66
+ rateLimitRps: integer("rate_limit_rps"),
67
+ rateLimitBurst: integer("rate_limit_burst"),
68
+ rateLimitPriorityGates: jsonb("rate_limit_priority_gates").$type(),
69
+ /**
70
+ * Per-contract policy bag for channel-push behavior. Currently
71
+ * carries the compensation policy ("strict-atomic" vs
72
+ * "eventually-consistent"), per-mapping field include/exclude
73
+ * lists, and adapter-specific commercial parameters echoed to push
74
+ * calls. Treated as opaque JSON; the channel-push pipeline reads
75
+ * known keys and ignores the rest.
76
+ */
77
+ policy: jsonb("policy").$type(),
78
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
79
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
80
+ }, (table) => [
81
+ index("idx_channel_contracts_channel_created").on(table.channelId, table.createdAt),
82
+ index("idx_channel_contracts_supplier_created").on(table.supplierId, table.createdAt),
83
+ index("idx_channel_contracts_status_created").on(table.status, table.createdAt),
84
+ ]);
85
+ export const channelCommissionRules = pgTable("channel_commission_rules", {
86
+ id: typeId("channel_commission_rules"),
87
+ contractId: typeIdRef("contract_id")
88
+ .notNull()
89
+ .references(() => channelContracts.id, { onDelete: "cascade" }),
90
+ scope: channelCommissionScopeEnum("scope").notNull(),
91
+ productId: typeIdRef("product_id"),
92
+ externalRateId: text("external_rate_id"),
93
+ externalCategoryId: text("external_category_id"),
94
+ commissionType: channelCommissionTypeEnum("commission_type").notNull(),
95
+ amountCents: integer("amount_cents"),
96
+ percentBasisPoints: integer("percent_basis_points"),
97
+ validFrom: date("valid_from"),
98
+ validTo: date("valid_to"),
99
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
100
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
101
+ }, (table) => [
102
+ index("idx_channel_commission_rules_contract_created").on(table.contractId, table.createdAt),
103
+ index("idx_channel_commission_rules_product_created").on(table.productId, table.createdAt),
104
+ index("idx_channel_commission_rules_scope_created").on(table.scope, table.createdAt),
105
+ ]);
106
+ export const channelProductMappings = pgTable("channel_product_mappings", {
107
+ id: typeId("channel_product_mappings"),
108
+ channelId: typeIdRef("channel_id")
109
+ .notNull()
110
+ .references(() => channels.id, { onDelete: "cascade" }),
111
+ productId: typeIdRef("product_id").notNull(),
112
+ externalProductId: text("external_product_id"),
113
+ externalRateId: text("external_rate_id"),
114
+ externalCategoryId: text("external_category_id"),
115
+ active: boolean("active").notNull().default(true),
116
+ // ── Channel push: per-mapping routing + idempotency ──────────────
117
+ // Per §3.1 + §7.2 + §6.1.
118
+ /** Mirrors adapter kind for routing (e.g. "voyant-connect", "direct:tui"). */
119
+ sourceKind: text("source_kind"),
120
+ /** Connection id resolving to a registered SourceAdapter. */
121
+ sourceConnectionId: text("source_connection_id"),
122
+ /** Per-flow push toggles. Default true: a mapping with no override
123
+ * participates in all push flows. */
124
+ pushBookings: boolean("push_bookings").notNull().default(true),
125
+ pushAvailability: boolean("push_availability").notNull().default(true),
126
+ pushContent: boolean("push_content").notNull().default(true),
127
+ /**
128
+ * Per-mapping policy (rate caps, field include/exclude, push-time
129
+ * overrides). Treated as opaque JSON; the channel-push pipeline
130
+ * reads known keys and ignores the rest.
131
+ */
132
+ policy: jsonb("policy").$type(),
133
+ /**
134
+ * Last content hash the upstream acknowledged. Idempotency for the
135
+ * content-push flow: skip when current `sha256(canonicalJson(content))`
136
+ * equals this value. Per §6.1.
137
+ */
138
+ lastPushedContentHash: text("last_pushed_content_hash"),
139
+ /** Time of the last successful content push. */
140
+ lastPushedContentAt: timestamp("last_pushed_content_at", { withTimezone: true }),
141
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
142
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
143
+ }, (table) => [
144
+ index("idx_channel_product_mappings_channel_created").on(table.channelId, table.createdAt),
145
+ index("idx_channel_product_mappings_product_created").on(table.productId, table.createdAt),
146
+ index("idx_channel_product_mappings_active_created").on(table.active, table.createdAt),
147
+ index("idx_channel_product_mappings_source_connection").on(table.sourceConnectionId),
148
+ ]);
149
+ export const channelBookingLinks = pgTable("channel_booking_links", {
150
+ id: typeId("channel_booking_links"),
151
+ channelId: typeIdRef("channel_id")
152
+ .notNull()
153
+ .references(() => channels.id, { onDelete: "cascade" }),
154
+ bookingId: text("booking_id").notNull(),
155
+ /**
156
+ * Booking-item scope. Null = booking-level link. Multi-line bookings
157
+ * fan out into one row per `(booking_item_id, channel_id)` so each
158
+ * line can target a different channel. Per §7.1.
159
+ */
160
+ bookingItemId: text("booking_item_id"),
161
+ externalBookingId: text("external_booking_id"),
162
+ externalReference: text("external_reference"),
163
+ externalStatus: text("external_status"),
164
+ bookedAtExternal: timestamp("booked_at_external", { withTimezone: true }),
165
+ lastSyncedAt: timestamp("last_synced_at", { withTimezone: true }),
166
+ // ── Channel push lifecycle ───────────────────────────────────────
167
+ // Per §7.1. The row IS the durable intent: pending → ok | failed |
168
+ // compensated. The subscriber INSERTs with `pushStatus = 'pending'`
169
+ // and the durable workflow drains it.
170
+ sourceKind: text("source_kind"),
171
+ sourceConnectionId: text("source_connection_id"),
172
+ /** "pending" | "ok" | "failed" | "compensated" */
173
+ pushStatus: text("push_status").notNull().default("pending"),
174
+ pushAttempts: integer("push_attempts").notNull().default(0),
175
+ lastPushAt: timestamp("last_push_at", { withTimezone: true }),
176
+ lastError: text("last_error"),
177
+ /**
178
+ * SHA-256 of the canonical pushed payload — drift detection on
179
+ * subsequent pushes. Per §7.1.
180
+ */
181
+ pushedPayloadHash: text("pushed_payload_hash"),
182
+ /**
183
+ * Stable idempotency key for the upstream call. Generated from
184
+ * `(booking_id, booking_item_id, channel_id)` so retries don't
185
+ * double-push.
186
+ */
187
+ idempotencyKey: text("idempotency_key"),
188
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
189
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
190
+ }, (table) => [
191
+ index("idx_channel_booking_links_channel_created").on(table.channelId, table.createdAt),
192
+ index("idx_channel_booking_links_booking_created").on(table.bookingId, table.createdAt),
193
+ index("idx_channel_booking_links_external_booking_created").on(table.externalBookingId, table.createdAt),
194
+ index("idx_channel_booking_links_push_status").on(table.pushStatus, table.lastPushAt),
195
+ index("idx_channel_booking_links_booking_item")
196
+ .on(table.bookingItemId)
197
+ // agent-quality: raw-sql reviewed -- owner: distribution; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
198
+ .where(sql `${table.bookingItemId} IS NOT NULL`),
199
+ /**
200
+ * Per §7.1: the subscriber's `INSERT ... ON CONFLICT DO NOTHING`
201
+ * needs a stable durable-handoff key. `COALESCE(booking_item_id,
202
+ * '')` collapses booking-level rows (item id null) and item-scoped
203
+ * rows into one uniqueness rule.
204
+ */
205
+ uniqueIndex("uniq_channel_booking_links_per_item").on(table.channelId, table.bookingId,
206
+ // agent-quality: raw-sql reviewed -- owner: distribution; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
207
+ sql `COALESCE(${table.bookingItemId}, '')`),
208
+ ]);
209
+ export const channelWebhookEvents = pgTable("channel_webhook_events", {
210
+ id: typeId("channel_webhook_events"),
211
+ channelId: typeIdRef("channel_id")
212
+ .notNull()
213
+ .references(() => channels.id, { onDelete: "cascade" }),
214
+ eventType: text("event_type").notNull(),
215
+ externalEventId: text("external_event_id"),
216
+ payload: jsonb("payload").$type().notNull(),
217
+ receivedAt: timestamp("received_at", { withTimezone: true }).notNull().defaultNow(),
218
+ processedAt: timestamp("processed_at", { withTimezone: true }),
219
+ status: channelWebhookStatusEnum("status").notNull().default("pending"),
220
+ errorMessage: text("error_message"),
221
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
222
+ }, (table) => [
223
+ index("idx_channel_webhook_events_channel_received").on(table.channelId, table.receivedAt),
224
+ index("idx_channel_webhook_events_status_received").on(table.status, table.receivedAt),
225
+ index("idx_channel_webhook_events_event_type_received").on(table.eventType, table.receivedAt),
226
+ index("idx_channel_webhook_events_external_event").on(table.externalEventId),
227
+ ]);