@peektravel/app-utilities 0.1.1
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 +262 -0
- package/dist/index.cjs +2961 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1272 -0
- package/dist/index.d.ts +1272 -0
- package/dist/index.js +2924 -0
- package/dist/index.js.map +1 -0
- package/llms.txt +77 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peek Travel Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @peektravel/app-utilities
|
|
2
|
+
|
|
3
|
+
GraphQL JS mapping utilities extracted from the Peek Pro Autopilot connector.
|
|
4
|
+
The package owns the GraphQL queries, authentication, transport, and the
|
|
5
|
+
conversion into clean TypeScript data models — callers work only with the
|
|
6
|
+
high-level `PeekAccessService` and the plain data shapes it returns.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
This package is published to **GitHub Packages** (a private registry), not the
|
|
11
|
+
public npm registry. Point the `@peek-travel` scope at GitHub Packages once per
|
|
12
|
+
consuming project by adding an `.npmrc` next to its `package.json`:
|
|
13
|
+
|
|
14
|
+
```ini
|
|
15
|
+
@peek-travel:registry=https://npm.pkg.github.com
|
|
16
|
+
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then install (and later update) it like any other dependency:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @peektravel/app-utilities
|
|
23
|
+
npm update @peektravel/app-utilities
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`NPM_TOKEN` must be a GitHub token with the `read:packages` scope. Locally that's
|
|
27
|
+
a personal access token in your environment; in cloud builds (Firebase
|
|
28
|
+
Functions / Google Cloud Build, CI) set it as a build secret/env var. See
|
|
29
|
+
[Releasing](#releasing-github-packages) for how new versions are published.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Configure one access service per install with everything it needs to
|
|
34
|
+
authenticate and reach the gateway. It mints and caches a short-lived JWT on
|
|
35
|
+
demand and hands out per-resource services that own the resource-specific calls.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { PeekAccessService, type Product } from '@peektravel/app-utilities';
|
|
39
|
+
|
|
40
|
+
const peek = new PeekAccessService({
|
|
41
|
+
installId: 'install-123', // JWT subject
|
|
42
|
+
jwtSecret: process.env.PEEK_INTERNAL_SECRET!, // signs the JWT
|
|
43
|
+
issuer: process.env.APP_NAME!, // JWT issuer
|
|
44
|
+
appId: process.env.PEEK_APP_ID!, // gateway path segment
|
|
45
|
+
gatewayKey: process.env.PEEK_GATEWAY_KEY!, // pk-api-key header
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const products: Product[] = await peek.getProductService().getAllProducts();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The access service is the authenticated root; each `get<Resource>Service()`
|
|
52
|
+
returns a (memoized) service that owns that resource's calls.
|
|
53
|
+
|
|
54
|
+
`getAllProducts()` returns a single flat list of activities **and** add-ons
|
|
55
|
+
(add-ons tagged with `ADD_ON_PRODUCT_TYPE`), gathering all cursor-paginated
|
|
56
|
+
add-on pages for you.
|
|
57
|
+
|
|
58
|
+
## Resources
|
|
59
|
+
|
|
60
|
+
| Accessor | Methods |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `getProductService()` | `getAllProducts()` |
|
|
63
|
+
| `getAccountUserService()` | `getAll()`, `getById(userId)` |
|
|
64
|
+
| `getResourcePoolService()` | `getAll(mode?)` |
|
|
65
|
+
| `getTimeslotService()` | `getForDay()`, `getById()`, `setAvailability()`, `setNotes()`, `assignGuide()` |
|
|
66
|
+
| `getResellerService()` | `getAllChannels(agentsPerChannel?)` |
|
|
67
|
+
| `getPromoCodeService()` | `getAll()`, `create(input)` |
|
|
68
|
+
| `getDailyNoteService()` | `getToday()`, `update(note)` |
|
|
69
|
+
| `getAvailabilityService()` | `getAvailabilityTimes(query)` |
|
|
70
|
+
| `getMembershipService()` | `getAll()`, `purchase(input)` |
|
|
71
|
+
| `getBookingService()` | `getById()`, `searchByTimeRange()`, `searchByTimeslot()`, `getGuests()`, `getPaymentsOnFile()`, `appendNote()`, `setCheckinStatus()`, `cancel()`, `makePayment()`, `refund()`, `createInvoiceLink()`, `addAddon()`, `create()` |
|
|
72
|
+
|
|
73
|
+
### Optional configuration
|
|
74
|
+
|
|
75
|
+
| Option | Default | Purpose |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `baseUrl` | Peek production gateway | Override the GraphQL gateway base URL |
|
|
78
|
+
| `tokenTtlSeconds` | `3600` | JWT lifetime |
|
|
79
|
+
| `tokenRefreshLeewaySeconds` | `60` | Re-mint this long before expiry |
|
|
80
|
+
| `retryDelaysMs` | `[1000, 2000, 4000]` | Backoff for HTTP 429 retries |
|
|
81
|
+
| `logger` | no-op | Inject a `Logger` for diagnostics |
|
|
82
|
+
| `fetch` | global `fetch` | Custom fetch (e.g. for tests) |
|
|
83
|
+
| `itemOptionsPageSize` | `50` | Add-on pagination page size |
|
|
84
|
+
|
|
85
|
+
### Errors
|
|
86
|
+
|
|
87
|
+
Two kinds of failures surface as exceptions:
|
|
88
|
+
|
|
89
|
+
**Typed gateway errors** (importable, branch on the class):
|
|
90
|
+
|
|
91
|
+
- `AdminAccountRequiredError` — gateway returned HTTP 418 (install lacks admin
|
|
92
|
+
rights). Carries `.statusCode === 418`.
|
|
93
|
+
- `RateLimitError` — HTTP 429 after the configured `retryDelaysMs` backoff was
|
|
94
|
+
exhausted. Carries `.statusCode === 429`.
|
|
95
|
+
- `PeekGraphQLError` — the response contained a GraphQL `errors` array, preserved
|
|
96
|
+
on `.graphqlErrors`.
|
|
97
|
+
|
|
98
|
+
**Plain `Error` validation/precondition failures** thrown by the service layer
|
|
99
|
+
*before* any network call — e.g. an empty config field, a `bookingId` that
|
|
100
|
+
doesn't resolve to a `b_…` id, a non-positive-integer `quantity`, a malformed
|
|
101
|
+
currency, or a "booking not found". Branch on `.message` only as a last resort;
|
|
102
|
+
prefer guarding inputs to the documented formats below.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import {
|
|
106
|
+
PeekAccessService,
|
|
107
|
+
RateLimitError,
|
|
108
|
+
AdminAccountRequiredError,
|
|
109
|
+
PeekGraphQLError,
|
|
110
|
+
} from '@peektravel/app-utilities';
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await peek.getBookingService().makePayment({ /* … */ });
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err instanceof RateLimitError) {
|
|
116
|
+
// back off and retry later
|
|
117
|
+
} else if (err instanceof AdminAccountRequiredError) {
|
|
118
|
+
// this install can't perform admin-only operations
|
|
119
|
+
} else if (err instanceof PeekGraphQLError) {
|
|
120
|
+
console.error(err.graphqlErrors); // raw gateway errors
|
|
121
|
+
} else {
|
|
122
|
+
throw err; // validation / precondition failure
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Conventions & input formats
|
|
128
|
+
|
|
129
|
+
These rules are enforced in the service layer (a violation throws a plain
|
|
130
|
+
`Error` before any request):
|
|
131
|
+
|
|
132
|
+
- **Booking ids** are normalized internally — lowercased with `-` → `_` — so
|
|
133
|
+
`B-ABC123` and `b_abc123` are equivalent. Payment/refund operations require an
|
|
134
|
+
id that resolves to the `b_…` form.
|
|
135
|
+
- **Quantities** (add-ons, etc.) are **positive-integer strings**: `"1"`, `"2"`.
|
|
136
|
+
- **Currency** is a 3-letter uppercase ISO code: `"USD"`, `"EUR"`.
|
|
137
|
+
- **Amounts** are numeric strings: `"25.00"`.
|
|
138
|
+
- **Payment source ids** are `ps_…`, or one of `cash/cash`, `custom/other`,
|
|
139
|
+
`custom/voucher`. **Payment ids** (refunds) are `pmt_…`.
|
|
140
|
+
- **Idempotency keys** are required on `makePayment`, `refund`, and any
|
|
141
|
+
`create({ markAsPaid: true })`; pass a stable UUID (`crypto.randomUUID()`).
|
|
142
|
+
- **`create()` takes pre-resolved ids only** — no free-text matching. Resolve
|
|
143
|
+
`activityId` + ticket `resourceOptionId`s from `getProductService()` and
|
|
144
|
+
`availabilityTimeId` from `getAvailabilityService()`.
|
|
145
|
+
- **Add-on option ids** are ticket ids on products whose `type` is
|
|
146
|
+
`ADD_ON_PRODUCT_TYPE`.
|
|
147
|
+
|
|
148
|
+
## Recipes
|
|
149
|
+
|
|
150
|
+
**Find an activity and its add-ons**
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { ADD_ON_PRODUCT_TYPE, type Product } from '@peektravel/app-utilities';
|
|
154
|
+
|
|
155
|
+
const products: Product[] = await peek.getProductService().getAllProducts();
|
|
156
|
+
const activities = products.filter((p) => p.type !== ADD_ON_PRODUCT_TYPE);
|
|
157
|
+
const addons = products.filter((p) => p.type === ADD_ON_PRODUCT_TYPE);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Create a paid booking end-to-end**
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { randomUUID } from 'node:crypto';
|
|
164
|
+
|
|
165
|
+
const products = await peek.getProductService().getAllProducts();
|
|
166
|
+
const activity = products.find((p) => p.name === 'Sunset Kayak Tour')!;
|
|
167
|
+
|
|
168
|
+
const [slot] = await peek.getAvailabilityService().getAvailabilityTimes({
|
|
169
|
+
activityId: activity.productId,
|
|
170
|
+
date: '2026-06-20',
|
|
171
|
+
resourceOptionQuantities: [{ resourceOptionId: activity.tickets[0]!.id, quantity: 2 }],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const created = await peek.getBookingService().create({
|
|
175
|
+
activityId: activity.productId,
|
|
176
|
+
availabilityTimeId: slot.availabilityTimeId,
|
|
177
|
+
tickets: [{ resourceOptionId: activity.tickets[0]!.id, quantity: 2 }],
|
|
178
|
+
guest: { name: 'Sam Rivera', email: 'sam@example.com' },
|
|
179
|
+
markAsPaid: true,
|
|
180
|
+
idempotencyKey: randomUUID(),
|
|
181
|
+
});
|
|
182
|
+
console.log(created.bookingId, created.balanceFormatted);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Add an add-on to an existing booking**
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const { updatedBookingAddons } = await peek
|
|
189
|
+
.getBookingService()
|
|
190
|
+
.addAddon('b_abc123', { addonOptionId: 'io_helmet', quantity: '2' });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Look up a booking with guests and balance**
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const booking = await peek.getBookingService().getById('b_abc123', {
|
|
197
|
+
includeGuests: true,
|
|
198
|
+
includePriceBreakdown: true,
|
|
199
|
+
});
|
|
200
|
+
if (booking) {
|
|
201
|
+
console.log(booking.displayId, booking.outstandingBalanceDisplay);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The package ships dual ESM + CommonJS builds with bundled type declarations, so
|
|
206
|
+
both `import` and `require` consumers (including the Node 22 / CommonJS Firebase
|
|
207
|
+
Functions runtime) resolve correctly. Its only runtime dependency is
|
|
208
|
+
`jsonwebtoken`.
|
|
209
|
+
|
|
210
|
+
## Releasing (GitHub Packages)
|
|
211
|
+
|
|
212
|
+
Releases are automated. Pushing a `v*.*.*` git tag triggers
|
|
213
|
+
`.github/workflows/publish.yml`, which typechecks, lints, runs the test suite
|
|
214
|
+
(95% coverage gate), then publishes to GitHub Packages. `npm publish` runs
|
|
215
|
+
`prepublishOnly` first, so the build plus `publint` + `attw` checks gate every
|
|
216
|
+
release.
|
|
217
|
+
|
|
218
|
+
To cut a release:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npm version patch # or minor / major — bumps package.json and creates a git tag
|
|
222
|
+
git push --follow-tags # pushes the commit + tag; the workflow publishes
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The workflow asserts the tag matches the `package.json` version, so the two
|
|
226
|
+
never drift. The repo's built-in `GITHUB_TOKEN` (with `packages: write`) handles
|
|
227
|
+
publish auth — no personal token needed in CI. Consumers then pick the new
|
|
228
|
+
version up with a normal `npm update` (see [Install](#install)).
|
|
229
|
+
|
|
230
|
+
> One-time setup: the package scope `@peek-travel` must match the GitHub org
|
|
231
|
+
> that owns the package, and the repo needs the GitHub Actions permission to
|
|
232
|
+
> write packages (granted by the `packages: write` permission in the workflow).
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
npm install # install dependencies
|
|
238
|
+
npm run build # bundle ESM + CJS + .d.ts into dist/ (tsup)
|
|
239
|
+
npm run dev # rebuild on change
|
|
240
|
+
npm test # run unit tests (vitest)
|
|
241
|
+
npm run test:coverage
|
|
242
|
+
npm run typecheck # tsc --noEmit
|
|
243
|
+
npm run lint # eslint (flat config)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Release checks
|
|
247
|
+
|
|
248
|
+
`prepublishOnly` builds the package and runs [`publint`](https://publint.dev)
|
|
249
|
+
and [`@arethetypeswrong/cli`](https://github.com/arethetypeswrong/arethetypeswrong.github.io)
|
|
250
|
+
to verify the `exports` map and type resolution are correct for both module
|
|
251
|
+
systems. The publish workflow runs these automatically — see
|
|
252
|
+
[Releasing](#releasing-github-packages).
|
|
253
|
+
|
|
254
|
+
## Project layout
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
src/ source (public API barrel: src/index.ts)
|
|
258
|
+
test/ vitest unit tests
|
|
259
|
+
dist/ build output (generated, git-ignored)
|
|
260
|
+
docs/internal/ maintainer docs (ARCHITECTURE.md — not shipped)
|
|
261
|
+
llms.txt AI-agent quickstart (shipped in the package)
|
|
262
|
+
```
|