@peektravel/app-utilities 0.9.4 → 1.0.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/README.md +38 -3
- package/dist/index.cjs +149 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -12
- package/dist/index.d.ts +86 -12
- package/dist/index.js +149 -66
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +6 -0
- package/llms.txt +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,8 +34,8 @@ const peek = new PeekAccessService({
|
|
|
34
34
|
installId: 'install-123', // JWT subject
|
|
35
35
|
jwtSecret: process.env.PEEK_INTERNAL_SECRET!, // signs the JWT
|
|
36
36
|
issuer: process.env.APP_NAME!, // JWT issuer
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
mode: 'v2', // connection generation — required (see below)
|
|
38
|
+
apiUrl: install.apiUrl, // per-install base URL from the install webhook (persisted)
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
const products: Product[] = await peek.getProductService().getAllProducts();
|
|
@@ -44,6 +44,41 @@ const products: Product[] = await peek.getProductService().getAllProducts();
|
|
|
44
44
|
The access service is the authenticated root; each `get<Resource>Service()`
|
|
45
45
|
returns a (memoized) service that owns that resource's calls.
|
|
46
46
|
|
|
47
|
+
### Connection mode (`v1` / `v2`) — **required**
|
|
48
|
+
|
|
49
|
+
`mode` is a **mandatory** config field — you must choose a connection generation
|
|
50
|
+
explicitly. (It used to be optional, defaulting to `v1`; it is now required so
|
|
51
|
+
that updating the package forces a deliberate choice instead of silently keeping
|
|
52
|
+
the old behaviour.)
|
|
53
|
+
|
|
54
|
+
- **`mode: 'v2'`** — **use this going forward.** The app-registry connection. It
|
|
55
|
+
**requires** the install's `apiUrl` — the per-install base URL delivered on the
|
|
56
|
+
install webhook (`api.url`); the constructor throws without it. Persist that URL
|
|
57
|
+
per install (see [Webhooks](#webhooks)) and pass it as `apiUrl`; no `gatewayKey`
|
|
58
|
+
is needed. If you don't yet have a webhook `apiUrl` (e.g. an older install you
|
|
59
|
+
don't want to uninstall/reinstall just to get one), build one by appending your
|
|
60
|
+
**app slug** to the registry prefix and pass that as the `apiUrl` yourself —
|
|
61
|
+
`https://apps.peek.com/installations-api/<app-slug>` (same shape as the
|
|
62
|
+
webhook's `api.url`; the trailing app-slug segment is required — the prefix
|
|
63
|
+
alone is not a valid URL). Treat it as a transitional bridge and move to the
|
|
64
|
+
webhook URL.
|
|
65
|
+
- **`mode: 'v1'`** — **deprecated; will be removed.** The legacy gateway; needs
|
|
66
|
+
`appId` + `gatewayKey`. If your install relied on the previous default, the
|
|
67
|
+
fastest no-behaviour-change update is to pass `mode: 'v1'` — then plan the
|
|
68
|
+
migration to `v2`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// Legacy v1 (deprecated) — fastest update for an install that used the old default:
|
|
72
|
+
const peek = new PeekAccessService({
|
|
73
|
+
installId: 'install-123',
|
|
74
|
+
jwtSecret: process.env.PEEK_INTERNAL_SECRET!,
|
|
75
|
+
issuer: process.env.APP_NAME!,
|
|
76
|
+
appId: process.env.PEEK_APP_ID!, // gateway path segment
|
|
77
|
+
gatewayKey: process.env.PEEK_GATEWAY_KEY!, // pk-api-key header
|
|
78
|
+
mode: 'v1',
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
47
82
|
`getAllProducts()` returns a single flat list of activities **and** add-ons
|
|
48
83
|
(add-ons tagged with `ADD_ON_PRODUCT_TYPE`), gathering all cursor-paginated
|
|
49
84
|
add-on pages for you.
|
|
@@ -102,7 +137,7 @@ With `fullCustomerAccess` **off** (the default):
|
|
|
102
137
|
|
|
103
138
|
```ts
|
|
104
139
|
const peek = new PeekAccessService({
|
|
105
|
-
installId, jwtSecret, issuer,
|
|
140
|
+
installId, jwtSecret, issuer, mode: 'v2', apiUrl,
|
|
106
141
|
accessOptions: { fullCustomerAccess: true }, // opt into PII + payment operations
|
|
107
142
|
});
|
|
108
143
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -2496,6 +2496,123 @@ function buildCancellation(items, addonOptionId, quantity) {
|
|
|
2496
2496
|
return { bookingQuotes, canceledCount };
|
|
2497
2497
|
}
|
|
2498
2498
|
|
|
2499
|
+
// src/internal/peek/custom-questions/custom-question-converter.ts
|
|
2500
|
+
function fromQuestionConfigurations(configs) {
|
|
2501
|
+
return configs.map(
|
|
2502
|
+
(config) => fromQuestionNode(config.question, config.order, config.isRequired)
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
function fromQuestionNodes(questions) {
|
|
2506
|
+
return questions.map((question) => fromQuestionNode(question));
|
|
2507
|
+
}
|
|
2508
|
+
function fromQuestionNode(question, order, isRequired) {
|
|
2509
|
+
return {
|
|
2510
|
+
id: question.id,
|
|
2511
|
+
order: order ?? 0,
|
|
2512
|
+
isRequired: isRequired ?? false,
|
|
2513
|
+
questionText: question.text,
|
|
2514
|
+
hintText: question.hint ?? null,
|
|
2515
|
+
questionType: question.questionType,
|
|
2516
|
+
internalLabel: question.internalLabel ?? null,
|
|
2517
|
+
perGuest: question.perGuest ?? false,
|
|
2518
|
+
defaultValue: question.answerDefaultValue ?? null,
|
|
2519
|
+
options: (question.options ?? []).map(fromQuestionOption)
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function fromQuestionOption(option) {
|
|
2523
|
+
return {
|
|
2524
|
+
id: option.id,
|
|
2525
|
+
order: option.order,
|
|
2526
|
+
value: option.text
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/internal/peek/custom-questions/custom-question-queries.ts
|
|
2531
|
+
var ALL_CUSTOM_QUESTIONS_QUERY = `
|
|
2532
|
+
query Sales {
|
|
2533
|
+
questions {
|
|
2534
|
+
id
|
|
2535
|
+
questionType
|
|
2536
|
+
text
|
|
2537
|
+
answerDefaultValue
|
|
2538
|
+
hint
|
|
2539
|
+
internalLabel
|
|
2540
|
+
perGuest
|
|
2541
|
+
... on ChoiceQuestion {
|
|
2542
|
+
options {
|
|
2543
|
+
id
|
|
2544
|
+
text
|
|
2545
|
+
order
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
`;
|
|
2551
|
+
var CUSTOM_QUESTIONS_QUERY = `
|
|
2552
|
+
query Sales($id: ID!) {
|
|
2553
|
+
activity(id: $id) {
|
|
2554
|
+
id
|
|
2555
|
+
name
|
|
2556
|
+
questionActivityConfigurations {
|
|
2557
|
+
isRequired
|
|
2558
|
+
order
|
|
2559
|
+
question {
|
|
2560
|
+
id
|
|
2561
|
+
questionType
|
|
2562
|
+
text
|
|
2563
|
+
answerDefaultValue
|
|
2564
|
+
hint
|
|
2565
|
+
internalLabel
|
|
2566
|
+
perGuest
|
|
2567
|
+
... on ChoiceQuestion {
|
|
2568
|
+
options {
|
|
2569
|
+
id
|
|
2570
|
+
text
|
|
2571
|
+
order
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
`;
|
|
2579
|
+
|
|
2580
|
+
// src/internal/peek/custom-questions/custom-question-service.ts
|
|
2581
|
+
var CustomQuestionService = class {
|
|
2582
|
+
constructor(client) {
|
|
2583
|
+
this.client = client;
|
|
2584
|
+
}
|
|
2585
|
+
client;
|
|
2586
|
+
/**
|
|
2587
|
+
* Returns every custom question defined on the account, across all
|
|
2588
|
+
* activities. Choice-style questions (`SELECT_ONE`, `LOCATION`, …) carry their
|
|
2589
|
+
* selectable {@link CustomQuestion.options}; free-text/checkbox questions
|
|
2590
|
+
* return an empty option list.
|
|
2591
|
+
*
|
|
2592
|
+
* These are account-wide question **definitions**, not customer answers — no
|
|
2593
|
+
* PII is involved, so the result is unaffected by `fullCustomerAccess`. Unlike
|
|
2594
|
+
* {@link ProductService.getCustomQuestions}, the catalog carries no
|
|
2595
|
+
* per-activity configuration, so {@link CustomQuestion.order} is `0` and
|
|
2596
|
+
* {@link CustomQuestion.isRequired} is `false` on every entry.
|
|
2597
|
+
*
|
|
2598
|
+
* @returns Every custom question on the account, or an empty list when none
|
|
2599
|
+
* are defined.
|
|
2600
|
+
*
|
|
2601
|
+
* @example
|
|
2602
|
+
* ```ts
|
|
2603
|
+
* const questions = await peek.getCustomQuestionService().getAll();
|
|
2604
|
+
* ```
|
|
2605
|
+
*/
|
|
2606
|
+
async getAll() {
|
|
2607
|
+
const body = await this.client.request(
|
|
2608
|
+
SALES_ENDPOINT,
|
|
2609
|
+
ALL_CUSTOM_QUESTIONS_QUERY,
|
|
2610
|
+
{}
|
|
2611
|
+
);
|
|
2612
|
+
return fromQuestionNodes(body.data?.questions ?? []);
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2499
2616
|
// src/internal/peek/daily-notes/daily-note-converter.ts
|
|
2500
2617
|
function toDailyNote(response) {
|
|
2501
2618
|
const union = response?.dailyNote;
|
|
@@ -2674,7 +2791,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
|
|
|
2674
2791
|
// src/internal/sdk-headers.ts
|
|
2675
2792
|
var SDK_HEADER_NAME = "x-peek-sdk";
|
|
2676
2793
|
var SDK_PLATFORM = "js";
|
|
2677
|
-
var SDK_VERSION = "0.
|
|
2794
|
+
var SDK_VERSION = "1.0.0";
|
|
2678
2795
|
var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
|
|
2679
2796
|
|
|
2680
2797
|
// src/internal/peek/graphql-client.ts
|
|
@@ -3700,33 +3817,6 @@ function fromItemOptionNodes(nodes) {
|
|
|
3700
3817
|
return Array.from(grouped.values());
|
|
3701
3818
|
}
|
|
3702
3819
|
|
|
3703
|
-
// src/internal/peek/products/custom-question-converter.ts
|
|
3704
|
-
function fromQuestionConfigurations(configs) {
|
|
3705
|
-
return configs.map(fromQuestionConfiguration);
|
|
3706
|
-
}
|
|
3707
|
-
function fromQuestionConfiguration(config) {
|
|
3708
|
-
const question = config.question;
|
|
3709
|
-
return {
|
|
3710
|
-
id: question.id,
|
|
3711
|
-
order: config.order ?? 0,
|
|
3712
|
-
isRequired: config.isRequired ?? false,
|
|
3713
|
-
questionText: question.text,
|
|
3714
|
-
hintText: question.hint ?? null,
|
|
3715
|
-
questionType: question.questionType,
|
|
3716
|
-
internalLabel: question.internalLabel ?? null,
|
|
3717
|
-
perGuest: question.perGuest ?? false,
|
|
3718
|
-
defaultValue: question.answerDefaultValue ?? null,
|
|
3719
|
-
options: (question.options ?? []).map(fromQuestionOption)
|
|
3720
|
-
};
|
|
3721
|
-
}
|
|
3722
|
-
function fromQuestionOption(option) {
|
|
3723
|
-
return {
|
|
3724
|
-
id: option.id,
|
|
3725
|
-
order: option.order,
|
|
3726
|
-
value: option.text
|
|
3727
|
-
};
|
|
3728
|
-
}
|
|
3729
|
-
|
|
3730
3820
|
// src/internal/peek/products/product-queries.ts
|
|
3731
3821
|
var PRODUCTS_QUERY = `
|
|
3732
3822
|
query Sales {
|
|
@@ -3785,36 +3875,6 @@ var ITEM_OPTIONS_QUERY = `
|
|
|
3785
3875
|
}
|
|
3786
3876
|
`;
|
|
3787
3877
|
|
|
3788
|
-
// src/internal/peek/products/custom-question-queries.ts
|
|
3789
|
-
var CUSTOM_QUESTIONS_QUERY = `
|
|
3790
|
-
query Sales($id: ID!) {
|
|
3791
|
-
activity(id: $id) {
|
|
3792
|
-
id
|
|
3793
|
-
name
|
|
3794
|
-
questionActivityConfigurations {
|
|
3795
|
-
isRequired
|
|
3796
|
-
order
|
|
3797
|
-
question {
|
|
3798
|
-
id
|
|
3799
|
-
questionType
|
|
3800
|
-
text
|
|
3801
|
-
answerDefaultValue
|
|
3802
|
-
hint
|
|
3803
|
-
internalLabel
|
|
3804
|
-
perGuest
|
|
3805
|
-
... on ChoiceQuestion {
|
|
3806
|
-
options {
|
|
3807
|
-
id
|
|
3808
|
-
text
|
|
3809
|
-
order
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
}
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
`;
|
|
3817
|
-
|
|
3818
3878
|
// src/internal/peek/products/product-service.ts
|
|
3819
3879
|
var ERROR_PRODUCT_ID_REQUIRED2 = "A non-empty product id is required.";
|
|
3820
3880
|
var DEFAULT_ITEM_OPTIONS_PAGE_SIZE = 50;
|
|
@@ -4374,7 +4434,9 @@ var noopLogger = {
|
|
|
4374
4434
|
|
|
4375
4435
|
// src/peek-access-service.ts
|
|
4376
4436
|
var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
|
|
4377
|
-
var
|
|
4437
|
+
var V2_FALLBACK_URL_PREFIX = "https://apps.peek.com/installations-api";
|
|
4438
|
+
var ERROR_MODE_REQUIRED = `PeekAccessService: "mode" is required and must be "v1" or "v2". Existing installs used the previous default of "v1" \u2014 pass mode: "v1" for the fastest update. Note: "v1" is deprecated and will be removed; migrate to "v2" and pass the install webhook's apiUrl.`;
|
|
4439
|
+
var ERROR_V2_API_URL_REQUIRED = `PeekAccessService: mode "v2" requires an "apiUrl" \u2014 the per-install URL delivered on the install webhook (api.url) and persisted per install. If you do not have one yet, append your app slug to the registry prefix and pass that, e.g. "${V2_FALLBACK_URL_PREFIX}/<app-slug>" (the trailing app-slug segment is required \u2014 the prefix alone is not a valid apiUrl).`;
|
|
4378
4440
|
var PeekAccessService = class {
|
|
4379
4441
|
client;
|
|
4380
4442
|
productServiceOptions;
|
|
@@ -4393,21 +4455,27 @@ var PeekAccessService = class {
|
|
|
4393
4455
|
membershipService;
|
|
4394
4456
|
bookingServices = /* @__PURE__ */ new Map();
|
|
4395
4457
|
reviewService;
|
|
4458
|
+
customQuestionService;
|
|
4396
4459
|
constructor(config) {
|
|
4397
4460
|
const hasApiUrl = !!config.apiUrl;
|
|
4461
|
+
if (config.mode !== "v1" && config.mode !== "v2") {
|
|
4462
|
+
throw new Error(ERROR_MODE_REQUIRED);
|
|
4463
|
+
}
|
|
4398
4464
|
const isV2 = config.mode === "v2";
|
|
4399
4465
|
requireNonEmpty(config.installId, "installId", "PeekAccessService");
|
|
4400
4466
|
requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
|
|
4401
4467
|
requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
|
|
4402
|
-
if (!hasApiUrl)
|
|
4403
|
-
if (!
|
|
4468
|
+
if (isV2 && !hasApiUrl) throw new Error(ERROR_V2_API_URL_REQUIRED);
|
|
4469
|
+
if (!isV2 && !hasApiUrl) {
|
|
4470
|
+
requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
|
|
4471
|
+
requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
|
|
4472
|
+
}
|
|
4404
4473
|
this.jwtSecret = config.jwtSecret;
|
|
4405
4474
|
this.issuer = config.issuer;
|
|
4406
4475
|
const logger = config.logger ?? noopLogger;
|
|
4407
4476
|
const tokens = createTokenManager(config);
|
|
4408
|
-
const useRegistrySlug = hasApiUrl
|
|
4409
|
-
const
|
|
4410
|
-
const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, V2_EXTENDABLE_SLUG, "PeekAccessService") : `${config.baseUrl ?? defaultBaseUrl}/${config.appId}`;
|
|
4477
|
+
const useRegistrySlug = hasApiUrl;
|
|
4478
|
+
const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, V2_EXTENDABLE_SLUG, "PeekAccessService") : `${config.baseUrl ?? DEFAULT_BASE_URL}/${config.appId}`;
|
|
4411
4479
|
this.client = new GraphQLClient({
|
|
4412
4480
|
baseApiUrl,
|
|
4413
4481
|
endpoints: peekApiEndpoints(useRegistrySlug),
|
|
@@ -4593,6 +4661,16 @@ var PeekAccessService = class {
|
|
|
4593
4661
|
}
|
|
4594
4662
|
return this.reviewService;
|
|
4595
4663
|
}
|
|
4664
|
+
/**
|
|
4665
|
+
* Returns the {@link CustomQuestionService} for this install, bound to the
|
|
4666
|
+
* shared authenticated transport. The instance is created lazily and reused.
|
|
4667
|
+
*/
|
|
4668
|
+
getCustomQuestionService() {
|
|
4669
|
+
if (!this.customQuestionService) {
|
|
4670
|
+
this.customQuestionService = new CustomQuestionService(this.client);
|
|
4671
|
+
}
|
|
4672
|
+
return this.customQuestionService;
|
|
4673
|
+
}
|
|
4596
4674
|
// ─── Product short-forms ─────────────────────────────────────────────────
|
|
4597
4675
|
/** All products (activities + add-ons). @deprecated The access service exposes only its resource-service accessors; call the service directly instead. See {@link ProductService.getAllProducts}. */
|
|
4598
4676
|
getAllProducts() {
|
|
@@ -5062,7 +5140,12 @@ var AcmeAccessService = class {
|
|
|
5062
5140
|
|
|
5063
5141
|
// src/access-service-factory.ts
|
|
5064
5142
|
function createAccessServiceForInstall(install, config) {
|
|
5065
|
-
const shared = {
|
|
5143
|
+
const shared = {
|
|
5144
|
+
...config,
|
|
5145
|
+
installId: install.installId,
|
|
5146
|
+
apiUrl: install.apiUrl,
|
|
5147
|
+
mode: "v2"
|
|
5148
|
+
};
|
|
5066
5149
|
switch (install.platform) {
|
|
5067
5150
|
case "peek":
|
|
5068
5151
|
return new PeekAccessService(shared);
|
|
@@ -5242,6 +5325,7 @@ exports.CngAccessService = CngAccessService;
|
|
|
5242
5325
|
exports.CngApiError = CngApiError;
|
|
5243
5326
|
exports.CngPermissionError = CngPermissionError;
|
|
5244
5327
|
exports.CngProductService = CngProductService;
|
|
5328
|
+
exports.CustomQuestionService = CustomQuestionService;
|
|
5245
5329
|
exports.DailyNoteService = DailyNoteService;
|
|
5246
5330
|
exports.INSTALL_STATUSES = INSTALL_STATUSES;
|
|
5247
5331
|
exports.MembershipService = MembershipService;
|