kaafil-js 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/client-entry.cjs +1782 -0
- package/dist/client-entry.cjs.map +1 -0
- package/dist/client-entry.d.cts +187 -0
- package/dist/client-entry.d.ts +187 -0
- package/dist/client-entry.js +1739 -0
- package/dist/client-entry.js.map +1 -0
- package/dist/index.cjs +3541 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2311 -0
- package/dist/index.d.ts +2311 -0
- package/dist/index.js +3458 -0
- package/dist/index.js.map +1 -0
- package/dist/paginator-CE9JhsLi.d.cts +24009 -0
- package/dist/paginator-CE9JhsLi.d.ts +24009 -0
- package/package.json +75 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,2311 @@
|
|
|
1
|
+
import { a3 as components, w as KaafilResponse, a4 as operations, t as KaafilPaginator, p as KaafilPagedResponse, R as Resolvable, E as Environment, V as VendorsResource, J as JourneyResource, W as ResponseMeta, a5 as RoomingHeuristicKey, G as Gender } from './paginator-CE9JhsLi.cjs';
|
|
2
|
+
export { A as AgencyAdminRefreshResult, a6 as ApiKeyEnvironmentMismatchError, a7 as BoardStatus, B as BookingStatus, a8 as ChecklistAudience, a9 as ChecklistGate, aa as ChecklistItemStatus, ab as ChecklistPhase, ac as ChecklistPullTemplateMode, C as ClaimStatusIngest, ad as CollectionMode, ae as Currency, a as CursorPageMeta, b as ERROR_CODE_TABLE, c as ErrorCodeTableEntry, d as EventType, af as ExpenseCategory, ag as ExpensePaymentMode, F as FetchPage, ah as FileContentType, ai as FilePurpose, aj as FloatDirection, ak as ItineraryItemStatusUpdate, al as ItineraryItemType, am as JourneyTriggersResource, K as KaafilAbortError, e as KaafilApiError, f as KaafilCapabilityUnavailableError, an as KaafilConfigError, g as KaafilEntitlementError, h as KaafilError, i as KaafilErrorCode, j as KaafilErrorKind, k as KaafilInvalidRequestError, l as KaafilLockedError, m as KaafilNetworkError, n as KaafilNotFoundError, o as KaafilNotImplementedError, q as KaafilPaginationError, r as KaafilPaginationExhaustedError, s as KaafilPaginationInFlightError, u as KaafilRateLimitedError, v as KaafilReadOnlyRoleError, x as KaafilShareTokenExpiredError, y as KaafilShareTokenRevokedError, z as KaafilTimeoutError, D as KaafilTransportError, H as KaafilUnauthenticatedError, I as KaafilValidationError, L as KaafilVersionConflictError, ao as ListJourneyTriggersOptions, ap as ListTripVendorsOptions, M as ManagerRefreshResult, N as ManagerRole, O as ManifestMode, P as OutboxClass, Q as PagedResponseMeta, S as PaginatorPage, T as PartyKind, aq as PatchJourneyTriggerOptions, ar as PickupKind, U as PlanRefusalReason, as as ReadJourneyCapabilitiesOptions, at as ReadJourneyOptions, au as RebuildJourneyOptions, X as Retryability, av as RoomType, aw as RoomingFillOrder, ax as RoomingGenderPolicy, ay as RunJourneyStepOptions, az as SeatingFillOrder, aA as SeatingGenderAdjacency, aB as SeatingHeuristicKey, Y as TriggerAnchor, Z as TripMode, _ as TripStatus, $ as UnsatisfiableSchemeError, aC as VehicleLayout, aD as VehicleType, a0 as WaitUntilJourneyReadyOptions, a1 as isKaafilError, a2 as isRetryable } from './paginator-CE9JhsLi.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `auth` — minting a manager session or an agency-admin session.
|
|
6
|
+
* `architecture/08-sdk.md §3.2, §4`.
|
|
7
|
+
*
|
|
8
|
+
* Rotation is deliberately absent as a public method for either kind:
|
|
9
|
+
* `../auth/credentials.ts` calls each kind's own refresh endpoint itself,
|
|
10
|
+
* pre-emptively and reactively, as part of resolving every request's
|
|
11
|
+
* credential — a consumer never calls it directly (this wave's governing
|
|
12
|
+
* principle: the SDK does the refresh, the consumer only ever hands the SDK
|
|
13
|
+
* the tokens it was given).
|
|
14
|
+
*
|
|
15
|
+
* `08-sdk.md §4`'s table also lists a `revoke` method for this group. No such
|
|
16
|
+
* operation exists anywhere in the vendored spec: the `Auth` tag in
|
|
17
|
+
* `openapi/openapi.json` names exactly four operations —
|
|
18
|
+
* `mintManagerTokens`, `refreshManagerTokens`, `mintAgencyAdminTokens`,
|
|
19
|
+
* `refreshAgencyAdminTokens` (verified via `jq '.paths[][] | select(.tags ==
|
|
20
|
+
* ["Auth"]) | .operationId'`) — and the two `refresh*` ones are the
|
|
21
|
+
* resolver's job, not a public method here. Per this lane's brief — "if this
|
|
22
|
+
* list and the spec disagree, the spec wins, and you say so" — no `revoke`
|
|
23
|
+
* method ships. Flagged here rather than silently dropped.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
type MintManagerTokenRequest = components['schemas']['MintManagerTokensRequest'];
|
|
27
|
+
type ManagerSessionResponse = components['schemas']['ManagerSessionResponse'];
|
|
28
|
+
type MintAgencyAdminTokenRequest = components['schemas']['MintAgencyAdminTokensRequest'];
|
|
29
|
+
type AgencyAdminSessionResponse = components['schemas']['AgencyAdminSessionResponse'];
|
|
30
|
+
interface MintManagerTokenOptions extends MintManagerTokenRequest {
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
interface MintAgencyAdminTokenOptions extends MintAgencyAdminTokenRequest {
|
|
34
|
+
readonly signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
interface AuthResource {
|
|
37
|
+
/**
|
|
38
|
+
* `POST /api/v1/auth/manager-tokens` — opens a manager session. Server-side
|
|
39
|
+
* only (API key; `mintManagerTokens` accepts only `apiKeyAuth`). The
|
|
40
|
+
* returned `refreshToken` is shown exactly once, here and at every
|
|
41
|
+
* subsequent rotation — hand `accessToken`, `refreshToken`, and
|
|
42
|
+
* `expiresAt` straight to a `managerSession`-credentialed client
|
|
43
|
+
* (`../auth/credentials.ts`'s `ManagerSessionCredentialConfig`, or the
|
|
44
|
+
* browser entry's `session.open(...)`).
|
|
45
|
+
*
|
|
46
|
+
* Not idempotent — no `idempotencyKey` field on this options type — by the
|
|
47
|
+
* operation's own design (`refreshManagerTokens`'s response carries a
|
|
48
|
+
* plaintext refresh token; replaying it from an idempotency cache would
|
|
49
|
+
* mean persisting that secret, which the hash-only refresh-token storage
|
|
50
|
+
* rule forbids). A caller that loses this response to a network error
|
|
51
|
+
* simply mints again.
|
|
52
|
+
*/
|
|
53
|
+
mintManagerToken(options: MintManagerTokenOptions): Promise<KaafilResponse<ManagerSessionResponse>>;
|
|
54
|
+
/**
|
|
55
|
+
* `POST /api/v1/auth/agency-admin-tokens` — opens an agency-admin session,
|
|
56
|
+
* the agency-admin analogue of `mintManagerToken` above. Server-side only
|
|
57
|
+
* (API key; `mintAgencyAdminTokens` accepts only `apiKeyAuth`) — hand the
|
|
58
|
+
* response's `accessToken`, `refreshToken`, and `expiresAt` straight to an
|
|
59
|
+
* `agencyAdmin`-credentialed client (`../auth/credentials.ts`'s
|
|
60
|
+
* `AgencyAdminCredentialConfig`, or the browser entry's `admin.open(...)`).
|
|
61
|
+
*
|
|
62
|
+
* Not idempotent, for the same reason as `mintManagerToken`: the response
|
|
63
|
+
* carries a plaintext refresh token, and the hash-only refresh-token
|
|
64
|
+
* storage rule forbids persisting one in an idempotency cache to replay.
|
|
65
|
+
*/
|
|
66
|
+
mintAgencyAdminToken(options: MintAgencyAdminTokenOptions): Promise<KaafilResponse<AgencyAdminSessionResponse>>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `checklists` — sections, items, toggle, and the agency's reusable template
|
|
71
|
+
* library. `architecture/08-sdk.md §4`. `checklists` FRD §3, RULES R1–R14,
|
|
72
|
+
* `D-029` (pull-template copy semantics), `modules/checklists/checklists
|
|
73
|
+
* .routes.ts`'s own header (the source of every auth/lock line below).
|
|
74
|
+
*
|
|
75
|
+
* `read` and `templates.list` accept a manager session OR an API key; every
|
|
76
|
+
* WRITE (`items.add/patch/remove/toggle`, `templates.pull`) is
|
|
77
|
+
* `managerAuth`-only — admin template CONFIG (`POST/PATCH` against the
|
|
78
|
+
* library itself) does not exist yet (`checklists.routes.ts`'s "ADMIN
|
|
79
|
+
* TEMPLATE CONFIG IS DEFERRED" note), so there is no write here an API key
|
|
80
|
+
* could ever reach even in principle. Every write also declares
|
|
81
|
+
* `closeoutLock: 'ref'` engine-side — a write racing trip close-out answers
|
|
82
|
+
* `423 LOCKED` (`KaafilLockedError`), terminal, never retried.
|
|
83
|
+
*
|
|
84
|
+
* ── FOUR QUIRKS A CONSUMER WILL OTHERWISE GET WRONG ─────────────────────────
|
|
85
|
+
*
|
|
86
|
+
* 1. **`toggle`'s concurrency guard is on the item's STATUS, not its
|
|
87
|
+
* `version`.** Every OTHER version-guarded write in this API takes an
|
|
88
|
+
* `If-Match`/`version` and answers `409 CONFLICT_VERSION` with
|
|
89
|
+
* `details.currentVersion` on staleness. `toggle` takes `expectedStatus`
|
|
90
|
+
* in the BODY instead, and a stale value still answers
|
|
91
|
+
* `409 CONFLICT_VERSION` — same code, `KaafilVersionConflictError` still
|
|
92
|
+
* catches it — but the payload carries `details.currentStatus`, never
|
|
93
|
+
* `details.currentVersion`. Reading `.currentVersion` off the caught
|
|
94
|
+
* error here is silently `undefined`; read `error.details?.currentStatus`
|
|
95
|
+
* (typed `ChecklistItemStatus`) instead. This file does not add a second
|
|
96
|
+
* getter to `KaafilVersionConflictError` for it — `../http/errors.ts`'s
|
|
97
|
+
* own header states the bar for a code-specific getter, and one class per
|
|
98
|
+
* code that merely carries an unusual `details` shape is exactly what
|
|
99
|
+
* that bar exists to avoid.
|
|
100
|
+
* 2. **A `COMPLETE` item cannot be deleted.** `items.remove` on a `COMPLETE`
|
|
101
|
+
* item answers `422 BUSINESS_RULE_VIOLATION` with
|
|
102
|
+
* `details.rule: 'item_complete_delete_blocked'` — un-toggle it first.
|
|
103
|
+
* Not a module-local code: the FRD only describes the refusal, never
|
|
104
|
+
* names it as an identity a client branches on, so it never got minted
|
|
105
|
+
* one (`checklists.constants.ts`'s own line on the point).
|
|
106
|
+
* 3. **`templates.pull` COPIES (`D-029`).** Both `append` and `replace`
|
|
107
|
+
* write TRIP-OWNED rows; neither creates a live link. A template edited
|
|
108
|
+
* AFTER a pull never reaches the trip's copy, and an edit to the trip's
|
|
109
|
+
* copy never reaches the template. `sourceSectionId` on the pulled
|
|
110
|
+
* section is the only trace of where it came from, stamped once, on
|
|
111
|
+
* first pull only — a second pull of the same template onto a section
|
|
112
|
+
* that already exists never rewrites it.
|
|
113
|
+
* 4. **Editing an item's `phase` RE-DERIVES its `gate`, unless `gate` is
|
|
114
|
+
* sent in the SAME request.** `phase` is a hint only — `ChecklistItem`
|
|
115
|
+
* has no `phase` column (phase is the SECTION's) — so `items.patch` never
|
|
116
|
+
* echoes it back on the response, and an explicit `gate` in the same call
|
|
117
|
+
* always wins over the fixed phase→gate map.
|
|
118
|
+
*
|
|
119
|
+
* Beyond those four: the aggregate's `sections[]` is always the FULL live
|
|
120
|
+
* set (a trip's sections are a handful); `items[]` is the ONLY delta axis —
|
|
121
|
+
* `?since=` narrows it, with a deletion arriving as the same tombstone shape
|
|
122
|
+
* `itinerary`/`pickups` already use. `progress` and `hasOpenMandatoryByPhase`
|
|
123
|
+
* are always computed over the full live set, independent of `?since=`. The
|
|
124
|
+
* four reserved sections (`medical`/`documents`/`logistics`/`handover`) are
|
|
125
|
+
* seeded at TRIP-INGEST, inside the upsert transaction — never by this
|
|
126
|
+
* read — so a fresh trip's `read()` already carries them on its very first
|
|
127
|
+
* call; there is no seed-on-read call to make or race against.
|
|
128
|
+
*
|
|
129
|
+
* ── THE SIX `console`-PLANE READS ARE NOT IN THIS PACKAGE ───────────────────
|
|
130
|
+
*
|
|
131
|
+
* `GET /api/v1/console/trips/{ref}/checklist` (`readConsoleChecklist`) and
|
|
132
|
+
* its five siblings (`listConsoleTrips`, `…/itinerary`, `…/rooming`,
|
|
133
|
+
* `…/seating`, `…/pickups`) exist in the vendored spec and are excluded from
|
|
134
|
+
* this SDK on purpose — the same decision `../auth/credentials.ts` and
|
|
135
|
+
* `webhooks.ts` already state for `consoleAuth` generally. They authenticate
|
|
136
|
+
* with a console session cookie (`consoleAuth`), which is the partner
|
|
137
|
+
* console's OWN administrative UI's credential, never something a CRM
|
|
138
|
+
* integrating this package holds — a partner API key satisfies none of
|
|
139
|
+
* `readConsoleChecklist`'s `security` alternatives (there is only one:
|
|
140
|
+
* `consoleAuth`), so a method wrapping it would be uncallable by every
|
|
141
|
+
* consumer of this package. `scripts/gen-meta.mjs`'s
|
|
142
|
+
* `nonIntegrationRequestSchemaNames` already enforces the analogous exclusion
|
|
143
|
+
* for request-body enums reachable only from a console/platform path; this
|
|
144
|
+
* resource file is the read-side statement of the identical decision. If a
|
|
145
|
+
* console read ever gains a second, API-key-satisfiable security
|
|
146
|
+
* alternative, that is a spec change this SDK would need to notice and
|
|
147
|
+
* decide about again — not something to pre-empt here.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
type ChecklistAggregateResponse = components['schemas']['ChecklistAggregateResponse'];
|
|
151
|
+
type ChecklistItemResponse = components['schemas']['ChecklistItemResponse'];
|
|
152
|
+
type DeleteItemResponse = components['schemas']['DeleteItemResponse'];
|
|
153
|
+
type ToggleResponse = components['schemas']['ToggleResponse'];
|
|
154
|
+
type TemplatesListResponse = components['schemas']['TemplatesListResponse'];
|
|
155
|
+
type AddChecklistItemRequest = components['schemas']['AddChecklistItemRequest'];
|
|
156
|
+
type PatchChecklistItemRequest = components['schemas']['PatchChecklistItemRequest'];
|
|
157
|
+
type ToggleChecklistItemRequest = components['schemas']['ToggleChecklistItemRequest'];
|
|
158
|
+
type PullChecklistTemplateRequest = components['schemas']['PullChecklistTemplateRequest'];
|
|
159
|
+
/**
|
|
160
|
+
* One row of `read`'s `items[]`: a live item, or the tombstone for one
|
|
161
|
+
* deleted since the cursor. Aliased off the response type, never restated —
|
|
162
|
+
* `itinerary.ts#ItineraryItemDeltaRow`'s own reasoning applies verbatim.
|
|
163
|
+
*/
|
|
164
|
+
type ChecklistItemDeltaRow = ChecklistAggregateResponse['items'][number];
|
|
165
|
+
interface ReadChecklistOptions {
|
|
166
|
+
readonly tripRef: string;
|
|
167
|
+
/**
|
|
168
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and
|
|
169
|
+
* nothing else. Omit for the full aggregate — `sections[]` and the two
|
|
170
|
+
* progress figures are always full regardless; only `items[]` narrows.
|
|
171
|
+
* Prefer `createDeltaCursor` (`../delta/cursor.ts`) over passing this by
|
|
172
|
+
* hand, same as every other delta read in this SDK.
|
|
173
|
+
*/
|
|
174
|
+
readonly since?: string;
|
|
175
|
+
readonly signal?: AbortSignal;
|
|
176
|
+
}
|
|
177
|
+
interface AddChecklistItemOptions extends AddChecklistItemRequest {
|
|
178
|
+
readonly tripRef: string;
|
|
179
|
+
readonly idempotencyKey?: string;
|
|
180
|
+
readonly signal?: AbortSignal;
|
|
181
|
+
}
|
|
182
|
+
interface PatchChecklistItemOptions extends PatchChecklistItemRequest {
|
|
183
|
+
readonly tripRef: string;
|
|
184
|
+
readonly itemId: string;
|
|
185
|
+
/**
|
|
186
|
+
* Required, not optional — `itinerary.ts#PatchItineraryItemOptions
|
|
187
|
+
* .version`'s own argument for why a missing `If-Match` and a stale one
|
|
188
|
+
* answering the identical `409 CONFLICT_VERSION` means this SDK should
|
|
189
|
+
* make forgetting it impossible rather than merely discouraged. Pass the
|
|
190
|
+
* `version` from the item as last read; the SDK builds the `If-Match`
|
|
191
|
+
* header itself. NOT the same guard as `toggle`'s `expectedStatus` — see
|
|
192
|
+
* this file's header, quirk 1.
|
|
193
|
+
*/
|
|
194
|
+
readonly version: number;
|
|
195
|
+
readonly signal?: AbortSignal;
|
|
196
|
+
}
|
|
197
|
+
interface DeleteChecklistItemOptions {
|
|
198
|
+
readonly tripRef: string;
|
|
199
|
+
readonly itemId: string;
|
|
200
|
+
/** See `PatchChecklistItemOptions.version` — same contract. Refused with
|
|
201
|
+
* `422 BUSINESS_RULE_VIOLATION` while the item is `COMPLETE` — this file's
|
|
202
|
+
* header, quirk 2. */
|
|
203
|
+
readonly version: number;
|
|
204
|
+
readonly signal?: AbortSignal;
|
|
205
|
+
}
|
|
206
|
+
interface ToggleChecklistItemOptions extends ToggleChecklistItemRequest {
|
|
207
|
+
readonly tripRef: string;
|
|
208
|
+
readonly itemId: string;
|
|
209
|
+
readonly idempotencyKey?: string;
|
|
210
|
+
readonly signal?: AbortSignal;
|
|
211
|
+
}
|
|
212
|
+
interface ChecklistItemsResource {
|
|
213
|
+
/**
|
|
214
|
+
* `POST …/checklist/items` — auto-creates the section when `sectionKey`
|
|
215
|
+
* is new FOR THIS TRIP (taking `sectionTitle`/`audience` as the new
|
|
216
|
+
* section's display; both are ignored on an already-existing section). An
|
|
217
|
+
* omitted `key` is slugged from `title` and de-duplicated within the
|
|
218
|
+
* section; an explicit colliding `key` is `422 VALIDATION_ERROR`, never a
|
|
219
|
+
* silent rename. `gate` is derived from the section's `phase` — the
|
|
220
|
+
* create body carries no `gate` of its own.
|
|
221
|
+
*/
|
|
222
|
+
add(options: AddChecklistItemOptions): Promise<KaafilResponse<ChecklistItemResponse>>;
|
|
223
|
+
/** See this file's header, quirk 4 — a `phase` sent here without an
|
|
224
|
+
* explicit `gate` re-derives the item's `gate`. */
|
|
225
|
+
patch(options: PatchChecklistItemOptions): Promise<KaafilResponse<ChecklistItemResponse>>;
|
|
226
|
+
/** See this file's header, quirk 2. */
|
|
227
|
+
remove(options: DeleteChecklistItemOptions): Promise<KaafilResponse<DeleteItemResponse>>;
|
|
228
|
+
/**
|
|
229
|
+
* `POST …/checklist/items/{itemId}/toggle` — flips OPEN↔COMPLETE. See
|
|
230
|
+
* this file's header, quirk 1, before catching `KaafilVersionConflictError`
|
|
231
|
+
* off this call. Returns the updated item PLUS its section's and the
|
|
232
|
+
* trip's own progress, so a caller patches its local state without a
|
|
233
|
+
* refetch. `checklist.item_completed` fires on every `OPEN → COMPLETE`
|
|
234
|
+
* transition; `checklist.completed` fires additionally when it was the
|
|
235
|
+
* LAST mandatory item trip-wide. `COMPLETE → OPEN` emits nothing.
|
|
236
|
+
*/
|
|
237
|
+
toggle(options: ToggleChecklistItemOptions): Promise<KaafilResponse<ToggleResponse>>;
|
|
238
|
+
}
|
|
239
|
+
interface ListChecklistTemplatesOptions {
|
|
240
|
+
readonly tripRef: string;
|
|
241
|
+
/** Default `"en"`, with `"en"` fallback on no exact match. */
|
|
242
|
+
readonly locale?: string;
|
|
243
|
+
readonly signal?: AbortSignal;
|
|
244
|
+
}
|
|
245
|
+
interface PullChecklistTemplateOptions extends PullChecklistTemplateRequest {
|
|
246
|
+
readonly tripRef: string;
|
|
247
|
+
readonly idempotencyKey?: string;
|
|
248
|
+
readonly signal?: AbortSignal;
|
|
249
|
+
}
|
|
250
|
+
interface ChecklistTemplatesResource {
|
|
251
|
+
/** `GET …/checklist/templates` — the agency's library (`tripId` is
|
|
252
|
+
* `null`), resolved to `?locale=`. `lastUsedAt` is `null` until this
|
|
253
|
+
* template is pulled onto some trip, or the pull marker has expired. */
|
|
254
|
+
list(options: ListChecklistTemplatesOptions): Promise<KaafilResponse<TemplatesListResponse>>;
|
|
255
|
+
/** See this file's header, quirk 3 — COPY semantics, never a live link.
|
|
256
|
+
* Returns the FULL new aggregate (cold, no `?since=`) for atomic
|
|
257
|
+
* replacement. */
|
|
258
|
+
pull(options: PullChecklistTemplateOptions): Promise<KaafilResponse<ChecklistAggregateResponse>>;
|
|
259
|
+
}
|
|
260
|
+
interface ChecklistsResource {
|
|
261
|
+
/** `GET …/trips/{ref}/checklist` — the full aggregate: sections, items,
|
|
262
|
+
* both progress figures, and the agency's available templates. A PURE
|
|
263
|
+
* READ — it never seeds; the four reserved sections are already present
|
|
264
|
+
* by trip-ingest time (this file's header). */
|
|
265
|
+
read(options: ReadChecklistOptions): Promise<KaafilResponse<ChecklistAggregateResponse>>;
|
|
266
|
+
readonly items: ChecklistItemsResource;
|
|
267
|
+
readonly templates: ChecklistTemplatesResource;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Coercion for the caller-supplied date-time fields scattered across the
|
|
272
|
+
* ingest surface (`trips.upsert`, `trips.travellers.*`, `trips.managers.*`,
|
|
273
|
+
* `trips.balance.push`, `trips.bulk.push`, `shareTokens.create`). Every one
|
|
274
|
+
* of those fields is, in the generated schema, a bare `string` that the API
|
|
275
|
+
* requires to be a full ISO-8601 datetime WITH an offset — a caller who
|
|
276
|
+
* hand-builds that string is the single most common way to fail this API's
|
|
277
|
+
* validation, and JavaScript already has a `Date` for exactly this job.
|
|
278
|
+
*
|
|
279
|
+
* This module is the one seam every such field routes through. It is not
|
|
280
|
+
* wired into the generated schema types (those stay a faithful mirror of
|
|
281
|
+
* the OpenAPI spec) — it is wired into the hand-written `*Options` types in
|
|
282
|
+
* `resources/trips.ts` and `resources/share-tokens.ts`, which widen the
|
|
283
|
+
* relevant fields from `string` to `DateTimeInput` and normalize back down
|
|
284
|
+
* to the schema's `string` shape before the body reaches `client.execute`.
|
|
285
|
+
*/
|
|
286
|
+
/**
|
|
287
|
+
* What a caller may hand a date-time field on the ingest surface.
|
|
288
|
+
*
|
|
289
|
+
* - `Date` is the point of this whole module: build it however you like,
|
|
290
|
+
* the SDK converts it correctly.
|
|
291
|
+
* - `number` is epoch milliseconds — `Date.now()` and friends, without
|
|
292
|
+
* forcing a caller to wrap it in `new Date(...)` first.
|
|
293
|
+
* - `string` is the escape hatch for a caller who already has a correctly
|
|
294
|
+
* formatted ISO datetime (e.g. echoed back from another system) and does
|
|
295
|
+
* not want it touched — see `normalizeDateTimeInput` for why a full
|
|
296
|
+
* datetime string passes through byte-for-byte instead of round-tripping
|
|
297
|
+
* through `Date`.
|
|
298
|
+
*/
|
|
299
|
+
type DateTimeInput = Date | number | string;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* `collections` — customer payments recorded against a traveller's balance,
|
|
303
|
+
* who still owes, and the void correction. `architecture/08-sdk.md §4`.
|
|
304
|
+
*
|
|
305
|
+
* `list`/`eligible` accept a manager session OR an API key; `record`/`void`
|
|
306
|
+
* are `managerAuth`-only — collecting cash and correcting a mistake both
|
|
307
|
+
* happen on the ground.
|
|
308
|
+
*
|
|
309
|
+
* `mode ∈ CollectionMode` (`CASH|UPI|CARD|BANK`) is a CUSTOMER payment
|
|
310
|
+
* mode, disjoint from `expenses`' agency-spend `paymentMode` — the two
|
|
311
|
+
* enums never overlap and are never interchangeable even where a value
|
|
312
|
+
* name might coincide.
|
|
313
|
+
*
|
|
314
|
+
* ── THE OVERPAY GUARD IS A HARD REFUSAL ──────────────────────────────────
|
|
315
|
+
*
|
|
316
|
+
* `record()`'s `amountMinor` must not exceed the traveller's DERIVED
|
|
317
|
+
* `outstandingMinor` (computed live from the ingested `Balance`, never
|
|
318
|
+
* stored) — a caller cannot record more than what is actually owed.
|
|
319
|
+
* Exceeding it answers `422 BUSINESS_RULE_VIOLATION` with
|
|
320
|
+
* `details.remainingMinor`, the room actually left. `eligible()` is the
|
|
321
|
+
* companion read: only travellers with `outstandingMinor > 0` appear in
|
|
322
|
+
* it — a traveller with nothing outstanding, or with no `Balance` row at
|
|
323
|
+
* all, is simply absent, though still recordable through `record()` as an
|
|
324
|
+
* advance.
|
|
325
|
+
*
|
|
326
|
+
* `void()` is a correction, never an edit or a hard delete — there is no
|
|
327
|
+
* edit-after-record. `If-Match: <version>` is required; a missing or stale
|
|
328
|
+
* one answers `409 CONFLICT_VERSION` with `details.currentVersion`, never
|
|
329
|
+
* silently overwriting a version the caller has not seen.
|
|
330
|
+
*/
|
|
331
|
+
|
|
332
|
+
type CollectionResponse = components['schemas']['CollectionResponse'];
|
|
333
|
+
type CollectionDeltaResponse = components['schemas']['CollectionDeltaResponse'];
|
|
334
|
+
type EligibleRowResponse = components['schemas']['EligibleRowResponse'];
|
|
335
|
+
type RecordCollectionRequest = components['schemas']['RecordCollectionRequest'];
|
|
336
|
+
type VoidCollectionRequest = components['schemas']['VoidCollectionRequest'];
|
|
337
|
+
/**
|
|
338
|
+
* One row of `list`'s result: a live collection with its derived
|
|
339
|
+
* `outstandingMinor`, or the tombstone for one deleted since the cursor.
|
|
340
|
+
* Aliased off the response type rather than restated — see
|
|
341
|
+
* `../delta/cursor.ts`'s `isTombstone`.
|
|
342
|
+
*/
|
|
343
|
+
type CollectionDeltaRow = CollectionDeltaResponse;
|
|
344
|
+
interface ListCollectionsOptions {
|
|
345
|
+
readonly tripRef: string;
|
|
346
|
+
/** Narrows to one traveller. Omitted lists every traveller's rows. */
|
|
347
|
+
readonly travellerId?: string;
|
|
348
|
+
/**
|
|
349
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and
|
|
350
|
+
* nothing else. Omit it for the full list. Prefer `createDeltaCursor`
|
|
351
|
+
* (`../delta/cursor.ts`) over passing this by hand — the window is
|
|
352
|
+
* INCLUSIVE and overlapped by five seconds, so a pull is at-least-once
|
|
353
|
+
* and re-delivers rows a caller already holds; that module owns the
|
|
354
|
+
* bookkeeping this endpoint's overlap assumes.
|
|
355
|
+
*/
|
|
356
|
+
readonly since?: string;
|
|
357
|
+
readonly signal?: AbortSignal;
|
|
358
|
+
}
|
|
359
|
+
interface ListEligibleCollectionsOptions {
|
|
360
|
+
readonly tripRef: string;
|
|
361
|
+
readonly signal?: AbortSignal;
|
|
362
|
+
}
|
|
363
|
+
interface RecordCollectionOptions extends Omit<RecordCollectionRequest, 'collectedAt'> {
|
|
364
|
+
readonly tripRef: string;
|
|
365
|
+
readonly collectedAt?: DateTimeInput;
|
|
366
|
+
readonly idempotencyKey?: string;
|
|
367
|
+
readonly signal?: AbortSignal;
|
|
368
|
+
}
|
|
369
|
+
interface VoidCollectionOptions extends VoidCollectionRequest {
|
|
370
|
+
readonly tripRef: string;
|
|
371
|
+
readonly collectionId: string;
|
|
372
|
+
/**
|
|
373
|
+
* Required, not optional. A missing `If-Match` and a stale one both
|
|
374
|
+
* answer `409 CONFLICT_VERSION` — identically confusing either way — so
|
|
375
|
+
* this method makes forgetting it impossible instead of merely
|
|
376
|
+
* discouraged. Pass the `version` from a collection just read via
|
|
377
|
+
* `list()` or the row `record()` returned. The SDK builds the `If-Match`
|
|
378
|
+
* header itself; never set it in `headers`.
|
|
379
|
+
*/
|
|
380
|
+
readonly version: number;
|
|
381
|
+
readonly signal?: AbortSignal;
|
|
382
|
+
}
|
|
383
|
+
interface CollectionsResource {
|
|
384
|
+
/** `GET /api/v1/trips/{ref}/collections` — recorded collections, each row's
|
|
385
|
+
* `outstandingMinor` derived live on this read. See this file's header for
|
|
386
|
+
* the delta-cursor semantics. */
|
|
387
|
+
list(options: ListCollectionsOptions): Promise<KaafilResponse<readonly CollectionDeltaRow[]>>;
|
|
388
|
+
/** `GET /api/v1/trips/{ref}/collections/eligible` — travellers with a
|
|
389
|
+
* derived `outstandingMinor > 0`; "who has a balance to chase", not "who
|
|
390
|
+
* may be collected from". */
|
|
391
|
+
eligible(options: ListEligibleCollectionsOptions): Promise<KaafilResponse<readonly EligibleRowResponse[]>>;
|
|
392
|
+
/** `POST /api/v1/trips/{ref}/collections` — records a customer payment.
|
|
393
|
+
* See this file's header for the overpay guard. */
|
|
394
|
+
record(options: RecordCollectionOptions): Promise<KaafilResponse<CollectionResponse>>;
|
|
395
|
+
/** `DELETE /api/v1/trips/{ref}/collections/{id}/void` — a correction, not
|
|
396
|
+
* an edit or a hard delete. `DELETE`, not the FRD table's nominal `POST`:
|
|
397
|
+
* a real `If-Match` guard is unsatisfiable on a `POST` under this
|
|
398
|
+
* engine's OpenAPI generator, and a version-guarded soft void is the
|
|
399
|
+
* shape the generator already blesses `DELETE` for — same path, same
|
|
400
|
+
* body, same response either way. */
|
|
401
|
+
void(options: VoidCollectionOptions): Promise<KaafilResponse<CollectionResponse>>;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* `events` — the event feed. `architecture/08-sdk.md §4`: "the delta feed for
|
|
406
|
+
* missed-webhook recovery." `listEvents` is `apiKeyAuth`-only and
|
|
407
|
+
* cursor-paginated (`cursor`+`limit` query params, `meta.page.hasNext`);
|
|
408
|
+
* `list()` returns a `KaafilPaginator` and `listPage()` is the manual
|
|
409
|
+
* single-page escape hatch (`../pagination/paginator.ts`).
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
type EventEnvelopeResponse = components['schemas']['EventEnvelopeResponse'];
|
|
413
|
+
type ListEventsQuery = NonNullable<operations['listEvents']['parameters']['query']>;
|
|
414
|
+
/** The filterable half of `listEvents`'s query params — `cursor` and
|
|
415
|
+
* `limit` are the paginator's job, never the caller's to hold. */
|
|
416
|
+
type EventFilters = Omit<ListEventsQuery, 'cursor' | 'limit'>;
|
|
417
|
+
interface ListEventsOptions {
|
|
418
|
+
readonly filters?: EventFilters;
|
|
419
|
+
/** Page size sent as the `limit` query param on every page this paginator fetches. */
|
|
420
|
+
readonly limit?: number;
|
|
421
|
+
}
|
|
422
|
+
interface ListEventsPageOptions extends EventFilters {
|
|
423
|
+
readonly cursor?: string;
|
|
424
|
+
readonly limit?: number;
|
|
425
|
+
readonly signal?: AbortSignal;
|
|
426
|
+
}
|
|
427
|
+
interface EventsResource {
|
|
428
|
+
/** `GET /api/v1/events` as a `KaafilPaginator` — the SDK holds the cursor;
|
|
429
|
+
* the consumer never sees or stores one. */
|
|
430
|
+
list(options?: ListEventsOptions): KaafilPaginator<EventEnvelopeResponse, EventFilters>;
|
|
431
|
+
/** The manual single-page escape hatch: fetches exactly one page and
|
|
432
|
+
* returns it directly, for a caller who wants to drive paging itself
|
|
433
|
+
* rather than use the paginator `list()` returns. */
|
|
434
|
+
listPage(options?: ListEventsPageOptions): Promise<KaafilPagedResponse<readonly EventEnvelopeResponse[]>>;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* `expenses` — on-trip agency spend, its receipt lane, and the claim
|
|
439
|
+
* lifecycle for a manager's own `PERSONAL` spend. `architecture/08-
|
|
440
|
+
* sdk.md §4`.
|
|
441
|
+
*
|
|
442
|
+
* `list`/`read` accept a manager session OR an API key; `log`,
|
|
443
|
+
* `linkReceipt`, `void` and every `claims.*` method except
|
|
444
|
+
* `claims.ingest` are `managerAuth`-only. `claims.ingest` is the one
|
|
445
|
+
* method on this resource an API key alone may call — see its own doc
|
|
446
|
+
* below.
|
|
447
|
+
*
|
|
448
|
+
* `paymentMode ∈ ExpensePaymentMode` (`FLOAT_CASH|PERSONAL|OTHER`) is
|
|
449
|
+
* IMMUTABLE after `log()` and never accepted on any later call — an agency
|
|
450
|
+
* spend mode, disjoint from `collections`' customer payment `mode`.
|
|
451
|
+
* `category ∈ ExpenseCategory`.
|
|
452
|
+
*
|
|
453
|
+
* ── FLOAT_CASH AUTO-COUPLES TO THE FLOAT LEDGER ──────────────────────────
|
|
454
|
+
*
|
|
455
|
+
* `log()` with `paymentMode: 'FLOAT_CASH'` creates, in ONE transaction, the
|
|
456
|
+
* expense, a paired `FloatMovement.EXPENSE(OUT)`, and sets the expense's
|
|
457
|
+
* `floatMovementId` — the negative-float guard (`float.ts`'s header)
|
|
458
|
+
* applies unconditionally, so an expense that would overdraw the manager's
|
|
459
|
+
* float is refused rather than logged. `PERSONAL`/`OTHER` touch no float
|
|
460
|
+
* at all. `log({ submitClaim: true, ... })` (PERSONAL only) files the
|
|
461
|
+
* claim in the SAME transaction — equivalent to a `log()` immediately
|
|
462
|
+
* followed by `claims.submit()`, but atomic.
|
|
463
|
+
*
|
|
464
|
+
* ── THE CLAIM STATE MACHINE, AND WHO MAY MOVE IT ─────────────────────────
|
|
465
|
+
*
|
|
466
|
+
* `ExpenseResponse.claimStatus` is a five-member vocabulary —
|
|
467
|
+
* `SUBMITTED|WITHDRAWN|APPROVED|PAID|REJECTED` — split down the middle by
|
|
468
|
+
* WHO can set each value. `claims.submit()`/`claims.withdraw()` are the
|
|
469
|
+
* only two a Kaafil caller ever drives, and neither takes a `status` field
|
|
470
|
+
* at all: submitting always produces `SUBMITTED`, withdrawing always
|
|
471
|
+
* produces `WITHDRAWN`, so there is no enum a manager writes here.
|
|
472
|
+
* `APPROVED|PAID|REJECTED` arrive ONLY through `claims.ingest()` — the
|
|
473
|
+
* CRM's own decision, mirrored, never decided by Kaafil. That three-member
|
|
474
|
+
* subset is `ClaimStatusIngest` (`../generated/enums.ts`), deliberately not
|
|
475
|
+
* named `ClaimStatus` — see `scripts/gen-meta.mjs`'s `REQUEST_ENUM_NAMES`
|
|
476
|
+
* comment on that entry for why a bare domain name here would be the same
|
|
477
|
+
* trap as `ItineraryItemStatusUpdate`'s.
|
|
478
|
+
*
|
|
479
|
+
* `claims.submit()` only succeeds on a row the caller themselves logged —
|
|
480
|
+
* a different manager's row answers `404 RESOURCE_NOT_FOUND`, never `403`
|
|
481
|
+
* — with `paymentMode: 'PERSONAL'` and `claimStatus` currently `null` or
|
|
482
|
+
* `WITHDRAWN`. `claims.withdraw()` requires `If-Match` and only succeeds
|
|
483
|
+
* while `claimStatus = SUBMITTED` AND no CRM decision has landed yet — a
|
|
484
|
+
* decision arriving mid-flight wins the race cleanly, the withdraw loses.
|
|
485
|
+
* `claims.ingest()` accepts ONLY the three CRM-only values; `SUBMITTED` or
|
|
486
|
+
* `WITHDRAWN` on this route is `422 VALIDATION_ERROR` — the ingest route is
|
|
487
|
+
* one-directional by contract, not just by convention.
|
|
488
|
+
*
|
|
489
|
+
* `void()` reverses the paired float movement (if any) and cascades an
|
|
490
|
+
* open claim; `linkReceipt()` back-fills `receiptFileKey` for a client that
|
|
491
|
+
* logged an expense offline before an upload could confirm — it takes an
|
|
492
|
+
* `Idempotency-Key`, never an `If-Match`: it addresses no client-held
|
|
493
|
+
* version, only a key the log call could not carry offline. Both refuse on
|
|
494
|
+
* an already-voided row or one the CRM has already decided.
|
|
495
|
+
*/
|
|
496
|
+
|
|
497
|
+
type ExpenseResponse = components['schemas']['ExpenseResponse'];
|
|
498
|
+
type ExpenseListResponse = components['schemas']['ExpenseListResponse'];
|
|
499
|
+
type ClaimStatusIngestResponse = components['schemas']['ClaimStatusIngestResponse'];
|
|
500
|
+
type LogExpenseRequest = components['schemas']['LogExpenseRequest'];
|
|
501
|
+
type LinkExpenseReceiptRequest = components['schemas']['LinkExpenseReceiptRequest'];
|
|
502
|
+
type VoidExpenseRequest = components['schemas']['VoidExpenseRequest'];
|
|
503
|
+
type ClaimStatusIngestRequest = components['schemas']['ClaimStatusIngestRequest'];
|
|
504
|
+
type ListExpensesQuery = NonNullable<operations['listExpenses']['parameters']['query']>;
|
|
505
|
+
/** The filterable half of `list`'s query params — `since` is the delta
|
|
506
|
+
* cursor and is passed as its own option, never bundled in here. */
|
|
507
|
+
type ExpenseFilters = Omit<ListExpensesQuery, 'since'>;
|
|
508
|
+
interface ListExpensesOptions extends ExpenseFilters {
|
|
509
|
+
readonly tripRef: string;
|
|
510
|
+
/**
|
|
511
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and
|
|
512
|
+
* nothing else. Omit it for the full list. Prefer `createDeltaCursor`
|
|
513
|
+
* (`../delta/cursor.ts`) over passing this by hand — the window is
|
|
514
|
+
* INCLUSIVE and overlapped by five seconds, so a pull is at-least-once.
|
|
515
|
+
*/
|
|
516
|
+
readonly since?: string;
|
|
517
|
+
readonly signal?: AbortSignal;
|
|
518
|
+
}
|
|
519
|
+
interface ReadExpenseOptions {
|
|
520
|
+
readonly tripRef: string;
|
|
521
|
+
readonly expenseId: string;
|
|
522
|
+
readonly signal?: AbortSignal;
|
|
523
|
+
}
|
|
524
|
+
interface LogExpenseOptions extends Omit<LogExpenseRequest, 'spentAt'> {
|
|
525
|
+
readonly tripRef: string;
|
|
526
|
+
readonly spentAt?: DateTimeInput;
|
|
527
|
+
readonly idempotencyKey?: string;
|
|
528
|
+
readonly signal?: AbortSignal;
|
|
529
|
+
}
|
|
530
|
+
interface LinkExpenseReceiptOptions extends LinkExpenseReceiptRequest {
|
|
531
|
+
readonly tripRef: string;
|
|
532
|
+
readonly expenseId: string;
|
|
533
|
+
/** Required — this route addresses no client-held version; see this
|
|
534
|
+
* file's header for why `If-Match` never applies here. */
|
|
535
|
+
readonly idempotencyKey: string;
|
|
536
|
+
readonly signal?: AbortSignal;
|
|
537
|
+
}
|
|
538
|
+
interface VoidExpenseOptions extends VoidExpenseRequest {
|
|
539
|
+
readonly tripRef: string;
|
|
540
|
+
readonly expenseId: string;
|
|
541
|
+
/**
|
|
542
|
+
* Required, not optional. A missing `If-Match` and a stale one both
|
|
543
|
+
* answer `409 CONFLICT_VERSION` — identically confusing either way — so
|
|
544
|
+
* this method makes forgetting it impossible instead of merely
|
|
545
|
+
* discouraged. Pass the `version` from an expense just read via `list()`
|
|
546
|
+
* or `read()`. The SDK builds the `If-Match` header itself; never set it
|
|
547
|
+
* in `headers`.
|
|
548
|
+
*/
|
|
549
|
+
readonly version: number;
|
|
550
|
+
readonly signal?: AbortSignal;
|
|
551
|
+
}
|
|
552
|
+
interface SubmitExpenseClaimOptions {
|
|
553
|
+
readonly tripRef: string;
|
|
554
|
+
readonly expenseId: string;
|
|
555
|
+
readonly idempotencyKey?: string;
|
|
556
|
+
readonly signal?: AbortSignal;
|
|
557
|
+
}
|
|
558
|
+
interface WithdrawExpenseClaimOptions {
|
|
559
|
+
readonly tripRef: string;
|
|
560
|
+
readonly expenseId: string;
|
|
561
|
+
/** Required — see `VoidExpenseOptions.version` above for why this SDK
|
|
562
|
+
* never makes `If-Match` optional on a versioned write. */
|
|
563
|
+
readonly version: number;
|
|
564
|
+
readonly signal?: AbortSignal;
|
|
565
|
+
}
|
|
566
|
+
interface IngestClaimStatusOptions extends Omit<ClaimStatusIngestRequest, 'decisionAt'> {
|
|
567
|
+
readonly tripRef: string;
|
|
568
|
+
readonly expenseId: string;
|
|
569
|
+
readonly decisionAt: DateTimeInput;
|
|
570
|
+
readonly idempotencyKey?: string;
|
|
571
|
+
readonly signal?: AbortSignal;
|
|
572
|
+
}
|
|
573
|
+
interface ExpenseClaimsResource {
|
|
574
|
+
/** `POST /api/v1/trips/{ref}/expenses/{id}/claim` — files a claim on the
|
|
575
|
+
* caller's own `PERSONAL` expense. No body: submitting always produces
|
|
576
|
+
* `SUBMITTED`, never a status the caller names. */
|
|
577
|
+
submit(options: SubmitExpenseClaimOptions): Promise<KaafilResponse<ExpenseResponse>>;
|
|
578
|
+
/** `DELETE /api/v1/trips/{ref}/expenses/{id}/claim/withdraw` — only while
|
|
579
|
+
* still `SUBMITTED` with no CRM decision landed. `DELETE`, not the FRD
|
|
580
|
+
* table's nominal `POST` — the same forced deviation `expenses.void()`
|
|
581
|
+
* and `collections.void()` make, for the identical `If-Match`-on-`POST`
|
|
582
|
+
* reason; nothing else about the contract changes. */
|
|
583
|
+
withdraw(options: WithdrawExpenseClaimOptions): Promise<KaafilResponse<ExpenseResponse>>;
|
|
584
|
+
/** `POST /api/v1/trips/{ref}/expenses/{id}/claim-status` — partner API
|
|
585
|
+
* key ONLY; a manager bearer answers `401 UNAUTHENTICATED`, never `403`.
|
|
586
|
+
* The CRM's own decision, mirrored — Kaafil never approves, rejects or
|
|
587
|
+
* pays. `status` accepts only `ClaimStatusIngest`'s three values;
|
|
588
|
+
* `SUBMITTED`/`WITHDRAWN` here is `422 VALIDATION_ERROR`. The response's
|
|
589
|
+
* `verdict` (`applied`/`ignored_stale`) tells the CRM whether this
|
|
590
|
+
* decision actually landed or arrived after a newer one already had. */
|
|
591
|
+
ingest(options: IngestClaimStatusOptions): Promise<KaafilResponse<ClaimStatusIngestResponse>>;
|
|
592
|
+
}
|
|
593
|
+
interface ExpensesResource {
|
|
594
|
+
/** `GET /api/v1/trips/{ref}/expenses` — a trip's expenses, with category
|
|
595
|
+
* totals and a spend summary over the filtered set (live rows only). See
|
|
596
|
+
* this file's header for the delta-cursor semantics. */
|
|
597
|
+
list(options: ListExpensesOptions): Promise<KaafilResponse<ExpenseListResponse>>;
|
|
598
|
+
/** `GET /api/v1/trips/{ref}/expenses/{id}` — one expense, including its
|
|
599
|
+
* claim view. `id` is a plain Kaafil id, never dual-resolved. */
|
|
600
|
+
read(options: ReadExpenseOptions): Promise<KaafilResponse<ExpenseResponse>>;
|
|
601
|
+
/** `POST /api/v1/trips/{ref}/expenses` — logs an on-trip spend. See this
|
|
602
|
+
* file's header for `FLOAT_CASH` auto-coupling and `submitClaim`. */
|
|
603
|
+
log(options: LogExpenseOptions): Promise<KaafilResponse<ExpenseResponse>>;
|
|
604
|
+
/** `PATCH /api/v1/trips/{ref}/expenses/{id}/receipt` — back-fills
|
|
605
|
+
* `receiptFileKey` against a confirmed (`ready`) upload from `files.*`. */
|
|
606
|
+
linkReceipt(options: LinkExpenseReceiptOptions): Promise<KaafilResponse<ExpenseResponse>>;
|
|
607
|
+
/** `DELETE /api/v1/trips/{ref}/expenses/{id}/void` — reverses the paired
|
|
608
|
+
* float movement and cascades an open claim. See this file's header for
|
|
609
|
+
* the `DELETE`-not-`POST` note. */
|
|
610
|
+
void(options: VoidExpenseOptions): Promise<KaafilResponse<ExpenseResponse>>;
|
|
611
|
+
readonly claims: ExpenseClaimsResource;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* `files` — the blob lane: request a presigned upload slot, confirm it,
|
|
616
|
+
* read metadata, and mint a short-lived signed GET. `architecture/08-
|
|
617
|
+
* sdk.md §4`. `request`/`meta`/`url` accept a manager session OR an API
|
|
618
|
+
* key; `confirm` is `managerAuth`-only — a manager confirms the upload
|
|
619
|
+
* they themselves just made.
|
|
620
|
+
*
|
|
621
|
+
* ── THE FOUR-STATUS LIFECYCLE ────────────────────────────────────────────
|
|
622
|
+
*
|
|
623
|
+
* `FileResponse.status` is `pending → ready → orphaned`/`purged`. Bytes
|
|
624
|
+
* never pass through this SDK or the Kaafil API at all: `request()` hands
|
|
625
|
+
* back a presigned `PUT` URL (`CreateFileResponse.uploadUrl`) that the
|
|
626
|
+
* caller's own HTTP stack uses to write directly to object storage, with
|
|
627
|
+
* NO Kaafil auth header — the signed URL itself is the authorization. This
|
|
628
|
+
* package does not perform that `PUT`; it only mints the URL and later
|
|
629
|
+
* confirms what landed.
|
|
630
|
+
*
|
|
631
|
+
* `confirm()` verifies the object actually arrived — existence, the
|
|
632
|
+
* declared `sizeBytes`, and the leading bytes against `contentType`'s file
|
|
633
|
+
* signature — because the presigned `PUT` pins `sizeBytes` into its
|
|
634
|
+
* signature but NOT `contentType`, so a caller-declared type is otherwise
|
|
635
|
+
* unverified. A mismatch answers `422 UPLOAD_MISMATCH` and the file stays
|
|
636
|
+
* `pending`; re-`PUT` and re-confirm until the presigned URL's window
|
|
637
|
+
* lapses. On success `retentionClass` is stamped SERVER-SIDE from
|
|
638
|
+
* `purpose` (never client-declared) and the file flips to `ready`.
|
|
639
|
+
*
|
|
640
|
+
* `url()` mints a fresh 5-minute signed GET on every call — never stored,
|
|
641
|
+
* never reused — and only for a `ready` file. A `purged` file answers
|
|
642
|
+
* `410 FILE_PURGED`: the metadata row survives forever, the bytes do not.
|
|
643
|
+
* `meta()` itself never 404s for a purged file — it is the one read that
|
|
644
|
+
* still answers `200` whatever `status` says, which is how a caller tells
|
|
645
|
+
* "gone" (`410` from `url()`) apart from "never existed" (`404` from
|
|
646
|
+
* `meta()`) without a second not-found class.
|
|
647
|
+
*
|
|
648
|
+
* `purpose ∈ FilePurpose` gates what `contentType ∈ FileContentType` may
|
|
649
|
+
* accompany it server-side; both are `../generated/enums.ts` constants
|
|
650
|
+
* that come from the same vendored spec.
|
|
651
|
+
*/
|
|
652
|
+
|
|
653
|
+
type CreateFileResponse = components['schemas']['CreateFileResponse'];
|
|
654
|
+
type FileResponse = components['schemas']['FileResponse'];
|
|
655
|
+
type FileUrlResponse = components['schemas']['FileUrlResponse'];
|
|
656
|
+
type RequestFileUploadRequest = components['schemas']['RequestFileUploadRequest'];
|
|
657
|
+
interface RequestFileUploadOptions extends RequestFileUploadRequest {
|
|
658
|
+
readonly idempotencyKey?: string;
|
|
659
|
+
readonly signal?: AbortSignal;
|
|
660
|
+
}
|
|
661
|
+
interface ReadFileOptions {
|
|
662
|
+
readonly fileId: string;
|
|
663
|
+
readonly signal?: AbortSignal;
|
|
664
|
+
}
|
|
665
|
+
interface ConfirmFileUploadOptions {
|
|
666
|
+
readonly fileId: string;
|
|
667
|
+
readonly idempotencyKey?: string;
|
|
668
|
+
readonly signal?: AbortSignal;
|
|
669
|
+
}
|
|
670
|
+
interface ReadFileUrlOptions {
|
|
671
|
+
readonly fileId: string;
|
|
672
|
+
readonly signal?: AbortSignal;
|
|
673
|
+
}
|
|
674
|
+
interface FilesResource {
|
|
675
|
+
/** `POST /api/v1/files` — mints a `pending` metadata row plus a presigned
|
|
676
|
+
* `PUT`, 15-minute window, `contentType`/`sizeBytes` pinned as request
|
|
677
|
+
* conditions. This method never uploads bytes itself — see this file's
|
|
678
|
+
* header. */
|
|
679
|
+
request(options: RequestFileUploadOptions): Promise<KaafilResponse<CreateFileResponse>>;
|
|
680
|
+
/** `GET /api/v1/files/{id}` — the metadata skeleton, `200` for every
|
|
681
|
+
* status including `purged`. Never carries `storageKey`, `tripRef` or
|
|
682
|
+
* `agencyId`; `retentionClass` is `null` until the file is `ready`. */
|
|
683
|
+
meta(options: ReadFileOptions): Promise<KaafilResponse<FileResponse>>;
|
|
684
|
+
/** `POST /api/v1/files/{id}/confirm` — verifies the uploaded object and
|
|
685
|
+
* flips `pending` → `ready`. See this file's header for the mismatch and
|
|
686
|
+
* retention-stamping behaviour. */
|
|
687
|
+
confirm(options: ConfirmFileUploadOptions): Promise<KaafilResponse<FileResponse>>;
|
|
688
|
+
/** `GET /api/v1/files/{id}/url` — a fresh 5-minute signed GET for a
|
|
689
|
+
* `ready` file. `410 FILE_PURGED` once the bytes are gone. */
|
|
690
|
+
url(options: ReadFileUrlOptions): Promise<KaafilResponse<FileUrlResponse>>;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* `float` — cash advanced to a manager, its ledger, and the derived
|
|
695
|
+
* per-manager balance. `architecture/08-sdk.md §4`.
|
|
696
|
+
*
|
|
697
|
+
* `readSummary`/`readLedger`/`issue`/`adjust` accept a manager session, an
|
|
698
|
+
* API key, OR an agency-admin session; `return` is `managerAuth`-only —
|
|
699
|
+
* only the manager holding the cash may hand it back (an admin is
|
|
700
|
+
* view-only on that row).
|
|
701
|
+
*
|
|
702
|
+
* Every movement is one of four `type`s (`ISSUE|RETURN|ADJUSTMENT|EXPENSE`)
|
|
703
|
+
* with a `direction ∈ FloatDirection` (`IN|OUT`) — `EXPENSE` movements are
|
|
704
|
+
* created by `expenses.log` when `paymentMode: 'FLOAT_CASH'`, never by a
|
|
705
|
+
* method on this resource directly.
|
|
706
|
+
*
|
|
707
|
+
* ── THE NEGATIVE-FLOAT GUARD ─────────────────────────────────────────────
|
|
708
|
+
*
|
|
709
|
+
* Every `OUT` movement — `return()` and `adjust()` with `direction: 'OUT'`
|
|
710
|
+
* — is guarded: `amountMinor` must not exceed the manager's current
|
|
711
|
+
* derived `balanceMinor`, checked and enforced under an advisory lock so
|
|
712
|
+
* two concurrent `OUT`s can never both pass the same stale read. A refusal
|
|
713
|
+
* is `422 BUSINESS_RULE_VIOLATION` with `details.currentBalanceMinor` — the
|
|
714
|
+
* figure the guard actually compared against. `issue()` (`direction: 'IN'`,
|
|
715
|
+
* always additive) and `adjust()` with `direction: 'IN'` are never guarded.
|
|
716
|
+
*
|
|
717
|
+
* `adjust()`'s `note` is REQUIRED (the audit trail of every manual
|
|
718
|
+
* correction) — unlike `issue()`/`return()`, where it is optional.
|
|
719
|
+
* `issue()` never accepts a `currency` field: every write takes the trip's
|
|
720
|
+
* own currency, never a caller-named one.
|
|
721
|
+
*
|
|
722
|
+
* ── DERIVED, NOT DELTA-MERGEABLE ─────────────────────────────────────────
|
|
723
|
+
*
|
|
724
|
+
* `readSummary()`'s rows are entirely derived (`issuedMinor`,
|
|
725
|
+
* `returnedMinor`, `spentMinor`, `adjustmentsMinor`, `balanceMinor`) —
|
|
726
|
+
* nothing here is stored. `since` narrows WHICH managers are returned (only
|
|
727
|
+
* those with a movement at or after the cursor) but every returned row
|
|
728
|
+
* still carries its FULL current balance, never a partial delta — a
|
|
729
|
+
* derived scalar cannot be delta-merged the way a versioned list row can.
|
|
730
|
+
* `mergeDeltaRows`/`isTombstone` (`../delta/cursor.ts`) do not apply here:
|
|
731
|
+
* there is no tombstone case and no per-row `version` to compare.
|
|
732
|
+
* `readLedger()` carries no `since` at all — it always returns one
|
|
733
|
+
* manager's complete movement history, each row stamped with
|
|
734
|
+
* `runningBalanceMinor` immediately after that row.
|
|
735
|
+
*/
|
|
736
|
+
|
|
737
|
+
type FloatSummaryResponse = components['schemas']['FloatSummaryResponse'];
|
|
738
|
+
type FloatLedgerResponse = components['schemas']['FloatLedgerResponse'];
|
|
739
|
+
type AdjustFloatResponse = components['schemas']['AdjustFloatResponse'];
|
|
740
|
+
type IssueFloatResponse = components['schemas']['IssueFloatResponse'];
|
|
741
|
+
type ReturnFloatResponse = components['schemas']['ReturnFloatResponse'];
|
|
742
|
+
type AdjustFloatRequest = components['schemas']['AdjustFloatRequest'];
|
|
743
|
+
type IssueFloatRequest = components['schemas']['IssueFloatRequest'];
|
|
744
|
+
type ReturnFloatRequest = components['schemas']['ReturnFloatRequest'];
|
|
745
|
+
interface ReadFloatSummaryOptions {
|
|
746
|
+
readonly tripRef: string;
|
|
747
|
+
/** Narrows which managers are returned to those with a movement at or
|
|
748
|
+
* after this cursor. See this file's header — the rows themselves are
|
|
749
|
+
* never partial, so this is not a `../delta/cursor.ts` cursor. */
|
|
750
|
+
readonly since?: string;
|
|
751
|
+
readonly signal?: AbortSignal;
|
|
752
|
+
}
|
|
753
|
+
interface ReadFloatLedgerOptions {
|
|
754
|
+
readonly tripRef: string;
|
|
755
|
+
readonly managerId: string;
|
|
756
|
+
readonly signal?: AbortSignal;
|
|
757
|
+
}
|
|
758
|
+
interface IssueFloatOptions extends IssueFloatRequest {
|
|
759
|
+
readonly tripRef: string;
|
|
760
|
+
readonly idempotencyKey?: string;
|
|
761
|
+
readonly signal?: AbortSignal;
|
|
762
|
+
}
|
|
763
|
+
interface ReturnFloatOptions extends ReturnFloatRequest {
|
|
764
|
+
readonly tripRef: string;
|
|
765
|
+
readonly idempotencyKey?: string;
|
|
766
|
+
readonly signal?: AbortSignal;
|
|
767
|
+
}
|
|
768
|
+
interface AdjustFloatOptions extends AdjustFloatRequest {
|
|
769
|
+
readonly tripRef: string;
|
|
770
|
+
readonly idempotencyKey?: string;
|
|
771
|
+
readonly signal?: AbortSignal;
|
|
772
|
+
}
|
|
773
|
+
interface FloatResource {
|
|
774
|
+
/** `GET /api/v1/trips/{ref}/float` — one row per manager who has ever had
|
|
775
|
+
* a movement on this trip, entirely derived. See this file's header. */
|
|
776
|
+
readSummary(options: ReadFloatSummaryOptions): Promise<KaafilResponse<FloatSummaryResponse>>;
|
|
777
|
+
/** `GET /api/v1/trips/{ref}/float/{managerId}/ledger` — one manager's
|
|
778
|
+
* full movement history, ordered `(createdAt asc, id asc)`, each row
|
|
779
|
+
* carrying `runningBalanceMinor`. `404` if `managerId` does not name a
|
|
780
|
+
* manager currently assigned to this trip. */
|
|
781
|
+
readLedger(options: ReadFloatLedgerOptions): Promise<KaafilResponse<FloatLedgerResponse>>;
|
|
782
|
+
/** `POST /api/v1/trips/{ref}/float/issue` — `type=ISSUE`, `direction=IN`,
|
|
783
|
+
* never negative-float guarded. An admin or the manager themselves may
|
|
784
|
+
* call this; both paths produce the identical row. */
|
|
785
|
+
issue(options: IssueFloatOptions): Promise<KaafilResponse<IssueFloatResponse>>;
|
|
786
|
+
/** `POST /api/v1/trips/{ref}/float/return` — `type=RETURN`,
|
|
787
|
+
* `direction=OUT`, `managerAuth`-only and negative-float guarded. See
|
|
788
|
+
* this file's header. */
|
|
789
|
+
return(options: ReturnFloatOptions): Promise<KaafilResponse<ReturnFloatResponse>>;
|
|
790
|
+
/** `POST /api/v1/trips/{ref}/float/adjust` — `type=ADJUSTMENT`, direction
|
|
791
|
+
* named by the caller; `note` is required. `direction: 'OUT'` is
|
|
792
|
+
* negative-float guarded identically to `return()`; `direction: 'IN'`
|
|
793
|
+
* never is. */
|
|
794
|
+
adjust(options: AdjustFloatOptions): Promise<KaafilResponse<AdjustFloatResponse>>;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* `itinerary` — the day cards and their ordered items, plus the change log.
|
|
799
|
+
* `architecture/08-sdk.md §4`.
|
|
800
|
+
*
|
|
801
|
+
* `read` and `changeLog.list` accept an API key, a manager session OR an
|
|
802
|
+
* agency-admin session (`readItinerary`/`readItineraryChangeLog` declare all
|
|
803
|
+
* three as alternatives, never an AND) — the consumer never chooses which;
|
|
804
|
+
* `../auth/credentials.ts`'s `CredentialResolver` picks whichever scheme the
|
|
805
|
+
* configured credential satisfies. Every WRITE here is `managerAuth`-only: the
|
|
806
|
+
* itinerary is edited on the ground, and an API-key write answers `401`.
|
|
807
|
+
*
|
|
808
|
+
* Every path parameter is a named field on its options object — never a
|
|
809
|
+
* positional string — so `items.patch` (a trip ref AND an item id) and
|
|
810
|
+
* `days.patch` (a trip ref AND a day index) can't be called with their
|
|
811
|
+
* arguments swapped.
|
|
812
|
+
*
|
|
813
|
+
* `read` is one operation with three modes, because OpenAPI keys an operation
|
|
814
|
+
* by (path, method) and all three are `GET /trips/{ref}/itinerary`: the full
|
|
815
|
+
* aggregate, `?dayIndex=` for one day, and `?since=` for a delta. The two
|
|
816
|
+
* compose. For the delta mode, drive `since` through
|
|
817
|
+
* `../delta/cursor.ts`'s `createDeltaCursor` rather than by hand — that file's
|
|
818
|
+
* header is the argument for why any other cursor loses rows.
|
|
819
|
+
*/
|
|
820
|
+
|
|
821
|
+
type ItineraryReadResponse = components['schemas']['ItineraryReadResponse'];
|
|
822
|
+
type ItineraryDayResponse = components['schemas']['ItineraryDayResponse'];
|
|
823
|
+
type ItineraryItemResponse = components['schemas']['ItineraryItemResponse'];
|
|
824
|
+
type ItineraryItemTombstoneResponse = components['schemas']['ItineraryItemTombstoneResponse'];
|
|
825
|
+
type ItineraryReorderResponse = components['schemas']['ItineraryReorderResponse'];
|
|
826
|
+
type ItineraryChangeLogEntryResponse = components['schemas']['ItineraryChangeLogEntryResponse'];
|
|
827
|
+
type AddItineraryItemRequest = components['schemas']['AddItineraryItemRequest'];
|
|
828
|
+
type PatchItineraryItemRequest = components['schemas']['PatchItineraryItemRequest'];
|
|
829
|
+
type ReorderItineraryItemRequest = components['schemas']['ReorderItineraryItemRequest'];
|
|
830
|
+
type PatchItineraryDayRequest = components['schemas']['PatchItineraryDayRequest'];
|
|
831
|
+
/**
|
|
832
|
+
* One row of `read`'s `items[]`: a live item, or the tombstone for one deleted
|
|
833
|
+
* since the cursor. Aliased off the response type rather than restated, so the
|
|
834
|
+
* union stays whatever the contract says it is.
|
|
835
|
+
*
|
|
836
|
+
* Named and exported because a consumer writing a render function over
|
|
837
|
+
* `items` has to name its parameter, and the union is the whole point — see
|
|
838
|
+
* `../delta/cursor.ts`'s `isTombstone`.
|
|
839
|
+
*/
|
|
840
|
+
type ItineraryItemDeltaRow = ItineraryReadResponse['items'][number];
|
|
841
|
+
interface ReadItineraryOptions {
|
|
842
|
+
readonly tripRef: string;
|
|
843
|
+
/** Narrows the same shape to one day. 0-based, as `days[].dayIndex` reports it. */
|
|
844
|
+
readonly dayIndex?: number;
|
|
845
|
+
/**
|
|
846
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and nothing
|
|
847
|
+
* else. Omit it for the full aggregate. Prefer `createDeltaCursor`
|
|
848
|
+
* (`../delta/cursor.ts`) over passing this by hand — the boundary is the one
|
|
849
|
+
* thing that silently loses rows, and that module owns it.
|
|
850
|
+
*/
|
|
851
|
+
readonly since?: string;
|
|
852
|
+
readonly signal?: AbortSignal;
|
|
853
|
+
}
|
|
854
|
+
interface AddItineraryItemOptions extends Omit<AddItineraryItemRequest, 'isoDate' | 'startTime' | 'endTime'> {
|
|
855
|
+
readonly tripRef: string;
|
|
856
|
+
/**
|
|
857
|
+
* Which day the item belongs to, as a date. The engine resolves it to a day
|
|
858
|
+
* index in the TRIP's timezone and find-or-creates the day — a client never
|
|
859
|
+
* computes a day index itself, and must not, because the answer depends on a
|
|
860
|
+
* timezone the client may not be in.
|
|
861
|
+
*/
|
|
862
|
+
readonly isoDate: DateTimeInput;
|
|
863
|
+
readonly startTime?: DateTimeInput | null;
|
|
864
|
+
readonly endTime?: DateTimeInput | null;
|
|
865
|
+
readonly idempotencyKey?: string;
|
|
866
|
+
readonly signal?: AbortSignal;
|
|
867
|
+
}
|
|
868
|
+
interface PatchItineraryItemOptions extends Omit<PatchItineraryItemRequest, 'startTime' | 'endTime'> {
|
|
869
|
+
readonly tripRef: string;
|
|
870
|
+
readonly itemId: string;
|
|
871
|
+
readonly startTime?: DateTimeInput | null;
|
|
872
|
+
readonly endTime?: DateTimeInput | null;
|
|
873
|
+
/**
|
|
874
|
+
* Required, not optional. A missing `If-Match` and a stale one both answer
|
|
875
|
+
* `409 CONFLICT_VERSION` — identically confusing either way — so this method
|
|
876
|
+
* makes forgetting it impossible instead of merely discouraged. Pass the
|
|
877
|
+
* `version` from the item as last read. The SDK builds the `If-Match` header
|
|
878
|
+
* itself; never set it in `headers`.
|
|
879
|
+
*/
|
|
880
|
+
readonly version: number;
|
|
881
|
+
readonly signal?: AbortSignal;
|
|
882
|
+
}
|
|
883
|
+
interface DeleteItineraryItemOptions {
|
|
884
|
+
readonly tripRef: string;
|
|
885
|
+
readonly itemId: string;
|
|
886
|
+
/** See `PatchItineraryItemOptions.version` — same contract. */
|
|
887
|
+
readonly version: number;
|
|
888
|
+
readonly signal?: AbortSignal;
|
|
889
|
+
}
|
|
890
|
+
interface ReorderItineraryItemOptions extends ReorderItineraryItemRequest {
|
|
891
|
+
readonly tripRef: string;
|
|
892
|
+
readonly itemId: string;
|
|
893
|
+
readonly idempotencyKey?: string;
|
|
894
|
+
readonly signal?: AbortSignal;
|
|
895
|
+
}
|
|
896
|
+
interface ItineraryItemsResource {
|
|
897
|
+
/** `POST …/itinerary/items` — appends at the tail of its day. */
|
|
898
|
+
add(options: AddItineraryItemOptions): Promise<KaafilResponse<ItineraryItemResponse>>;
|
|
899
|
+
patch(options: PatchItineraryItemOptions): Promise<KaafilResponse<ItineraryItemResponse>>;
|
|
900
|
+
/**
|
|
901
|
+
* Soft-deletes the item. Answers the TOMBSTONE — the same `_tombstone`
|
|
902
|
+
* marker a later `?since=` pull would carry for it — rather than an empty
|
|
903
|
+
* body, so the caller that issued the delete applies the identical drop to
|
|
904
|
+
* its local store as one that learned about it from a delta.
|
|
905
|
+
*/
|
|
906
|
+
remove(options: DeleteItineraryItemOptions): Promise<KaafilResponse<ItineraryItemTombstoneResponse>>;
|
|
907
|
+
/**
|
|
908
|
+
* Moves the item to `index` within its own day; the index is clamped
|
|
909
|
+
* server-side, and the whole day is re-stamped densely so two devices
|
|
910
|
+
* replaying the same reorder land on identical `sortOrder` values.
|
|
911
|
+
*
|
|
912
|
+
* No `version` and no `If-Match`, deliberately: a reorder is last-write-wins
|
|
913
|
+
* by design, and a conflict is resolved by re-reading the day (the response
|
|
914
|
+
* already carries the day's full item list, in its new order).
|
|
915
|
+
*/
|
|
916
|
+
reorder(options: ReorderItineraryItemOptions): Promise<KaafilResponse<ItineraryReorderResponse>>;
|
|
917
|
+
}
|
|
918
|
+
interface PatchItineraryDayOptions extends PatchItineraryDayRequest {
|
|
919
|
+
readonly tripRef: string;
|
|
920
|
+
/** 0-based, as `days[].dayIndex` reports it — the day's position, not its id. */
|
|
921
|
+
readonly dayIndex: number;
|
|
922
|
+
/** See `PatchItineraryItemOptions.version` — same contract. */
|
|
923
|
+
readonly version: number;
|
|
924
|
+
readonly signal?: AbortSignal;
|
|
925
|
+
}
|
|
926
|
+
interface ItineraryDaysResource {
|
|
927
|
+
/** `PATCH …/itinerary/days/{dayIndex}` — the card title and the summary line. */
|
|
928
|
+
patch(options: PatchItineraryDayOptions): Promise<KaafilResponse<ItineraryDayResponse>>;
|
|
929
|
+
}
|
|
930
|
+
interface ListItineraryChangeLogOptions {
|
|
931
|
+
readonly tripRef: string;
|
|
932
|
+
/** See `ReadItineraryOptions.since`. */
|
|
933
|
+
readonly since?: string;
|
|
934
|
+
readonly signal?: AbortSignal;
|
|
935
|
+
}
|
|
936
|
+
interface ItineraryChangeLogResource {
|
|
937
|
+
/** `GET …/itinerary/change-log` — the most recent entries, newest first.
|
|
938
|
+
* Not cursor-paginated (no `cursor`/`limit` query params in the spec). */
|
|
939
|
+
list(options: ListItineraryChangeLogOptions): Promise<KaafilResponse<readonly ItineraryChangeLogEntryResponse[]>>;
|
|
940
|
+
}
|
|
941
|
+
interface ItineraryResource {
|
|
942
|
+
read(options: ReadItineraryOptions): Promise<KaafilResponse<ItineraryReadResponse>>;
|
|
943
|
+
readonly items: ItineraryItemsResource;
|
|
944
|
+
readonly days: ItineraryDaysResource;
|
|
945
|
+
readonly changeLog: ItineraryChangeLogResource;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* `pickups` — the stop list, reorder, manifest-by-pickup, assign/board and
|
|
950
|
+
* the close/reopen policy. `architecture/08-sdk.md §4`.
|
|
951
|
+
*
|
|
952
|
+
* `list` and `manifestByPickup` accept a manager session, an agency-admin
|
|
953
|
+
* session OR an API key (three alternatives, never an AND); every WRITE is
|
|
954
|
+
* `managerAuth`-only, the same split `seating`, `rooming` and `itinerary`
|
|
955
|
+
* have and for the same reason — pickups are worked on the ground.
|
|
956
|
+
*
|
|
957
|
+
* Every path parameter is a named field on its options object — never a
|
|
958
|
+
* positional string — so `assign` (a trip ref AND a stop id) can't be called
|
|
959
|
+
* with its arguments swapped.
|
|
960
|
+
*
|
|
961
|
+
* ── ONE `close` METHOD, TWO POLICIES, ONE CODE ──────────────────────────────
|
|
962
|
+
*
|
|
963
|
+
* `close()` is a single operation whichever `Trip.eventType` the trip carries
|
|
964
|
+
* — never two endpoints and never two codes. On a `TRIP`, every `PENDING`
|
|
965
|
+
* traveller must appear in `resolutions[]` with a terminal status, and
|
|
966
|
+
* `confirm` is ignored; on a `TREK`, `resolutions[]` is optional and a short
|
|
967
|
+
* close (`boardedCount < expectedCount`) needs `confirm: true` PLUS
|
|
968
|
+
* `confirmedHeadCount`. Both branches answer the SAME `422 STOP_HAS_PENDING`
|
|
969
|
+
* when they refuse — a TREK short-close refusal additionally carries
|
|
970
|
+
* `details.requiresConfirm: true`, which is what tells a caller to show the
|
|
971
|
+
* confirm sheet rather than the per-traveller resolver, and NOT a second
|
|
972
|
+
* code. `details` is untyped at the transport boundary (the envelope
|
|
973
|
+
* schema's own `additionalProperties: {}`), so read it defensively:
|
|
974
|
+
* `error.details?.requiresConfirm === true`.
|
|
975
|
+
*
|
|
976
|
+
* `confirmedHeadCount` disagreeing with the actual count is FLAGGED
|
|
977
|
+
* (`CloseResponse.headCountMismatch`), never blocking — "a manager on a
|
|
978
|
+
* trailhead can't wait forever."
|
|
979
|
+
*/
|
|
980
|
+
|
|
981
|
+
type PickupStopResponse = components['schemas']['PickupStopResponse'];
|
|
982
|
+
type PickupStopDeltaResponse = components['schemas']['PickupStopDeltaResponse'];
|
|
983
|
+
type PickupStopDeleteResponse = components['schemas']['PickupStopDeleteResponse'];
|
|
984
|
+
type ManifestByPickupResponse = components['schemas']['ManifestByPickupResponse'];
|
|
985
|
+
type AssignResponse = components['schemas']['AssignResponse'];
|
|
986
|
+
type BoardResponse = components['schemas']['BoardResponse'];
|
|
987
|
+
type CloseResponse = components['schemas']['CloseResponse'];
|
|
988
|
+
type ReopenResponse = components['schemas']['ReopenResponse'];
|
|
989
|
+
type CreatePickupStopRequest = components['schemas']['CreatePickupStopRequest'];
|
|
990
|
+
type PatchPickupStopRequest = components['schemas']['PatchPickupStopRequest'];
|
|
991
|
+
type ReorderPickupStopsRequest = components['schemas']['ReorderPickupStopsRequest'];
|
|
992
|
+
type AssignPickupTravellerRequest = components['schemas']['AssignPickupTravellerRequest'];
|
|
993
|
+
type BoardPickupTravellerRequest = components['schemas']['BoardPickupTravellerRequest'];
|
|
994
|
+
type ClosePickupStopRequest = components['schemas']['ClosePickupStopRequest'];
|
|
995
|
+
/**
|
|
996
|
+
* One row of `list`'s result: a live stop, or the tombstone for one deleted
|
|
997
|
+
* since the cursor. Aliased off the response type rather than restated, so
|
|
998
|
+
* the union stays whatever the contract says it is. See `../delta/cursor.ts`'s
|
|
999
|
+
* `isTombstone`.
|
|
1000
|
+
*/
|
|
1001
|
+
type PickupStopDeltaRow = PickupStopDeltaResponse;
|
|
1002
|
+
interface ListPickupStopsOptions {
|
|
1003
|
+
readonly tripRef: string;
|
|
1004
|
+
/** Narrows to one stop kind. Omitted lists both `PICKUP` and `DROP`. */
|
|
1005
|
+
readonly kind?: components['schemas']['PickupStopResponse']['kind'];
|
|
1006
|
+
/**
|
|
1007
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and
|
|
1008
|
+
* nothing else. Omit it for the full list. Prefer `createDeltaCursor`
|
|
1009
|
+
* (`../delta/cursor.ts`) over passing this by hand — the boundary is the
|
|
1010
|
+
* one thing that silently loses rows, and that module owns it.
|
|
1011
|
+
*/
|
|
1012
|
+
readonly since?: string;
|
|
1013
|
+
readonly signal?: AbortSignal;
|
|
1014
|
+
}
|
|
1015
|
+
interface ReadManifestByPickupOptions {
|
|
1016
|
+
readonly tripRef: string;
|
|
1017
|
+
/** See `ListPickupStopsOptions.kind`. */
|
|
1018
|
+
readonly kind?: components['schemas']['PickupStopResponse']['kind'];
|
|
1019
|
+
readonly signal?: AbortSignal;
|
|
1020
|
+
}
|
|
1021
|
+
interface CreatePickupStopOptions extends Omit<CreatePickupStopRequest, 'scheduledTime'> {
|
|
1022
|
+
readonly tripRef: string;
|
|
1023
|
+
readonly scheduledTime: DateTimeInput;
|
|
1024
|
+
readonly idempotencyKey?: string;
|
|
1025
|
+
readonly signal?: AbortSignal;
|
|
1026
|
+
}
|
|
1027
|
+
interface PatchPickupStopOptions extends Omit<PatchPickupStopRequest, 'scheduledTime'> {
|
|
1028
|
+
readonly tripRef: string;
|
|
1029
|
+
readonly pointId: string;
|
|
1030
|
+
readonly scheduledTime?: DateTimeInput;
|
|
1031
|
+
/**
|
|
1032
|
+
* Required, not optional. A missing `If-Match` and a stale one both answer
|
|
1033
|
+
* `409 CONFLICT_VERSION` — identically confusing either way — so this
|
|
1034
|
+
* method makes forgetting it impossible instead of merely discouraged.
|
|
1035
|
+
* Pass the `version` from the stop as last read. The SDK builds the
|
|
1036
|
+
* `If-Match` header itself; never set it in `headers`.
|
|
1037
|
+
*
|
|
1038
|
+
* Changing `kind` while the stop holds assignments is refused with
|
|
1039
|
+
* `422 BUSINESS_RULE_VIOLATION` and `details.assignedCount` — reassign or
|
|
1040
|
+
* clear the stop first.
|
|
1041
|
+
*/
|
|
1042
|
+
readonly version: number;
|
|
1043
|
+
readonly signal?: AbortSignal;
|
|
1044
|
+
}
|
|
1045
|
+
interface DeletePickupStopOptions {
|
|
1046
|
+
readonly tripRef: string;
|
|
1047
|
+
readonly pointId: string;
|
|
1048
|
+
/** See `PatchPickupStopOptions.version` — same contract. */
|
|
1049
|
+
readonly version: number;
|
|
1050
|
+
/** Releases every assignment on this stop back to unassigned before
|
|
1051
|
+
* deleting it. Without it, a stop with assignments is refused. */
|
|
1052
|
+
readonly force?: boolean;
|
|
1053
|
+
readonly signal?: AbortSignal;
|
|
1054
|
+
}
|
|
1055
|
+
interface ReorderPickupStopsOptions extends ReorderPickupStopsRequest {
|
|
1056
|
+
readonly tripRef: string;
|
|
1057
|
+
readonly idempotencyKey?: string;
|
|
1058
|
+
readonly signal?: AbortSignal;
|
|
1059
|
+
}
|
|
1060
|
+
interface AssignPickupTravellerOptions extends AssignPickupTravellerRequest {
|
|
1061
|
+
readonly tripRef: string;
|
|
1062
|
+
readonly pointId: string;
|
|
1063
|
+
readonly idempotencyKey?: string;
|
|
1064
|
+
readonly signal?: AbortSignal;
|
|
1065
|
+
}
|
|
1066
|
+
interface BoardPickupTravellerOptions extends BoardPickupTravellerRequest {
|
|
1067
|
+
readonly tripRef: string;
|
|
1068
|
+
readonly pointId: string;
|
|
1069
|
+
readonly idempotencyKey?: string;
|
|
1070
|
+
readonly signal?: AbortSignal;
|
|
1071
|
+
}
|
|
1072
|
+
interface ClosePickupStopOptions extends ClosePickupStopRequest {
|
|
1073
|
+
readonly tripRef: string;
|
|
1074
|
+
readonly pointId: string;
|
|
1075
|
+
readonly idempotencyKey?: string;
|
|
1076
|
+
readonly signal?: AbortSignal;
|
|
1077
|
+
}
|
|
1078
|
+
interface ReopenPickupStopOptions {
|
|
1079
|
+
readonly tripRef: string;
|
|
1080
|
+
readonly pointId: string;
|
|
1081
|
+
readonly idempotencyKey?: string;
|
|
1082
|
+
readonly signal?: AbortSignal;
|
|
1083
|
+
}
|
|
1084
|
+
interface PickupsResource {
|
|
1085
|
+
/** `GET /api/v1/trips/{ref}/pickups`. With `?since=` the rows are
|
|
1086
|
+
* `PickupStopDeltaResponse` — a union with the tombstone, so the drop case
|
|
1087
|
+
* cannot be forgotten. Without one, no tombstone can appear. */
|
|
1088
|
+
list(options: ListPickupStopsOptions): Promise<KaafilResponse<readonly PickupStopDeltaRow[]>>;
|
|
1089
|
+
create(options: CreatePickupStopOptions): Promise<KaafilResponse<PickupStopResponse>>;
|
|
1090
|
+
patch(options: PatchPickupStopOptions): Promise<KaafilResponse<PickupStopResponse>>;
|
|
1091
|
+
remove(options: DeletePickupStopOptions): Promise<KaafilResponse<PickupStopDeleteResponse>>;
|
|
1092
|
+
reorder(options: ReorderPickupStopsOptions): Promise<KaafilResponse<readonly PickupStopResponse[]>>;
|
|
1093
|
+
/**
|
|
1094
|
+
* `POST …/pickups/{pointId}/assign` — puts one traveller on one stop. If
|
|
1095
|
+
* they were already on a different stop of the SAME `kind`, the move is one
|
|
1096
|
+
* atomic delete+create (R1: at most one `PICKUP` and one `DROP` per
|
|
1097
|
+
* traveller, enforced by a composite unique, not merely a service-layer
|
|
1098
|
+
* promise), and the response's `previousPickupPointId` names where they
|
|
1099
|
+
* moved from.
|
|
1100
|
+
*/
|
|
1101
|
+
assign(options: AssignPickupTravellerOptions): Promise<KaafilResponse<AssignResponse>>;
|
|
1102
|
+
board(options: BoardPickupTravellerOptions): Promise<KaafilResponse<BoardResponse>>;
|
|
1103
|
+
/** See this file's header for the close policy: one method, two branches
|
|
1104
|
+
* on `Trip.eventType`, one code (`STOP_HAS_PENDING`) either way. */
|
|
1105
|
+
close(options: ClosePickupStopOptions): Promise<KaafilResponse<CloseResponse>>;
|
|
1106
|
+
/** Flips `CLOSED → OPEN` and clears `closedAt`/`closedByManagerId` — there
|
|
1107
|
+
* is no `REOPENED` state. The next `close()` on this stop emits a fresh
|
|
1108
|
+
* CORRECTIVE `pickup.stop_closed` event, `reopened: true`. */
|
|
1109
|
+
reopen(options: ReopenPickupStopOptions): Promise<KaafilResponse<ReopenResponse>>;
|
|
1110
|
+
/** `GET …/manifest-by-pickup` — the roster grouped by stop, for a printed
|
|
1111
|
+
* or on-screen boarding sheet. Renders the same glyph/tone canon as
|
|
1112
|
+
* `rooming`'s and `seating`'s occupants — no second derivation. */
|
|
1113
|
+
manifestByPickup(options: ReadManifestByPickupOptions): Promise<KaafilResponse<ManifestByPickupResponse>>;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* `rooming` — the board, stay windows, rooms, manual bed assignment and
|
|
1118
|
+
* auto-assign. `architecture/08-sdk.md §4`.
|
|
1119
|
+
*
|
|
1120
|
+
* `board.read` and `stayWindows.list` accept a manager session, an
|
|
1121
|
+
* agency-admin session OR an API key (three alternatives, never an AND); every
|
|
1122
|
+
* WRITE is `managerAuth`-only, the same split `itinerary` has and for the same
|
|
1123
|
+
* reason — rooming is decided on the ground.
|
|
1124
|
+
*
|
|
1125
|
+
* Rooming is a gated capability (`GROUP` trips only, behind the `rooming` plan
|
|
1126
|
+
* flag), so its refusals are ordinary typed errors a caller should expect:
|
|
1127
|
+
* `402` for a flag that is off, `422 CAPABILITY_UNAVAILABLE` on a
|
|
1128
|
+
* `PERSONALIZED` trip, `422 READ_ONLY_ROLE` for a COORDINATOR. See
|
|
1129
|
+
* `../http/errors.ts`.
|
|
1130
|
+
*
|
|
1131
|
+
* Every path parameter is a named field on its options object — never a
|
|
1132
|
+
* positional string — so `rooms.patch` (a trip ref AND a room id) and
|
|
1133
|
+
* `stayWindows.patch` (a trip ref AND a window id) can't be called with their
|
|
1134
|
+
* arguments swapped. The two child ids are named `roomId` and `windowId` after
|
|
1135
|
+
* the thing they identify, never both `id`.
|
|
1136
|
+
*
|
|
1137
|
+
* `HEURISTIC_SEVERITY` and the glyph/tone vocabulary a client needs to RENDER
|
|
1138
|
+
* this module's output are not here — they are not on the wire. See
|
|
1139
|
+
* `../rooming/vocabulary.ts`.
|
|
1140
|
+
*/
|
|
1141
|
+
|
|
1142
|
+
type RoomingBoardResponse = components['schemas']['RoomingBoardResponse'];
|
|
1143
|
+
type RoomingStayWindowResponse = components['schemas']['RoomingStayWindowResponse'];
|
|
1144
|
+
type RoomingStayWindowDeltaResponse = components['schemas']['RoomingStayWindowDeltaResponse'];
|
|
1145
|
+
type RoomingRoomResponse = components['schemas']['RoomingRoomResponse'];
|
|
1146
|
+
type RoomingDeleteResponse = components['schemas']['RoomingDeleteResponse'];
|
|
1147
|
+
type RoomingAssignResponse = components['schemas']['RoomingAssignResponse'];
|
|
1148
|
+
type RoomingAutoAssignResponse = components['schemas']['RoomingAutoAssignResponse'];
|
|
1149
|
+
type CreateRoomingStayWindowRequest = components['schemas']['CreateRoomingStayWindowRequest'];
|
|
1150
|
+
type PatchRoomingStayWindowRequest = components['schemas']['PatchRoomingStayWindowRequest'];
|
|
1151
|
+
type CreateRoomingRoomRequest = components['schemas']['CreateRoomingRoomRequest'];
|
|
1152
|
+
type PatchRoomingRoomRequest = components['schemas']['PatchRoomingRoomRequest'];
|
|
1153
|
+
type AssignRoomingBedRequest = components['schemas']['AssignRoomingBedRequest'];
|
|
1154
|
+
type AutoAssignRoomingRequest = components['schemas']['AutoAssignRoomingRequest'];
|
|
1155
|
+
/**
|
|
1156
|
+
* One row of the board's `rooms[]`: a live room, or the tombstone for one
|
|
1157
|
+
* deleted since the cursor. Aliased off the response type, never restated.
|
|
1158
|
+
*/
|
|
1159
|
+
type RoomingRoomDeltaRow = RoomingBoardResponse['rooms'][number];
|
|
1160
|
+
/** One traveller's chip, wherever it appears — in a bed, or in `unassigned[]`. */
|
|
1161
|
+
type RoomingOccupant = RoomingBoardResponse['unassigned'][number];
|
|
1162
|
+
/**
|
|
1163
|
+
* `autoAssign`'s rules block, named so a caller can build one and hold it in a
|
|
1164
|
+
* variable. Indexed off the request type, so the three knobs stay whatever the
|
|
1165
|
+
* contract says they are.
|
|
1166
|
+
*/
|
|
1167
|
+
type RoomingAutoAssignRules = NonNullable<AutoAssignRoomingRequest['rules']>;
|
|
1168
|
+
interface ReadRoomingBoardOptions {
|
|
1169
|
+
readonly tripRef: string;
|
|
1170
|
+
/** Narrows the board to one stay window. Omitted reads every window. */
|
|
1171
|
+
readonly stayWindowId?: string;
|
|
1172
|
+
/**
|
|
1173
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and nothing
|
|
1174
|
+
* else. Omit it for the full board. Prefer `createDeltaCursor`
|
|
1175
|
+
* (`../delta/cursor.ts`) over passing this by hand — the boundary is the one
|
|
1176
|
+
* thing that silently loses rows, and that module owns it.
|
|
1177
|
+
*/
|
|
1178
|
+
readonly since?: string;
|
|
1179
|
+
readonly signal?: AbortSignal;
|
|
1180
|
+
}
|
|
1181
|
+
interface ListRoomingStayWindowsOptions {
|
|
1182
|
+
readonly tripRef: string;
|
|
1183
|
+
/** See `ReadRoomingBoardOptions.since`. */
|
|
1184
|
+
readonly since?: string;
|
|
1185
|
+
readonly signal?: AbortSignal;
|
|
1186
|
+
}
|
|
1187
|
+
interface CreateRoomingStayWindowOptions extends Omit<CreateRoomingStayWindowRequest, 'startDate' | 'endDate'> {
|
|
1188
|
+
readonly tripRef: string;
|
|
1189
|
+
readonly startDate: DateTimeInput;
|
|
1190
|
+
/**
|
|
1191
|
+
* Half-open with `startDate`: a checkout date equal to the next window's
|
|
1192
|
+
* check-in is legal, because multi-hotel trips are contiguous by nature.
|
|
1193
|
+
* Overlap beyond that is refused with `422 BUSINESS_RULE_VIOLATION` and
|
|
1194
|
+
* `details.rule === 'stay_window_overlap'`.
|
|
1195
|
+
*/
|
|
1196
|
+
readonly endDate: DateTimeInput;
|
|
1197
|
+
readonly idempotencyKey?: string;
|
|
1198
|
+
readonly signal?: AbortSignal;
|
|
1199
|
+
}
|
|
1200
|
+
interface PatchRoomingStayWindowOptions extends Omit<PatchRoomingStayWindowRequest, 'startDate' | 'endDate'> {
|
|
1201
|
+
readonly tripRef: string;
|
|
1202
|
+
readonly windowId: string;
|
|
1203
|
+
readonly startDate?: DateTimeInput;
|
|
1204
|
+
readonly endDate?: DateTimeInput;
|
|
1205
|
+
/**
|
|
1206
|
+
* Required, not optional. A missing `If-Match` and a stale one both answer
|
|
1207
|
+
* `409 CONFLICT_VERSION` — identically confusing either way — so this method
|
|
1208
|
+
* makes forgetting it impossible instead of merely discouraged. Pass the
|
|
1209
|
+
* `version` from the window as last read. The SDK builds the `If-Match`
|
|
1210
|
+
* header itself; never set it in `headers`.
|
|
1211
|
+
*/
|
|
1212
|
+
readonly version: number;
|
|
1213
|
+
readonly signal?: AbortSignal;
|
|
1214
|
+
}
|
|
1215
|
+
interface DeleteRoomingStayWindowOptions {
|
|
1216
|
+
readonly tripRef: string;
|
|
1217
|
+
readonly windowId: string;
|
|
1218
|
+
/** See `PatchRoomingStayWindowOptions.version` — same contract. */
|
|
1219
|
+
readonly version: number;
|
|
1220
|
+
/**
|
|
1221
|
+
* Deleting a window that still has rooms with occupants is REFUSED
|
|
1222
|
+
* (`422 BUSINESS_RULE_VIOLATION`, `details.rule ===
|
|
1223
|
+
* 'stay_window_has_assignments'`) unless this is `true`, which also releases
|
|
1224
|
+
* the beds. A query param and never a body, because a DELETE body is not
|
|
1225
|
+
* reliably carried; an explicit opt-in and never the default, because the
|
|
1226
|
+
* refusal is the whole safety property — a manager who did not mean to empty
|
|
1227
|
+
* a hotel gets told, not obeyed.
|
|
1228
|
+
*/
|
|
1229
|
+
readonly force?: boolean;
|
|
1230
|
+
readonly signal?: AbortSignal;
|
|
1231
|
+
}
|
|
1232
|
+
interface RoomingStayWindowsResource {
|
|
1233
|
+
/**
|
|
1234
|
+
* `GET …/rooming/stay-windows`. With `?since=` the rows are
|
|
1235
|
+
* `RoomingStayWindowDeltaResponse` — a union with the tombstone, so the drop
|
|
1236
|
+
* case cannot be forgotten (`../delta/cursor.ts`'s `isTombstone`). Without
|
|
1237
|
+
* one, no tombstone can appear: a cold read has nothing to drop.
|
|
1238
|
+
*/
|
|
1239
|
+
list(options: ListRoomingStayWindowsOptions): Promise<KaafilResponse<readonly RoomingStayWindowDeltaResponse[]>>;
|
|
1240
|
+
create(options: CreateRoomingStayWindowOptions): Promise<KaafilResponse<RoomingStayWindowResponse>>;
|
|
1241
|
+
patch(options: PatchRoomingStayWindowOptions): Promise<KaafilResponse<RoomingStayWindowResponse>>;
|
|
1242
|
+
remove(options: DeleteRoomingStayWindowOptions): Promise<KaafilResponse<RoomingDeleteResponse>>;
|
|
1243
|
+
}
|
|
1244
|
+
interface CreateRoomingRoomOptions extends CreateRoomingRoomRequest {
|
|
1245
|
+
readonly tripRef: string;
|
|
1246
|
+
readonly idempotencyKey?: string;
|
|
1247
|
+
readonly signal?: AbortSignal;
|
|
1248
|
+
}
|
|
1249
|
+
interface PatchRoomingRoomOptions extends PatchRoomingRoomRequest {
|
|
1250
|
+
readonly tripRef: string;
|
|
1251
|
+
readonly roomId: string;
|
|
1252
|
+
/** See `PatchRoomingStayWindowOptions.version` — same contract. */
|
|
1253
|
+
readonly version: number;
|
|
1254
|
+
readonly signal?: AbortSignal;
|
|
1255
|
+
}
|
|
1256
|
+
interface DeleteRoomingRoomOptions {
|
|
1257
|
+
readonly tripRef: string;
|
|
1258
|
+
readonly roomId: string;
|
|
1259
|
+
/** See `PatchRoomingStayWindowOptions.version` — same contract. */
|
|
1260
|
+
readonly version: number;
|
|
1261
|
+
/** See `DeleteRoomingStayWindowOptions.force` — same contract, one room's worth. */
|
|
1262
|
+
readonly force?: boolean;
|
|
1263
|
+
readonly signal?: AbortSignal;
|
|
1264
|
+
}
|
|
1265
|
+
interface RoomingRoomsResource {
|
|
1266
|
+
create(options: CreateRoomingRoomOptions): Promise<KaafilResponse<RoomingRoomResponse>>;
|
|
1267
|
+
/**
|
|
1268
|
+
* Beds are synthesised from `capacity` (`A`..`H`, truncated), never stored,
|
|
1269
|
+
* so `beds[]` on the response is derived — dropping `capacity` below the
|
|
1270
|
+
* room's current occupancy is refused with `422 BUSINESS_RULE_VIOLATION` and
|
|
1271
|
+
* `details.rule === 'room_capacity_below_occupancy'` rather than silently
|
|
1272
|
+
* evicting whoever was in the last bed.
|
|
1273
|
+
*/
|
|
1274
|
+
patch(options: PatchRoomingRoomOptions): Promise<KaafilResponse<RoomingRoomResponse>>;
|
|
1275
|
+
remove(options: DeleteRoomingRoomOptions): Promise<KaafilResponse<RoomingDeleteResponse>>;
|
|
1276
|
+
}
|
|
1277
|
+
interface AssignRoomingBedOptions extends AssignRoomingBedRequest {
|
|
1278
|
+
readonly tripRef: string;
|
|
1279
|
+
readonly idempotencyKey?: string;
|
|
1280
|
+
readonly signal?: AbortSignal;
|
|
1281
|
+
}
|
|
1282
|
+
interface AutoAssignRoomingOptions extends AutoAssignRoomingRequest {
|
|
1283
|
+
readonly tripRef: string;
|
|
1284
|
+
readonly idempotencyKey?: string;
|
|
1285
|
+
readonly signal?: AbortSignal;
|
|
1286
|
+
}
|
|
1287
|
+
interface RoomingResource {
|
|
1288
|
+
/** `GET /api/v1/trips/{ref}/rooming` — windows, rooms with their beds, the
|
|
1289
|
+
* unassigned roster, and the summary counters. */
|
|
1290
|
+
read(options: ReadRoomingBoardOptions): Promise<KaafilResponse<RoomingBoardResponse>>;
|
|
1291
|
+
/**
|
|
1292
|
+
* `POST …/rooming/assign` — puts one traveller in one bed. `bedLabel: null`
|
|
1293
|
+
* un-assigns them. If the target bed is taken the occupant is displaced, and
|
|
1294
|
+
* the response names them in `displacedTravellerId` rather than leaving the
|
|
1295
|
+
* caller to diff two boards to notice a second person moved.
|
|
1296
|
+
*/
|
|
1297
|
+
assign(options: AssignRoomingBedOptions): Promise<KaafilResponse<RoomingAssignResponse>>;
|
|
1298
|
+
/**
|
|
1299
|
+
* `POST …/rooming/auto-assign`. `dryRun: true` returns the plan without
|
|
1300
|
+
* writing it; `dryRun: false` returns the plan it actually applied. The two
|
|
1301
|
+
* are the SAME computation — the solver is pure and the only difference is
|
|
1302
|
+
* whether the result is also written — so a preview and its apply agree by
|
|
1303
|
+
* construction, and a client that stored the preview can diff it against the
|
|
1304
|
+
* applied plan to see whether the rooms moved underneath it.
|
|
1305
|
+
*
|
|
1306
|
+
* Every rule reports an outcome in `perRule`, including rules that had
|
|
1307
|
+
* nothing to do; a relaxation is never silent. How loudly to render each one
|
|
1308
|
+
* is `HEURISTIC_SEVERITY` in `../rooming/vocabulary.ts`. A traveller who got
|
|
1309
|
+
* no bed is in `unassigned[]` WITH a reason — absence from `plan` alone is
|
|
1310
|
+
* ambiguous, because a manually-pinned traveller is also absent and is fine.
|
|
1311
|
+
*/
|
|
1312
|
+
autoAssign(options: AutoAssignRoomingOptions): Promise<KaafilResponse<RoomingAutoAssignResponse>>;
|
|
1313
|
+
readonly stayWindows: RoomingStayWindowsResource;
|
|
1314
|
+
readonly rooms: RoomingRoomsResource;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* `seating` — the transport board, vehicles, manual seat assignment,
|
|
1319
|
+
* auto-assign and the vehicle-manager link. `architecture/08-sdk.md §4`.
|
|
1320
|
+
*
|
|
1321
|
+
* `read` accepts a manager session, an agency-admin session OR an API key
|
|
1322
|
+
* (three alternatives, never an AND); every WRITE is `managerAuth`-only, the
|
|
1323
|
+
* same split `rooming` and `itinerary` have and for the same reason — seating
|
|
1324
|
+
* is decided on the ground.
|
|
1325
|
+
*
|
|
1326
|
+
* Every path parameter is a named field on its options object — never a
|
|
1327
|
+
* positional string — so `vehicles.patch` (a trip ref AND a vehicle id) can't
|
|
1328
|
+
* be called with its arguments swapped.
|
|
1329
|
+
*
|
|
1330
|
+
* `deriveOccupantMark` and `synthesiseBedLabels` — the glyph/tone and
|
|
1331
|
+
* bed-label logic a client needs to RENDER this module's output — are not
|
|
1332
|
+
* here; they are rooming's, imported by the engine's seating module rather
|
|
1333
|
+
* than re-derived (`architecture` 10B notes), and are not on the wire either.
|
|
1334
|
+
* A client renders an occupant's `glyph`/`tone` fields straight off the
|
|
1335
|
+
* response, the same as `rooming`'s occupants.
|
|
1336
|
+
*
|
|
1337
|
+
* ── THE TYPE↔LAYOUT RULE, AND WHY `seatLabel` CANNOT BE STATICALLY TIED TO IT ──
|
|
1338
|
+
*
|
|
1339
|
+
* A vehicle's `layout` is non-null only for `type ∈ {FLIGHT, TRAIN}`
|
|
1340
|
+
* (`Vehicle_layout_requires_seatable_type`, enforced server-side by a CHECK).
|
|
1341
|
+
* `layout: null` means the vehicle carries no seat map at all — most road
|
|
1342
|
+
* vehicles, because "the label grid was a fiction the manager maintained and
|
|
1343
|
+
* the driver ignored" — and on such a vehicle every assignment's `seatLabel`
|
|
1344
|
+
* is `null`, enforced on write. The converse is deliberately NOT an invariant:
|
|
1345
|
+
* a seat-mapped vehicle may still hold `seatLabel: null` rows (a group
|
|
1346
|
+
* confirmed on a flight before the airline issues seat numbers), so `layout`
|
|
1347
|
+
* being non-null does not mean `seatLabel` is required either.
|
|
1348
|
+
*
|
|
1349
|
+
* `assign()` below is intentionally NOT generic over "which vehicle", so
|
|
1350
|
+
* `AssignSeatingOptions.seatLabel` cannot be narrowed to "required when this
|
|
1351
|
+
* vehicle has a layout, forbidden when it doesn't" at the type level: the
|
|
1352
|
+
* call site names a `vehicleId`, not the vehicle object it was read from, and
|
|
1353
|
+
* whether that id currently has a layout is server-side state this SDK does
|
|
1354
|
+
* not — and must not — cache and re-derive locally (the whole point of a
|
|
1355
|
+
* board read is that the server is the source of truth for it). Encoding the
|
|
1356
|
+
* rule here would mean either accepting a vehicle object just to read one
|
|
1357
|
+
* field, which no other write on this surface does, or building a second,
|
|
1358
|
+
* client-side copy of `seatMapped` that could drift from what the server
|
|
1359
|
+
* actually enforces. So `seatLabel` stays `string | null | undefined` on every
|
|
1360
|
+
* vehicle unconditionally, and the rule is enforced where the truth lives:
|
|
1361
|
+
* the server accepts a `seatLabel` for a seat-less vehicle and silently
|
|
1362
|
+
* ignores it into `null`; the honest type-level statement of this surface is
|
|
1363
|
+
* that `seatLabel` is ALWAYS optional and ALWAYS nullable, never that it
|
|
1364
|
+
* varies by vehicle. Read a vehicle's own `seatMapped` (`SeatingBoardResponse`
|
|
1365
|
+
* / `SeatingVehicleResponse`) before deciding whether to show a seat picker.
|
|
1366
|
+
*/
|
|
1367
|
+
|
|
1368
|
+
type SeatingBoardResponse = components['schemas']['SeatingBoardResponse'];
|
|
1369
|
+
type SeatingVehicleResponse = components['schemas']['SeatingVehicleResponse'];
|
|
1370
|
+
type SeatingDeleteVehicleResponse = components['schemas']['SeatingDeleteVehicleResponse'];
|
|
1371
|
+
type SeatingAssignResponse = components['schemas']['SeatingAssignResponse'];
|
|
1372
|
+
type SeatingAutoAssignResponse = components['schemas']['SeatingAutoAssignResponse'];
|
|
1373
|
+
type SeatingManagerLinkResponse = components['schemas']['SeatingManagerLinkResponse'];
|
|
1374
|
+
type CreateSeatingVehicleRequest = components['schemas']['CreateSeatingVehicleRequest'];
|
|
1375
|
+
type PatchSeatingVehicleRequest = components['schemas']['PatchSeatingVehicleRequest'];
|
|
1376
|
+
type AssignSeatingRequest = components['schemas']['AssignSeatingRequest'];
|
|
1377
|
+
type AutoAssignSeatingRequest = components['schemas']['AutoAssignSeatingRequest'];
|
|
1378
|
+
type LinkSeatingManagerRequest = components['schemas']['LinkSeatingManagerRequest'];
|
|
1379
|
+
/**
|
|
1380
|
+
* One row of the board's `vehicles[]`: a live vehicle, or the tombstone for
|
|
1381
|
+
* one deleted since the cursor. Aliased off the response type, never
|
|
1382
|
+
* restated.
|
|
1383
|
+
*/
|
|
1384
|
+
type SeatingVehicleDeltaRow = SeatingBoardResponse['vehicles'][number];
|
|
1385
|
+
/** One traveller's chip in `unassignedPool[]`, or (per vehicle) in
|
|
1386
|
+
* `occupants[]`/`unseatedOnVehicle[]`/a seat's `occupant`. */
|
|
1387
|
+
type SeatingOccupant = SeatingBoardResponse['unassignedPool'][number];
|
|
1388
|
+
/**
|
|
1389
|
+
* `autoAssign`'s rules block, named so a caller can build one and hold it in
|
|
1390
|
+
* a variable. Indexed off the request type, so the three knobs stay whatever
|
|
1391
|
+
* the contract says they are.
|
|
1392
|
+
*/
|
|
1393
|
+
type SeatingAutoAssignRules = NonNullable<AutoAssignSeatingRequest['rules']>;
|
|
1394
|
+
interface ReadSeatingBoardOptions {
|
|
1395
|
+
readonly tripRef: string;
|
|
1396
|
+
/**
|
|
1397
|
+
* The delta cursor: a previous response's own `meta.serverTime`, and
|
|
1398
|
+
* nothing else. Omit it for the full board. Prefer `createDeltaCursor`
|
|
1399
|
+
* (`../delta/cursor.ts`) over passing this by hand — the boundary is the
|
|
1400
|
+
* one thing that silently loses rows, and that module owns it.
|
|
1401
|
+
*/
|
|
1402
|
+
readonly since?: string;
|
|
1403
|
+
readonly signal?: AbortSignal;
|
|
1404
|
+
}
|
|
1405
|
+
interface CreateSeatingVehicleOptions extends CreateSeatingVehicleRequest {
|
|
1406
|
+
readonly tripRef: string;
|
|
1407
|
+
readonly idempotencyKey?: string;
|
|
1408
|
+
readonly signal?: AbortSignal;
|
|
1409
|
+
}
|
|
1410
|
+
interface PatchSeatingVehicleOptions extends PatchSeatingVehicleRequest {
|
|
1411
|
+
readonly tripRef: string;
|
|
1412
|
+
readonly vehicleId: string;
|
|
1413
|
+
/**
|
|
1414
|
+
* Required, not optional. A missing `If-Match` and a stale one both answer
|
|
1415
|
+
* `409 CONFLICT_VERSION` — identically confusing either way — so this
|
|
1416
|
+
* method makes forgetting it impossible instead of merely discouraged.
|
|
1417
|
+
* Pass the `version` from the vehicle as last read. The SDK builds the
|
|
1418
|
+
* `If-Match` header itself; never set it in `headers`.
|
|
1419
|
+
*
|
|
1420
|
+
* A capacity-down or a `layout` change that would orphan a recorded seat
|
|
1421
|
+
* label is refused with `422 SEATING_CAPACITY_ORPHAN` rather than silently
|
|
1422
|
+
* dropping whoever held that label — see this file's header for the three
|
|
1423
|
+
* forms of the guard.
|
|
1424
|
+
*/
|
|
1425
|
+
readonly version: number;
|
|
1426
|
+
readonly signal?: AbortSignal;
|
|
1427
|
+
}
|
|
1428
|
+
interface DeleteSeatingVehicleOptions {
|
|
1429
|
+
readonly tripRef: string;
|
|
1430
|
+
readonly vehicleId: string;
|
|
1431
|
+
/** See `PatchSeatingVehicleOptions.version` — same contract. */
|
|
1432
|
+
readonly version: number;
|
|
1433
|
+
readonly signal?: AbortSignal;
|
|
1434
|
+
}
|
|
1435
|
+
interface LinkSeatingManagerOptions extends LinkSeatingManagerRequest {
|
|
1436
|
+
readonly tripRef: string;
|
|
1437
|
+
readonly vehicleId: string;
|
|
1438
|
+
readonly idempotencyKey?: string;
|
|
1439
|
+
readonly signal?: AbortSignal;
|
|
1440
|
+
}
|
|
1441
|
+
interface UnlinkSeatingManagerOptions {
|
|
1442
|
+
readonly tripRef: string;
|
|
1443
|
+
readonly vehicleId: string;
|
|
1444
|
+
/** See `PatchSeatingVehicleOptions.version` — same contract. */
|
|
1445
|
+
readonly version: number;
|
|
1446
|
+
readonly signal?: AbortSignal;
|
|
1447
|
+
}
|
|
1448
|
+
interface SeatingVehicleManagerResource {
|
|
1449
|
+
/** `POST …/seating/vehicles/{vehicleId}/manager` — links a manager to this
|
|
1450
|
+
* vehicle. Demotes whichever vehicle that manager was previously linked to,
|
|
1451
|
+
* atomically: a manager only ever rides one vehicle. */
|
|
1452
|
+
link(options: LinkSeatingManagerOptions): Promise<KaafilResponse<SeatingManagerLinkResponse>>;
|
|
1453
|
+
unlink(options: UnlinkSeatingManagerOptions): Promise<KaafilResponse<SeatingManagerLinkResponse>>;
|
|
1454
|
+
}
|
|
1455
|
+
interface SeatingVehiclesResource {
|
|
1456
|
+
create(options: CreateSeatingVehicleOptions): Promise<KaafilResponse<SeatingVehicleResponse>>;
|
|
1457
|
+
patch(options: PatchSeatingVehicleOptions): Promise<KaafilResponse<SeatingVehicleResponse>>;
|
|
1458
|
+
/** Clears every occupant on this vehicle back to the unassigned pool and
|
|
1459
|
+
* unlinks its manager, then deletes it. */
|
|
1460
|
+
remove(options: DeleteSeatingVehicleOptions): Promise<KaafilResponse<SeatingDeleteVehicleResponse>>;
|
|
1461
|
+
readonly manager: SeatingVehicleManagerResource;
|
|
1462
|
+
}
|
|
1463
|
+
interface AssignSeatingOptions extends AssignSeatingRequest {
|
|
1464
|
+
readonly tripRef: string;
|
|
1465
|
+
readonly idempotencyKey?: string;
|
|
1466
|
+
readonly signal?: AbortSignal;
|
|
1467
|
+
}
|
|
1468
|
+
interface AutoAssignSeatingOptions extends AutoAssignSeatingRequest {
|
|
1469
|
+
readonly tripRef: string;
|
|
1470
|
+
readonly idempotencyKey?: string;
|
|
1471
|
+
readonly signal?: AbortSignal;
|
|
1472
|
+
}
|
|
1473
|
+
interface SeatingResource {
|
|
1474
|
+
/** `GET /api/v1/trips/{ref}/seating` — vehicles with their seats and
|
|
1475
|
+
* occupants, the unassigned pool, and the summary counters. */
|
|
1476
|
+
read(options: ReadSeatingBoardOptions): Promise<KaafilResponse<SeatingBoardResponse>>;
|
|
1477
|
+
/**
|
|
1478
|
+
* `POST …/seating/assign` — puts one traveller in one vehicle (and, on a
|
|
1479
|
+
* seat-mapped vehicle, one seat). `vehicleId: null` moves them to the
|
|
1480
|
+
* unassigned pool. If the target seat is taken the occupant is displaced,
|
|
1481
|
+
* and the response names them in `displacedTravellerId` (plus
|
|
1482
|
+
* `displacedVehicleId`/`displacedSeatLabel`) rather than leaving the caller
|
|
1483
|
+
* to diff two boards to notice a second person moved.
|
|
1484
|
+
*/
|
|
1485
|
+
assign(options: AssignSeatingOptions): Promise<KaafilResponse<SeatingAssignResponse>>;
|
|
1486
|
+
/**
|
|
1487
|
+
* `POST …/seating/auto-assign`. `dryRun: true` returns the plan without
|
|
1488
|
+
* writing it; `dryRun: false` returns the plan it actually applied. The two
|
|
1489
|
+
* are the SAME computation — the solver is pure and the only difference is
|
|
1490
|
+
* whether the result is also written — so a preview and its apply agree by
|
|
1491
|
+
* construction.
|
|
1492
|
+
*
|
|
1493
|
+
* Every rule reports an outcome in `perRule`: `applied`, `relaxed`, or
|
|
1494
|
+
* `noop` with `noopReason: 'no_seat_map'` — the third outcome seating has
|
|
1495
|
+
* that rooming does not, because `medicalFirst` and `gender` are
|
|
1496
|
+
* seat-adjacency heuristics with nothing to do on a seat-less vehicle. A
|
|
1497
|
+
* `noop` and a relaxed rule are different facts (a heuristic that could not
|
|
1498
|
+
* apply vs. one that applied a weaker version of itself), so both are
|
|
1499
|
+
* reported rather than one masquerading as the other.
|
|
1500
|
+
*/
|
|
1501
|
+
autoAssign(options: AutoAssignSeatingOptions): Promise<KaafilResponse<SeatingAutoAssignResponse>>;
|
|
1502
|
+
readonly vehicles: SeatingVehiclesResource;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* `shareTokens` — create, read, revoke. `architecture/08-sdk.md §3.3, §4`;
|
|
1507
|
+
* all three operations are `apiKeyAuth`-only in the spec (server-side mint,
|
|
1508
|
+
* matching `03-auth-and-tokens.md §1`: browsers only ever hold the *minted*
|
|
1509
|
+
* token, never the key that mints it).
|
|
1510
|
+
*/
|
|
1511
|
+
|
|
1512
|
+
type MintShareTokenRequest = components['schemas']['MintShareTokenRequest'];
|
|
1513
|
+
type MintedShareTokenViewResponse = components['schemas']['MintedShareTokenViewResponse'];
|
|
1514
|
+
type ShareTokenViewResponse = components['schemas']['ShareTokenViewResponse'];
|
|
1515
|
+
interface CreateShareTokenOptions extends Omit<MintShareTokenRequest, 'expiresAt'> {
|
|
1516
|
+
/** Optional on the wire (`ttlDays` is the other way to set expiry) — see
|
|
1517
|
+
* `../datetime` for the coercion contract; a full ISO string with an
|
|
1518
|
+
* offset still passes through unchanged. */
|
|
1519
|
+
readonly expiresAt?: DateTimeInput;
|
|
1520
|
+
/** Stable across retries — reused as-is, never re-minted per attempt. */
|
|
1521
|
+
readonly idempotencyKey?: string;
|
|
1522
|
+
readonly signal?: AbortSignal;
|
|
1523
|
+
}
|
|
1524
|
+
interface ReadShareTokenOptions {
|
|
1525
|
+
readonly id: string;
|
|
1526
|
+
readonly signal?: AbortSignal;
|
|
1527
|
+
}
|
|
1528
|
+
interface RevokeShareTokenOptions {
|
|
1529
|
+
readonly id: string;
|
|
1530
|
+
readonly signal?: AbortSignal;
|
|
1531
|
+
}
|
|
1532
|
+
interface ShareTokensResource {
|
|
1533
|
+
/**
|
|
1534
|
+
* `POST /api/v1/share-tokens` — mints a config-scoped, expiring share
|
|
1535
|
+
* token. Expiry is top-level (`ttlDays` OR `expiresAt` on this options
|
|
1536
|
+
* object — never inside `config`); the server clamps it as documented on
|
|
1537
|
+
* `MintShareTokenRequest`. The CRM builds its own link around the returned
|
|
1538
|
+
* `token`; this SDK never constructs one.
|
|
1539
|
+
*/
|
|
1540
|
+
create(options: CreateShareTokenOptions): Promise<KaafilResponse<MintedShareTokenViewResponse>>;
|
|
1541
|
+
/** `GET /api/v1/share-tokens/{id}` — a share token's metadata, never its plaintext value. */
|
|
1542
|
+
read(options: ReadShareTokenOptions): Promise<KaafilResponse<ShareTokenViewResponse>>;
|
|
1543
|
+
/** `DELETE /api/v1/share-tokens/{id}`. */
|
|
1544
|
+
revoke(options: RevokeShareTokenOptions): Promise<KaafilResponse<ShareTokenViewResponse>>;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* `treks` — the boarding-day board, walk-ins, and postpone. `architecture/08-
|
|
1549
|
+
* sdk.md §4`. Deliberately small: board/close/reopen belong to `pickups`, and
|
|
1550
|
+
* this module contributes only trek POLICY on top of them.
|
|
1551
|
+
*
|
|
1552
|
+
* `board` and `walkIns.meta` accept a manager session OR an API key; `postpone`
|
|
1553
|
+
* and `walkIns.create` are `managerAuth`-only — a trek is worked and postponed
|
|
1554
|
+
* on the ground.
|
|
1555
|
+
*
|
|
1556
|
+
* ── THE `active` SENTINEL ────────────────────────────────────────────────────
|
|
1557
|
+
*
|
|
1558
|
+
* `trekRef` in these paths is a dual-mode identity like every other `ref` in
|
|
1559
|
+
* this SDK — Kaafil id or the CRM's external id — PLUS one reserved word,
|
|
1560
|
+
* `ACTIVE_TREK_REF` (`'active'`), which resolves to the manager's CURRENT
|
|
1561
|
+
* trek rather than being looked up as an id at all. `RESERVED_TREK_REF` in the
|
|
1562
|
+
* engine already guards the ingest direction (an external trip id can never
|
|
1563
|
+
* literally be `"active"`); this path position is a second, distinct place
|
|
1564
|
+
* the same reservation applies, and the engine's own docblock says a new path
|
|
1565
|
+
* position "must opt in explicitly" rather than inheriting the ingest guard
|
|
1566
|
+
* for free. Exposed here as a named constant rather than left for a caller to
|
|
1567
|
+
* type the string, so `treks.board({ trekRef: ACTIVE_TREK_REF })` reads as
|
|
1568
|
+
* "whichever trek this manager is on" at the call site instead of a magic
|
|
1569
|
+
* string that happens to mean something special.
|
|
1570
|
+
*
|
|
1571
|
+
* The consequence for a real external trip id: one literally equal to
|
|
1572
|
+
* `"active"` can NEVER be addressed through any of these four routes — it
|
|
1573
|
+
* would always resolve as the sentinel instead. That is a property of the
|
|
1574
|
+
* contract, not a bug in this SDK; a CRM minting external ids should treat
|
|
1575
|
+
* `"active"` as unusable here the same way it would a reserved keyword in any
|
|
1576
|
+
* other system.
|
|
1577
|
+
*
|
|
1578
|
+
* When no trek is currently assigned, `board` still answers `200` with an
|
|
1579
|
+
* `emptyState` block (`{ reason: 'no_trek_assigned', message }`) rather than
|
|
1580
|
+
* `404` — the manager stays where they tapped instead of hitting an error
|
|
1581
|
+
* screen. Every OTHER trek endpoint (`walkIns.*`, `postpone`) answers `404`
|
|
1582
|
+
* for "no such trek" the normal way.
|
|
1583
|
+
*
|
|
1584
|
+
* A trek endpoint reached on an `eventType: 'TRIP'` trip is `422 NOT_A_TREK`
|
|
1585
|
+
* — wrong kind, not missing. Unknown or cross-scope stays `404`, the same
|
|
1586
|
+
* not-found used everywhere else in this SDK (`../http/errors.ts`'s standing
|
|
1587
|
+
* argument for why there is exactly one not-found class applies here too).
|
|
1588
|
+
*/
|
|
1589
|
+
|
|
1590
|
+
type TrekBoardResponse = components['schemas']['TrekBoardResponse'];
|
|
1591
|
+
type WalkInMetaResponse = components['schemas']['WalkInMetaResponse'];
|
|
1592
|
+
type WalkInResponse = components['schemas']['WalkInResponse'];
|
|
1593
|
+
type PostponeResponse = components['schemas']['PostponeResponse'];
|
|
1594
|
+
type CreateTrekWalkInRequest = components['schemas']['CreateTrekWalkInRequest'];
|
|
1595
|
+
type PostponeTrekRequest = components['schemas']['PostponeTrekRequest'];
|
|
1596
|
+
/**
|
|
1597
|
+
* The reserved `trekRef` value meaning "the manager's current trek" — see
|
|
1598
|
+
* this file's header. Never a real external trip id: the engine refuses to
|
|
1599
|
+
* ingest one equal to this string (`RESERVED_TREK_REF` in
|
|
1600
|
+
* `shared/ingest/external-ref.resolver.ts`), so no trip that reaches this SDK
|
|
1601
|
+
* can ever collide with it.
|
|
1602
|
+
*/
|
|
1603
|
+
declare const ACTIVE_TREK_REF = "active";
|
|
1604
|
+
interface ReadTrekBoardOptions {
|
|
1605
|
+
/** A trek's dual-mode ref, or `ACTIVE_TREK_REF` for the manager's current
|
|
1606
|
+
* one — see this file's header. */
|
|
1607
|
+
readonly trekRef: string;
|
|
1608
|
+
readonly signal?: AbortSignal;
|
|
1609
|
+
}
|
|
1610
|
+
interface ReadTrekWalkInMetaOptions {
|
|
1611
|
+
readonly trekRef: string;
|
|
1612
|
+
readonly signal?: AbortSignal;
|
|
1613
|
+
}
|
|
1614
|
+
interface CreateTrekWalkInOptions extends CreateTrekWalkInRequest {
|
|
1615
|
+
readonly trekRef: string;
|
|
1616
|
+
readonly idempotencyKey?: string;
|
|
1617
|
+
readonly signal?: AbortSignal;
|
|
1618
|
+
}
|
|
1619
|
+
interface TrekWalkInsResource {
|
|
1620
|
+
/** `GET /api/v1/treks/{trekRef}/walk-ins/meta` — the open pickup points a
|
|
1621
|
+
* walk-in may be assigned to, plus field hints for the intake form. */
|
|
1622
|
+
meta(options: ReadTrekWalkInMetaOptions): Promise<KaafilResponse<WalkInMetaResponse>>;
|
|
1623
|
+
/**
|
|
1624
|
+
* `POST /api/v1/treks/{trekRef}/walk-ins` — mints a Kaafil-owned traveller
|
|
1625
|
+
* (no `externalId`) plus a `WalkIn` row, and upserts the manifest so
|
|
1626
|
+
* rooming, seating and this trek's own board see them immediately. Name and
|
|
1627
|
+
* phone live on the traveller, never on the walk-in row — one PII home.
|
|
1628
|
+
* Reconciliation against a later CRM upsert happens by normalised phone and
|
|
1629
|
+
* emits no event of its own; a walk-in on a `CLOSED` stop auto-reopens it
|
|
1630
|
+
* through `pickups.reopen` in-process, so the eventual re-close carries the
|
|
1631
|
+
* corrective event, not this call.
|
|
1632
|
+
*/
|
|
1633
|
+
create(options: CreateTrekWalkInOptions): Promise<KaafilResponse<WalkInResponse>>;
|
|
1634
|
+
}
|
|
1635
|
+
interface PostponeTrekOptions extends Omit<PostponeTrekRequest, 'newStartDate' | 'newEndDate'> {
|
|
1636
|
+
readonly trekRef: string;
|
|
1637
|
+
/** Must be in the future AND after the trek's current `startDate` — a
|
|
1638
|
+
* postpone moves the trip forward, never back. `CANCELLED`/`COMPLETED`
|
|
1639
|
+
* trips answer `422 CANNOT_POSTPONE`; a write racing the close-out lock
|
|
1640
|
+
* answers `423 LOCKED`. */
|
|
1641
|
+
readonly newStartDate: DateTimeInput;
|
|
1642
|
+
readonly newEndDate: DateTimeInput;
|
|
1643
|
+
readonly idempotencyKey?: string;
|
|
1644
|
+
readonly signal?: AbortSignal;
|
|
1645
|
+
}
|
|
1646
|
+
interface TreksResource {
|
|
1647
|
+
/** `GET /api/v1/treks/{trekRef}/board` — stops with expected-vs-boarded,
|
|
1648
|
+
* the running head-count, and the current phase. See this file's header
|
|
1649
|
+
* for the sentinel and the no-trek `emptyState`. */
|
|
1650
|
+
board(options: ReadTrekBoardOptions): Promise<KaafilResponse<TrekBoardResponse>>;
|
|
1651
|
+
readonly walkIns: TrekWalkInsResource;
|
|
1652
|
+
/**
|
|
1653
|
+
* `POST /api/v1/treks/{trekRef}/postpone`. Shifts every `ItineraryDay` and
|
|
1654
|
+
* `StayWindow` by the same delta in ONE transaction; `sortOrder` and bed/seat
|
|
1655
|
+
* assignments are untouched throughout. Pickup `scheduledTime` is
|
|
1656
|
+
* DELIBERATELY NOT shifted — stop times are re-confirmed by the manager on
|
|
1657
|
+
* the ground, because they usually change with the new departure — so
|
|
1658
|
+
* `ripple` on the response reports itinerary and stay-window counts only,
|
|
1659
|
+
* never a pickup count. `postponedFromDate` on the response always holds the
|
|
1660
|
+
* FIRST original start date, even across a second postpone.
|
|
1661
|
+
*/
|
|
1662
|
+
postpone(options: PostponeTrekOptions): Promise<KaafilResponse<PostponeResponse>>;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* `trips` — upsert, get, cancel, plus `travellers`, `managers`, `balance`,
|
|
1667
|
+
* and the bulk push. `architecture/08-sdk.md §4`. All operations here are
|
|
1668
|
+
* `apiKeyAuth`-only in the spec (CRM-backend ingest, `03-auth-and-tokens.md
|
|
1669
|
+
* §1`).
|
|
1670
|
+
*
|
|
1671
|
+
* Every path parameter is a named field on its options object — never a
|
|
1672
|
+
* positional string — so a two-parameter route (`travellers.remove` takes a
|
|
1673
|
+
* trip ref AND a traveller ref; `managers.unassign` takes a trip ref AND a
|
|
1674
|
+
* manager ref) can't be called with its arguments swapped.
|
|
1675
|
+
*/
|
|
1676
|
+
|
|
1677
|
+
type UpsertTripRequest = components['schemas']['UpsertTripRequest'];
|
|
1678
|
+
type TripUpsertResponse = components['schemas']['TripUpsertResponse'];
|
|
1679
|
+
type TripEchoResponse = components['schemas']['TripEchoResponse'];
|
|
1680
|
+
type TripCancelResponse = components['schemas']['TripCancelResponse'];
|
|
1681
|
+
type UpsertTravellerProfileRequest = components['schemas']['UpsertTravellerProfileRequest'];
|
|
1682
|
+
type TravellerUpsertResponse = components['schemas']['TravellerUpsertResponse'];
|
|
1683
|
+
type PushManifestRequest = components['schemas']['PushManifestRequest'];
|
|
1684
|
+
type ManifestPushResponse = components['schemas']['ManifestPushResponse'];
|
|
1685
|
+
type ManifestRemoveResponse = components['schemas']['ManifestRemoveResponse'];
|
|
1686
|
+
type UpsertManagerRequest = components['schemas']['UpsertManagerRequest'];
|
|
1687
|
+
type ManagerResponse = components['schemas']['ManagerResponse'];
|
|
1688
|
+
type AssignManagerRequest = components['schemas']['AssignManagerRequest'];
|
|
1689
|
+
type TripManagerResponse = components['schemas']['TripManagerResponse'];
|
|
1690
|
+
/** `PushTripBalancesRequest` is itself an array in the spec — there is no wrapper object. */
|
|
1691
|
+
type PushTripBalancesRow = components['schemas']['PushTripBalancesRequest'][number];
|
|
1692
|
+
type BalancePushResponse = components['schemas']['BalancePushResponse'];
|
|
1693
|
+
/** Likewise `PushBulkTripsRequest`. */
|
|
1694
|
+
type PushBulkTripsItem = components['schemas']['PushBulkTripsRequest'][number];
|
|
1695
|
+
type BulkTripsPushResponse = components['schemas']['BulkTripsPushResponse'];
|
|
1696
|
+
type UpsertTripSegment = NonNullable<UpsertTripRequest['segments']>[number];
|
|
1697
|
+
type UpsertTripSegmentInput = Omit<UpsertTripSegment, 'startDate' | 'endDate'> & {
|
|
1698
|
+
readonly startDate: DateTimeInput;
|
|
1699
|
+
readonly endDate: DateTimeInput;
|
|
1700
|
+
};
|
|
1701
|
+
type PushManifestTraveller = PushManifestRequest['travellers'][number];
|
|
1702
|
+
type PushManifestTravellerInput = Omit<PushManifestTraveller, 'sourceUpdatedAt'> & {
|
|
1703
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1704
|
+
};
|
|
1705
|
+
type PushBulkTripsTraveller = NonNullable<PushBulkTripsItem['travellers']>[number];
|
|
1706
|
+
type PushBulkTripsTravellerInput = Omit<PushBulkTripsTraveller, 'sourceUpdatedAt'> & {
|
|
1707
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1708
|
+
};
|
|
1709
|
+
type PushBulkTripsBalanceRow = NonNullable<PushBulkTripsItem['balance']>[number];
|
|
1710
|
+
type PushBulkTripsBalanceRowInput = Omit<PushBulkTripsBalanceRow, 'sourceUpdatedAt'> & {
|
|
1711
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1712
|
+
};
|
|
1713
|
+
type PushBulkTripsManager = NonNullable<PushBulkTripsItem['managers']>[number];
|
|
1714
|
+
type PushBulkTripsManagerInput = Omit<PushBulkTripsManager, 'sourceUpdatedAt'> & {
|
|
1715
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1716
|
+
};
|
|
1717
|
+
type PushBulkTripsItemInput = Omit<PushBulkTripsItem, 'startDate' | 'endDate' | 'sourceUpdatedAt' | 'travellers' | 'balance' | 'managers'> & {
|
|
1718
|
+
readonly startDate: DateTimeInput;
|
|
1719
|
+
readonly endDate: DateTimeInput;
|
|
1720
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1721
|
+
readonly travellers?: readonly PushBulkTripsTravellerInput[];
|
|
1722
|
+
readonly balance?: readonly PushBulkTripsBalanceRowInput[];
|
|
1723
|
+
readonly managers?: readonly PushBulkTripsManagerInput[];
|
|
1724
|
+
};
|
|
1725
|
+
interface UpsertTripOptions extends Omit<UpsertTripRequest, 'startDate' | 'endDate' | 'sourceUpdatedAt' | 'segments'> {
|
|
1726
|
+
readonly startDate: DateTimeInput;
|
|
1727
|
+
readonly endDate: DateTimeInput;
|
|
1728
|
+
/** The CRM's own record-updated timestamp, used for last-write-wins
|
|
1729
|
+
* staleness detection (`06-offline-first.md §6`). Deliberately still
|
|
1730
|
+
* required, with no "defaults to now" — defaulting it would make every
|
|
1731
|
+
* write look like the newest write and silently defeat the staleness
|
|
1732
|
+
* check on out-of-order delivery. Accepting a `Date` here (via
|
|
1733
|
+
* `DateTimeInput`) is the correct amount of help: the caller still
|
|
1734
|
+
* supplies the CRM's real timestamp, just without hand-formatting it. */
|
|
1735
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1736
|
+
readonly segments?: readonly UpsertTripSegmentInput[];
|
|
1737
|
+
readonly idempotencyKey?: string;
|
|
1738
|
+
readonly signal?: AbortSignal;
|
|
1739
|
+
}
|
|
1740
|
+
interface GetTripOptions {
|
|
1741
|
+
readonly tripRef: string;
|
|
1742
|
+
readonly signal?: AbortSignal;
|
|
1743
|
+
}
|
|
1744
|
+
interface CancelTripOptions {
|
|
1745
|
+
readonly tripRef: string;
|
|
1746
|
+
readonly idempotencyKey?: string;
|
|
1747
|
+
readonly signal?: AbortSignal;
|
|
1748
|
+
}
|
|
1749
|
+
interface UpsertTravellerProfileOptions extends Omit<UpsertTravellerProfileRequest, 'sourceUpdatedAt'> {
|
|
1750
|
+
/** The traveller's own ref — dual-mode identity (Kaafil id or the CRM's
|
|
1751
|
+
* `externalTravellerId`); resolution is the engine's job. */
|
|
1752
|
+
readonly travellerRef: string;
|
|
1753
|
+
/** See the comment on `UpsertTripOptions.sourceUpdatedAt` — same contract,
|
|
1754
|
+
* required, never defaulted. */
|
|
1755
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1756
|
+
readonly idempotencyKey?: string;
|
|
1757
|
+
readonly signal?: AbortSignal;
|
|
1758
|
+
}
|
|
1759
|
+
interface PushManifestOptions extends Omit<PushManifestRequest, 'travellers'> {
|
|
1760
|
+
readonly tripRef: string;
|
|
1761
|
+
readonly travellers: readonly PushManifestTravellerInput[];
|
|
1762
|
+
readonly idempotencyKey?: string;
|
|
1763
|
+
readonly signal?: AbortSignal;
|
|
1764
|
+
}
|
|
1765
|
+
interface RemoveFromManifestOptions {
|
|
1766
|
+
readonly tripRef: string;
|
|
1767
|
+
readonly travellerRef: string;
|
|
1768
|
+
readonly idempotencyKey?: string;
|
|
1769
|
+
readonly signal?: AbortSignal;
|
|
1770
|
+
}
|
|
1771
|
+
interface TripTravellersResource {
|
|
1772
|
+
upsert(options: UpsertTravellerProfileOptions): Promise<KaafilResponse<TravellerUpsertResponse>>;
|
|
1773
|
+
pushManifest(options: PushManifestOptions): Promise<KaafilResponse<ManifestPushResponse>>;
|
|
1774
|
+
remove(options: RemoveFromManifestOptions): Promise<KaafilResponse<ManifestRemoveResponse>>;
|
|
1775
|
+
}
|
|
1776
|
+
interface UpsertManagerOptions extends Omit<UpsertManagerRequest, 'sourceUpdatedAt'> {
|
|
1777
|
+
/** See the comment on `UpsertTripOptions.sourceUpdatedAt` — same contract,
|
|
1778
|
+
* required, never defaulted. */
|
|
1779
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1780
|
+
readonly idempotencyKey?: string;
|
|
1781
|
+
readonly signal?: AbortSignal;
|
|
1782
|
+
}
|
|
1783
|
+
interface AssignManagerOptions extends Omit<AssignManagerRequest, 'sourceUpdatedAt'> {
|
|
1784
|
+
readonly tripRef: string;
|
|
1785
|
+
/** See the comment on `UpsertTripOptions.sourceUpdatedAt` — same contract,
|
|
1786
|
+
* required, never defaulted. */
|
|
1787
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1788
|
+
readonly idempotencyKey?: string;
|
|
1789
|
+
readonly signal?: AbortSignal;
|
|
1790
|
+
}
|
|
1791
|
+
interface UnassignManagerOptions {
|
|
1792
|
+
readonly tripRef: string;
|
|
1793
|
+
readonly managerRef: string;
|
|
1794
|
+
readonly idempotencyKey?: string;
|
|
1795
|
+
readonly signal?: AbortSignal;
|
|
1796
|
+
}
|
|
1797
|
+
interface TripManagersResource {
|
|
1798
|
+
/** Not trip-scoped — `POST /api/v1/managers` upserts the manager entity
|
|
1799
|
+
* itself (dual-mode identity), independent of any one trip assignment. */
|
|
1800
|
+
upsert(options: UpsertManagerOptions): Promise<KaafilResponse<ManagerResponse>>;
|
|
1801
|
+
assign(options: AssignManagerOptions): Promise<KaafilResponse<TripManagerResponse>>;
|
|
1802
|
+
unassign(options: UnassignManagerOptions): Promise<KaafilResponse<TripManagerResponse>>;
|
|
1803
|
+
}
|
|
1804
|
+
type PushTripBalancesRowInput = Omit<PushTripBalancesRow, 'sourceUpdatedAt'> & {
|
|
1805
|
+
readonly sourceUpdatedAt: DateTimeInput;
|
|
1806
|
+
};
|
|
1807
|
+
interface PushTripBalancesOptions {
|
|
1808
|
+
readonly tripRef: string;
|
|
1809
|
+
readonly balances: readonly PushTripBalancesRowInput[];
|
|
1810
|
+
readonly idempotencyKey?: string;
|
|
1811
|
+
readonly signal?: AbortSignal;
|
|
1812
|
+
}
|
|
1813
|
+
interface TripBalanceResource {
|
|
1814
|
+
push(options: PushTripBalancesOptions): Promise<KaafilResponse<BalancePushResponse>>;
|
|
1815
|
+
}
|
|
1816
|
+
interface PushBulkTripsOptions {
|
|
1817
|
+
readonly trips: readonly PushBulkTripsItemInput[];
|
|
1818
|
+
readonly idempotencyKey?: string;
|
|
1819
|
+
readonly signal?: AbortSignal;
|
|
1820
|
+
}
|
|
1821
|
+
interface TripsBulkResource {
|
|
1822
|
+
/** `POST /api/v1/bulk/trips` — a batch of full trip bundles (trip +
|
|
1823
|
+
* manifest + balance + managers) in one call. */
|
|
1824
|
+
push(options: PushBulkTripsOptions): Promise<KaafilResponse<BulkTripsPushResponse>>;
|
|
1825
|
+
}
|
|
1826
|
+
interface TripsResource {
|
|
1827
|
+
upsert(options: UpsertTripOptions): Promise<KaafilResponse<TripUpsertResponse>>;
|
|
1828
|
+
get(options: GetTripOptions): Promise<KaafilResponse<TripEchoResponse>>;
|
|
1829
|
+
cancel(options: CancelTripOptions): Promise<KaafilResponse<TripCancelResponse>>;
|
|
1830
|
+
readonly travellers: TripTravellersResource;
|
|
1831
|
+
readonly managers: TripManagersResource;
|
|
1832
|
+
readonly balance: TripBalanceResource;
|
|
1833
|
+
readonly bulk: TripsBulkResource;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
/**
|
|
1837
|
+
* `webhooks` — the delivery operations only: list deliveries, read a
|
|
1838
|
+
* delivery, redeliver, replay. `architecture/08-sdk.md §4`, amended `C-028`:
|
|
1839
|
+
* "the delivery-facing operations … and `replayEndpoint` are API-key" —
|
|
1840
|
+
* `configure`/`rotateSecret` (console-session, endpoint CRUD) are a
|
|
1841
|
+
* different auth model entirely and are out of this lane's scope by the
|
|
1842
|
+
* brief's own instruction.
|
|
1843
|
+
*
|
|
1844
|
+
* `listWebhookDeliveries` is cursor-paginated (`cursor`+`limit` query params,
|
|
1845
|
+
* `meta.page.hasNext`); `deliveries.list()` returns a `KaafilPaginator` and
|
|
1846
|
+
* `deliveries.listPage()` is the manual single-page escape hatch for a
|
|
1847
|
+
* caller who wants to drive paging itself (`../pagination/paginator.ts`).
|
|
1848
|
+
*/
|
|
1849
|
+
|
|
1850
|
+
type DeliveryResponse = components['schemas']['DeliveryResponse'];
|
|
1851
|
+
type DeliveryDetailResponse = components['schemas']['DeliveryDetailResponse'];
|
|
1852
|
+
type RedeliverResponse = components['schemas']['RedeliverResponse'];
|
|
1853
|
+
type ReplayResponse = components['schemas']['ReplayResponse'];
|
|
1854
|
+
type ReplayWebhookEndpointRequest = components['schemas']['ReplayWebhookEndpointRequest'];
|
|
1855
|
+
type ListWebhookDeliveriesQuery = NonNullable<operations['listWebhookDeliveries']['parameters']['query']>;
|
|
1856
|
+
/** The filterable half of `listWebhookDeliveries`'s query params — `cursor`
|
|
1857
|
+
* and `limit` are the paginator's job, never the caller's to hold. */
|
|
1858
|
+
type WebhookDeliveryFilters = Omit<ListWebhookDeliveriesQuery, 'cursor' | 'limit'>;
|
|
1859
|
+
interface ListWebhookDeliveriesOptions {
|
|
1860
|
+
readonly filters?: WebhookDeliveryFilters;
|
|
1861
|
+
/** Page size sent as the `limit` query param on every page this paginator fetches. */
|
|
1862
|
+
readonly limit?: number;
|
|
1863
|
+
}
|
|
1864
|
+
interface ListWebhookDeliveriesPageOptions extends WebhookDeliveryFilters {
|
|
1865
|
+
readonly cursor?: string;
|
|
1866
|
+
readonly limit?: number;
|
|
1867
|
+
readonly signal?: AbortSignal;
|
|
1868
|
+
}
|
|
1869
|
+
interface ReadWebhookDeliveryOptions {
|
|
1870
|
+
readonly id: string;
|
|
1871
|
+
readonly signal?: AbortSignal;
|
|
1872
|
+
}
|
|
1873
|
+
interface RedeliverWebhookDeliveryOptions {
|
|
1874
|
+
readonly id: string;
|
|
1875
|
+
readonly idempotencyKey?: string;
|
|
1876
|
+
readonly signal?: AbortSignal;
|
|
1877
|
+
}
|
|
1878
|
+
interface ReplayWebhookEndpointOptions extends ReplayWebhookEndpointRequest {
|
|
1879
|
+
/** The endpoint to replay against — `replayWebhookEndpoint`'s path
|
|
1880
|
+
* parameter is the webhook endpoint id, not a delivery id. */
|
|
1881
|
+
readonly endpointId: string;
|
|
1882
|
+
readonly idempotencyKey?: string;
|
|
1883
|
+
readonly signal?: AbortSignal;
|
|
1884
|
+
}
|
|
1885
|
+
interface WebhookDeliveriesResource {
|
|
1886
|
+
/** `GET /api/v1/webhooks/deliveries` as a `KaafilPaginator` — the SDK holds
|
|
1887
|
+
* the cursor; the consumer never sees or stores one. */
|
|
1888
|
+
list(options?: ListWebhookDeliveriesOptions): KaafilPaginator<DeliveryResponse, WebhookDeliveryFilters>;
|
|
1889
|
+
/** The manual single-page escape hatch: fetches exactly one page and
|
|
1890
|
+
* returns it directly, for a caller who wants to drive paging itself
|
|
1891
|
+
* rather than use the paginator `list()` returns. */
|
|
1892
|
+
listPage(options?: ListWebhookDeliveriesPageOptions): Promise<KaafilPagedResponse<readonly DeliveryResponse[]>>;
|
|
1893
|
+
read(options: ReadWebhookDeliveryOptions): Promise<KaafilResponse<DeliveryDetailResponse>>;
|
|
1894
|
+
redeliver(options: RedeliverWebhookDeliveryOptions): Promise<KaafilResponse<RedeliverResponse>>;
|
|
1895
|
+
}
|
|
1896
|
+
interface WebhooksResource {
|
|
1897
|
+
readonly deliveries: WebhookDeliveriesResource;
|
|
1898
|
+
/** `POST /api/v1/webhooks/endpoints/{id}/replay` — re-delivers every event
|
|
1899
|
+
* since `from` to one endpoint. */
|
|
1900
|
+
replay(options: ReplayWebhookEndpointOptions): Promise<KaafilResponse<ReplayResponse>>;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* `Kaafil` — the server client, exported from `src/index.ts`
|
|
1905
|
+
* (`kaafil-js`). Constructed with the partner API key; carries every
|
|
1906
|
+
* resource group an API key can reach. `architecture/08-sdk.md §2, §3.1`.
|
|
1907
|
+
*
|
|
1908
|
+
* This module is the ONLY place an API-key credential is ever assembled and
|
|
1909
|
+
* checked — `guardApiKeyEnvironment`/`assertApiKeyMatchesEnvironment`
|
|
1910
|
+
* (`./config.ts`) and the `{ kind: 'apiKey', apiKey }` config object below.
|
|
1911
|
+
* `./client-entry.ts` (the browser entry) does not import this file, does
|
|
1912
|
+
* not import those two guards, and builds no credential of this shape —
|
|
1913
|
+
* see that file's header comment for the structural argument, not just a
|
|
1914
|
+
* convention, for why a bundler following only the browser entry's import
|
|
1915
|
+
* graph has no path to this one.
|
|
1916
|
+
*/
|
|
1917
|
+
|
|
1918
|
+
interface KaafilOptions {
|
|
1919
|
+
/** `kf_live_…` / `kf_test_…`, checked against `environment` at
|
|
1920
|
+
* construction for a literal key, or on every resolution for a resolver
|
|
1921
|
+
* (`./config.ts`'s `guardApiKeyEnvironment`). */
|
|
1922
|
+
readonly apiKey: Resolvable<string>;
|
|
1923
|
+
readonly environment: Environment;
|
|
1924
|
+
/** Overrides the environment's default base URL outright — see
|
|
1925
|
+
* `./config.ts`'s `resolveBaseUrl` for why the two are never merged. */
|
|
1926
|
+
readonly baseUrl?: string;
|
|
1927
|
+
readonly timeoutMs?: number;
|
|
1928
|
+
readonly maxAttempts?: number;
|
|
1929
|
+
readonly userAgent?: string;
|
|
1930
|
+
readonly random?: () => number;
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Thrown by any resource method called after `close()`. Every resource
|
|
1934
|
+
* getter reads through `#requireState()`, so a stale reference to
|
|
1935
|
+
* `kaafil.trips` obtained before `close()` still throws this on its next
|
|
1936
|
+
* call — there is no way to keep using a closed client's credential.
|
|
1937
|
+
*/
|
|
1938
|
+
declare class KaafilClosedError extends Error {
|
|
1939
|
+
constructor();
|
|
1940
|
+
}
|
|
1941
|
+
declare class Kaafil {
|
|
1942
|
+
#private;
|
|
1943
|
+
constructor(options: KaafilOptions);
|
|
1944
|
+
get auth(): AuthResource;
|
|
1945
|
+
get shareTokens(): ShareTokensResource;
|
|
1946
|
+
get trips(): TripsResource;
|
|
1947
|
+
get itinerary(): ItineraryResource;
|
|
1948
|
+
get rooming(): RoomingResource;
|
|
1949
|
+
get seating(): SeatingResource;
|
|
1950
|
+
get pickups(): PickupsResource;
|
|
1951
|
+
get treks(): TreksResource;
|
|
1952
|
+
get checklists(): ChecklistsResource;
|
|
1953
|
+
get vendors(): VendorsResource;
|
|
1954
|
+
get journey(): JourneyResource;
|
|
1955
|
+
get webhooks(): WebhooksResource;
|
|
1956
|
+
get events(): EventsResource;
|
|
1957
|
+
get files(): FilesResource;
|
|
1958
|
+
get collections(): CollectionsResource;
|
|
1959
|
+
get expenses(): ExpensesResource;
|
|
1960
|
+
get float(): FloatResource;
|
|
1961
|
+
/**
|
|
1962
|
+
* Total teardown. One assignment, no field to forget — see `KaafilState`
|
|
1963
|
+
* above. Every subsequent call through any resource getter throws
|
|
1964
|
+
* `KaafilClosedError` instead of quietly continuing to work off a
|
|
1965
|
+
* credential this client no longer considers live.
|
|
1966
|
+
*/
|
|
1967
|
+
close(): void;
|
|
1968
|
+
/**
|
|
1969
|
+
* Mints an idempotency key outside the normal per-call flow. Every
|
|
1970
|
+
* mutating method already mints and reuses one automatically
|
|
1971
|
+
* (`http/client.ts`) — this exists for the rare case of pinning a key
|
|
1972
|
+
* ahead of time, to replay the exact same logical operation across a
|
|
1973
|
+
* process restart.
|
|
1974
|
+
*/
|
|
1975
|
+
static newIdempotencyKey(): string;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
/**
|
|
1979
|
+
* The `?since=` delta cursor. `architecture/06-offline-first.md §1, §5`.
|
|
1980
|
+
*
|
|
1981
|
+
* ── WHY THE CURSOR IS THE SERVER'S OWN CLOCK, AND NOTHING ELSE ──────────────
|
|
1982
|
+
*
|
|
1983
|
+
* Every delta read on this API answers with `meta.serverTime`. That value —
|
|
1984
|
+
* the timestamp of the response you are holding, taken by the machine that ran
|
|
1985
|
+
* the query — is the ONLY correct cursor to send back as the next `?since=`.
|
|
1986
|
+
* The two tempting alternatives both lose rows, silently, and only under load:
|
|
1987
|
+
*
|
|
1988
|
+
* - **A local clock** (`new Date()` when the response arrived) is skewed
|
|
1989
|
+
* against the server's by an unknown amount and drifts. Skew in the wrong
|
|
1990
|
+
* direction advances the cursor past rows the server had not committed yet
|
|
1991
|
+
* when it answered, and those rows are then never in any window again.
|
|
1992
|
+
* - **A row's own `updatedAt`** (the newest row in the page) is worse: it is a
|
|
1993
|
+
* bound on the rows you RECEIVED, not on the interval the server READ. Any
|
|
1994
|
+
* row committed between the query's snapshot and the response being stamped
|
|
1995
|
+
* sits after your cursor and before the server's, and is lost permanently.
|
|
1996
|
+
* An empty page also leaves you with no cursor at all.
|
|
1997
|
+
*
|
|
1998
|
+
* This module exists so that reasoning happens once, here, instead of at every
|
|
1999
|
+
* integrator's call site. `pull()` takes the cursor from the response's own
|
|
2000
|
+
* `meta.serverTime` and nothing else, and it advances it only after a
|
|
2001
|
+
* successful read — a failed pull leaves the cursor exactly where it was, so
|
|
2002
|
+
* the next attempt re-reads the same interval rather than skipping it.
|
|
2003
|
+
*
|
|
2004
|
+
* ── WHY DUPLICATES ARE EXPECTED, AND WHY THEY ARE FREE ──────────────────────
|
|
2005
|
+
*
|
|
2006
|
+
* The server window is deliberately AT-LEAST-ONCE: it matches
|
|
2007
|
+
* `updatedAt >= since - 5s`, and a cursor in the future clamps to now rather
|
|
2008
|
+
* than erroring. So a pull normally re-delivers rows you already have. That is
|
|
2009
|
+
* the design, not a defect, and the asymmetry is the reason:
|
|
2010
|
+
*
|
|
2011
|
+
* - A DUPLICATED row costs nothing. Every row — live or tombstone — carries
|
|
2012
|
+
* `id` and `version`, so re-applying one is a no-op rebase onto the state
|
|
2013
|
+
* you already hold. `mergeDeltaRows` below does exactly that, by version.
|
|
2014
|
+
* - A SKIPPED row is DATA LOSS. It is a hotel booking, a cancelled activity or
|
|
2015
|
+
* a deleted bed that the offline client never learns about and never asks
|
|
2016
|
+
* for again, because the cursor has already moved past it. Nothing detects
|
|
2017
|
+
* it; the client is simply, quietly wrong.
|
|
2018
|
+
*
|
|
2019
|
+
* A strict `> since` boundary would be exactly that trade in reverse — no
|
|
2020
|
+
* duplicates, and permanent loss from three separate mechanisms (the interval
|
|
2021
|
+
* between reading rows and stamping the response, clock skew between replicas,
|
|
2022
|
+
* and millisecond truncation). The engine chose the overlap; a consumer must
|
|
2023
|
+
* not undo it by narrowing the window on the client side.
|
|
2024
|
+
*
|
|
2025
|
+
* ── WHY A DELTA ROW IS A UNION ──────────────────────────────────────────────
|
|
2026
|
+
*
|
|
2027
|
+
* A delta read returns `Row | DeltaTombstone` in ONE array, never a second
|
|
2028
|
+
* `deleted[]` array, so a consumer cannot forget the drop case: the union does
|
|
2029
|
+
* not typecheck until the tombstone branch is handled. Ordering is
|
|
2030
|
+
* `(updatedAt asc, id asc)`, so each id appears at most once per page and at
|
|
2031
|
+
* its final state — "deleted before or after updated" is already answered
|
|
2032
|
+
* server-side.
|
|
2033
|
+
*/
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* The drop marker on a delta read.
|
|
2037
|
+
*
|
|
2038
|
+
* Aliased off the generated schema rather than hand-restated. The spec uses
|
|
2039
|
+
* one tombstone shape for every delta read (itinerary items, rooming rooms,
|
|
2040
|
+
* rooming stay windows) and `ItineraryItemTombstoneResponse` is the single
|
|
2041
|
+
* place it appears as a NAMED component — the others inline it inside their
|
|
2042
|
+
* own `anyOf`, so there is no more general name to alias. If the engine ever
|
|
2043
|
+
* gives the shape its own component, this alias moves; the four fields are
|
|
2044
|
+
* still never typed by hand here.
|
|
2045
|
+
*/
|
|
2046
|
+
type DeltaTombstone = components['schemas']['ItineraryItemTombstoneResponse'];
|
|
2047
|
+
/** One row from a delta read: the live row, or the marker saying it is gone. */
|
|
2048
|
+
type DeltaRow<TRow> = TRow | DeltaTombstone;
|
|
2049
|
+
/**
|
|
2050
|
+
* Narrows a delta row to the drop case.
|
|
2051
|
+
*
|
|
2052
|
+
* Structural, and provably safe rather than conventional: every response
|
|
2053
|
+
* schema in this contract is `additionalProperties: false`, so a live row can
|
|
2054
|
+
* never carry a `_tombstone` key and the two branches of the union are
|
|
2055
|
+
* disjoint.
|
|
2056
|
+
*/
|
|
2057
|
+
declare function isTombstone<TRow extends object>(row: DeltaRow<TRow>): row is DeltaTombstone;
|
|
2058
|
+
/**
|
|
2059
|
+
* What `mergeDeltaRows` requires of a live row, and what makes a duplicate
|
|
2060
|
+
* free: an identity and a version. Both delta variants carry them.
|
|
2061
|
+
*/
|
|
2062
|
+
interface DeltaIdentity {
|
|
2063
|
+
readonly id: string;
|
|
2064
|
+
readonly version: number;
|
|
2065
|
+
}
|
|
2066
|
+
/** What one `mergeDeltaRows` call actually changed. */
|
|
2067
|
+
interface DeltaMergeStats {
|
|
2068
|
+
/** Rows written that the store had not seen before. */
|
|
2069
|
+
readonly added: number;
|
|
2070
|
+
/** Rows written over an older version of a row already held. */
|
|
2071
|
+
readonly updated: number;
|
|
2072
|
+
/** Rows dropped because a tombstone arrived for them. */
|
|
2073
|
+
readonly removed: number;
|
|
2074
|
+
/**
|
|
2075
|
+
* Rows the store already held at this version or newer. This is the
|
|
2076
|
+
* at-least-once overlap doing its job — a non-zero count is normal on every
|
|
2077
|
+
* pull after the first and is not worth reporting to a user.
|
|
2078
|
+
*/
|
|
2079
|
+
readonly unchanged: number;
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Rebases one delta page onto a local store, keyed by `id`.
|
|
2083
|
+
*
|
|
2084
|
+
* Version-guarded in both directions, which is what makes re-applying a
|
|
2085
|
+
* duplicated row a genuine no-op instead of merely a harmless-looking one: a
|
|
2086
|
+
* row whose `version` is not NEWER than the held one is left alone, and a
|
|
2087
|
+
* tombstone whose `version` is older than the held row is ignored rather than
|
|
2088
|
+
* deleting a row that has since been recreated or updated. The store is
|
|
2089
|
+
* mutated in place — a caller's `Map` is its own cache, and returning a fresh
|
|
2090
|
+
* copy per page would make a long offline catch-up quadratic.
|
|
2091
|
+
*/
|
|
2092
|
+
declare function mergeDeltaRows<TRow extends DeltaIdentity>(store: Map<string, TRow>, rows: readonly DeltaRow<TRow>[]): DeltaMergeStats;
|
|
2093
|
+
/**
|
|
2094
|
+
* Runs one delta read for a given cursor. A caller supplies this as a thin
|
|
2095
|
+
* closure over a resource method — this module knows nothing about operation
|
|
2096
|
+
* ids, paths or query-param names, only that a read takes a `since` and
|
|
2097
|
+
* answers with an envelope carrying `meta.serverTime`.
|
|
2098
|
+
*
|
|
2099
|
+
* `since` is `undefined` on a cold start, which is a full read: the server
|
|
2100
|
+
* emits no tombstones without a cursor, because a client that has never
|
|
2101
|
+
* synced has nothing to drop.
|
|
2102
|
+
*/
|
|
2103
|
+
type FetchDelta<T> = (request: {
|
|
2104
|
+
readonly since: string | undefined;
|
|
2105
|
+
readonly signal: AbortSignal | undefined;
|
|
2106
|
+
}) => Promise<KaafilResponse<T>>;
|
|
2107
|
+
/** One completed pull, with the cursor bookkeeping made visible. */
|
|
2108
|
+
interface DeltaPull<T> {
|
|
2109
|
+
readonly data: T;
|
|
2110
|
+
readonly meta: ResponseMeta;
|
|
2111
|
+
/** The cursor now stored: THIS response's `meta.serverTime`. */
|
|
2112
|
+
readonly cursor: string;
|
|
2113
|
+
/** What was sent as `?since=` — `undefined` on the cold start. */
|
|
2114
|
+
readonly previousCursor: string | undefined;
|
|
2115
|
+
}
|
|
2116
|
+
/** `pull()` was re-entered while a previous pull on the same cursor was in flight. */
|
|
2117
|
+
declare class KaafilDeltaCursorInFlightError extends Error {
|
|
2118
|
+
constructor();
|
|
2119
|
+
}
|
|
2120
|
+
interface KaafilDeltaCursor<T> {
|
|
2121
|
+
/**
|
|
2122
|
+
* The cursor that will be sent as `?since=` on the next `pull()`, or
|
|
2123
|
+
* `undefined` before the first successful one. Read this to PERSIST it; a
|
|
2124
|
+
* durable consumer stores it beside its rows and hands it back to
|
|
2125
|
+
* `restore()` after a restart, so a process death costs one overlapping
|
|
2126
|
+
* window rather than a full resync.
|
|
2127
|
+
*/
|
|
2128
|
+
readonly cursor: string | undefined;
|
|
2129
|
+
readonly isLoading: boolean;
|
|
2130
|
+
/** Whether any pull has succeeded yet — i.e. whether the next one is a cold start. */
|
|
2131
|
+
readonly hasSynced: boolean;
|
|
2132
|
+
/**
|
|
2133
|
+
* Reads everything that changed since the stored cursor, then advances the
|
|
2134
|
+
* cursor to this response's own `meta.serverTime`. On failure the cursor is
|
|
2135
|
+
* left untouched, so a retry re-reads the same interval instead of stepping
|
|
2136
|
+
* over it.
|
|
2137
|
+
*/
|
|
2138
|
+
pull(options?: {
|
|
2139
|
+
readonly signal?: AbortSignal;
|
|
2140
|
+
}): Promise<DeltaPull<T>>;
|
|
2141
|
+
/**
|
|
2142
|
+
* Adopts a cursor persisted from an earlier session. Takes a `string`
|
|
2143
|
+
* only — deliberately not a `Date` or an epoch number, the two things that
|
|
2144
|
+
* would let a local clock become a cursor, which is the failure this whole
|
|
2145
|
+
* module exists to prevent. Must be a value some response's
|
|
2146
|
+
* `meta.serverTime` actually carried.
|
|
2147
|
+
*/
|
|
2148
|
+
restore(cursor: string): void;
|
|
2149
|
+
/**
|
|
2150
|
+
* Discards the cursor, so the next `pull()` is a cold start: a full read,
|
|
2151
|
+
* with no tombstones. The honest recovery when a local store is corrupt or
|
|
2152
|
+
* of unknown provenance — cheaper to re-read the trip than to reconcile a
|
|
2153
|
+
* cache you cannot vouch for.
|
|
2154
|
+
*/
|
|
2155
|
+
reset(): void;
|
|
2156
|
+
}
|
|
2157
|
+
interface CreateDeltaCursorConfig<T> {
|
|
2158
|
+
readonly fetchDelta: FetchDelta<T>;
|
|
2159
|
+
/** A cursor persisted from an earlier session, as `restore()` would take it. */
|
|
2160
|
+
readonly cursor?: string;
|
|
2161
|
+
}
|
|
2162
|
+
declare function createDeltaCursor<T>(config: CreateDeltaCursorConfig<T>): KaafilDeltaCursor<T>;
|
|
2163
|
+
|
|
2164
|
+
/**
|
|
2165
|
+
* The rooming vocabulary a client needs to RENDER what
|
|
2166
|
+
* `rooming.autoAssign` and `rooming.read` answer — and which is deliberately
|
|
2167
|
+
* not on the wire.
|
|
2168
|
+
*
|
|
2169
|
+
* Two things live here, for the same reason: each is a property of the RULE or
|
|
2170
|
+
* of the CANON, not of a particular run, so shipping either inside every
|
|
2171
|
+
* response would put a second copy of one judgement in every payload and let
|
|
2172
|
+
* the two disagree after a contract change. The engine states this in
|
|
2173
|
+
* `src/modules/rooming/index.ts`, which re-exports both explicitly "for the
|
|
2174
|
+
* SDK". This file is that re-export's other end, so a client renders
|
|
2175
|
+
* relaxations and traveller chips without inventing its own severity map or
|
|
2176
|
+
* its own colour maths.
|
|
2177
|
+
*
|
|
2178
|
+
* ── HOW THIS STAYS HONEST ────────────────────────────────────────────────────
|
|
2179
|
+
*
|
|
2180
|
+
* These values are not in `openapi.json`, so `gen:check` cannot police them.
|
|
2181
|
+
* What does: both maps are typed as TOTAL `Record`s over GENERATED key types
|
|
2182
|
+
* (`RoomingHeuristicKey` and `Gender`, from `../generated/enums.ts`, which
|
|
2183
|
+
* `scripts/gen-meta.mjs` builds out of the vendored spec's own request enums).
|
|
2184
|
+
* Add a heuristic or a gender to the contract and re-vendor, and this file
|
|
2185
|
+
* stops compiling until someone decides what the new member's severity or hue
|
|
2186
|
+
* family is. A `Record<string, …>` here would have accepted the drift
|
|
2187
|
+
* silently, which is exactly the failure a hand-maintained mirror invites.
|
|
2188
|
+
*/
|
|
2189
|
+
|
|
2190
|
+
/**
|
|
2191
|
+
* How loudly to render a rule's relaxation.
|
|
2192
|
+
*
|
|
2193
|
+
* - `warn` — the manager should look: a preference they set was given up.
|
|
2194
|
+
* - `split` — a group was broken up. Louder than `warn`; it is the complaint
|
|
2195
|
+
* the trip will actually generate.
|
|
2196
|
+
* - `info` — a soft preference, worth showing but not worth interrupting for.
|
|
2197
|
+
* - `never` — this rule is never relaxed at all. It exists so the map is TOTAL:
|
|
2198
|
+
* a missing entry would be indistinguishable from a rule nobody had
|
|
2199
|
+
* classified yet.
|
|
2200
|
+
*/
|
|
2201
|
+
type RoomingHeuristicSeverity = 'warn' | 'split' | 'info' | 'never';
|
|
2202
|
+
/**
|
|
2203
|
+
* The frozen severity map, mirroring the engine's `HEURISTIC_SEVERITY`.
|
|
2204
|
+
*
|
|
2205
|
+
* `gender` is `'never'` because a gender rule is never relaxed — the traveller
|
|
2206
|
+
* stays unassigned instead, and turns up in `autoAssign`'s `unassigned[]` with
|
|
2207
|
+
* a reason. So a `perRule` entry for `gender` with `outcome: 'relaxed'` is not
|
|
2208
|
+
* something to render loudly; it is a contract violation, and a client is
|
|
2209
|
+
* entitled to treat it as a bug rather than as a user-facing warning.
|
|
2210
|
+
*/
|
|
2211
|
+
declare const HEURISTIC_SEVERITY: Readonly<Record<RoomingHeuristicKey, RoomingHeuristicSeverity>>;
|
|
2212
|
+
/**
|
|
2213
|
+
* Whether a string is one of the heuristic keys this SDK knows about.
|
|
2214
|
+
*
|
|
2215
|
+
* Exists because `autoAssign`'s `perRule[].rule` is an open `string` on the
|
|
2216
|
+
* wire, not the closed enum `strategyOrder` is on the request side — so a
|
|
2217
|
+
* response may name a rule a given SDK build has never heard of, and a client
|
|
2218
|
+
* must be able to ask.
|
|
2219
|
+
*/
|
|
2220
|
+
declare function isRoomingHeuristicKey(rule: string): rule is RoomingHeuristicKey;
|
|
2221
|
+
/**
|
|
2222
|
+
* How loudly to render one `perRule` entry's relaxation.
|
|
2223
|
+
*
|
|
2224
|
+
* ── WHY THIS FUNCTION EXISTS AND THE MAP IS NOT ENOUGH ──────────────────────
|
|
2225
|
+
*
|
|
2226
|
+
* `HEURISTIC_SEVERITY[entry.rule]` does not compile, and that is correct
|
|
2227
|
+
* rather than an oversight to paper over: `entry.rule` is `string` in the
|
|
2228
|
+
* contract, and a total `Record` over a closed key set legitimately refuses a
|
|
2229
|
+
* `string` index. The two possible fixes are not equivalent. Loosening the map
|
|
2230
|
+
* to `Record<string, …>` would also throw away the compile-time drift gate
|
|
2231
|
+
* described in this file's header, which is the only thing keeping this
|
|
2232
|
+
* hand-maintained mirror honest against a re-vendored spec. So the map stays
|
|
2233
|
+
* total and this accessor takes the wire's `string`.
|
|
2234
|
+
*
|
|
2235
|
+
* Returns `undefined` for a rule this build does not know. A caller should
|
|
2236
|
+
* render that entry rather than drop it — an unrecognised rule that was
|
|
2237
|
+
* RELAXED is still a relaxation the manager needs to see, and hiding it
|
|
2238
|
+
* because the severity lookup missed is the silent-relaxation failure the
|
|
2239
|
+
* `perRule` array exists to prevent. `'info'` is a reasonable default to draw
|
|
2240
|
+
* it at; `undefined` is deliberately not pre-defaulted here, so that choice is
|
|
2241
|
+
* visible at the call site instead of buried in this function.
|
|
2242
|
+
*/
|
|
2243
|
+
declare function roomingHeuristicSeverity(rule: string): RoomingHeuristicSeverity | undefined;
|
|
2244
|
+
/**
|
|
2245
|
+
* The hue FAMILIES a `tone` token is built from, keyed by the occupant's
|
|
2246
|
+
* `gender`.
|
|
2247
|
+
*
|
|
2248
|
+
* `null` gender and `UNKNOWN` map to the SAME family: the field is nullable
|
|
2249
|
+
* AND the enum has an `UNKNOWN` member, so unknown-ness arrives two ways and
|
|
2250
|
+
* means one thing. Rendering them in two palettes would show the same fact
|
|
2251
|
+
* twice over as two different facts.
|
|
2252
|
+
*/
|
|
2253
|
+
declare const TONE_FAMILIES: Readonly<Record<Gender, string>>;
|
|
2254
|
+
/** The family for a `null` gender. Identical to `UNKNOWN`'s, by the reasoning above. */
|
|
2255
|
+
declare const UNKNOWN_TONE_FAMILY: string;
|
|
2256
|
+
/**
|
|
2257
|
+
* How many shades each family has, so a client's palette knows how many to
|
|
2258
|
+
* define: `shade` in a `tone` token is always `0 .. SHADES_PER_FAMILY - 1`.
|
|
2259
|
+
*
|
|
2260
|
+
* It equals the maximum room capacity, and that is the whole argument. The
|
|
2261
|
+
* promise is that two same-gender travellers IN ONE ROOM still look different —
|
|
2262
|
+
* a room being the only place two chips sit side by side and have to be told
|
|
2263
|
+
* apart. A room holds at most 8 people, so with 8 shades per family a
|
|
2264
|
+
* collision is never FORCED inside that scope. It is frozen rather than
|
|
2265
|
+
* configurable because the mark is a property of the TRAVELLER, not of the
|
|
2266
|
+
* viewer: two agencies with different values would render the same person in
|
|
2267
|
+
* two different shades, and `transport-seating` — which draws the same chip
|
|
2268
|
+
* from the same canon — would have to know whose grid it was on to agree with
|
|
2269
|
+
* the rooming board.
|
|
2270
|
+
*/
|
|
2271
|
+
declare const SHADES_PER_FAMILY = 8;
|
|
2272
|
+
/**
|
|
2273
|
+
* The glyph the engine sends for a traveller whose `fullName` has no
|
|
2274
|
+
* renderable characters at all. Reachable, because the name is CRM-supplied,
|
|
2275
|
+
* and an unnameable traveller must still get a chip rather than vanish off the
|
|
2276
|
+
* board.
|
|
2277
|
+
*/
|
|
2278
|
+
declare const FALLBACK_GLYPH = "?";
|
|
2279
|
+
/** A parsed `tone` token. */
|
|
2280
|
+
interface RoomingTone {
|
|
2281
|
+
/** One of `TONE_FAMILIES`' values. */
|
|
2282
|
+
readonly family: string;
|
|
2283
|
+
/** `0 .. SHADES_PER_FAMILY - 1`. */
|
|
2284
|
+
readonly shade: number;
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Splits an occupant's `tone` into its family and shade.
|
|
2288
|
+
*
|
|
2289
|
+
* ── `tone` IS A TOKEN, NEVER A COLOUR ───────────────────────────────────────
|
|
2290
|
+
*
|
|
2291
|
+
* The engine sends `"male.3"`, not `"#3B82F6"`, and that is deliberate: the
|
|
2292
|
+
* engine must not own brand colour values. If it did, an agency re-theme would
|
|
2293
|
+
* be an engine deploy, and the same traveller would render in two different
|
|
2294
|
+
* palettes depending on which module's copy of the hex table was newer. What
|
|
2295
|
+
* the engine owns is the CONSISTENCY claim — `rooming` and `transport-seating`
|
|
2296
|
+
* handing you the same string for the same person IS the consistency —
|
|
2297
|
+
* and what you paint it is entirely yours. So the shape a consumer wants is
|
|
2298
|
+
* `palette[family][shade]`, with `palette` living in your own design system,
|
|
2299
|
+
* and this function is how you get the two indices out without string-slicing
|
|
2300
|
+
* by hand.
|
|
2301
|
+
*
|
|
2302
|
+
* Returns `undefined` rather than throwing for anything that is not a
|
|
2303
|
+
* recognisable token, because this runs while drawing a board: a chip with an
|
|
2304
|
+
* unreadable tone should fall back to a neutral swatch, not take the screen
|
|
2305
|
+
* down. The family is NOT checked against `TONE_FAMILIES` — a family added to
|
|
2306
|
+
* the contract before your palette knows about it should still parse, so you
|
|
2307
|
+
* can fall back on the family you do not recognise instead of on every chip.
|
|
2308
|
+
*/
|
|
2309
|
+
declare function parseRoomingTone(tone: string): RoomingTone | undefined;
|
|
2310
|
+
|
|
2311
|
+
export { ACTIVE_TREK_REF, type AddChecklistItemOptions, type AddItineraryItemOptions, type AdjustFloatOptions, type AssignManagerOptions, type AssignPickupTravellerOptions, type AssignRoomingBedOptions, type AssignSeatingOptions, type AuthResource, type AutoAssignRoomingOptions, type AutoAssignSeatingOptions, type BoardPickupTravellerOptions, type CancelTripOptions, type ChecklistItemDeltaRow, type ChecklistItemsResource, type ChecklistTemplatesResource, type ChecklistsResource, type ClosePickupStopOptions, type CollectionDeltaRow, type CollectionsResource, type ConfirmFileUploadOptions, type CreateDeltaCursorConfig, type CreatePickupStopOptions, type CreateRoomingRoomOptions, type CreateRoomingStayWindowOptions, type CreateSeatingVehicleOptions, type CreateShareTokenOptions, type CreateTrekWalkInOptions, type DateTimeInput, type DeleteChecklistItemOptions, type DeleteItineraryItemOptions, type DeletePickupStopOptions, type DeleteRoomingRoomOptions, type DeleteRoomingStayWindowOptions, type DeleteSeatingVehicleOptions, type DeltaIdentity, type DeltaMergeStats, type DeltaPull, type DeltaRow, type DeltaTombstone, Environment, type EventFilters, type EventsResource, type ExpenseClaimsResource, type ExpenseFilters, type ExpensesResource, FALLBACK_GLYPH, type FetchDelta, type FilesResource, type FloatResource, Gender, type GetTripOptions, HEURISTIC_SEVERITY, type IngestClaimStatusOptions, type IssueFloatOptions, type ItineraryChangeLogResource, type ItineraryDaysResource, type ItineraryItemDeltaRow, type ItineraryItemsResource, type ItineraryResource, JourneyResource, Kaafil, KaafilClosedError, type KaafilDeltaCursor, KaafilDeltaCursorInFlightError, type KaafilOptions, KaafilPagedResponse, KaafilPaginator, KaafilResponse, type LinkExpenseReceiptOptions, type LinkSeatingManagerOptions, type ListChecklistTemplatesOptions, type ListCollectionsOptions, type ListEligibleCollectionsOptions, type ListEventsOptions, type ListEventsPageOptions, type ListExpensesOptions, type ListItineraryChangeLogOptions, type ListPickupStopsOptions, type ListRoomingStayWindowsOptions, type ListWebhookDeliveriesOptions, type ListWebhookDeliveriesPageOptions, type LogExpenseOptions, type MintAgencyAdminTokenOptions, type MintManagerTokenOptions, type PatchChecklistItemOptions, type PatchItineraryDayOptions, type PatchItineraryItemOptions, type PatchPickupStopOptions, type PatchRoomingRoomOptions, type PatchRoomingStayWindowOptions, type PatchSeatingVehicleOptions, type PickupStopDeltaRow, type PickupsResource, type PostponeTrekOptions, type PullChecklistTemplateOptions, type PushBulkTripsOptions, type PushManifestOptions, type PushTripBalancesOptions, type ReadChecklistOptions, type ReadExpenseOptions, type ReadFileOptions, type ReadFileUrlOptions, type ReadFloatLedgerOptions, type ReadFloatSummaryOptions, type ReadItineraryOptions, type ReadManifestByPickupOptions, type ReadRoomingBoardOptions, type ReadSeatingBoardOptions, type ReadShareTokenOptions, type ReadTrekBoardOptions, type ReadTrekWalkInMetaOptions, type ReadWebhookDeliveryOptions, type RecordCollectionOptions, type RedeliverWebhookDeliveryOptions, type RemoveFromManifestOptions, type ReopenPickupStopOptions, type ReorderItineraryItemOptions, type ReorderPickupStopsOptions, type ReplayWebhookEndpointOptions, type RequestFileUploadOptions, Resolvable, ResponseMeta, type ReturnFloatOptions, type RevokeShareTokenOptions, type RoomingAutoAssignRules, RoomingHeuristicKey, type RoomingHeuristicSeverity, type RoomingOccupant, type RoomingResource, type RoomingRoomDeltaRow, type RoomingRoomsResource, type RoomingStayWindowsResource, type RoomingTone, SHADES_PER_FAMILY, type SeatingAutoAssignRules, type SeatingOccupant, type SeatingResource, type SeatingVehicleDeltaRow, type SeatingVehicleManagerResource, type SeatingVehiclesResource, type ShareTokensResource, type SubmitExpenseClaimOptions, TONE_FAMILIES, type ToggleChecklistItemOptions, type TrekWalkInsResource, type TreksResource, type TripBalanceResource, type TripManagersResource, type TripTravellersResource, type TripsBulkResource, type TripsResource, UNKNOWN_TONE_FAMILY, type UnassignManagerOptions, type UnlinkSeatingManagerOptions, type UpsertManagerOptions, type UpsertTravellerProfileOptions, type UpsertTripOptions, VendorsResource, type VoidCollectionOptions, type VoidExpenseOptions, type WebhookDeliveriesResource, type WebhookDeliveryFilters, type WebhooksResource, type WithdrawExpenseClaimOptions, createDeltaCursor, isRoomingHeuristicKey, isTombstone, mergeDeltaRows, parseRoomingTone, roomingHeuristicSeverity };
|