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.mjs
CHANGED
|
@@ -205,13 +205,287 @@ function createApiError(error) {
|
|
|
205
205
|
};
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// src/cache/in-memory-adapter.ts
|
|
209
|
+
var InMemoryCacheAdapter = class {
|
|
210
|
+
store = /* @__PURE__ */ new Map();
|
|
211
|
+
async get(key) {
|
|
212
|
+
const entry = this.store.get(key);
|
|
213
|
+
if (!entry) {
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
if (entry.expiresAt && entry.expiresAt <= Date.now()) {
|
|
217
|
+
this.store.delete(key);
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
return entry.value;
|
|
221
|
+
}
|
|
222
|
+
async set(key, value, options) {
|
|
223
|
+
const expiresAt = options?.ttlSeconds && options.ttlSeconds > 0 ? Date.now() + options.ttlSeconds * 1e3 : void 0;
|
|
224
|
+
this.store.set(key, { value, expiresAt });
|
|
225
|
+
}
|
|
226
|
+
async delete(key) {
|
|
227
|
+
this.store.delete(key);
|
|
228
|
+
}
|
|
229
|
+
async deleteMany(keys) {
|
|
230
|
+
keys.forEach((key) => this.store.delete(key));
|
|
231
|
+
}
|
|
232
|
+
async clear() {
|
|
233
|
+
this.store.clear();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/cache/noop-adapter.ts
|
|
238
|
+
var NoopCacheAdapter = class {
|
|
239
|
+
async get() {
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
async set() {
|
|
243
|
+
}
|
|
244
|
+
async delete() {
|
|
245
|
+
}
|
|
246
|
+
async deleteMany() {
|
|
247
|
+
}
|
|
248
|
+
async clear() {
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/cache/cache-manager.ts
|
|
253
|
+
var TAG_PREFIX = "__tag__";
|
|
254
|
+
var CacheManager = class {
|
|
255
|
+
adapter;
|
|
256
|
+
defaultTtlSeconds;
|
|
257
|
+
keyPrefix;
|
|
258
|
+
enabled;
|
|
259
|
+
constructor(config) {
|
|
260
|
+
const defaultOptions = {
|
|
261
|
+
defaultTtlSeconds: 300,
|
|
262
|
+
keyPrefix: "perspectapi"
|
|
263
|
+
};
|
|
264
|
+
const mergedConfig = {
|
|
265
|
+
...defaultOptions,
|
|
266
|
+
...config
|
|
267
|
+
};
|
|
268
|
+
if (config && config.enabled === false) {
|
|
269
|
+
this.enabled = false;
|
|
270
|
+
this.adapter = new NoopCacheAdapter();
|
|
271
|
+
} else if (config && config.adapter) {
|
|
272
|
+
this.enabled = true;
|
|
273
|
+
this.adapter = config.adapter;
|
|
274
|
+
} else if (config) {
|
|
275
|
+
this.enabled = true;
|
|
276
|
+
this.adapter = new InMemoryCacheAdapter();
|
|
277
|
+
} else {
|
|
278
|
+
this.enabled = false;
|
|
279
|
+
this.adapter = new NoopCacheAdapter();
|
|
280
|
+
}
|
|
281
|
+
this.defaultTtlSeconds = mergedConfig.defaultTtlSeconds ?? 300;
|
|
282
|
+
this.keyPrefix = mergedConfig.keyPrefix ?? "perspectapi";
|
|
283
|
+
}
|
|
284
|
+
isEnabled() {
|
|
285
|
+
return this.enabled;
|
|
286
|
+
}
|
|
287
|
+
getKeyPrefix() {
|
|
288
|
+
return this.keyPrefix;
|
|
289
|
+
}
|
|
290
|
+
buildKey(parts) {
|
|
291
|
+
const normalized = parts.flatMap((part) => this.normalizeKeyPart(part)).filter((part) => part !== void 0 && part !== null && part !== "");
|
|
292
|
+
return normalized.join(":");
|
|
293
|
+
}
|
|
294
|
+
async getOrSet(key, resolveValue, policy) {
|
|
295
|
+
if (!this.enabled || policy?.skipCache) {
|
|
296
|
+
console.log("[Cache] Cache disabled or skipped", { key, enabled: this.enabled, skipCache: policy?.skipCache });
|
|
297
|
+
const value2 = await resolveValue();
|
|
298
|
+
if (this.enabled && !policy?.skipCache && policy?.ttlSeconds !== 0) {
|
|
299
|
+
await this.set(key, value2, policy);
|
|
300
|
+
}
|
|
301
|
+
return value2;
|
|
302
|
+
}
|
|
303
|
+
const namespacedKey = this.namespacedKey(key);
|
|
304
|
+
const cachedRaw = await this.adapter.get(namespacedKey);
|
|
305
|
+
if (cachedRaw) {
|
|
306
|
+
const entry = this.deserialize(cachedRaw);
|
|
307
|
+
if (!entry.expiresAt || entry.expiresAt > Date.now()) {
|
|
308
|
+
console.log("[Cache] \u2713 HIT", { key, tags: entry.tags });
|
|
309
|
+
return entry.value;
|
|
310
|
+
}
|
|
311
|
+
console.log("[Cache] \u2717 EXPIRED", { key, expiresAt: new Date(entry.expiresAt) });
|
|
312
|
+
await this.adapter.delete(namespacedKey);
|
|
313
|
+
if (entry.tags?.length) {
|
|
314
|
+
await this.removeKeyFromTags(namespacedKey, entry.tags);
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
console.log("[Cache] \u2717 MISS", { key });
|
|
318
|
+
}
|
|
319
|
+
const value = await resolveValue();
|
|
320
|
+
await this.set(key, value, policy);
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
async set(key, value, options) {
|
|
324
|
+
if (!this.enabled || options?.ttlSeconds === 0) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const namespacedKey = this.namespacedKey(key);
|
|
328
|
+
const ttlSeconds = options?.ttlSeconds ?? this.defaultTtlSeconds;
|
|
329
|
+
const entry = {
|
|
330
|
+
value,
|
|
331
|
+
expiresAt: ttlSeconds > 0 ? Date.now() + ttlSeconds * 1e3 : void 0,
|
|
332
|
+
tags: options?.tags,
|
|
333
|
+
metadata: options?.metadata
|
|
334
|
+
};
|
|
335
|
+
console.log("[Cache] SET", { key, ttlSeconds, tags: options?.tags });
|
|
336
|
+
await this.adapter.set(namespacedKey, this.serialize(entry), {
|
|
337
|
+
ttlSeconds: ttlSeconds > 0 ? ttlSeconds : void 0
|
|
338
|
+
});
|
|
339
|
+
if (options?.tags?.length) {
|
|
340
|
+
await this.registerKeyTags(namespacedKey, options.tags);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async delete(key) {
|
|
344
|
+
if (!this.enabled) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const namespacedKey = this.namespacedKey(key);
|
|
348
|
+
await this.adapter.delete(namespacedKey);
|
|
349
|
+
}
|
|
350
|
+
async invalidate(options) {
|
|
351
|
+
if (!this.enabled) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
let totalInvalidated = 0;
|
|
355
|
+
if (options.keys?.length) {
|
|
356
|
+
const namespacedKeys = options.keys.map((key) => this.namespacedKey(key));
|
|
357
|
+
console.log("[Cache] INVALIDATE by keys", { count: options.keys.length, keys: options.keys });
|
|
358
|
+
if (this.adapter.deleteMany) {
|
|
359
|
+
await this.adapter.deleteMany(namespacedKeys);
|
|
360
|
+
} else {
|
|
361
|
+
await Promise.all(namespacedKeys.map((key) => this.adapter.delete(key)));
|
|
362
|
+
}
|
|
363
|
+
totalInvalidated += options.keys.length;
|
|
364
|
+
}
|
|
365
|
+
if (options.tags?.length) {
|
|
366
|
+
console.log("[Cache] INVALIDATE by tags", { tags: options.tags });
|
|
367
|
+
await Promise.all(
|
|
368
|
+
options.tags.map(async (tag) => {
|
|
369
|
+
const tagKey = this.tagKey(tag);
|
|
370
|
+
const payload = await this.adapter.get(tagKey);
|
|
371
|
+
if (!payload) {
|
|
372
|
+
console.log("[Cache] No entries for tag", { tag });
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const keys = this.deserializeTagSet(payload);
|
|
376
|
+
if (keys.length) {
|
|
377
|
+
console.log("[Cache] Invalidating entries for tag", { tag, count: keys.length });
|
|
378
|
+
if (this.adapter.deleteMany) {
|
|
379
|
+
await this.adapter.deleteMany(keys);
|
|
380
|
+
} else {
|
|
381
|
+
await Promise.all(keys.map((key) => this.adapter.delete(key)));
|
|
382
|
+
}
|
|
383
|
+
totalInvalidated += keys.length;
|
|
384
|
+
}
|
|
385
|
+
await this.adapter.delete(tagKey);
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
console.log("[Cache] \u2713 INVALIDATED", { totalEntries: totalInvalidated, keys: options.keys?.length || 0, tags: options.tags?.length || 0 });
|
|
390
|
+
}
|
|
391
|
+
namespacedKey(key) {
|
|
392
|
+
return `${this.keyPrefix}:${key}`;
|
|
393
|
+
}
|
|
394
|
+
tagKey(tag) {
|
|
395
|
+
return this.namespacedKey(`${TAG_PREFIX}:${tag}`);
|
|
396
|
+
}
|
|
397
|
+
serialize(entry) {
|
|
398
|
+
return JSON.stringify(entry);
|
|
399
|
+
}
|
|
400
|
+
deserialize(payload) {
|
|
401
|
+
try {
|
|
402
|
+
return JSON.parse(payload);
|
|
403
|
+
} catch {
|
|
404
|
+
return { value: payload };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
deserializeTagSet(payload) {
|
|
408
|
+
try {
|
|
409
|
+
const parsed = JSON.parse(payload);
|
|
410
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
411
|
+
} catch {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async registerKeyTags(namespacedKey, tags) {
|
|
416
|
+
await Promise.all(
|
|
417
|
+
tags.map(async (tag) => {
|
|
418
|
+
const tagKey = this.tagKey(tag);
|
|
419
|
+
const existingPayload = await this.adapter.get(tagKey);
|
|
420
|
+
const keys = existingPayload ? this.deserializeTagSet(existingPayload) : [];
|
|
421
|
+
if (!keys.includes(namespacedKey)) {
|
|
422
|
+
keys.push(namespacedKey);
|
|
423
|
+
await this.adapter.set(tagKey, JSON.stringify(keys));
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
async removeKeyFromTags(namespacedKey, tags) {
|
|
429
|
+
await Promise.all(
|
|
430
|
+
tags.map(async (tag) => {
|
|
431
|
+
const tagKey = this.tagKey(tag);
|
|
432
|
+
const existingPayload = await this.adapter.get(tagKey);
|
|
433
|
+
if (!existingPayload) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const keys = this.deserializeTagSet(existingPayload);
|
|
437
|
+
const updated = keys.filter((key) => key !== namespacedKey);
|
|
438
|
+
if (updated.length === 0) {
|
|
439
|
+
await this.adapter.delete(tagKey);
|
|
440
|
+
} else if (updated.length !== keys.length) {
|
|
441
|
+
await this.adapter.set(tagKey, JSON.stringify(updated));
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
normalizeKeyPart(part) {
|
|
447
|
+
if (part === void 0 || part === null || part === "") {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
if (Array.isArray(part)) {
|
|
451
|
+
return part.flatMap((item) => this.normalizeKeyPart(item));
|
|
452
|
+
}
|
|
453
|
+
if (typeof part === "object") {
|
|
454
|
+
return [this.normalizeObject(part)];
|
|
455
|
+
}
|
|
456
|
+
return [String(part)];
|
|
457
|
+
}
|
|
458
|
+
normalizeObject(input) {
|
|
459
|
+
const sortedKeys = Object.keys(input).sort();
|
|
460
|
+
const normalized = {};
|
|
461
|
+
for (const key of sortedKeys) {
|
|
462
|
+
const value = input[key];
|
|
463
|
+
if (value === void 0) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
467
|
+
normalized[key] = JSON.parse(this.normalizeObject(value));
|
|
468
|
+
} else if (Array.isArray(value)) {
|
|
469
|
+
normalized[key] = value.map(
|
|
470
|
+
(item) => typeof item === "object" && item !== null ? JSON.parse(this.normalizeObject(item)) : item
|
|
471
|
+
);
|
|
472
|
+
} else {
|
|
473
|
+
normalized[key] = value;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return JSON.stringify(normalized);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
208
480
|
// src/client/base-client.ts
|
|
209
481
|
var BaseClient = class {
|
|
210
482
|
http;
|
|
211
483
|
basePath;
|
|
212
|
-
|
|
484
|
+
cache;
|
|
485
|
+
constructor(http, basePath, cache) {
|
|
213
486
|
this.http = http;
|
|
214
487
|
this.basePath = basePath;
|
|
488
|
+
this.cache = cache && cache.isEnabled() ? cache : void 0;
|
|
215
489
|
}
|
|
216
490
|
/**
|
|
217
491
|
* Build a site-scoped endpoint relative to the API base path
|
|
@@ -271,12 +545,71 @@ var BaseClient = class {
|
|
|
271
545
|
async delete(endpoint, csrfToken) {
|
|
272
546
|
return this.http.delete(this.buildPath(endpoint), { csrfToken });
|
|
273
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Fetch a GET endpoint with optional caching support.
|
|
550
|
+
*/
|
|
551
|
+
async fetchWithCache(endpoint, params, tags, policy, fetcher) {
|
|
552
|
+
if (!this.cache) {
|
|
553
|
+
return fetcher();
|
|
554
|
+
}
|
|
555
|
+
const cacheKey = this.buildCacheKey(endpoint, params);
|
|
556
|
+
const combinedPolicy = policy ? { ...policy, tags: this.mergeTags(tags, policy.tags) } : { tags };
|
|
557
|
+
return this.cache.getOrSet(cacheKey, fetcher, combinedPolicy);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Invalidate cache entries by keys or tags.
|
|
561
|
+
*/
|
|
562
|
+
async invalidateCache(options) {
|
|
563
|
+
if (!this.cache) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
await this.cache.invalidate(options);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Build a consistent cache key for an endpoint + params combination.
|
|
570
|
+
*/
|
|
571
|
+
buildCacheKey(endpoint, params) {
|
|
572
|
+
const sanitizedEndpoint = endpoint.replace(/^\//, "");
|
|
573
|
+
const baseSegment = this.basePath.replace(/^\//, "");
|
|
574
|
+
const parts = [baseSegment, sanitizedEndpoint];
|
|
575
|
+
if (params && Object.keys(params).length > 0) {
|
|
576
|
+
parts.push(this.sortObject(params));
|
|
577
|
+
}
|
|
578
|
+
if (this.cache) {
|
|
579
|
+
return this.cache.buildKey(parts);
|
|
580
|
+
}
|
|
581
|
+
return parts.map((part) => typeof part === "string" ? part : JSON.stringify(part)).join(":");
|
|
582
|
+
}
|
|
583
|
+
mergeTags(defaultTags, overrideTags) {
|
|
584
|
+
const combined = /* @__PURE__ */ new Set();
|
|
585
|
+
defaultTags.forEach((tag) => combined.add(tag));
|
|
586
|
+
overrideTags?.forEach((tag) => combined.add(tag));
|
|
587
|
+
return Array.from(combined.values());
|
|
588
|
+
}
|
|
589
|
+
sortObject(input) {
|
|
590
|
+
const sortedKeys = Object.keys(input).sort();
|
|
591
|
+
const result = {};
|
|
592
|
+
for (const key of sortedKeys) {
|
|
593
|
+
const value = input[key];
|
|
594
|
+
if (value === void 0) continue;
|
|
595
|
+
if (Array.isArray(value)) {
|
|
596
|
+
result[key] = value.map(
|
|
597
|
+
(item) => typeof item === "object" && item !== null ? this.sortObject(item) : item
|
|
598
|
+
);
|
|
599
|
+
} else if (value && typeof value === "object") {
|
|
600
|
+
result[key] = this.sortObject(value);
|
|
601
|
+
} else {
|
|
602
|
+
result[key] = value;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return result;
|
|
606
|
+
}
|
|
274
607
|
};
|
|
275
608
|
|
|
276
609
|
// src/client/auth-client.ts
|
|
277
610
|
var AuthClient = class extends BaseClient {
|
|
278
|
-
constructor(http) {
|
|
279
|
-
super(http, "/api/v1");
|
|
611
|
+
constructor(http, cache) {
|
|
612
|
+
super(http, "/api/v1", cache);
|
|
280
613
|
}
|
|
281
614
|
/**
|
|
282
615
|
* Get CSRF token
|
|
@@ -337,28 +670,50 @@ var AuthClient = class extends BaseClient {
|
|
|
337
670
|
|
|
338
671
|
// src/client/content-client.ts
|
|
339
672
|
var ContentClient = class extends BaseClient {
|
|
340
|
-
constructor(http) {
|
|
341
|
-
super(http, "/api/v1");
|
|
673
|
+
constructor(http, cache) {
|
|
674
|
+
super(http, "/api/v1", cache);
|
|
342
675
|
}
|
|
343
676
|
/**
|
|
344
677
|
* Get all content with pagination and filtering for a site
|
|
345
678
|
*/
|
|
346
|
-
async getContent(siteName, params) {
|
|
347
|
-
|
|
679
|
+
async getContent(siteName, params, cachePolicy) {
|
|
680
|
+
const endpoint = this.siteScopedEndpoint(siteName);
|
|
681
|
+
const path = this.buildPath(endpoint);
|
|
682
|
+
return this.fetchWithCache(
|
|
683
|
+
endpoint,
|
|
684
|
+
params,
|
|
685
|
+
this.buildContentTags(siteName),
|
|
686
|
+
cachePolicy,
|
|
687
|
+
() => this.http.get(path, params)
|
|
688
|
+
);
|
|
348
689
|
}
|
|
349
690
|
/**
|
|
350
691
|
* Get content by ID
|
|
351
692
|
*/
|
|
352
|
-
async getContentById(id) {
|
|
353
|
-
|
|
693
|
+
async getContentById(id, cachePolicy) {
|
|
694
|
+
const endpoint = `/content/${id}`;
|
|
695
|
+
const path = this.buildPath(endpoint);
|
|
696
|
+
return this.fetchWithCache(
|
|
697
|
+
endpoint,
|
|
698
|
+
void 0,
|
|
699
|
+
this.buildContentTags(void 0, void 0, id),
|
|
700
|
+
cachePolicy,
|
|
701
|
+
() => this.http.get(path)
|
|
702
|
+
);
|
|
354
703
|
}
|
|
355
704
|
/**
|
|
356
705
|
* Get content by slug for a site
|
|
357
706
|
*/
|
|
358
|
-
async getContentBySlug(siteName, slug) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
707
|
+
async getContentBySlug(siteName, slug, cachePolicy) {
|
|
708
|
+
const endpoint = this.siteScopedEndpoint(siteName, `/slug/${encodeURIComponent(slug)}`);
|
|
709
|
+
const path = this.buildPath(endpoint);
|
|
710
|
+
return this.fetchWithCache(
|
|
711
|
+
endpoint,
|
|
712
|
+
void 0,
|
|
713
|
+
this.buildContentTags(siteName, slug),
|
|
714
|
+
cachePolicy,
|
|
715
|
+
() => this.http.get(path)
|
|
716
|
+
);
|
|
362
717
|
}
|
|
363
718
|
/**
|
|
364
719
|
* Create new content
|
|
@@ -420,12 +775,25 @@ var ContentClient = class extends BaseClient {
|
|
|
420
775
|
async duplicateContent(id) {
|
|
421
776
|
return this.create(`/content/${id}/duplicate`, {});
|
|
422
777
|
}
|
|
778
|
+
buildContentTags(siteName, slug, id) {
|
|
779
|
+
const tags = /* @__PURE__ */ new Set(["content"]);
|
|
780
|
+
if (siteName) {
|
|
781
|
+
tags.add(`content:site:${siteName}`);
|
|
782
|
+
}
|
|
783
|
+
if (slug) {
|
|
784
|
+
tags.add(`content:slug:${siteName}:${slug}`);
|
|
785
|
+
}
|
|
786
|
+
if (typeof id === "number") {
|
|
787
|
+
tags.add(`content:id:${id}`);
|
|
788
|
+
}
|
|
789
|
+
return Array.from(tags.values());
|
|
790
|
+
}
|
|
423
791
|
};
|
|
424
792
|
|
|
425
793
|
// src/client/api-keys-client.ts
|
|
426
794
|
var ApiKeysClient = class extends BaseClient {
|
|
427
|
-
constructor(http) {
|
|
428
|
-
super(http, "/api/v1");
|
|
795
|
+
constructor(http, cache) {
|
|
796
|
+
super(http, "/api/v1", cache);
|
|
429
797
|
}
|
|
430
798
|
/**
|
|
431
799
|
* Get all API keys
|
|
@@ -491,8 +859,8 @@ var ApiKeysClient = class extends BaseClient {
|
|
|
491
859
|
|
|
492
860
|
// src/client/organizations-client.ts
|
|
493
861
|
var OrganizationsClient = class extends BaseClient {
|
|
494
|
-
constructor(http) {
|
|
495
|
-
super(http, "/api/v1");
|
|
862
|
+
constructor(http, cache) {
|
|
863
|
+
super(http, "/api/v1", cache);
|
|
496
864
|
}
|
|
497
865
|
/**
|
|
498
866
|
* Get all organizations
|
|
@@ -564,8 +932,8 @@ var OrganizationsClient = class extends BaseClient {
|
|
|
564
932
|
|
|
565
933
|
// src/client/sites-client.ts
|
|
566
934
|
var SitesClient = class extends BaseClient {
|
|
567
|
-
constructor(http) {
|
|
568
|
-
super(http, "/api/v1");
|
|
935
|
+
constructor(http, cache) {
|
|
936
|
+
super(http, "/api/v1", cache);
|
|
569
937
|
}
|
|
570
938
|
/**
|
|
571
939
|
* Get all sites
|
|
@@ -661,13 +1029,13 @@ var SitesClient = class extends BaseClient {
|
|
|
661
1029
|
|
|
662
1030
|
// src/client/products-client.ts
|
|
663
1031
|
var ProductsClient = class extends BaseClient {
|
|
664
|
-
constructor(http) {
|
|
665
|
-
super(http, "/api/v1");
|
|
1032
|
+
constructor(http, cache) {
|
|
1033
|
+
super(http, "/api/v1", cache);
|
|
666
1034
|
}
|
|
667
1035
|
/**
|
|
668
1036
|
* Get all products for a site
|
|
669
1037
|
*/
|
|
670
|
-
async getProducts(siteName, params) {
|
|
1038
|
+
async getProducts(siteName, params, cachePolicy) {
|
|
671
1039
|
const normalizeList = (value) => {
|
|
672
1040
|
if (value === void 0 || value === null) {
|
|
673
1041
|
return void 0;
|
|
@@ -691,30 +1059,61 @@ var ProductsClient = class extends BaseClient {
|
|
|
691
1059
|
delete normalizedParams.category_id;
|
|
692
1060
|
}
|
|
693
1061
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1062
|
+
const endpoint = this.siteScopedEndpoint(siteName, "/products", { includeSitesSegment: false });
|
|
1063
|
+
const path = this.buildPath(endpoint);
|
|
1064
|
+
return this.fetchWithCache(
|
|
1065
|
+
endpoint,
|
|
1066
|
+
normalizedParams,
|
|
1067
|
+
this.buildProductTags(siteName, ["products:list"]),
|
|
1068
|
+
cachePolicy,
|
|
1069
|
+
() => this.http.get(path, normalizedParams)
|
|
697
1070
|
);
|
|
698
1071
|
}
|
|
699
1072
|
/**
|
|
700
1073
|
* Get product by ID
|
|
701
1074
|
*/
|
|
702
|
-
async getProductById(id) {
|
|
703
|
-
|
|
1075
|
+
async getProductById(id, cachePolicy) {
|
|
1076
|
+
const endpoint = `/products/${id}`;
|
|
1077
|
+
const path = this.buildPath(endpoint);
|
|
1078
|
+
return this.fetchWithCache(
|
|
1079
|
+
endpoint,
|
|
1080
|
+
void 0,
|
|
1081
|
+
this.buildProductTags(void 0, [`products:id:${id}`]),
|
|
1082
|
+
cachePolicy,
|
|
1083
|
+
() => this.http.get(path)
|
|
1084
|
+
);
|
|
704
1085
|
}
|
|
705
1086
|
/**
|
|
706
1087
|
* Get product by SKU
|
|
707
1088
|
*/
|
|
708
|
-
async getProductBySku(sku) {
|
|
709
|
-
|
|
1089
|
+
async getProductBySku(sku, cachePolicy) {
|
|
1090
|
+
const endpoint = `/products/sku/${encodeURIComponent(sku)}`;
|
|
1091
|
+
const path = this.buildPath(endpoint);
|
|
1092
|
+
return this.fetchWithCache(
|
|
1093
|
+
endpoint,
|
|
1094
|
+
void 0,
|
|
1095
|
+
this.buildProductTags(void 0, [`products:sku:${sku.toLowerCase()}`]),
|
|
1096
|
+
cachePolicy,
|
|
1097
|
+
() => this.http.get(path)
|
|
1098
|
+
);
|
|
710
1099
|
}
|
|
711
1100
|
/**
|
|
712
1101
|
* Get product by slug and site name
|
|
713
1102
|
*/
|
|
714
|
-
async getProductBySlug(siteName, slug) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1103
|
+
async getProductBySlug(siteName, slug, cachePolicy) {
|
|
1104
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1105
|
+
siteName,
|
|
1106
|
+
`/products/slug/${encodeURIComponent(slug)}`,
|
|
1107
|
+
{ includeSitesSegment: false }
|
|
1108
|
+
);
|
|
1109
|
+
const path = this.buildPath(endpoint);
|
|
1110
|
+
return this.fetchWithCache(
|
|
1111
|
+
endpoint,
|
|
1112
|
+
void 0,
|
|
1113
|
+
this.buildProductTags(siteName, [`products:slug:${siteName}:${slug}`]),
|
|
1114
|
+
cachePolicy,
|
|
1115
|
+
() => this.http.get(path)
|
|
1116
|
+
);
|
|
718
1117
|
}
|
|
719
1118
|
/**
|
|
720
1119
|
* Create new product
|
|
@@ -803,27 +1202,44 @@ var ProductsClient = class extends BaseClient {
|
|
|
803
1202
|
/**
|
|
804
1203
|
* Get products by category slug
|
|
805
1204
|
*/
|
|
806
|
-
async getProductsByCategorySlug(siteName, categorySlug, params) {
|
|
1205
|
+
async getProductsByCategorySlug(siteName, categorySlug, params, cachePolicy) {
|
|
807
1206
|
const queryParams = params ? {
|
|
808
1207
|
limit: params.limit,
|
|
809
1208
|
offset: params.page ? (params.page - 1) * (params.limit || 20) : void 0,
|
|
810
1209
|
published: params.published,
|
|
811
1210
|
search: params.search
|
|
812
1211
|
} : void 0;
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1212
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1213
|
+
siteName,
|
|
1214
|
+
`/products/category/${encodeURIComponent(categorySlug)}`,
|
|
1215
|
+
{ includeSitesSegment: false }
|
|
1216
|
+
);
|
|
1217
|
+
const path = this.buildPath(endpoint);
|
|
1218
|
+
return this.fetchWithCache(
|
|
1219
|
+
endpoint,
|
|
1220
|
+
queryParams,
|
|
1221
|
+
this.buildProductTags(siteName, [
|
|
1222
|
+
"products:category",
|
|
1223
|
+
`products:category:${siteName}:${categorySlug}`
|
|
1224
|
+
]),
|
|
1225
|
+
cachePolicy,
|
|
1226
|
+
() => this.http.get(path, queryParams)
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
buildProductTags(siteName, extraTags = []) {
|
|
1230
|
+
const tags = /* @__PURE__ */ new Set(["products"]);
|
|
1231
|
+
if (siteName) {
|
|
1232
|
+
tags.add(`products:site:${siteName}`);
|
|
1233
|
+
}
|
|
1234
|
+
extraTags.filter(Boolean).forEach((tag) => tags.add(tag));
|
|
1235
|
+
return Array.from(tags.values());
|
|
820
1236
|
}
|
|
821
1237
|
};
|
|
822
1238
|
|
|
823
1239
|
// src/client/categories-client.ts
|
|
824
1240
|
var CategoriesClient = class extends BaseClient {
|
|
825
|
-
constructor(http) {
|
|
826
|
-
super(http, "/api/v1");
|
|
1241
|
+
constructor(http, cache) {
|
|
1242
|
+
super(http, "/api/v1", cache);
|
|
827
1243
|
}
|
|
828
1244
|
/**
|
|
829
1245
|
* Get all categories
|
|
@@ -846,14 +1262,20 @@ var CategoriesClient = class extends BaseClient {
|
|
|
846
1262
|
/**
|
|
847
1263
|
* Get product category by slug (for products)
|
|
848
1264
|
*/
|
|
849
|
-
async getProductCategoryBySlug(siteName, slug) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1265
|
+
async getProductCategoryBySlug(siteName, slug, cachePolicy) {
|
|
1266
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1267
|
+
siteName,
|
|
1268
|
+
`/product_category/slug/${encodeURIComponent(slug)}`,
|
|
1269
|
+
{ includeSitesSegment: false }
|
|
1270
|
+
);
|
|
1271
|
+
const path = this.buildPath(endpoint);
|
|
1272
|
+
return this.fetchWithCache(
|
|
1273
|
+
endpoint,
|
|
1274
|
+
void 0,
|
|
1275
|
+
this.buildCategoryTags(siteName, slug),
|
|
1276
|
+
cachePolicy,
|
|
1277
|
+
() => this.http.get(path)
|
|
1278
|
+
);
|
|
857
1279
|
}
|
|
858
1280
|
/**
|
|
859
1281
|
* Create new category
|
|
@@ -922,12 +1344,22 @@ var CategoriesClient = class extends BaseClient {
|
|
|
922
1344
|
async searchCategories(query, params) {
|
|
923
1345
|
return this.http.get(`/categories/search`, { q: query, ...params });
|
|
924
1346
|
}
|
|
1347
|
+
buildCategoryTags(siteName, slug) {
|
|
1348
|
+
const tags = /* @__PURE__ */ new Set(["categories"]);
|
|
1349
|
+
if (siteName) {
|
|
1350
|
+
tags.add(`categories:site:${siteName}`);
|
|
1351
|
+
}
|
|
1352
|
+
if (slug) {
|
|
1353
|
+
tags.add(`categories:product:${siteName}:${slug}`);
|
|
1354
|
+
}
|
|
1355
|
+
return Array.from(tags.values());
|
|
1356
|
+
}
|
|
925
1357
|
};
|
|
926
1358
|
|
|
927
1359
|
// src/client/webhooks-client.ts
|
|
928
1360
|
var WebhooksClient = class extends BaseClient {
|
|
929
|
-
constructor(http) {
|
|
930
|
-
super(http, "/api/v1");
|
|
1361
|
+
constructor(http, cache) {
|
|
1362
|
+
super(http, "/api/v1", cache);
|
|
931
1363
|
}
|
|
932
1364
|
/**
|
|
933
1365
|
* Get all webhooks
|
|
@@ -1024,8 +1456,8 @@ var WebhooksClient = class extends BaseClient {
|
|
|
1024
1456
|
|
|
1025
1457
|
// src/client/checkout-client.ts
|
|
1026
1458
|
var CheckoutClient = class extends BaseClient {
|
|
1027
|
-
constructor(http) {
|
|
1028
|
-
super(http, "/api/v1");
|
|
1459
|
+
constructor(http, cache) {
|
|
1460
|
+
super(http, "/api/v1", cache);
|
|
1029
1461
|
}
|
|
1030
1462
|
/**
|
|
1031
1463
|
* Get CSRF token for a specific site
|
|
@@ -1136,8 +1568,8 @@ var CheckoutClient = class extends BaseClient {
|
|
|
1136
1568
|
|
|
1137
1569
|
// src/client/contact-client.ts
|
|
1138
1570
|
var ContactClient = class extends BaseClient {
|
|
1139
|
-
constructor(http) {
|
|
1140
|
-
super(http, "/api/v1");
|
|
1571
|
+
constructor(http, cache) {
|
|
1572
|
+
super(http, "/api/v1", cache);
|
|
1141
1573
|
}
|
|
1142
1574
|
/**
|
|
1143
1575
|
* Build a contact endpoint scoped to a site (without /sites prefix)
|
|
@@ -1243,8 +1675,8 @@ var ContactClient = class extends BaseClient {
|
|
|
1243
1675
|
|
|
1244
1676
|
// src/client/newsletter-client.ts
|
|
1245
1677
|
var NewsletterClient = class extends BaseClient {
|
|
1246
|
-
constructor(http) {
|
|
1247
|
-
super(http, "/api/v1");
|
|
1678
|
+
constructor(http, cache) {
|
|
1679
|
+
super(http, "/api/v1", cache);
|
|
1248
1680
|
}
|
|
1249
1681
|
/**
|
|
1250
1682
|
* Build a newsletter endpoint scoped to a site (without /sites prefix)
|
|
@@ -1439,6 +1871,7 @@ var NewsletterClient = class extends BaseClient {
|
|
|
1439
1871
|
// src/perspect-api-client.ts
|
|
1440
1872
|
var PerspectApiClient = class {
|
|
1441
1873
|
http;
|
|
1874
|
+
cache;
|
|
1442
1875
|
// Service clients
|
|
1443
1876
|
auth;
|
|
1444
1877
|
content;
|
|
@@ -1456,17 +1889,18 @@ var PerspectApiClient = class {
|
|
|
1456
1889
|
throw new Error("baseUrl is required in PerspectApiConfig");
|
|
1457
1890
|
}
|
|
1458
1891
|
this.http = new HttpClient(config);
|
|
1459
|
-
this.
|
|
1460
|
-
this.
|
|
1461
|
-
this.
|
|
1462
|
-
this.
|
|
1463
|
-
this.
|
|
1464
|
-
this.
|
|
1465
|
-
this.
|
|
1466
|
-
this.
|
|
1467
|
-
this.
|
|
1468
|
-
this.
|
|
1469
|
-
this.
|
|
1892
|
+
this.cache = new CacheManager(config.cache);
|
|
1893
|
+
this.auth = new AuthClient(this.http, this.cache);
|
|
1894
|
+
this.content = new ContentClient(this.http, this.cache);
|
|
1895
|
+
this.apiKeys = new ApiKeysClient(this.http, this.cache);
|
|
1896
|
+
this.organizations = new OrganizationsClient(this.http, this.cache);
|
|
1897
|
+
this.sites = new SitesClient(this.http, this.cache);
|
|
1898
|
+
this.products = new ProductsClient(this.http, this.cache);
|
|
1899
|
+
this.categories = new CategoriesClient(this.http, this.cache);
|
|
1900
|
+
this.webhooks = new WebhooksClient(this.http, this.cache);
|
|
1901
|
+
this.checkout = new CheckoutClient(this.http, this.cache);
|
|
1902
|
+
this.contact = new ContactClient(this.http, this.cache);
|
|
1903
|
+
this.newsletter = new NewsletterClient(this.http, this.cache);
|
|
1470
1904
|
}
|
|
1471
1905
|
/**
|
|
1472
1906
|
* Update authentication token
|
|
@@ -2046,13 +2480,16 @@ export {
|
|
|
2046
2480
|
ApiKeysClient,
|
|
2047
2481
|
AuthClient,
|
|
2048
2482
|
BaseClient,
|
|
2483
|
+
CacheManager,
|
|
2049
2484
|
CategoriesClient,
|
|
2050
2485
|
CheckoutClient,
|
|
2051
2486
|
ContactClient,
|
|
2052
2487
|
ContentClient,
|
|
2053
2488
|
DEFAULT_IMAGE_SIZES,
|
|
2054
2489
|
HttpClient,
|
|
2490
|
+
InMemoryCacheAdapter,
|
|
2055
2491
|
NewsletterClient,
|
|
2492
|
+
NoopCacheAdapter,
|
|
2056
2493
|
OrganizationsClient,
|
|
2057
2494
|
PerspectApiClient,
|
|
2058
2495
|
ProductsClient,
|