perspectapi-ts-sdk 1.5.2 → 2.1.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 +148 -0
- package/dist/index.d.mts +154 -22
- package/dist/index.d.ts +154 -22
- package/dist/index.js +510 -70
- package/dist/index.mjs +507 -70
- package/package.json +1 -1
- package/src/cache/cache-manager.ts +302 -0
- package/src/cache/in-memory-adapter.ts +49 -0
- package/src/cache/noop-adapter.ts +27 -0
- package/src/cache/types.ts +78 -0
- package/src/client/api-keys-client.ts +3 -2
- package/src/client/auth-client.ts +3 -2
- package/src/client/base-client.ts +88 -1
- package/src/client/categories-client.ts +34 -10
- package/src/client/checkout-client.ts +3 -2
- package/src/client/contact-client.ts +3 -2
- package/src/client/content-client.ts +59 -10
- package/src/client/newsletter-client.ts +4 -3
- package/src/client/organizations-client.ts +3 -2
- package/src/client/products-client.ts +115 -22
- package/src/client/sites-client.ts +3 -2
- package/src/client/webhooks-client.ts +3 -2
- package/src/index.ts +5 -0
- package/src/perspect-api-client.ts +14 -11
- package/src/types/index.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -23,13 +23,16 @@ __export(index_exports, {
|
|
|
23
23
|
ApiKeysClient: () => ApiKeysClient,
|
|
24
24
|
AuthClient: () => AuthClient,
|
|
25
25
|
BaseClient: () => BaseClient,
|
|
26
|
+
CacheManager: () => CacheManager,
|
|
26
27
|
CategoriesClient: () => CategoriesClient,
|
|
27
28
|
CheckoutClient: () => CheckoutClient,
|
|
28
29
|
ContactClient: () => ContactClient,
|
|
29
30
|
ContentClient: () => ContentClient,
|
|
30
31
|
DEFAULT_IMAGE_SIZES: () => DEFAULT_IMAGE_SIZES,
|
|
31
32
|
HttpClient: () => HttpClient,
|
|
33
|
+
InMemoryCacheAdapter: () => InMemoryCacheAdapter,
|
|
32
34
|
NewsletterClient: () => NewsletterClient,
|
|
35
|
+
NoopCacheAdapter: () => NoopCacheAdapter,
|
|
33
36
|
OrganizationsClient: () => OrganizationsClient,
|
|
34
37
|
PerspectApiClient: () => PerspectApiClient,
|
|
35
38
|
ProductsClient: () => ProductsClient,
|
|
@@ -263,13 +266,287 @@ function createApiError(error) {
|
|
|
263
266
|
};
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
// src/cache/in-memory-adapter.ts
|
|
270
|
+
var InMemoryCacheAdapter = class {
|
|
271
|
+
store = /* @__PURE__ */ new Map();
|
|
272
|
+
async get(key) {
|
|
273
|
+
const entry = this.store.get(key);
|
|
274
|
+
if (!entry) {
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
if (entry.expiresAt && entry.expiresAt <= Date.now()) {
|
|
278
|
+
this.store.delete(key);
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
return entry.value;
|
|
282
|
+
}
|
|
283
|
+
async set(key, value, options) {
|
|
284
|
+
const expiresAt = options?.ttlSeconds && options.ttlSeconds > 0 ? Date.now() + options.ttlSeconds * 1e3 : void 0;
|
|
285
|
+
this.store.set(key, { value, expiresAt });
|
|
286
|
+
}
|
|
287
|
+
async delete(key) {
|
|
288
|
+
this.store.delete(key);
|
|
289
|
+
}
|
|
290
|
+
async deleteMany(keys) {
|
|
291
|
+
keys.forEach((key) => this.store.delete(key));
|
|
292
|
+
}
|
|
293
|
+
async clear() {
|
|
294
|
+
this.store.clear();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/cache/noop-adapter.ts
|
|
299
|
+
var NoopCacheAdapter = class {
|
|
300
|
+
async get() {
|
|
301
|
+
return void 0;
|
|
302
|
+
}
|
|
303
|
+
async set() {
|
|
304
|
+
}
|
|
305
|
+
async delete() {
|
|
306
|
+
}
|
|
307
|
+
async deleteMany() {
|
|
308
|
+
}
|
|
309
|
+
async clear() {
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/cache/cache-manager.ts
|
|
314
|
+
var TAG_PREFIX = "__tag__";
|
|
315
|
+
var CacheManager = class {
|
|
316
|
+
adapter;
|
|
317
|
+
defaultTtlSeconds;
|
|
318
|
+
keyPrefix;
|
|
319
|
+
enabled;
|
|
320
|
+
constructor(config) {
|
|
321
|
+
const defaultOptions = {
|
|
322
|
+
defaultTtlSeconds: 300,
|
|
323
|
+
keyPrefix: "perspectapi"
|
|
324
|
+
};
|
|
325
|
+
const mergedConfig = {
|
|
326
|
+
...defaultOptions,
|
|
327
|
+
...config
|
|
328
|
+
};
|
|
329
|
+
if (config && config.enabled === false) {
|
|
330
|
+
this.enabled = false;
|
|
331
|
+
this.adapter = new NoopCacheAdapter();
|
|
332
|
+
} else if (config && config.adapter) {
|
|
333
|
+
this.enabled = true;
|
|
334
|
+
this.adapter = config.adapter;
|
|
335
|
+
} else if (config) {
|
|
336
|
+
this.enabled = true;
|
|
337
|
+
this.adapter = new InMemoryCacheAdapter();
|
|
338
|
+
} else {
|
|
339
|
+
this.enabled = false;
|
|
340
|
+
this.adapter = new NoopCacheAdapter();
|
|
341
|
+
}
|
|
342
|
+
this.defaultTtlSeconds = mergedConfig.defaultTtlSeconds ?? 300;
|
|
343
|
+
this.keyPrefix = mergedConfig.keyPrefix ?? "perspectapi";
|
|
344
|
+
}
|
|
345
|
+
isEnabled() {
|
|
346
|
+
return this.enabled;
|
|
347
|
+
}
|
|
348
|
+
getKeyPrefix() {
|
|
349
|
+
return this.keyPrefix;
|
|
350
|
+
}
|
|
351
|
+
buildKey(parts) {
|
|
352
|
+
const normalized = parts.flatMap((part) => this.normalizeKeyPart(part)).filter((part) => part !== void 0 && part !== null && part !== "");
|
|
353
|
+
return normalized.join(":");
|
|
354
|
+
}
|
|
355
|
+
async getOrSet(key, resolveValue, policy) {
|
|
356
|
+
if (!this.enabled || policy?.skipCache) {
|
|
357
|
+
console.log("[Cache] Cache disabled or skipped", { key, enabled: this.enabled, skipCache: policy?.skipCache });
|
|
358
|
+
const value2 = await resolveValue();
|
|
359
|
+
if (this.enabled && !policy?.skipCache && policy?.ttlSeconds !== 0) {
|
|
360
|
+
await this.set(key, value2, policy);
|
|
361
|
+
}
|
|
362
|
+
return value2;
|
|
363
|
+
}
|
|
364
|
+
const namespacedKey = this.namespacedKey(key);
|
|
365
|
+
const cachedRaw = await this.adapter.get(namespacedKey);
|
|
366
|
+
if (cachedRaw) {
|
|
367
|
+
const entry = this.deserialize(cachedRaw);
|
|
368
|
+
if (!entry.expiresAt || entry.expiresAt > Date.now()) {
|
|
369
|
+
console.log("[Cache] \u2713 HIT", { key, tags: entry.tags });
|
|
370
|
+
return entry.value;
|
|
371
|
+
}
|
|
372
|
+
console.log("[Cache] \u2717 EXPIRED", { key, expiresAt: new Date(entry.expiresAt) });
|
|
373
|
+
await this.adapter.delete(namespacedKey);
|
|
374
|
+
if (entry.tags?.length) {
|
|
375
|
+
await this.removeKeyFromTags(namespacedKey, entry.tags);
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
console.log("[Cache] \u2717 MISS", { key });
|
|
379
|
+
}
|
|
380
|
+
const value = await resolveValue();
|
|
381
|
+
await this.set(key, value, policy);
|
|
382
|
+
return value;
|
|
383
|
+
}
|
|
384
|
+
async set(key, value, options) {
|
|
385
|
+
if (!this.enabled || options?.ttlSeconds === 0) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const namespacedKey = this.namespacedKey(key);
|
|
389
|
+
const ttlSeconds = options?.ttlSeconds ?? this.defaultTtlSeconds;
|
|
390
|
+
const entry = {
|
|
391
|
+
value,
|
|
392
|
+
expiresAt: ttlSeconds > 0 ? Date.now() + ttlSeconds * 1e3 : void 0,
|
|
393
|
+
tags: options?.tags,
|
|
394
|
+
metadata: options?.metadata
|
|
395
|
+
};
|
|
396
|
+
console.log("[Cache] SET", { key, ttlSeconds, tags: options?.tags });
|
|
397
|
+
await this.adapter.set(namespacedKey, this.serialize(entry), {
|
|
398
|
+
ttlSeconds: ttlSeconds > 0 ? ttlSeconds : void 0
|
|
399
|
+
});
|
|
400
|
+
if (options?.tags?.length) {
|
|
401
|
+
await this.registerKeyTags(namespacedKey, options.tags);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async delete(key) {
|
|
405
|
+
if (!this.enabled) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const namespacedKey = this.namespacedKey(key);
|
|
409
|
+
await this.adapter.delete(namespacedKey);
|
|
410
|
+
}
|
|
411
|
+
async invalidate(options) {
|
|
412
|
+
if (!this.enabled) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
let totalInvalidated = 0;
|
|
416
|
+
if (options.keys?.length) {
|
|
417
|
+
const namespacedKeys = options.keys.map((key) => this.namespacedKey(key));
|
|
418
|
+
console.log("[Cache] INVALIDATE by keys", { count: options.keys.length, keys: options.keys });
|
|
419
|
+
if (this.adapter.deleteMany) {
|
|
420
|
+
await this.adapter.deleteMany(namespacedKeys);
|
|
421
|
+
} else {
|
|
422
|
+
await Promise.all(namespacedKeys.map((key) => this.adapter.delete(key)));
|
|
423
|
+
}
|
|
424
|
+
totalInvalidated += options.keys.length;
|
|
425
|
+
}
|
|
426
|
+
if (options.tags?.length) {
|
|
427
|
+
console.log("[Cache] INVALIDATE by tags", { tags: options.tags });
|
|
428
|
+
await Promise.all(
|
|
429
|
+
options.tags.map(async (tag) => {
|
|
430
|
+
const tagKey = this.tagKey(tag);
|
|
431
|
+
const payload = await this.adapter.get(tagKey);
|
|
432
|
+
if (!payload) {
|
|
433
|
+
console.log("[Cache] No entries for tag", { tag });
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const keys = this.deserializeTagSet(payload);
|
|
437
|
+
if (keys.length) {
|
|
438
|
+
console.log("[Cache] Invalidating entries for tag", { tag, count: keys.length });
|
|
439
|
+
if (this.adapter.deleteMany) {
|
|
440
|
+
await this.adapter.deleteMany(keys);
|
|
441
|
+
} else {
|
|
442
|
+
await Promise.all(keys.map((key) => this.adapter.delete(key)));
|
|
443
|
+
}
|
|
444
|
+
totalInvalidated += keys.length;
|
|
445
|
+
}
|
|
446
|
+
await this.adapter.delete(tagKey);
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
console.log("[Cache] \u2713 INVALIDATED", { totalEntries: totalInvalidated, keys: options.keys?.length || 0, tags: options.tags?.length || 0 });
|
|
451
|
+
}
|
|
452
|
+
namespacedKey(key) {
|
|
453
|
+
return `${this.keyPrefix}:${key}`;
|
|
454
|
+
}
|
|
455
|
+
tagKey(tag) {
|
|
456
|
+
return this.namespacedKey(`${TAG_PREFIX}:${tag}`);
|
|
457
|
+
}
|
|
458
|
+
serialize(entry) {
|
|
459
|
+
return JSON.stringify(entry);
|
|
460
|
+
}
|
|
461
|
+
deserialize(payload) {
|
|
462
|
+
try {
|
|
463
|
+
return JSON.parse(payload);
|
|
464
|
+
} catch {
|
|
465
|
+
return { value: payload };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
deserializeTagSet(payload) {
|
|
469
|
+
try {
|
|
470
|
+
const parsed = JSON.parse(payload);
|
|
471
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
472
|
+
} catch {
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async registerKeyTags(namespacedKey, tags) {
|
|
477
|
+
await Promise.all(
|
|
478
|
+
tags.map(async (tag) => {
|
|
479
|
+
const tagKey = this.tagKey(tag);
|
|
480
|
+
const existingPayload = await this.adapter.get(tagKey);
|
|
481
|
+
const keys = existingPayload ? this.deserializeTagSet(existingPayload) : [];
|
|
482
|
+
if (!keys.includes(namespacedKey)) {
|
|
483
|
+
keys.push(namespacedKey);
|
|
484
|
+
await this.adapter.set(tagKey, JSON.stringify(keys));
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
async removeKeyFromTags(namespacedKey, tags) {
|
|
490
|
+
await Promise.all(
|
|
491
|
+
tags.map(async (tag) => {
|
|
492
|
+
const tagKey = this.tagKey(tag);
|
|
493
|
+
const existingPayload = await this.adapter.get(tagKey);
|
|
494
|
+
if (!existingPayload) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const keys = this.deserializeTagSet(existingPayload);
|
|
498
|
+
const updated = keys.filter((key) => key !== namespacedKey);
|
|
499
|
+
if (updated.length === 0) {
|
|
500
|
+
await this.adapter.delete(tagKey);
|
|
501
|
+
} else if (updated.length !== keys.length) {
|
|
502
|
+
await this.adapter.set(tagKey, JSON.stringify(updated));
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
normalizeKeyPart(part) {
|
|
508
|
+
if (part === void 0 || part === null || part === "") {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
if (Array.isArray(part)) {
|
|
512
|
+
return part.flatMap((item) => this.normalizeKeyPart(item));
|
|
513
|
+
}
|
|
514
|
+
if (typeof part === "object") {
|
|
515
|
+
return [this.normalizeObject(part)];
|
|
516
|
+
}
|
|
517
|
+
return [String(part)];
|
|
518
|
+
}
|
|
519
|
+
normalizeObject(input) {
|
|
520
|
+
const sortedKeys = Object.keys(input).sort();
|
|
521
|
+
const normalized = {};
|
|
522
|
+
for (const key of sortedKeys) {
|
|
523
|
+
const value = input[key];
|
|
524
|
+
if (value === void 0) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
528
|
+
normalized[key] = JSON.parse(this.normalizeObject(value));
|
|
529
|
+
} else if (Array.isArray(value)) {
|
|
530
|
+
normalized[key] = value.map(
|
|
531
|
+
(item) => typeof item === "object" && item !== null ? JSON.parse(this.normalizeObject(item)) : item
|
|
532
|
+
);
|
|
533
|
+
} else {
|
|
534
|
+
normalized[key] = value;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return JSON.stringify(normalized);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
266
541
|
// src/client/base-client.ts
|
|
267
542
|
var BaseClient = class {
|
|
268
543
|
http;
|
|
269
544
|
basePath;
|
|
270
|
-
|
|
545
|
+
cache;
|
|
546
|
+
constructor(http, basePath, cache) {
|
|
271
547
|
this.http = http;
|
|
272
548
|
this.basePath = basePath;
|
|
549
|
+
this.cache = cache && cache.isEnabled() ? cache : void 0;
|
|
273
550
|
}
|
|
274
551
|
/**
|
|
275
552
|
* Build a site-scoped endpoint relative to the API base path
|
|
@@ -329,12 +606,71 @@ var BaseClient = class {
|
|
|
329
606
|
async delete(endpoint, csrfToken) {
|
|
330
607
|
return this.http.delete(this.buildPath(endpoint), { csrfToken });
|
|
331
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Fetch a GET endpoint with optional caching support.
|
|
611
|
+
*/
|
|
612
|
+
async fetchWithCache(endpoint, params, tags, policy, fetcher) {
|
|
613
|
+
if (!this.cache) {
|
|
614
|
+
return fetcher();
|
|
615
|
+
}
|
|
616
|
+
const cacheKey = this.buildCacheKey(endpoint, params);
|
|
617
|
+
const combinedPolicy = policy ? { ...policy, tags: this.mergeTags(tags, policy.tags) } : { tags };
|
|
618
|
+
return this.cache.getOrSet(cacheKey, fetcher, combinedPolicy);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Invalidate cache entries by keys or tags.
|
|
622
|
+
*/
|
|
623
|
+
async invalidateCache(options) {
|
|
624
|
+
if (!this.cache) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
await this.cache.invalidate(options);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Build a consistent cache key for an endpoint + params combination.
|
|
631
|
+
*/
|
|
632
|
+
buildCacheKey(endpoint, params) {
|
|
633
|
+
const sanitizedEndpoint = endpoint.replace(/^\//, "");
|
|
634
|
+
const baseSegment = this.basePath.replace(/^\//, "");
|
|
635
|
+
const parts = [baseSegment, sanitizedEndpoint];
|
|
636
|
+
if (params && Object.keys(params).length > 0) {
|
|
637
|
+
parts.push(this.sortObject(params));
|
|
638
|
+
}
|
|
639
|
+
if (this.cache) {
|
|
640
|
+
return this.cache.buildKey(parts);
|
|
641
|
+
}
|
|
642
|
+
return parts.map((part) => typeof part === "string" ? part : JSON.stringify(part)).join(":");
|
|
643
|
+
}
|
|
644
|
+
mergeTags(defaultTags, overrideTags) {
|
|
645
|
+
const combined = /* @__PURE__ */ new Set();
|
|
646
|
+
defaultTags.forEach((tag) => combined.add(tag));
|
|
647
|
+
overrideTags?.forEach((tag) => combined.add(tag));
|
|
648
|
+
return Array.from(combined.values());
|
|
649
|
+
}
|
|
650
|
+
sortObject(input) {
|
|
651
|
+
const sortedKeys = Object.keys(input).sort();
|
|
652
|
+
const result = {};
|
|
653
|
+
for (const key of sortedKeys) {
|
|
654
|
+
const value = input[key];
|
|
655
|
+
if (value === void 0) continue;
|
|
656
|
+
if (Array.isArray(value)) {
|
|
657
|
+
result[key] = value.map(
|
|
658
|
+
(item) => typeof item === "object" && item !== null ? this.sortObject(item) : item
|
|
659
|
+
);
|
|
660
|
+
} else if (value && typeof value === "object") {
|
|
661
|
+
result[key] = this.sortObject(value);
|
|
662
|
+
} else {
|
|
663
|
+
result[key] = value;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return result;
|
|
667
|
+
}
|
|
332
668
|
};
|
|
333
669
|
|
|
334
670
|
// src/client/auth-client.ts
|
|
335
671
|
var AuthClient = class extends BaseClient {
|
|
336
|
-
constructor(http) {
|
|
337
|
-
super(http, "/api/v1");
|
|
672
|
+
constructor(http, cache) {
|
|
673
|
+
super(http, "/api/v1", cache);
|
|
338
674
|
}
|
|
339
675
|
/**
|
|
340
676
|
* Get CSRF token
|
|
@@ -395,28 +731,50 @@ var AuthClient = class extends BaseClient {
|
|
|
395
731
|
|
|
396
732
|
// src/client/content-client.ts
|
|
397
733
|
var ContentClient = class extends BaseClient {
|
|
398
|
-
constructor(http) {
|
|
399
|
-
super(http, "/api/v1");
|
|
734
|
+
constructor(http, cache) {
|
|
735
|
+
super(http, "/api/v1", cache);
|
|
400
736
|
}
|
|
401
737
|
/**
|
|
402
738
|
* Get all content with pagination and filtering for a site
|
|
403
739
|
*/
|
|
404
|
-
async getContent(siteName, params) {
|
|
405
|
-
|
|
740
|
+
async getContent(siteName, params, cachePolicy) {
|
|
741
|
+
const endpoint = this.siteScopedEndpoint(siteName);
|
|
742
|
+
const path = this.buildPath(endpoint);
|
|
743
|
+
return this.fetchWithCache(
|
|
744
|
+
endpoint,
|
|
745
|
+
params,
|
|
746
|
+
this.buildContentTags(siteName),
|
|
747
|
+
cachePolicy,
|
|
748
|
+
() => this.http.get(path, params)
|
|
749
|
+
);
|
|
406
750
|
}
|
|
407
751
|
/**
|
|
408
752
|
* Get content by ID
|
|
409
753
|
*/
|
|
410
|
-
async getContentById(id) {
|
|
411
|
-
|
|
754
|
+
async getContentById(id, cachePolicy) {
|
|
755
|
+
const endpoint = `/content/${id}`;
|
|
756
|
+
const path = this.buildPath(endpoint);
|
|
757
|
+
return this.fetchWithCache(
|
|
758
|
+
endpoint,
|
|
759
|
+
void 0,
|
|
760
|
+
this.buildContentTags(void 0, void 0, id),
|
|
761
|
+
cachePolicy,
|
|
762
|
+
() => this.http.get(path)
|
|
763
|
+
);
|
|
412
764
|
}
|
|
413
765
|
/**
|
|
414
766
|
* Get content by slug for a site
|
|
415
767
|
*/
|
|
416
|
-
async getContentBySlug(siteName, slug) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
768
|
+
async getContentBySlug(siteName, slug, cachePolicy) {
|
|
769
|
+
const endpoint = this.siteScopedEndpoint(siteName, `/slug/${encodeURIComponent(slug)}`);
|
|
770
|
+
const path = this.buildPath(endpoint);
|
|
771
|
+
return this.fetchWithCache(
|
|
772
|
+
endpoint,
|
|
773
|
+
void 0,
|
|
774
|
+
this.buildContentTags(siteName, slug),
|
|
775
|
+
cachePolicy,
|
|
776
|
+
() => this.http.get(path)
|
|
777
|
+
);
|
|
420
778
|
}
|
|
421
779
|
/**
|
|
422
780
|
* Create new content
|
|
@@ -478,12 +836,25 @@ var ContentClient = class extends BaseClient {
|
|
|
478
836
|
async duplicateContent(id) {
|
|
479
837
|
return this.create(`/content/${id}/duplicate`, {});
|
|
480
838
|
}
|
|
839
|
+
buildContentTags(siteName, slug, id) {
|
|
840
|
+
const tags = /* @__PURE__ */ new Set(["content"]);
|
|
841
|
+
if (siteName) {
|
|
842
|
+
tags.add(`content:site:${siteName}`);
|
|
843
|
+
}
|
|
844
|
+
if (slug) {
|
|
845
|
+
tags.add(`content:slug:${siteName}:${slug}`);
|
|
846
|
+
}
|
|
847
|
+
if (typeof id === "number") {
|
|
848
|
+
tags.add(`content:id:${id}`);
|
|
849
|
+
}
|
|
850
|
+
return Array.from(tags.values());
|
|
851
|
+
}
|
|
481
852
|
};
|
|
482
853
|
|
|
483
854
|
// src/client/api-keys-client.ts
|
|
484
855
|
var ApiKeysClient = class extends BaseClient {
|
|
485
|
-
constructor(http) {
|
|
486
|
-
super(http, "/api/v1");
|
|
856
|
+
constructor(http, cache) {
|
|
857
|
+
super(http, "/api/v1", cache);
|
|
487
858
|
}
|
|
488
859
|
/**
|
|
489
860
|
* Get all API keys
|
|
@@ -549,8 +920,8 @@ var ApiKeysClient = class extends BaseClient {
|
|
|
549
920
|
|
|
550
921
|
// src/client/organizations-client.ts
|
|
551
922
|
var OrganizationsClient = class extends BaseClient {
|
|
552
|
-
constructor(http) {
|
|
553
|
-
super(http, "/api/v1");
|
|
923
|
+
constructor(http, cache) {
|
|
924
|
+
super(http, "/api/v1", cache);
|
|
554
925
|
}
|
|
555
926
|
/**
|
|
556
927
|
* Get all organizations
|
|
@@ -622,8 +993,8 @@ var OrganizationsClient = class extends BaseClient {
|
|
|
622
993
|
|
|
623
994
|
// src/client/sites-client.ts
|
|
624
995
|
var SitesClient = class extends BaseClient {
|
|
625
|
-
constructor(http) {
|
|
626
|
-
super(http, "/api/v1");
|
|
996
|
+
constructor(http, cache) {
|
|
997
|
+
super(http, "/api/v1", cache);
|
|
627
998
|
}
|
|
628
999
|
/**
|
|
629
1000
|
* Get all sites
|
|
@@ -719,13 +1090,13 @@ var SitesClient = class extends BaseClient {
|
|
|
719
1090
|
|
|
720
1091
|
// src/client/products-client.ts
|
|
721
1092
|
var ProductsClient = class extends BaseClient {
|
|
722
|
-
constructor(http) {
|
|
723
|
-
super(http, "/api/v1");
|
|
1093
|
+
constructor(http, cache) {
|
|
1094
|
+
super(http, "/api/v1", cache);
|
|
724
1095
|
}
|
|
725
1096
|
/**
|
|
726
1097
|
* Get all products for a site
|
|
727
1098
|
*/
|
|
728
|
-
async getProducts(siteName, params) {
|
|
1099
|
+
async getProducts(siteName, params, cachePolicy) {
|
|
729
1100
|
const normalizeList = (value) => {
|
|
730
1101
|
if (value === void 0 || value === null) {
|
|
731
1102
|
return void 0;
|
|
@@ -749,30 +1120,61 @@ var ProductsClient = class extends BaseClient {
|
|
|
749
1120
|
delete normalizedParams.category_id;
|
|
750
1121
|
}
|
|
751
1122
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1123
|
+
const endpoint = this.siteScopedEndpoint(siteName, "/products", { includeSitesSegment: false });
|
|
1124
|
+
const path = this.buildPath(endpoint);
|
|
1125
|
+
return this.fetchWithCache(
|
|
1126
|
+
endpoint,
|
|
1127
|
+
normalizedParams,
|
|
1128
|
+
this.buildProductTags(siteName, ["products:list"]),
|
|
1129
|
+
cachePolicy,
|
|
1130
|
+
() => this.http.get(path, normalizedParams)
|
|
755
1131
|
);
|
|
756
1132
|
}
|
|
757
1133
|
/**
|
|
758
1134
|
* Get product by ID
|
|
759
1135
|
*/
|
|
760
|
-
async getProductById(id) {
|
|
761
|
-
|
|
1136
|
+
async getProductById(id, cachePolicy) {
|
|
1137
|
+
const endpoint = `/products/${id}`;
|
|
1138
|
+
const path = this.buildPath(endpoint);
|
|
1139
|
+
return this.fetchWithCache(
|
|
1140
|
+
endpoint,
|
|
1141
|
+
void 0,
|
|
1142
|
+
this.buildProductTags(void 0, [`products:id:${id}`]),
|
|
1143
|
+
cachePolicy,
|
|
1144
|
+
() => this.http.get(path)
|
|
1145
|
+
);
|
|
762
1146
|
}
|
|
763
1147
|
/**
|
|
764
1148
|
* Get product by SKU
|
|
765
1149
|
*/
|
|
766
|
-
async getProductBySku(sku) {
|
|
767
|
-
|
|
1150
|
+
async getProductBySku(sku, cachePolicy) {
|
|
1151
|
+
const endpoint = `/products/sku/${encodeURIComponent(sku)}`;
|
|
1152
|
+
const path = this.buildPath(endpoint);
|
|
1153
|
+
return this.fetchWithCache(
|
|
1154
|
+
endpoint,
|
|
1155
|
+
void 0,
|
|
1156
|
+
this.buildProductTags(void 0, [`products:sku:${sku.toLowerCase()}`]),
|
|
1157
|
+
cachePolicy,
|
|
1158
|
+
() => this.http.get(path)
|
|
1159
|
+
);
|
|
768
1160
|
}
|
|
769
1161
|
/**
|
|
770
1162
|
* Get product by slug and site name
|
|
771
1163
|
*/
|
|
772
|
-
async getProductBySlug(siteName, slug) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1164
|
+
async getProductBySlug(siteName, slug, cachePolicy) {
|
|
1165
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1166
|
+
siteName,
|
|
1167
|
+
`/products/slug/${encodeURIComponent(slug)}`,
|
|
1168
|
+
{ includeSitesSegment: false }
|
|
1169
|
+
);
|
|
1170
|
+
const path = this.buildPath(endpoint);
|
|
1171
|
+
return this.fetchWithCache(
|
|
1172
|
+
endpoint,
|
|
1173
|
+
void 0,
|
|
1174
|
+
this.buildProductTags(siteName, [`products:slug:${siteName}:${slug}`]),
|
|
1175
|
+
cachePolicy,
|
|
1176
|
+
() => this.http.get(path)
|
|
1177
|
+
);
|
|
776
1178
|
}
|
|
777
1179
|
/**
|
|
778
1180
|
* Create new product
|
|
@@ -861,27 +1263,44 @@ var ProductsClient = class extends BaseClient {
|
|
|
861
1263
|
/**
|
|
862
1264
|
* Get products by category slug
|
|
863
1265
|
*/
|
|
864
|
-
async getProductsByCategorySlug(siteName, categorySlug, params) {
|
|
1266
|
+
async getProductsByCategorySlug(siteName, categorySlug, params, cachePolicy) {
|
|
865
1267
|
const queryParams = params ? {
|
|
866
1268
|
limit: params.limit,
|
|
867
1269
|
offset: params.page ? (params.page - 1) * (params.limit || 20) : void 0,
|
|
868
1270
|
published: params.published,
|
|
869
1271
|
search: params.search
|
|
870
1272
|
} : void 0;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1273
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1274
|
+
siteName,
|
|
1275
|
+
`/products/category/${encodeURIComponent(categorySlug)}`,
|
|
1276
|
+
{ includeSitesSegment: false }
|
|
1277
|
+
);
|
|
1278
|
+
const path = this.buildPath(endpoint);
|
|
1279
|
+
return this.fetchWithCache(
|
|
1280
|
+
endpoint,
|
|
1281
|
+
queryParams,
|
|
1282
|
+
this.buildProductTags(siteName, [
|
|
1283
|
+
"products:category",
|
|
1284
|
+
`products:category:${siteName}:${categorySlug}`
|
|
1285
|
+
]),
|
|
1286
|
+
cachePolicy,
|
|
1287
|
+
() => this.http.get(path, queryParams)
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
buildProductTags(siteName, extraTags = []) {
|
|
1291
|
+
const tags = /* @__PURE__ */ new Set(["products"]);
|
|
1292
|
+
if (siteName) {
|
|
1293
|
+
tags.add(`products:site:${siteName}`);
|
|
1294
|
+
}
|
|
1295
|
+
extraTags.filter(Boolean).forEach((tag) => tags.add(tag));
|
|
1296
|
+
return Array.from(tags.values());
|
|
878
1297
|
}
|
|
879
1298
|
};
|
|
880
1299
|
|
|
881
1300
|
// src/client/categories-client.ts
|
|
882
1301
|
var CategoriesClient = class extends BaseClient {
|
|
883
|
-
constructor(http) {
|
|
884
|
-
super(http, "/api/v1");
|
|
1302
|
+
constructor(http, cache) {
|
|
1303
|
+
super(http, "/api/v1", cache);
|
|
885
1304
|
}
|
|
886
1305
|
/**
|
|
887
1306
|
* Get all categories
|
|
@@ -904,14 +1323,20 @@ var CategoriesClient = class extends BaseClient {
|
|
|
904
1323
|
/**
|
|
905
1324
|
* Get product category by slug (for products)
|
|
906
1325
|
*/
|
|
907
|
-
async getProductCategoryBySlug(siteName, slug) {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1326
|
+
async getProductCategoryBySlug(siteName, slug, cachePolicy) {
|
|
1327
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1328
|
+
siteName,
|
|
1329
|
+
`/product_category/slug/${encodeURIComponent(slug)}`,
|
|
1330
|
+
{ includeSitesSegment: false }
|
|
1331
|
+
);
|
|
1332
|
+
const path = this.buildPath(endpoint);
|
|
1333
|
+
return this.fetchWithCache(
|
|
1334
|
+
endpoint,
|
|
1335
|
+
void 0,
|
|
1336
|
+
this.buildCategoryTags(siteName, slug),
|
|
1337
|
+
cachePolicy,
|
|
1338
|
+
() => this.http.get(path)
|
|
1339
|
+
);
|
|
915
1340
|
}
|
|
916
1341
|
/**
|
|
917
1342
|
* Create new category
|
|
@@ -980,12 +1405,22 @@ var CategoriesClient = class extends BaseClient {
|
|
|
980
1405
|
async searchCategories(query, params) {
|
|
981
1406
|
return this.http.get(`/categories/search`, { q: query, ...params });
|
|
982
1407
|
}
|
|
1408
|
+
buildCategoryTags(siteName, slug) {
|
|
1409
|
+
const tags = /* @__PURE__ */ new Set(["categories"]);
|
|
1410
|
+
if (siteName) {
|
|
1411
|
+
tags.add(`categories:site:${siteName}`);
|
|
1412
|
+
}
|
|
1413
|
+
if (slug) {
|
|
1414
|
+
tags.add(`categories:product:${siteName}:${slug}`);
|
|
1415
|
+
}
|
|
1416
|
+
return Array.from(tags.values());
|
|
1417
|
+
}
|
|
983
1418
|
};
|
|
984
1419
|
|
|
985
1420
|
// src/client/webhooks-client.ts
|
|
986
1421
|
var WebhooksClient = class extends BaseClient {
|
|
987
|
-
constructor(http) {
|
|
988
|
-
super(http, "/api/v1");
|
|
1422
|
+
constructor(http, cache) {
|
|
1423
|
+
super(http, "/api/v1", cache);
|
|
989
1424
|
}
|
|
990
1425
|
/**
|
|
991
1426
|
* Get all webhooks
|
|
@@ -1082,8 +1517,8 @@ var WebhooksClient = class extends BaseClient {
|
|
|
1082
1517
|
|
|
1083
1518
|
// src/client/checkout-client.ts
|
|
1084
1519
|
var CheckoutClient = class extends BaseClient {
|
|
1085
|
-
constructor(http) {
|
|
1086
|
-
super(http, "/api/v1");
|
|
1520
|
+
constructor(http, cache) {
|
|
1521
|
+
super(http, "/api/v1", cache);
|
|
1087
1522
|
}
|
|
1088
1523
|
/**
|
|
1089
1524
|
* Get CSRF token for a specific site
|
|
@@ -1194,8 +1629,8 @@ var CheckoutClient = class extends BaseClient {
|
|
|
1194
1629
|
|
|
1195
1630
|
// src/client/contact-client.ts
|
|
1196
1631
|
var ContactClient = class extends BaseClient {
|
|
1197
|
-
constructor(http) {
|
|
1198
|
-
super(http, "/api/v1");
|
|
1632
|
+
constructor(http, cache) {
|
|
1633
|
+
super(http, "/api/v1", cache);
|
|
1199
1634
|
}
|
|
1200
1635
|
/**
|
|
1201
1636
|
* Build a contact endpoint scoped to a site (without /sites prefix)
|
|
@@ -1301,8 +1736,8 @@ var ContactClient = class extends BaseClient {
|
|
|
1301
1736
|
|
|
1302
1737
|
// src/client/newsletter-client.ts
|
|
1303
1738
|
var NewsletterClient = class extends BaseClient {
|
|
1304
|
-
constructor(http) {
|
|
1305
|
-
super(http, "/api/v1");
|
|
1739
|
+
constructor(http, cache) {
|
|
1740
|
+
super(http, "/api/v1", cache);
|
|
1306
1741
|
}
|
|
1307
1742
|
/**
|
|
1308
1743
|
* Build a newsletter endpoint scoped to a site (without /sites prefix)
|
|
@@ -1497,6 +1932,7 @@ var NewsletterClient = class extends BaseClient {
|
|
|
1497
1932
|
// src/perspect-api-client.ts
|
|
1498
1933
|
var PerspectApiClient = class {
|
|
1499
1934
|
http;
|
|
1935
|
+
cache;
|
|
1500
1936
|
// Service clients
|
|
1501
1937
|
auth;
|
|
1502
1938
|
content;
|
|
@@ -1514,17 +1950,18 @@ var PerspectApiClient = class {
|
|
|
1514
1950
|
throw new Error("baseUrl is required in PerspectApiConfig");
|
|
1515
1951
|
}
|
|
1516
1952
|
this.http = new HttpClient(config);
|
|
1517
|
-
this.
|
|
1518
|
-
this.
|
|
1519
|
-
this.
|
|
1520
|
-
this.
|
|
1521
|
-
this.
|
|
1522
|
-
this.
|
|
1523
|
-
this.
|
|
1524
|
-
this.
|
|
1525
|
-
this.
|
|
1526
|
-
this.
|
|
1527
|
-
this.
|
|
1953
|
+
this.cache = new CacheManager(config.cache);
|
|
1954
|
+
this.auth = new AuthClient(this.http, this.cache);
|
|
1955
|
+
this.content = new ContentClient(this.http, this.cache);
|
|
1956
|
+
this.apiKeys = new ApiKeysClient(this.http, this.cache);
|
|
1957
|
+
this.organizations = new OrganizationsClient(this.http, this.cache);
|
|
1958
|
+
this.sites = new SitesClient(this.http, this.cache);
|
|
1959
|
+
this.products = new ProductsClient(this.http, this.cache);
|
|
1960
|
+
this.categories = new CategoriesClient(this.http, this.cache);
|
|
1961
|
+
this.webhooks = new WebhooksClient(this.http, this.cache);
|
|
1962
|
+
this.checkout = new CheckoutClient(this.http, this.cache);
|
|
1963
|
+
this.contact = new ContactClient(this.http, this.cache);
|
|
1964
|
+
this.newsletter = new NewsletterClient(this.http, this.cache);
|
|
1528
1965
|
}
|
|
1529
1966
|
/**
|
|
1530
1967
|
* Update authentication token
|
|
@@ -2105,13 +2542,16 @@ async function createCheckoutSession(options) {
|
|
|
2105
2542
|
ApiKeysClient,
|
|
2106
2543
|
AuthClient,
|
|
2107
2544
|
BaseClient,
|
|
2545
|
+
CacheManager,
|
|
2108
2546
|
CategoriesClient,
|
|
2109
2547
|
CheckoutClient,
|
|
2110
2548
|
ContactClient,
|
|
2111
2549
|
ContentClient,
|
|
2112
2550
|
DEFAULT_IMAGE_SIZES,
|
|
2113
2551
|
HttpClient,
|
|
2552
|
+
InMemoryCacheAdapter,
|
|
2114
2553
|
NewsletterClient,
|
|
2554
|
+
NoopCacheAdapter,
|
|
2115
2555
|
OrganizationsClient,
|
|
2116
2556
|
PerspectApiClient,
|
|
2117
2557
|
ProductsClient,
|