@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.
- package/LICENSE +201 -0
- package/README.md +42 -0
- package/dist/booking-extension.d.ts +168 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +102 -0
- package/dist/channel-push/admin-routes.d.ts +31 -0
- package/dist/channel-push/admin-routes.d.ts.map +1 -0
- package/dist/channel-push/admin-routes.js +165 -0
- package/dist/channel-push/availability-push.d.ts +76 -0
- package/dist/channel-push/availability-push.d.ts.map +1 -0
- package/dist/channel-push/availability-push.js +236 -0
- package/dist/channel-push/booking-push-helpers.d.ts +36 -0
- package/dist/channel-push/booking-push-helpers.d.ts.map +1 -0
- package/dist/channel-push/booking-push-helpers.js +169 -0
- package/dist/channel-push/booking-push.d.ts +108 -0
- package/dist/channel-push/booking-push.d.ts.map +1 -0
- package/dist/channel-push/booking-push.js +335 -0
- package/dist/channel-push/boundary-sql.d.ts +23 -0
- package/dist/channel-push/boundary-sql.d.ts.map +1 -0
- package/dist/channel-push/boundary-sql.js +75 -0
- package/dist/channel-push/content-push.d.ts +60 -0
- package/dist/channel-push/content-push.d.ts.map +1 -0
- package/dist/channel-push/content-push.js +252 -0
- package/dist/channel-push/index.d.ts +15 -0
- package/dist/channel-push/index.d.ts.map +1 -0
- package/dist/channel-push/index.js +18 -0
- package/dist/channel-push/plugin.d.ts +18 -0
- package/dist/channel-push/plugin.d.ts.map +1 -0
- package/dist/channel-push/plugin.js +21 -0
- package/dist/channel-push/reconciler.d.ts +85 -0
- package/dist/channel-push/reconciler.d.ts.map +1 -0
- package/dist/channel-push/reconciler.js +179 -0
- package/dist/channel-push/subscriber.d.ts +40 -0
- package/dist/channel-push/subscriber.d.ts.map +1 -0
- package/dist/channel-push/subscriber.js +199 -0
- package/dist/channel-push/types.d.ts +43 -0
- package/dist/channel-push/types.d.ts.map +1 -0
- package/dist/channel-push/types.js +32 -0
- package/dist/channel-push/workflows.d.ts +56 -0
- package/dist/channel-push/workflows.d.ts.map +1 -0
- package/dist/channel-push/workflows.js +100 -0
- package/dist/external-refs/index.d.ts +11 -0
- package/dist/external-refs/index.d.ts.map +1 -0
- package/dist/external-refs/index.js +12 -0
- package/dist/external-refs/routes.d.ts +253 -0
- package/dist/external-refs/routes.d.ts.map +1 -0
- package/dist/external-refs/routes.js +52 -0
- package/dist/external-refs/schema.d.ts +251 -0
- package/dist/external-refs/schema.d.ts.map +1 -0
- package/dist/external-refs/schema.js +32 -0
- package/dist/external-refs/service.d.ts +82 -0
- package/dist/external-refs/service.d.ts.map +1 -0
- package/dist/external-refs/service.js +112 -0
- package/dist/external-refs/validation.d.ts +91 -0
- package/dist/external-refs/validation.d.ts.map +1 -0
- package/dist/external-refs/validation.js +40 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/interface-types.d.ts +128 -0
- package/dist/interface-types.d.ts.map +1 -0
- package/dist/interface-types.js +1 -0
- package/dist/interface.d.ts +10 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +286 -0
- package/dist/rate-limit.d.ts +69 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +135 -0
- package/dist/routes/batch.d.ts +200 -0
- package/dist/routes/batch.d.ts.map +1 -0
- package/dist/routes/batch.js +52 -0
- package/dist/routes/env.d.ts +8 -0
- package/dist/routes/env.d.ts.map +1 -0
- package/dist/routes/env.js +1 -0
- package/dist/routes/inventory.d.ts +604 -0
- package/dist/routes/inventory.d.ts.map +1 -0
- package/dist/routes/inventory.js +138 -0
- package/dist/routes/settlements.d.ts +1649 -0
- package/dist/routes/settlements.d.ts.map +1 -0
- package/dist/routes/settlements.js +265 -0
- package/dist/routes.d.ts +3909 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +323 -0
- package/dist/schema-automation.d.ts +680 -0
- package/dist/schema-automation.d.ts.map +1 -0
- package/dist/schema-automation.js +76 -0
- package/dist/schema-core.d.ts +1674 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +227 -0
- package/dist/schema-finance.d.ts +1372 -0
- package/dist/schema-finance.d.ts.map +1 -0
- package/dist/schema-finance.js +153 -0
- package/dist/schema-inventory.d.ts +855 -0
- package/dist/schema-inventory.d.ts.map +1 -0
- package/dist/schema-inventory.js +102 -0
- package/dist/schema-push-intents.d.ts +387 -0
- package/dist/schema-push-intents.d.ts.map +1 -0
- package/dist/schema-push-intents.js +77 -0
- package/dist/schema-relations.d.ts +95 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +196 -0
- package/dist/schema-shared.d.ts +24 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +123 -0
- package/dist/schema.d.ts +9 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +8 -0
- package/dist/service/channels.d.ts +167 -0
- package/dist/service/channels.d.ts.map +1 -0
- package/dist/service/channels.js +305 -0
- package/dist/service/commercial.d.ts +385 -0
- package/dist/service/commercial.d.ts.map +1 -0
- package/dist/service/commercial.js +248 -0
- package/dist/service/helpers.d.ts +10 -0
- package/dist/service/helpers.d.ts.map +1 -0
- package/dist/service/helpers.js +7 -0
- package/dist/service/inventory.d.ts +193 -0
- package/dist/service/inventory.d.ts.map +1 -0
- package/dist/service/inventory.js +154 -0
- package/dist/service/settlement-policies.d.ts +325 -0
- package/dist/service/settlement-policies.d.ts.map +1 -0
- package/dist/service/settlement-policies.js +272 -0
- package/dist/service/settlements.d.ts +357 -0
- package/dist/service/settlements.d.ts.map +1 -0
- package/dist/service/settlements.js +319 -0
- package/dist/service/types.d.ts +60 -0
- package/dist/service/types.d.ts.map +1 -0
- package/dist/service/types.js +1 -0
- package/dist/service.d.ts +1418 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +17 -0
- package/dist/suppliers/index.d.ts +15 -0
- package/dist/suppliers/index.d.ts.map +1 -0
- package/dist/suppliers/index.js +23 -0
- package/dist/suppliers/routes.d.ts +1202 -0
- package/dist/suppliers/routes.d.ts.map +1 -0
- package/dist/suppliers/routes.js +290 -0
- package/dist/suppliers/schema.d.ts +1272 -0
- package/dist/suppliers/schema.d.ts.map +1 -0
- package/dist/suppliers/schema.js +219 -0
- package/dist/suppliers/service-aggregates.d.ts +23 -0
- package/dist/suppliers/service-aggregates.d.ts.map +1 -0
- package/dist/suppliers/service-aggregates.js +51 -0
- package/dist/suppliers/service-core.d.ts +89 -0
- package/dist/suppliers/service-core.d.ts.map +1 -0
- package/dist/suppliers/service-core.js +164 -0
- package/dist/suppliers/service-identity.d.ts +162 -0
- package/dist/suppliers/service-identity.d.ts.map +1 -0
- package/dist/suppliers/service-identity.js +101 -0
- package/dist/suppliers/service-operations.d.ts +1500 -0
- package/dist/suppliers/service-operations.d.ts.map +1 -0
- package/dist/suppliers/service-operations.js +157 -0
- package/dist/suppliers/service-shared.d.ts +45 -0
- package/dist/suppliers/service-shared.d.ts.map +1 -0
- package/dist/suppliers/service-shared.js +294 -0
- package/dist/suppliers/service.d.ts +41 -0
- package/dist/suppliers/service.d.ts.map +1 -0
- package/dist/suppliers/service.js +40 -0
- package/dist/suppliers/validation.d.ts +2 -0
- package/dist/suppliers/validation.d.ts.map +1 -0
- package/dist/suppliers/validation.js +1 -0
- package/dist/validation.d.ts +1371 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +445 -0
- package/dist/webhook-deliveries.d.ts +86 -0
- package/dist/webhook-deliveries.d.ts.map +1 -0
- package/dist/webhook-deliveries.js +296 -0
- 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
|
+
]);
|