@nitida/asset-client 0.24.0 → 0.24.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/README.md +5 -4
- package/dist/index.cjs +30 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +30 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/slots.ts +56 -13
- package/src/transform.ts +11 -0
package/dist/index.d.cts
CHANGED
|
@@ -689,6 +689,12 @@ type SlotResolution = {
|
|
|
689
689
|
/** The CDN URL the consumer should use. */
|
|
690
690
|
url: string | null;
|
|
691
691
|
};
|
|
692
|
+
/** Per-call resolver scope. When passed, it fully overrides the process globals. */
|
|
693
|
+
type SlotResolverConfig = {
|
|
694
|
+
endpoint?: string;
|
|
695
|
+
apiKey?: string | null;
|
|
696
|
+
tenantCode?: string | null;
|
|
697
|
+
};
|
|
692
698
|
/**
|
|
693
699
|
* Configure the resolver process-wide. Call once at boot from your
|
|
694
700
|
* storefront layout / server entry / worker init.
|
|
@@ -711,6 +717,12 @@ type ResolveSlotOptions = {
|
|
|
711
717
|
preset?: VariantPreset;
|
|
712
718
|
/** TTL for the in-process cache. Default 60s. Set 0 to bypass. */
|
|
713
719
|
ttlMs?: number;
|
|
720
|
+
/**
|
|
721
|
+
* Per-client auth/endpoint scope. Omit to use the process globals
|
|
722
|
+
* (configureSlotResolver). A NitidaClient passes its own here so its
|
|
723
|
+
* resolve() never uses another client's key (audit N15).
|
|
724
|
+
*/
|
|
725
|
+
config?: SlotResolverConfig;
|
|
714
726
|
};
|
|
715
727
|
/**
|
|
716
728
|
* Resolve a single slot to a CDN URL. Returns `{slot: null, url: null}`
|
package/dist/index.d.ts
CHANGED
|
@@ -689,6 +689,12 @@ type SlotResolution = {
|
|
|
689
689
|
/** The CDN URL the consumer should use. */
|
|
690
690
|
url: string | null;
|
|
691
691
|
};
|
|
692
|
+
/** Per-call resolver scope. When passed, it fully overrides the process globals. */
|
|
693
|
+
type SlotResolverConfig = {
|
|
694
|
+
endpoint?: string;
|
|
695
|
+
apiKey?: string | null;
|
|
696
|
+
tenantCode?: string | null;
|
|
697
|
+
};
|
|
692
698
|
/**
|
|
693
699
|
* Configure the resolver process-wide. Call once at boot from your
|
|
694
700
|
* storefront layout / server entry / worker init.
|
|
@@ -711,6 +717,12 @@ type ResolveSlotOptions = {
|
|
|
711
717
|
preset?: VariantPreset;
|
|
712
718
|
/** TTL for the in-process cache. Default 60s. Set 0 to bypass. */
|
|
713
719
|
ttlMs?: number;
|
|
720
|
+
/**
|
|
721
|
+
* Per-client auth/endpoint scope. Omit to use the process globals
|
|
722
|
+
* (configureSlotResolver). A NitidaClient passes its own here so its
|
|
723
|
+
* resolve() never uses another client's key (audit N15).
|
|
724
|
+
*/
|
|
725
|
+
config?: SlotResolverConfig;
|
|
714
726
|
};
|
|
715
727
|
/**
|
|
716
728
|
* Resolve a single slot to a CDN URL. Returns `{slot: null, url: null}`
|
package/dist/index.js
CHANGED
|
@@ -219,10 +219,16 @@ function extractAssetSha(url) {
|
|
|
219
219
|
function serializeTransform(opts) {
|
|
220
220
|
const entries = [];
|
|
221
221
|
const keys = Object.keys(opts).sort();
|
|
222
|
+
const VALUE_RE = /^[a-z0-9._-]+$/;
|
|
222
223
|
for (const k of keys) {
|
|
223
224
|
const v = opts[k];
|
|
224
225
|
if (v == null) continue;
|
|
225
226
|
const serialized = typeof v === "string" ? v.toLowerCase() : String(v);
|
|
227
|
+
if (!VALUE_RE.test(serialized)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`invalid transform value for "${k}": ${JSON.stringify(serialized)} (only [a-z0-9._-] allowed)`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
226
232
|
entries.push([k, serialized]);
|
|
227
233
|
}
|
|
228
234
|
return entries.map(([k, v]) => `${k}=${v}`).join(",");
|
|
@@ -418,6 +424,14 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
418
424
|
var endpoint = "https://api.nitida.gofuture.space";
|
|
419
425
|
var apiKey = null;
|
|
420
426
|
var tenantCode = null;
|
|
427
|
+
function effectiveConfig(cfg) {
|
|
428
|
+
if (!cfg) return { endpoint, apiKey, tenantCode };
|
|
429
|
+
return {
|
|
430
|
+
endpoint: cfg.endpoint ? cfg.endpoint.replace(/\/+$/, "") : endpoint,
|
|
431
|
+
apiKey: cfg.apiKey ?? null,
|
|
432
|
+
tenantCode: cfg.tenantCode ?? null
|
|
433
|
+
};
|
|
434
|
+
}
|
|
421
435
|
function configureSlotResolver(opts) {
|
|
422
436
|
if (opts.endpoint) endpoint = opts.endpoint.replace(/\/+$/, "");
|
|
423
437
|
if (opts.apiKey !== void 0) apiKey = opts.apiKey;
|
|
@@ -429,25 +443,25 @@ function invalidateSlotCache(slotKey) {
|
|
|
429
443
|
for (const k of cache.keys())
|
|
430
444
|
if (k.endsWith(`:${slotKey}`)) cache.delete(k);
|
|
431
445
|
}
|
|
432
|
-
var baseHeaders = () => {
|
|
446
|
+
var baseHeaders = (cfg) => {
|
|
433
447
|
const h = {};
|
|
434
|
-
if (apiKey) h.Authorization = `Bearer ${apiKey}`;
|
|
435
|
-
if (tenantCode) h["X-Tenant-Code"] = tenantCode;
|
|
448
|
+
if (cfg.apiKey) h.Authorization = `Bearer ${cfg.apiKey}`;
|
|
449
|
+
if (cfg.tenantCode) h["X-Tenant-Code"] = cfg.tenantCode;
|
|
436
450
|
return h;
|
|
437
451
|
};
|
|
438
|
-
async function fetchSlot(slotKey) {
|
|
439
|
-
const r = await fetch(`${endpoint}/slots/${encodeURIComponent(slotKey)}`, {
|
|
440
|
-
headers: baseHeaders()
|
|
452
|
+
async function fetchSlot(slotKey, cfg) {
|
|
453
|
+
const r = await fetch(`${cfg.endpoint}/slots/${encodeURIComponent(slotKey)}`, {
|
|
454
|
+
headers: baseHeaders(cfg)
|
|
441
455
|
});
|
|
442
456
|
if (r.status === 404) return null;
|
|
443
457
|
if (!r.ok) throw new Error(`slot fetch ${r.status}: ${await r.text()}`);
|
|
444
458
|
return await r.json();
|
|
445
459
|
}
|
|
446
|
-
async function fetchSlotsBulk(slotKeys) {
|
|
460
|
+
async function fetchSlotsBulk(slotKeys, cfg) {
|
|
447
461
|
if (slotKeys.length === 0) return {};
|
|
448
|
-
const r = await fetch(`${endpoint}/slots/resolve`, {
|
|
462
|
+
const r = await fetch(`${cfg.endpoint}/slots/resolve`, {
|
|
449
463
|
method: "POST",
|
|
450
|
-
headers: { ...baseHeaders(), "Content-Type": "application/json" },
|
|
464
|
+
headers: { ...baseHeaders(cfg), "Content-Type": "application/json" },
|
|
451
465
|
body: JSON.stringify({ keys: slotKeys })
|
|
452
466
|
});
|
|
453
467
|
if (!r.ok) throw new Error(`slots resolve ${r.status}: ${await r.text()}`);
|
|
@@ -456,14 +470,15 @@ async function fetchSlotsBulk(slotKeys) {
|
|
|
456
470
|
}
|
|
457
471
|
async function resolveSlot(slotKey, opts = {}) {
|
|
458
472
|
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
459
|
-
const
|
|
473
|
+
const cfg = effectiveConfig(opts.config);
|
|
474
|
+
const cacheKey = `${cfg.tenantCode ?? "_"}:${slotKey}`;
|
|
460
475
|
const now = Date.now();
|
|
461
476
|
let dto;
|
|
462
477
|
const hit = cache.get(cacheKey);
|
|
463
478
|
if (hit && now - hit.fetchedAt < ttl) {
|
|
464
479
|
dto = hit.value;
|
|
465
480
|
} else {
|
|
466
|
-
dto = await fetchSlot(slotKey);
|
|
481
|
+
dto = await fetchSlot(slotKey, cfg);
|
|
467
482
|
cache.set(cacheKey, { fetchedAt: now, value: dto });
|
|
468
483
|
}
|
|
469
484
|
return materializeResolution(dto, opts.preset);
|
|
@@ -471,11 +486,12 @@ async function resolveSlot(slotKey, opts = {}) {
|
|
|
471
486
|
async function resolveSlots(slotKeys, opts = {}) {
|
|
472
487
|
if (slotKeys.length === 0) return {};
|
|
473
488
|
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
489
|
+
const cfg = effectiveConfig(opts.config);
|
|
474
490
|
const now = Date.now();
|
|
475
491
|
const missing = [];
|
|
476
492
|
const out = {};
|
|
477
493
|
for (const k of slotKeys) {
|
|
478
|
-
const cacheKey = `${tenantCode ?? "_"}:${k}`;
|
|
494
|
+
const cacheKey = `${cfg.tenantCode ?? "_"}:${k}`;
|
|
479
495
|
const hit = cache.get(cacheKey);
|
|
480
496
|
if (hit && now - hit.fetchedAt < ttl) {
|
|
481
497
|
out[k] = materializeResolution(hit.value, opts.preset);
|
|
@@ -484,10 +500,10 @@ async function resolveSlots(slotKeys, opts = {}) {
|
|
|
484
500
|
}
|
|
485
501
|
}
|
|
486
502
|
if (missing.length > 0) {
|
|
487
|
-
const resolved = await fetchSlotsBulk(missing);
|
|
503
|
+
const resolved = await fetchSlotsBulk(missing, cfg);
|
|
488
504
|
for (const k of missing) {
|
|
489
505
|
const dto = resolved[k] ?? null;
|
|
490
|
-
cache.set(`${tenantCode ?? "_"}:${k}`, { fetchedAt: now, value: dto });
|
|
506
|
+
cache.set(`${cfg.tenantCode ?? "_"}:${k}`, { fetchedAt: now, value: dto });
|
|
491
507
|
out[k] = materializeResolution(dto, opts.preset);
|
|
492
508
|
}
|
|
493
509
|
}
|