perspectapi-ts-sdk 5.4.5 → 6.0.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/dist/index.d.mts +357 -2
- package/dist/index.d.ts +357 -2
- package/dist/index.js +314 -12
- package/dist/index.mjs +314 -12
- package/package.json +1 -1
- package/src/cache/cache-manager.ts +2 -8
- package/src/index.ts +1 -0
- package/src/v2/client/base-v2-client.ts +6 -0
- package/src/v2/client/content-client.ts +125 -4
- package/src/v2/client/credits-client.ts +57 -0
- package/src/v2/client/newsletter-client.ts +85 -1
- package/src/v2/client/orders-client.ts +40 -1
- package/src/v2/client/site-users-client.ts +76 -3
- package/src/v2/client/subscriptions-client.ts +126 -0
- package/src/v2/index.ts +8 -0
- package/src/v2/types.ts +276 -0
package/dist/index.mjs
CHANGED
|
@@ -331,11 +331,7 @@ var CacheManager = class {
|
|
|
331
331
|
async getOrSet(key, resolveValue, policy) {
|
|
332
332
|
if (!this.enabled || policy?.skipCache) {
|
|
333
333
|
console.log("[Cache] Cache disabled or skipped", { key, enabled: this.enabled, skipCache: policy?.skipCache });
|
|
334
|
-
|
|
335
|
-
if (this.enabled && !policy?.skipCache && policy?.ttlSeconds !== 0) {
|
|
336
|
-
await this.set(key, value2, policy);
|
|
337
|
-
}
|
|
338
|
-
return value2;
|
|
334
|
+
return resolveValue();
|
|
339
335
|
}
|
|
340
336
|
const namespacedKey = this.namespacedKey(key);
|
|
341
337
|
const cachedRaw = await this.adapter.get(namespacedKey);
|
|
@@ -358,7 +354,7 @@ var CacheManager = class {
|
|
|
358
354
|
return value;
|
|
359
355
|
}
|
|
360
356
|
async set(key, value, options) {
|
|
361
|
-
if (!this.enabled
|
|
357
|
+
if (!this.enabled) {
|
|
362
358
|
return;
|
|
363
359
|
}
|
|
364
360
|
const namespacedKey = this.namespacedKey(key);
|
|
@@ -3354,6 +3350,11 @@ var BaseV2Client = class {
|
|
|
3354
3350
|
const response = await this.http.patch(path, body);
|
|
3355
3351
|
return this.extractData(response);
|
|
3356
3352
|
}
|
|
3353
|
+
/** PUT to upsert a resource. */
|
|
3354
|
+
async putOne(path, body) {
|
|
3355
|
+
const response = await this.http.put(path, body);
|
|
3356
|
+
return this.extractData(response);
|
|
3357
|
+
}
|
|
3357
3358
|
/** DELETE a resource. */
|
|
3358
3359
|
async deleteOne(path) {
|
|
3359
3360
|
const response = await this.http.delete(path);
|
|
@@ -3427,13 +3428,47 @@ var BaseV2Client = class {
|
|
|
3427
3428
|
// src/v2/client/content-client.ts
|
|
3428
3429
|
var ContentV2Client = class extends BaseV2Client {
|
|
3429
3430
|
async list(siteName, params, cachePolicy) {
|
|
3430
|
-
return this.getList(
|
|
3431
|
+
return this.getList(
|
|
3432
|
+
this.sitePath(siteName, "content"),
|
|
3433
|
+
params,
|
|
3434
|
+
this.withContentTags(siteName, cachePolicy, {
|
|
3435
|
+
category: params?.category,
|
|
3436
|
+
slugPrefix: params?.slug_prefix,
|
|
3437
|
+
type: params?.type
|
|
3438
|
+
})
|
|
3439
|
+
);
|
|
3431
3440
|
}
|
|
3432
|
-
async *listAutoPaginated(siteName, params) {
|
|
3433
|
-
|
|
3441
|
+
async *listAutoPaginated(siteName, params, cachePolicy) {
|
|
3442
|
+
let startingAfter;
|
|
3443
|
+
let hasMore = true;
|
|
3444
|
+
while (hasMore) {
|
|
3445
|
+
const queryParams = { ...params ?? {} };
|
|
3446
|
+
if (startingAfter) {
|
|
3447
|
+
queryParams.starting_after = startingAfter;
|
|
3448
|
+
}
|
|
3449
|
+
const page = await this.list(siteName, queryParams, cachePolicy);
|
|
3450
|
+
for (const item of page.data) {
|
|
3451
|
+
yield item;
|
|
3452
|
+
}
|
|
3453
|
+
hasMore = page.has_more;
|
|
3454
|
+
if (page.data.length > 0) {
|
|
3455
|
+
startingAfter = page.data[page.data.length - 1].id;
|
|
3456
|
+
} else {
|
|
3457
|
+
hasMore = false;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3434
3460
|
}
|
|
3435
3461
|
async get(siteName, idOrSlug, cachePolicy) {
|
|
3436
|
-
|
|
3462
|
+
const isContentId = this.isContentId(idOrSlug);
|
|
3463
|
+
return this.getOne(
|
|
3464
|
+
this.sitePath(siteName, "content", idOrSlug),
|
|
3465
|
+
void 0,
|
|
3466
|
+
this.withContentTags(siteName, cachePolicy, {
|
|
3467
|
+
id: isContentId ? idOrSlug : void 0,
|
|
3468
|
+
slug: isContentId ? void 0 : idOrSlug,
|
|
3469
|
+
slugPrefix: isContentId ? void 0 : this.extractSlugPrefix(idOrSlug)
|
|
3470
|
+
})
|
|
3471
|
+
);
|
|
3437
3472
|
}
|
|
3438
3473
|
async create(siteName, data) {
|
|
3439
3474
|
return this.post(this.sitePath(siteName, "content"), data);
|
|
@@ -3450,6 +3485,56 @@ var ContentV2Client = class extends BaseV2Client {
|
|
|
3450
3485
|
async unpublish(siteName, id) {
|
|
3451
3486
|
return this.post(this.sitePath(siteName, "content", `${id}/unpublish`));
|
|
3452
3487
|
}
|
|
3488
|
+
withContentTags(siteName, cachePolicy, options) {
|
|
3489
|
+
const tags = new Set(cachePolicy?.tags ?? []);
|
|
3490
|
+
for (const tag of this.buildContentTags(siteName, options)) {
|
|
3491
|
+
tags.add(tag);
|
|
3492
|
+
}
|
|
3493
|
+
return {
|
|
3494
|
+
...cachePolicy,
|
|
3495
|
+
tags: Array.from(tags)
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
buildContentTags(siteName, options) {
|
|
3499
|
+
const tags = /* @__PURE__ */ new Set(["content", `content:site:${siteName}`]);
|
|
3500
|
+
const normalizedPrefix = this.normalizeTagPart(options.slugPrefix);
|
|
3501
|
+
const normalizedCategory = this.normalizeTagPart(options.category);
|
|
3502
|
+
const normalizedType = this.normalizeTagPart(options.type);
|
|
3503
|
+
if (options.slug) {
|
|
3504
|
+
tags.add(`content:slug:${siteName}:${options.slug}`);
|
|
3505
|
+
}
|
|
3506
|
+
if (options.id) {
|
|
3507
|
+
tags.add(`content:id:${options.id}`);
|
|
3508
|
+
}
|
|
3509
|
+
if (normalizedPrefix) {
|
|
3510
|
+
tags.add(`content:prefix:${normalizedPrefix}`);
|
|
3511
|
+
}
|
|
3512
|
+
if (normalizedType) {
|
|
3513
|
+
tags.add(`content:${normalizedType}`);
|
|
3514
|
+
}
|
|
3515
|
+
if (normalizedCategory) {
|
|
3516
|
+
tags.add("content:category");
|
|
3517
|
+
tags.add(`content:category:${siteName}:${normalizedCategory}`);
|
|
3518
|
+
}
|
|
3519
|
+
return Array.from(tags);
|
|
3520
|
+
}
|
|
3521
|
+
normalizeTagPart(value) {
|
|
3522
|
+
if (typeof value !== "string") {
|
|
3523
|
+
return void 0;
|
|
3524
|
+
}
|
|
3525
|
+
const normalized = value.trim().toLowerCase();
|
|
3526
|
+
return normalized === "" ? void 0 : normalized;
|
|
3527
|
+
}
|
|
3528
|
+
extractSlugPrefix(slug) {
|
|
3529
|
+
const slashIndex = slug.indexOf("/");
|
|
3530
|
+
if (slashIndex > 0) {
|
|
3531
|
+
return slug.slice(0, slashIndex);
|
|
3532
|
+
}
|
|
3533
|
+
return void 0;
|
|
3534
|
+
}
|
|
3535
|
+
isContentId(idOrSlug) {
|
|
3536
|
+
return /^cnt_/i.test(idOrSlug);
|
|
3537
|
+
}
|
|
3453
3538
|
};
|
|
3454
3539
|
|
|
3455
3540
|
// src/v2/client/products-client.ts
|
|
@@ -3546,18 +3631,38 @@ var OrdersV2Client = class extends BaseV2Client {
|
|
|
3546
3631
|
async get(siteName, id, cachePolicy) {
|
|
3547
3632
|
return this.getOne(this.sitePath(siteName, "orders", id), void 0, cachePolicy);
|
|
3548
3633
|
}
|
|
3634
|
+
/**
|
|
3635
|
+
* Create a checkout session via Stripe. Returns the session ID and a
|
|
3636
|
+
* `checkout_url` that the client should redirect to (or open in a new tab).
|
|
3637
|
+
*
|
|
3638
|
+
* This replaces the v1 `checkout.createCheckoutSession()` + `getCsrfToken()`
|
|
3639
|
+
* dance — v2 is API-key-only and requires no CSRF token.
|
|
3640
|
+
*/
|
|
3641
|
+
async create(siteName, data) {
|
|
3642
|
+
return this.post(
|
|
3643
|
+
this.sitePath(siteName, "orders"),
|
|
3644
|
+
data
|
|
3645
|
+
);
|
|
3646
|
+
}
|
|
3647
|
+
/** Update fulfillment status, tracking number, and/or notes on an order. */
|
|
3648
|
+
async updateFulfillment(siteName, id, data) {
|
|
3649
|
+
return this.patchOne(
|
|
3650
|
+
this.sitePath(siteName, "orders", `${id}/fulfillment`),
|
|
3651
|
+
data
|
|
3652
|
+
);
|
|
3653
|
+
}
|
|
3549
3654
|
};
|
|
3550
3655
|
|
|
3551
3656
|
// src/v2/client/site-users-client.ts
|
|
3552
3657
|
var SiteUsersV2Client = class extends BaseV2Client {
|
|
3553
|
-
// --- OTP Auth ---
|
|
3658
|
+
// --- OTP Auth (public, API-key-scoped) ---
|
|
3554
3659
|
async requestOtp(siteName, data) {
|
|
3555
3660
|
return this.post(this.sitePath(siteName, "users", "request-otp"), data);
|
|
3556
3661
|
}
|
|
3557
3662
|
async verifyOtp(siteName, data) {
|
|
3558
3663
|
return this.post(this.sitePath(siteName, "users", "verify-otp"), data);
|
|
3559
3664
|
}
|
|
3560
|
-
// --- Admin ---
|
|
3665
|
+
// --- Admin (API key) ---
|
|
3561
3666
|
async list(siteName, params) {
|
|
3562
3667
|
return this.getList(this.sitePath(siteName, "users"), params);
|
|
3563
3668
|
}
|
|
@@ -3570,6 +3675,48 @@ var SiteUsersV2Client = class extends BaseV2Client {
|
|
|
3570
3675
|
async update(siteName, id, data) {
|
|
3571
3676
|
return this.patchOne(this.sitePath(siteName, "users", id), data);
|
|
3572
3677
|
}
|
|
3678
|
+
// --- Authenticated "me" endpoints (site-user JWT) ---
|
|
3679
|
+
/**
|
|
3680
|
+
* Load the currently-authenticated site user's canonical record plus their
|
|
3681
|
+
* profile KV map. Requires `client.setAuth(jwt)` to have been called with
|
|
3682
|
+
* the token returned from `verifyOtp`.
|
|
3683
|
+
*/
|
|
3684
|
+
async getMe(siteName) {
|
|
3685
|
+
return this.getOne(
|
|
3686
|
+
this.sitePath(siteName, "users", "me")
|
|
3687
|
+
);
|
|
3688
|
+
}
|
|
3689
|
+
/** Update the authenticated user's own fields. */
|
|
3690
|
+
async updateMe(siteName, data) {
|
|
3691
|
+
return this.patchOne(
|
|
3692
|
+
this.sitePath(siteName, "users", "me"),
|
|
3693
|
+
data
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
/** Fetch the profile KV map as a dedicated `site_user_profile` envelope. */
|
|
3697
|
+
async getProfile(siteName) {
|
|
3698
|
+
return this.getOne(
|
|
3699
|
+
this.sitePath(siteName, "users", "me/profile")
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
/**
|
|
3703
|
+
* Set a single profile key. The value is persisted verbatim — callers wanting
|
|
3704
|
+
* structured data should JSON-stringify their value first.
|
|
3705
|
+
*/
|
|
3706
|
+
async setProfileValue(siteName, key, value) {
|
|
3707
|
+
const encoded = encodeURIComponent(key);
|
|
3708
|
+
return this.putOne(
|
|
3709
|
+
this.sitePath(siteName, "users", `me/profile/${encoded}`),
|
|
3710
|
+
{ value }
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
/** Delete a single profile key. */
|
|
3714
|
+
async deleteProfileValue(siteName, key) {
|
|
3715
|
+
const encoded = encodeURIComponent(key);
|
|
3716
|
+
return this.deleteOne(
|
|
3717
|
+
this.sitePath(siteName, "users", `me/profile/${encoded}`)
|
|
3718
|
+
);
|
|
3719
|
+
}
|
|
3573
3720
|
};
|
|
3574
3721
|
|
|
3575
3722
|
// src/v2/client/newsletter-client.ts
|
|
@@ -3641,6 +3788,61 @@ var NewsletterV2Client = class extends BaseV2Client {
|
|
|
3641
3788
|
cachePolicy
|
|
3642
3789
|
);
|
|
3643
3790
|
}
|
|
3791
|
+
// --- Admin writes: Lists CRUD ---
|
|
3792
|
+
async createList(siteName, data) {
|
|
3793
|
+
return this.post(
|
|
3794
|
+
this.sitePath(siteName, "newsletter", "lists"),
|
|
3795
|
+
data
|
|
3796
|
+
);
|
|
3797
|
+
}
|
|
3798
|
+
async updateList(siteName, id, data) {
|
|
3799
|
+
return this.patchOne(
|
|
3800
|
+
this.sitePath(siteName, "newsletter", `lists/${id}`),
|
|
3801
|
+
data
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
async deleteList(siteName, id) {
|
|
3805
|
+
return this.deleteOne(
|
|
3806
|
+
this.sitePath(siteName, "newsletter", `lists/${id}`)
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
// --- Admin writes: Subscription sync / membership / import ---
|
|
3810
|
+
/**
|
|
3811
|
+
* Upsert a subscription by email and (optionally) replace its list
|
|
3812
|
+
* memberships. Returns a `newsletter_sync_result` envelope with the
|
|
3813
|
+
* outcome (created / updated / resubscribed / already-unsubscribed).
|
|
3814
|
+
*/
|
|
3815
|
+
async syncSubscription(siteName, data) {
|
|
3816
|
+
return this.post(
|
|
3817
|
+
this.sitePath(siteName, "newsletter", "subscriptions/sync"),
|
|
3818
|
+
data
|
|
3819
|
+
);
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Add/remove/replace the list memberships for an existing subscription.
|
|
3823
|
+
* Returns the refreshed subscription record.
|
|
3824
|
+
*/
|
|
3825
|
+
async updateSubscriptionListMembership(siteName, subscriptionId, data) {
|
|
3826
|
+
return this.post(
|
|
3827
|
+
this.sitePath(
|
|
3828
|
+
siteName,
|
|
3829
|
+
"newsletter",
|
|
3830
|
+
`subscriptions/${subscriptionId}/list-membership`
|
|
3831
|
+
),
|
|
3832
|
+
data
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
/**
|
|
3836
|
+
* Bulk import subscriptions. Each row is upserted via the same sync
|
|
3837
|
+
* path; `refreshListCounts` is deferred until after all rows are
|
|
3838
|
+
* processed on the server.
|
|
3839
|
+
*/
|
|
3840
|
+
async importSubscriptions(siteName, data) {
|
|
3841
|
+
return this.post(
|
|
3842
|
+
this.sitePath(siteName, "newsletter", "subscriptions/import"),
|
|
3843
|
+
data
|
|
3844
|
+
);
|
|
3845
|
+
}
|
|
3644
3846
|
};
|
|
3645
3847
|
|
|
3646
3848
|
// src/v2/client/contacts-client.ts
|
|
@@ -3708,6 +3910,102 @@ var WebhooksV2Client = class extends BaseV2Client {
|
|
|
3708
3910
|
}
|
|
3709
3911
|
};
|
|
3710
3912
|
|
|
3913
|
+
// src/v2/client/subscriptions-client.ts
|
|
3914
|
+
var SubscriptionsV2Client = class extends BaseV2Client {
|
|
3915
|
+
// --- Authenticated "me" endpoints (site-user JWT) ---
|
|
3916
|
+
/** List all subscriptions for the authenticated user. */
|
|
3917
|
+
async listMySubscriptions(siteName) {
|
|
3918
|
+
return this.getList(
|
|
3919
|
+
this.sitePath(siteName, "users", "me/subscriptions")
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
/** Pause a subscription. */
|
|
3923
|
+
async pauseSubscription(siteName, subId, params) {
|
|
3924
|
+
return this.post(
|
|
3925
|
+
this.sitePath(siteName, "users", `me/subscriptions/${subId}/pause`),
|
|
3926
|
+
params ?? {}
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
/** Resume a paused subscription. */
|
|
3930
|
+
async resumeSubscription(siteName, subId) {
|
|
3931
|
+
return this.post(
|
|
3932
|
+
this.sitePath(siteName, "users", `me/subscriptions/${subId}/resume`)
|
|
3933
|
+
);
|
|
3934
|
+
}
|
|
3935
|
+
/** Cancel a subscription. */
|
|
3936
|
+
async cancelSubscription(siteName, subId, params) {
|
|
3937
|
+
return this.post(
|
|
3938
|
+
this.sitePath(siteName, "users", `me/subscriptions/${subId}/cancel`),
|
|
3939
|
+
params ?? {}
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
/** Change the plan (price) of a subscription. */
|
|
3943
|
+
async changeSubscriptionPlan(siteName, subId, params) {
|
|
3944
|
+
return this.post(
|
|
3945
|
+
this.sitePath(siteName, "users", `me/subscriptions/${subId}/change-plan`),
|
|
3946
|
+
params
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3949
|
+
// --- Admin endpoints (API key) ---
|
|
3950
|
+
/** List subscriptions for a specific user (admin). */
|
|
3951
|
+
async listUserSubscriptions(siteName, userId) {
|
|
3952
|
+
return this.getList(
|
|
3953
|
+
this.sitePath(siteName, "users", `${userId}/subscriptions`)
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
/** Pause a user's subscription (admin). */
|
|
3957
|
+
async pauseUserSubscription(siteName, userId, subId, params) {
|
|
3958
|
+
return this.post(
|
|
3959
|
+
this.sitePath(siteName, "users", `${userId}/subscriptions/${subId}/pause`),
|
|
3960
|
+
params ?? {}
|
|
3961
|
+
);
|
|
3962
|
+
}
|
|
3963
|
+
/** Resume a user's paused subscription (admin). */
|
|
3964
|
+
async resumeUserSubscription(siteName, userId, subId) {
|
|
3965
|
+
return this.post(
|
|
3966
|
+
this.sitePath(siteName, "users", `${userId}/subscriptions/${subId}/resume`)
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
/** Cancel a user's subscription (admin). */
|
|
3970
|
+
async cancelUserSubscription(siteName, userId, subId, params) {
|
|
3971
|
+
return this.post(
|
|
3972
|
+
this.sitePath(siteName, "users", `${userId}/subscriptions/${subId}/cancel`),
|
|
3973
|
+
params ?? {}
|
|
3974
|
+
);
|
|
3975
|
+
}
|
|
3976
|
+
};
|
|
3977
|
+
|
|
3978
|
+
// src/v2/client/credits-client.ts
|
|
3979
|
+
var CreditsV2Client = class extends BaseV2Client {
|
|
3980
|
+
// --- Authenticated "me" endpoints (site-user JWT) ---
|
|
3981
|
+
/** Get the current credit balance for the authenticated user. */
|
|
3982
|
+
async getMyBalance(siteName) {
|
|
3983
|
+
return this.getOne(
|
|
3984
|
+
this.sitePath(siteName, "users", "me/credits/balance")
|
|
3985
|
+
);
|
|
3986
|
+
}
|
|
3987
|
+
/** Get credit balance and transaction history for the authenticated user. */
|
|
3988
|
+
async getMyCredits(siteName) {
|
|
3989
|
+
return this.getOne(
|
|
3990
|
+
this.sitePath(siteName, "users", "me/credits")
|
|
3991
|
+
);
|
|
3992
|
+
}
|
|
3993
|
+
// --- Admin endpoints (API key) ---
|
|
3994
|
+
/** Get the credit balance for a specific user (admin). */
|
|
3995
|
+
async getUserBalance(siteName, userId) {
|
|
3996
|
+
return this.getOne(
|
|
3997
|
+
this.sitePath(siteName, "users", `${userId}/credits/balance`)
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
/** Grant credit to a specific user (admin). */
|
|
4001
|
+
async grantCredit(siteName, userId, data) {
|
|
4002
|
+
return this.post(
|
|
4003
|
+
this.sitePath(siteName, "users", `${userId}/credits/grant`),
|
|
4004
|
+
data
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
};
|
|
4008
|
+
|
|
3711
4009
|
// src/v2/index.ts
|
|
3712
4010
|
var PerspectApiV2Client = class {
|
|
3713
4011
|
http;
|
|
@@ -3724,6 +4022,8 @@ var PerspectApiV2Client = class {
|
|
|
3724
4022
|
sites;
|
|
3725
4023
|
apiKeys;
|
|
3726
4024
|
webhooks;
|
|
4025
|
+
subscriptions;
|
|
4026
|
+
credits;
|
|
3727
4027
|
constructor(config) {
|
|
3728
4028
|
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
3729
4029
|
const v2BaseUrl = baseUrl.endsWith("/api/v2") ? baseUrl : `${baseUrl}/api/v2`;
|
|
@@ -3743,6 +4043,8 @@ var PerspectApiV2Client = class {
|
|
|
3743
4043
|
this.sites = new SitesV2Client(this.http, basePath, cache);
|
|
3744
4044
|
this.apiKeys = new ApiKeysV2Client(this.http, basePath, cache);
|
|
3745
4045
|
this.webhooks = new WebhooksV2Client(this.http, basePath, cache);
|
|
4046
|
+
this.subscriptions = new SubscriptionsV2Client(this.http, basePath, cache);
|
|
4047
|
+
this.credits = new CreditsV2Client(this.http, basePath, cache);
|
|
3746
4048
|
}
|
|
3747
4049
|
/** Update the JWT token for authenticated requests. */
|
|
3748
4050
|
setAuth(jwt) {
|
package/package.json
CHANGED
|
@@ -76,13 +76,7 @@ export class CacheManager {
|
|
|
76
76
|
): Promise<T> {
|
|
77
77
|
if (!this.enabled || policy?.skipCache) {
|
|
78
78
|
console.log('[Cache] Cache disabled or skipped', { key, enabled: this.enabled, skipCache: policy?.skipCache });
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (this.enabled && !policy?.skipCache && policy?.ttlSeconds !== 0) {
|
|
82
|
-
await this.set(key, value, policy);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return value;
|
|
79
|
+
return resolveValue();
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
const namespacedKey = this.namespacedKey(key);
|
|
@@ -113,7 +107,7 @@ export class CacheManager {
|
|
|
113
107
|
}
|
|
114
108
|
|
|
115
109
|
async set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void> {
|
|
116
|
-
if (!this.enabled
|
|
110
|
+
if (!this.enabled) {
|
|
117
111
|
return;
|
|
118
112
|
}
|
|
119
113
|
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { default } from './perspect-api-client';
|
|
|
10
10
|
// v2 client
|
|
11
11
|
export { PerspectApiV2Client, createPerspectApiV2Client } from './v2';
|
|
12
12
|
export { PerspectV2Error } from './v2/client/base-v2-client';
|
|
13
|
+
export type * from './v2/types';
|
|
13
14
|
|
|
14
15
|
// Individual clients (for advanced usage)
|
|
15
16
|
export { AuthClient } from './client/auth-client';
|
|
@@ -129,6 +129,12 @@ export abstract class BaseV2Client {
|
|
|
129
129
|
return this.extractData<T>(response);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/** PUT to upsert a resource. */
|
|
133
|
+
protected async putOne<T>(path: string, body?: unknown): Promise<T> {
|
|
134
|
+
const response = await this.http.put<T>(path, body);
|
|
135
|
+
return this.extractData<T>(response);
|
|
136
|
+
}
|
|
137
|
+
|
|
132
138
|
/** DELETE a resource. */
|
|
133
139
|
protected async deleteOne(path: string): Promise<V2Deleted> {
|
|
134
140
|
const response = await this.http.delete<V2Deleted>(path);
|
|
@@ -12,15 +12,57 @@ import type {
|
|
|
12
12
|
export class ContentV2Client extends BaseV2Client {
|
|
13
13
|
|
|
14
14
|
async list(siteName: string, params?: V2ContentListParams, cachePolicy?: CachePolicy): Promise<V2List<V2Content>> {
|
|
15
|
-
return this.getList<V2Content>(
|
|
15
|
+
return this.getList<V2Content>(
|
|
16
|
+
this.sitePath(siteName, 'content'),
|
|
17
|
+
params,
|
|
18
|
+
this.withContentTags(siteName, cachePolicy, {
|
|
19
|
+
category: params?.category,
|
|
20
|
+
slugPrefix: params?.slug_prefix,
|
|
21
|
+
type: params?.type,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
async *listAutoPaginated(
|
|
19
|
-
|
|
26
|
+
async *listAutoPaginated(
|
|
27
|
+
siteName: string,
|
|
28
|
+
params?: Omit<V2ContentListParams, 'starting_after' | 'ending_before'>,
|
|
29
|
+
cachePolicy?: CachePolicy,
|
|
30
|
+
) {
|
|
31
|
+
let startingAfter: string | undefined;
|
|
32
|
+
let hasMore = true;
|
|
33
|
+
|
|
34
|
+
while (hasMore) {
|
|
35
|
+
const queryParams: V2ContentListParams = { ...(params ?? {}) };
|
|
36
|
+
if (startingAfter) {
|
|
37
|
+
queryParams.starting_after = startingAfter;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const page = await this.list(siteName, queryParams, cachePolicy);
|
|
41
|
+
for (const item of page.data) {
|
|
42
|
+
yield item;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
hasMore = page.has_more;
|
|
46
|
+
if (page.data.length > 0) {
|
|
47
|
+
startingAfter = page.data[page.data.length - 1].id;
|
|
48
|
+
} else {
|
|
49
|
+
hasMore = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
20
52
|
}
|
|
21
53
|
|
|
22
54
|
async get(siteName: string, idOrSlug: string, cachePolicy?: CachePolicy): Promise<V2Content> {
|
|
23
|
-
|
|
55
|
+
const isContentId = this.isContentId(idOrSlug);
|
|
56
|
+
|
|
57
|
+
return this.getOne<V2Content>(
|
|
58
|
+
this.sitePath(siteName, 'content', idOrSlug),
|
|
59
|
+
undefined,
|
|
60
|
+
this.withContentTags(siteName, cachePolicy, {
|
|
61
|
+
id: isContentId ? idOrSlug : undefined,
|
|
62
|
+
slug: isContentId ? undefined : idOrSlug,
|
|
63
|
+
slugPrefix: isContentId ? undefined : this.extractSlugPrefix(idOrSlug),
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
24
66
|
}
|
|
25
67
|
|
|
26
68
|
async create(siteName: string, data: V2ContentCreateParams): Promise<V2Content> {
|
|
@@ -42,4 +84,83 @@ export class ContentV2Client extends BaseV2Client {
|
|
|
42
84
|
async unpublish(siteName: string, id: string): Promise<V2Content> {
|
|
43
85
|
return this.post<V2Content>(this.sitePath(siteName, 'content', `${id}/unpublish`));
|
|
44
86
|
}
|
|
87
|
+
|
|
88
|
+
private withContentTags(
|
|
89
|
+
siteName: string,
|
|
90
|
+
cachePolicy: CachePolicy | undefined,
|
|
91
|
+
options: {
|
|
92
|
+
category?: string | null;
|
|
93
|
+
id?: string;
|
|
94
|
+
slug?: string;
|
|
95
|
+
slugPrefix?: string | null;
|
|
96
|
+
type?: string | null;
|
|
97
|
+
},
|
|
98
|
+
): CachePolicy {
|
|
99
|
+
const tags = new Set<string>(cachePolicy?.tags ?? []);
|
|
100
|
+
|
|
101
|
+
for (const tag of this.buildContentTags(siteName, options)) {
|
|
102
|
+
tags.add(tag);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...cachePolicy,
|
|
107
|
+
tags: Array.from(tags),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private buildContentTags(
|
|
112
|
+
siteName: string,
|
|
113
|
+
options: {
|
|
114
|
+
category?: string | null;
|
|
115
|
+
id?: string;
|
|
116
|
+
slug?: string;
|
|
117
|
+
slugPrefix?: string | null;
|
|
118
|
+
type?: string | null;
|
|
119
|
+
},
|
|
120
|
+
): string[] {
|
|
121
|
+
const tags = new Set<string>(['content', `content:site:${siteName}`]);
|
|
122
|
+
const normalizedPrefix = this.normalizeTagPart(options.slugPrefix);
|
|
123
|
+
const normalizedCategory = this.normalizeTagPart(options.category);
|
|
124
|
+
const normalizedType = this.normalizeTagPart(options.type);
|
|
125
|
+
|
|
126
|
+
if (options.slug) {
|
|
127
|
+
tags.add(`content:slug:${siteName}:${options.slug}`);
|
|
128
|
+
}
|
|
129
|
+
if (options.id) {
|
|
130
|
+
tags.add(`content:id:${options.id}`);
|
|
131
|
+
}
|
|
132
|
+
if (normalizedPrefix) {
|
|
133
|
+
tags.add(`content:prefix:${normalizedPrefix}`);
|
|
134
|
+
}
|
|
135
|
+
if (normalizedType) {
|
|
136
|
+
tags.add(`content:${normalizedType}`);
|
|
137
|
+
}
|
|
138
|
+
if (normalizedCategory) {
|
|
139
|
+
tags.add('content:category');
|
|
140
|
+
tags.add(`content:category:${siteName}:${normalizedCategory}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return Array.from(tags);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private normalizeTagPart(value?: string | null): string | undefined {
|
|
147
|
+
if (typeof value !== 'string') {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const normalized = value.trim().toLowerCase();
|
|
152
|
+
return normalized === '' ? undefined : normalized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private extractSlugPrefix(slug: string): string | undefined {
|
|
156
|
+
const slashIndex = slug.indexOf('/');
|
|
157
|
+
if (slashIndex > 0) {
|
|
158
|
+
return slug.slice(0, slashIndex);
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private isContentId(idOrSlug: string): boolean {
|
|
164
|
+
return /^cnt_/i.test(idOrSlug);
|
|
165
|
+
}
|
|
45
166
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 Credits Client — balance queries and admin grant operations.
|
|
3
|
+
*
|
|
4
|
+
* Two classes of endpoints:
|
|
5
|
+
* - /me paths require a site-user JWT (call `setAuth(jwt)` first)
|
|
6
|
+
* - Admin paths require an API key (call `setApiKey(key)` first)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { BaseV2Client } from './base-v2-client';
|
|
10
|
+
import type {
|
|
11
|
+
V2CreditBalance,
|
|
12
|
+
V2GrantCreditParams,
|
|
13
|
+
V2GrantCreditResult,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
export class CreditsV2Client extends BaseV2Client {
|
|
17
|
+
|
|
18
|
+
// --- Authenticated "me" endpoints (site-user JWT) ---
|
|
19
|
+
|
|
20
|
+
/** Get the current credit balance for the authenticated user. */
|
|
21
|
+
async getMyBalance(siteName: string): Promise<V2CreditBalance> {
|
|
22
|
+
return this.getOne<V2CreditBalance>(
|
|
23
|
+
this.sitePath(siteName, 'users', 'me/credits/balance'),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Get credit balance and transaction history for the authenticated user. */
|
|
28
|
+
async getMyCredits(siteName: string): Promise<V2CreditBalance> {
|
|
29
|
+
return this.getOne<V2CreditBalance>(
|
|
30
|
+
this.sitePath(siteName, 'users', 'me/credits'),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Admin endpoints (API key) ---
|
|
35
|
+
|
|
36
|
+
/** Get the credit balance for a specific user (admin). */
|
|
37
|
+
async getUserBalance(
|
|
38
|
+
siteName: string,
|
|
39
|
+
userId: string,
|
|
40
|
+
): Promise<V2CreditBalance> {
|
|
41
|
+
return this.getOne<V2CreditBalance>(
|
|
42
|
+
this.sitePath(siteName, 'users', `${userId}/credits/balance`),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Grant credit to a specific user (admin). */
|
|
47
|
+
async grantCredit(
|
|
48
|
+
siteName: string,
|
|
49
|
+
userId: string,
|
|
50
|
+
data: V2GrantCreditParams,
|
|
51
|
+
): Promise<V2GrantCreditResult> {
|
|
52
|
+
return this.post<V2GrantCreditResult>(
|
|
53
|
+
this.sitePath(siteName, 'users', `${userId}/credits/grant`),
|
|
54
|
+
data,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|