@org-quicko/silo-client 1.0.1 → 1.1.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.
Files changed (46) hide show
  1. package/README.md +49 -1
  2. package/dist/cache/cache-mode.d.cts +2 -0
  3. package/dist/cache/cache-mode.d.ts +2 -0
  4. package/dist/cache/response-cache.d.cts +16 -0
  5. package/dist/cache/response-cache.d.ts +16 -0
  6. package/dist/cache/silo-cache-options.d.cts +7 -0
  7. package/dist/cache/silo-cache-options.d.ts +7 -0
  8. package/dist/cache/silo-cache.d.cts +11 -0
  9. package/dist/cache/silo-cache.d.ts +11 -0
  10. package/dist/errors/network-error.d.cts +5 -0
  11. package/dist/errors/network-error.d.ts +5 -0
  12. package/dist/index.cjs +264 -74
  13. package/dist/index.d.cts +4 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.js +264 -74
  16. package/dist/media/media-asset.d.cts +17 -0
  17. package/dist/media/media-asset.d.ts +17 -0
  18. package/dist/media/media-file.d.cts +16 -0
  19. package/dist/media/media-file.d.ts +16 -0
  20. package/dist/media/media-page.d.cts +1 -0
  21. package/dist/media/media-page.d.ts +1 -0
  22. package/dist/media/media-replace.d.cts +17 -0
  23. package/dist/media/media-replace.d.ts +17 -0
  24. package/dist/media/media-usage-page.d.cts +1 -0
  25. package/dist/media/media-usage-page.d.ts +1 -0
  26. package/dist/media/media.d.cts +0 -4
  27. package/dist/media/media.d.ts +0 -4
  28. package/dist/request-options.d.cts +3 -0
  29. package/dist/request-options.d.ts +3 -0
  30. package/dist/silo-options.d.cts +4 -0
  31. package/dist/silo-options.d.ts +4 -0
  32. package/dist/silo.d.cts +3 -0
  33. package/dist/silo.d.ts +3 -0
  34. package/dist/transport/api-path.d.cts +1 -0
  35. package/dist/transport/api-path.d.ts +1 -0
  36. package/dist/transport/caching-transport.d.cts +14 -0
  37. package/dist/transport/caching-transport.d.ts +14 -0
  38. package/dist/transport/fetch-transport.d.cts +31 -0
  39. package/dist/transport/fetch-transport.d.ts +31 -0
  40. package/dist/transport/prepared-transport-request.d.cts +6 -0
  41. package/dist/transport/prepared-transport-request.d.ts +6 -0
  42. package/dist/transport/transport-request.d.cts +2 -0
  43. package/dist/transport/transport-request.d.ts +2 -0
  44. package/dist/transport/transport.d.cts +2 -39
  45. package/dist/transport/transport.d.ts +2 -39
  46. package/package.json +6 -3
package/dist/index.cjs CHANGED
@@ -47,6 +47,7 @@ __export(exports_src, {
47
47
  SortTerm: () => SortTerm,
48
48
  Sort: () => Sort,
49
49
  SiloError: () => SiloError,
50
+ SiloCache: () => SiloCache,
50
51
  Silo: () => Silo,
51
52
  SearchReach: () => SearchReach,
52
53
  SearchPage: () => SearchPage,
@@ -181,6 +182,9 @@ class ApiPath {
181
182
  static mediaAssetUsages(id) {
182
183
  return `${ApiPath.mediaAsset(id)}/usages`;
183
184
  }
185
+ static mediaAssetContent(id) {
186
+ return `${ApiPath.mediaAsset(id)}/content`;
187
+ }
184
188
  static mediaBulkDelete() {
185
189
  return "/api/media/delete";
186
190
  }
@@ -227,6 +231,22 @@ class MediaAssetMapper {
227
231
  }
228
232
  }
229
233
 
234
+ // src/media/media-file.ts
235
+ class MediaFile {
236
+ static toBlob(bytes, contentType) {
237
+ if (bytes instanceof Blob)
238
+ return bytes;
239
+ return new Blob([bytes], contentType ? { type: contentType } : undefined);
240
+ }
241
+ static nameOf(input, explicit) {
242
+ if (explicit)
243
+ return explicit;
244
+ if (input instanceof File && input.name)
245
+ return input.name;
246
+ throw new Error("media: a Blob has no filename — pass { filename } explicitly");
247
+ }
248
+ }
249
+
230
250
  // src/media/media-reference.ts
231
251
  class MediaReference {
232
252
  static Scheme = "silo://media/";
@@ -286,12 +306,14 @@ var DefaultUsageLimit = 50;
286
306
  class MediaUsagePage extends Page {
287
307
  transport;
288
308
  assetId;
309
+ requestOptions;
289
310
  visible;
290
311
  visibleCapped;
291
- constructor(rows, total, visible, visibleCapped, window, transport, assetId) {
312
+ constructor(rows, total, visible, visibleCapped, window, transport, assetId, requestOptions) {
292
313
  super(rows, total, window);
293
314
  this.transport = transport;
294
315
  this.assetId = assetId;
316
+ this.requestOptions = requestOptions;
295
317
  this.visible = visible;
296
318
  this.visibleCapped = visibleCapped;
297
319
  }
@@ -306,11 +328,11 @@ class MediaUsagePage extends Page {
306
328
  }
307
329
  async next(options) {
308
330
  const window = this.windowForNext();
309
- return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, options) : null;
331
+ return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, { ...this.requestOptions, ...options }) : null;
310
332
  }
311
333
  async previous(options) {
312
334
  const window = this.window.previous();
313
- return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, options) : null;
335
+ return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, { ...this.requestOptions, ...options }) : null;
314
336
  }
315
337
  static load(transport, assetId, query, options) {
316
338
  const window = new PageWindow(query.limit ?? DefaultUsageLimit, query.offset ?? 0);
@@ -322,10 +344,11 @@ class MediaUsagePage extends Page {
322
344
  path: ApiPath.mediaAssetUsages(assetId),
323
345
  query: { limit: window.limit, offset: window.offset },
324
346
  signal: options?.signal,
325
- timeoutMilliseconds: options?.timeoutMilliseconds
347
+ timeoutMilliseconds: options?.timeoutMilliseconds,
348
+ cache: options?.cache
326
349
  });
327
350
  const mapped = MediaAssetMapper.toUsagePage(body);
328
- return new MediaUsagePage(mapped.items, mapped.total, mapped.visible, mapped.visibleCapped, window, transport, assetId);
351
+ return new MediaUsagePage(mapped.items, mapped.total, mapped.visible, mapped.visibleCapped, window, transport, assetId, options);
329
352
  }
330
353
  }
331
354
 
@@ -388,6 +411,22 @@ class MediaAsset {
388
411
  async setTags(tags, options) {
389
412
  return this.patch({ tags: [...tags] }, options);
390
413
  }
414
+ async replace(input, options) {
415
+ const form = new FormData;
416
+ if (input instanceof Blob) {
417
+ form.set("file", input, MediaFile.nameOf(input, options?.filename));
418
+ } else {
419
+ form.set("file", MediaFile.toBlob(input.bytes, input.contentType), input.filename);
420
+ }
421
+ const payload = await this.transport.upload({
422
+ method: "POST",
423
+ path: ApiPath.mediaAssetContent(this.id),
424
+ signal: options?.signal,
425
+ timeoutMilliseconds: options?.timeoutMilliseconds
426
+ }, form);
427
+ this.record = MediaAssetMapper.toRecord(payload);
428
+ return this;
429
+ }
391
430
  async delete(options) {
392
431
  await this.transport.empty({
393
432
  method: "DELETE",
@@ -405,7 +444,8 @@ class MediaAsset {
405
444
  method: "GET",
406
445
  path: ApiPath.mediaAsset(this.id),
407
446
  signal: options?.signal,
408
- timeoutMilliseconds: options?.timeoutMilliseconds
447
+ timeoutMilliseconds: options?.timeoutMilliseconds,
448
+ cache: options?.cache ?? "refresh"
409
449
  });
410
450
  this.record = MediaAssetMapper.toRecord(payload);
411
451
  return this;
@@ -467,7 +507,8 @@ class MediaFolders {
467
507
  method: "GET",
468
508
  path: ApiPath.mediaFolders(),
469
509
  signal: options?.signal,
470
- timeoutMilliseconds: options?.timeoutMilliseconds
510
+ timeoutMilliseconds: options?.timeoutMilliseconds,
511
+ cache: options?.cache
471
512
  });
472
513
  return body.items;
473
514
  }
@@ -509,21 +550,23 @@ class MediaFolders {
509
550
  class MediaPage extends Page {
510
551
  transport;
511
552
  wireQuery;
512
- constructor(rows, total, window, transport, wireQuery) {
553
+ requestOptions;
554
+ constructor(rows, total, window, transport, wireQuery, requestOptions) {
513
555
  super(rows, total, window);
514
556
  this.transport = transport;
515
557
  this.wireQuery = wireQuery;
558
+ this.requestOptions = requestOptions;
516
559
  }
517
560
  get files() {
518
561
  return this.rows;
519
562
  }
520
563
  async next(options) {
521
564
  const window = this.windowForNext();
522
- return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, options) : null;
565
+ return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, { ...this.requestOptions, ...options }) : null;
523
566
  }
524
567
  async previous(options) {
525
568
  const window = this.window.previous();
526
- return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, options) : null;
569
+ return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, { ...this.requestOptions, ...options }) : null;
527
570
  }
528
571
  static async loadWindow(transport, wireQuery, window, options) {
529
572
  const body = await transport.json({
@@ -531,10 +574,11 @@ class MediaPage extends Page {
531
574
  path: ApiPath.media(),
532
575
  query: { ...wireQuery, limit: window.limit, offset: window.offset },
533
576
  signal: options?.signal,
534
- timeoutMilliseconds: options?.timeoutMilliseconds
577
+ timeoutMilliseconds: options?.timeoutMilliseconds,
578
+ cache: options?.cache
535
579
  });
536
580
  const rows = body.items.map((payload) => new MediaAsset(transport, MediaAssetMapper.toRecord(payload)));
537
- return new MediaPage(rows, body.total, new PageWindow(body.limit, body.offset), transport, wireQuery);
581
+ return new MediaPage(rows, body.total, new PageWindow(body.limit, body.offset), transport, wireQuery, options);
538
582
  }
539
583
  }
540
584
 
@@ -621,11 +665,11 @@ class Media {
621
665
  let options;
622
666
  if (input instanceof Blob) {
623
667
  const fileOptions = second ?? {};
624
- form.set("file", input, Media.filenameOf(input, fileOptions.filename));
668
+ form.set("file", input, MediaFile.nameOf(input, fileOptions.filename));
625
669
  folder = fileOptions.folder;
626
670
  options = third ?? fileOptions;
627
671
  } else {
628
- form.set("file", Media.toBlob(input.bytes, input.contentType), input.filename);
672
+ form.set("file", MediaFile.toBlob(input.bytes, input.contentType), input.filename);
629
673
  folder = input.folder;
630
674
  options = second;
631
675
  }
@@ -654,7 +698,8 @@ class Media {
654
698
  method: "GET",
655
699
  path: ApiPath.mediaAsset(id),
656
700
  signal: options?.signal,
657
- timeoutMilliseconds: options?.timeoutMilliseconds
701
+ timeoutMilliseconds: options?.timeoutMilliseconds,
702
+ cache: options?.cache
658
703
  });
659
704
  return new MediaAsset(this.transport, MediaAssetMapper.toRecord(payload));
660
705
  }
@@ -663,7 +708,8 @@ class Media {
663
708
  method: "GET",
664
709
  path: ApiPath.mediaExtensions(),
665
710
  signal: options?.signal,
666
- timeoutMilliseconds: options?.timeoutMilliseconds
711
+ timeoutMilliseconds: options?.timeoutMilliseconds,
712
+ cache: options?.cache
667
713
  });
668
714
  return body.items;
669
715
  }
@@ -693,18 +739,6 @@ class Media {
693
739
  sort: query.sort
694
740
  };
695
741
  }
696
- static toBlob(bytes, contentType) {
697
- if (bytes instanceof Blob)
698
- return bytes;
699
- return new Blob([bytes], contentType ? { type: contentType } : undefined);
700
- }
701
- static filenameOf(input, explicit) {
702
- if (explicit)
703
- return explicit;
704
- if (input instanceof File && input.name)
705
- return input.name;
706
- throw new Error("media upload: a Blob has no filename — pass { filename } explicitly");
707
- }
708
742
  }
709
743
 
710
744
  // src/variables/variable.ts
@@ -1388,6 +1422,132 @@ class Projects {
1388
1422
  }
1389
1423
  }
1390
1424
 
1425
+ // src/transport/caching-transport.ts
1426
+ class CachingTransport {
1427
+ #fetchTransport;
1428
+ #cache;
1429
+ constructor(fetchTransport, cache) {
1430
+ this.#fetchTransport = fetchTransport;
1431
+ this.#cache = cache;
1432
+ }
1433
+ async json(request) {
1434
+ if (request.method !== "GET")
1435
+ return this.write(() => this.#fetchTransport.json(request));
1436
+ if (request.cache === "bypass")
1437
+ return this.#fetchTransport.json(request);
1438
+ if (request.signal?.aborted)
1439
+ throw new RequestAbortedError(request.method, request.path);
1440
+ let prepared;
1441
+ try {
1442
+ prepared = this.#fetchTransport.prepare(request);
1443
+ } catch (caught) {
1444
+ throw this.#fetchTransport.preparationFailure(request, caught);
1445
+ }
1446
+ const key = JSON.stringify([prepared.url, [...prepared.headers]]);
1447
+ if (request.cache !== "refresh") {
1448
+ const cached = this.#cache.get(key);
1449
+ if (cached)
1450
+ return cached.body;
1451
+ }
1452
+ const ticket = this.#cache.start(key);
1453
+ try {
1454
+ const body = await this.#fetchTransport.jsonPrepared(request, prepared);
1455
+ this.#cache.set(key, ticket, body);
1456
+ return body;
1457
+ } finally {
1458
+ this.#cache.finish(key, ticket);
1459
+ }
1460
+ }
1461
+ empty(request) {
1462
+ return request.method === "GET" ? this.#fetchTransport.empty(request) : this.write(() => this.#fetchTransport.empty(request));
1463
+ }
1464
+ stream(request) {
1465
+ return request.method === "GET" ? this.#fetchTransport.stream(request) : this.write(() => this.#fetchTransport.stream(request));
1466
+ }
1467
+ upload(request, form) {
1468
+ return request.method === "GET" ? this.#fetchTransport.upload(request, form) : this.write(() => this.#fetchTransport.upload(request, form));
1469
+ }
1470
+ async write(dispatch) {
1471
+ this.#cache.clear();
1472
+ try {
1473
+ return await dispatch();
1474
+ } finally {
1475
+ this.#cache.clear();
1476
+ }
1477
+ }
1478
+ }
1479
+
1480
+ // src/cache/response-cache.ts
1481
+ var import_ttlcache = require("@isaacs/ttlcache");
1482
+
1483
+ class ResponseCache {
1484
+ #store;
1485
+ #pending = new Map;
1486
+ constructor(options) {
1487
+ this.#store = new import_ttlcache.TTLCache({
1488
+ ttl: options.ttlMilliseconds,
1489
+ ...options.maxEntries === undefined || options.maxEntries === Infinity ? {} : { max: options.maxEntries },
1490
+ checkAgeOnGet: true,
1491
+ updateAgeOnGet: false
1492
+ });
1493
+ }
1494
+ get size() {
1495
+ return this.#store.size;
1496
+ }
1497
+ get(key) {
1498
+ const cached = this.#store.get(key);
1499
+ return cached === undefined ? undefined : { body: structuredClone(cached.body) };
1500
+ }
1501
+ set(key, ticket, body) {
1502
+ if (this.#pending.get(key) !== ticket)
1503
+ return;
1504
+ this.#store.set(key, { body: structuredClone(body) });
1505
+ }
1506
+ start(key) {
1507
+ const ticket = Symbol();
1508
+ this.#pending.set(key, ticket);
1509
+ return ticket;
1510
+ }
1511
+ finish(key, ticket) {
1512
+ if (this.#pending.get(key) === ticket)
1513
+ this.#pending.delete(key);
1514
+ }
1515
+ clear() {
1516
+ this.#pending.clear();
1517
+ this.#store.clear();
1518
+ }
1519
+ }
1520
+
1521
+ // src/cache/silo-cache.ts
1522
+ class SiloCache {
1523
+ #responseCache;
1524
+ constructor(options) {
1525
+ if (options !== undefined)
1526
+ SiloCache.validate(options);
1527
+ this.#responseCache = options === undefined ? undefined : new ResponseCache(options);
1528
+ }
1529
+ get enabled() {
1530
+ return this.#responseCache !== undefined;
1531
+ }
1532
+ get size() {
1533
+ return this.#responseCache?.size ?? 0;
1534
+ }
1535
+ clear() {
1536
+ this.#responseCache?.clear();
1537
+ }
1538
+ wrap(fetchTransport) {
1539
+ return this.#responseCache ? new CachingTransport(fetchTransport, this.#responseCache) : fetchTransport;
1540
+ }
1541
+ static validate(options) {
1542
+ if (!Number.isSafeInteger(options.ttlMilliseconds) || options.ttlMilliseconds <= 0) {
1543
+ throw new TypeError("SiloOptions.cache.ttlMilliseconds must be a positive finite safe integer");
1544
+ }
1545
+ if (options.maxEntries !== undefined && options.maxEntries !== Infinity && (!Number.isSafeInteger(options.maxEntries) || options.maxEntries <= 0)) {
1546
+ throw new TypeError("SiloOptions.cache.maxEntries must be a positive safe integer or Infinity");
1547
+ }
1548
+ }
1549
+ }
1550
+
1391
1551
  // src/errors/silo-error.ts
1392
1552
  class SiloError extends Error {
1393
1553
  status;
@@ -1568,12 +1728,19 @@ class NetworkError extends Error {
1568
1728
  method;
1569
1729
  path;
1570
1730
  constructor(method, path, cause) {
1571
- super(`network error on ${method} ${path}: the request never reached the server`, { cause });
1731
+ super(`network error on ${method} ${path}: the request never reached the server${NetworkError.reason(cause)}`, { cause });
1572
1732
  this.name = "NetworkError";
1573
1733
  this.method = method;
1574
1734
  this.path = path;
1575
1735
  Object.setPrototypeOf(this, NetworkError.prototype);
1576
1736
  }
1737
+ static reason(cause) {
1738
+ if (cause instanceof Error && cause.message)
1739
+ return ` (${cause.message})`;
1740
+ if (typeof cause === "string" && cause)
1741
+ return ` (${cause})`;
1742
+ return "";
1743
+ }
1577
1744
  }
1578
1745
 
1579
1746
  // src/errors/timeout-error.ts
@@ -1675,60 +1842,80 @@ class ResponseDecoder {
1675
1842
  }
1676
1843
  }
1677
1844
 
1678
- // src/transport/transport.ts
1679
- class Transport {
1680
- url;
1681
- key;
1682
- headers;
1683
- timeoutMilliseconds;
1684
- fetchFunction;
1845
+ // src/transport/fetch-transport.ts
1846
+ class FetchTransport {
1847
+ #url;
1848
+ #key;
1849
+ #headers;
1850
+ #timeoutMilliseconds;
1851
+ #fetchFunction;
1685
1852
  constructor(options) {
1686
- this.url = Transport.normalizeUrl(options.url);
1687
- this.key = options.key;
1688
- this.headers = options.headers ?? {};
1689
- this.timeoutMilliseconds = options.timeoutMilliseconds;
1690
- this.fetchFunction = options.fetch ?? fetch;
1853
+ this.#url = FetchTransport.normalizeUrl(options.url);
1854
+ this.#key = options.key;
1855
+ this.#headers = options.headers ?? {};
1856
+ this.#timeoutMilliseconds = options.timeoutMilliseconds;
1857
+ this.#fetchFunction = FetchTransport.resolveFetch(options.fetch);
1691
1858
  }
1692
1859
  async json(request) {
1693
1860
  const response = await this.execute(request);
1694
1861
  return await ResponseDecoder.decode(response, request, true);
1695
1862
  }
1863
+ async jsonPrepared(request, prepared) {
1864
+ const response = await this.execute(request, prepared);
1865
+ return await ResponseDecoder.decode(response, request, true);
1866
+ }
1696
1867
  async empty(request) {
1697
1868
  const response = await this.execute(request);
1698
1869
  await ResponseDecoder.decode(response, request, false);
1699
1870
  }
1700
1871
  async stream(request) {
1701
- const response = await this.execute(request);
1702
- return response.body;
1872
+ return (await this.execute(request)).body;
1703
1873
  }
1704
1874
  async upload(request, form) {
1705
- const response = await this.execute(request, form);
1875
+ const response = await this.execute(request, undefined, form);
1706
1876
  return await ResponseDecoder.decode(response, request, true);
1707
1877
  }
1708
1878
  withKey(key) {
1709
- return new Transport({ ...this.snapshot(), key });
1879
+ return new FetchTransport({ ...this.snapshot(), key });
1710
1880
  }
1711
1881
  withUrl(url) {
1712
- return new Transport({ ...this.snapshot(), url });
1882
+ return new FetchTransport({ ...this.snapshot(), url });
1883
+ }
1884
+ prepare(request, form) {
1885
+ const rawHeaders = { ...this.#headers, ...request.headers };
1886
+ if (this.#key)
1887
+ rawHeaders.Authorization = `Bearer ${this.#key}`;
1888
+ if (!form && request.body !== undefined && !("Content-Type" in rawHeaders)) {
1889
+ rawHeaders["Content-Type"] = "application/json";
1890
+ }
1891
+ return {
1892
+ url: `${this.#url}${request.path}${QueryString.build(request.query)}`,
1893
+ headers: new Headers(rawHeaders),
1894
+ body: form ?? (request.body === undefined ? undefined : JSON.stringify(request.body))
1895
+ };
1896
+ }
1897
+ preparationFailure(request, caught) {
1898
+ return new NetworkError(request.method, request.path, caught);
1713
1899
  }
1714
1900
  snapshot() {
1715
1901
  return {
1716
- url: this.url,
1717
- key: this.key,
1718
- headers: this.headers,
1719
- timeoutMilliseconds: this.timeoutMilliseconds,
1720
- fetch: this.fetchFunction
1902
+ url: this.#url,
1903
+ key: this.#key,
1904
+ headers: this.#headers,
1905
+ timeoutMilliseconds: this.#timeoutMilliseconds,
1906
+ fetch: this.#fetchFunction
1721
1907
  };
1722
1908
  }
1723
- async execute(request, form) {
1724
- const abortSignals = new AbortSignals(request.signal, request.timeoutMilliseconds ?? this.timeoutMilliseconds);
1725
- const url = `${this.url}${request.path}${QueryString.build(request.query)}`;
1909
+ async execute(request, prepared, form) {
1910
+ const abortSignals = new AbortSignals(request.signal, request.timeoutMilliseconds ?? this.#timeoutMilliseconds);
1911
+ const sendRequest = this.#fetchFunction;
1726
1912
  let response;
1727
1913
  try {
1728
- response = await this.fetchFunction(url, {
1914
+ const outbound = prepared ?? this.prepare(request, form);
1915
+ response = await sendRequest(outbound.url, {
1729
1916
  method: request.method,
1730
- headers: this.buildHeaders(request, Boolean(form)),
1731
- body: form ?? (request.body === undefined ? undefined : JSON.stringify(request.body)),
1917
+ headers: outbound.headers,
1918
+ body: outbound.body,
1732
1919
  signal: abortSignals.signal
1733
1920
  });
1734
1921
  } catch (caught) {
@@ -1742,44 +1929,44 @@ class Transport {
1742
1929
  return response;
1743
1930
  }
1744
1931
  transportFailure(request, abortSignals, caught) {
1745
- const firedBy = abortSignals.firedBy();
1746
- if (firedBy === "timeout") {
1747
- return new TimeoutError(request.method, request.path, request.timeoutMilliseconds ?? this.timeoutMilliseconds ?? 0);
1932
+ if (abortSignals.firedBy() === "timeout") {
1933
+ return new TimeoutError(request.method, request.path, request.timeoutMilliseconds ?? this.#timeoutMilliseconds ?? 0);
1748
1934
  }
1749
- if (firedBy === "caller") {
1935
+ if (abortSignals.firedBy() === "caller")
1750
1936
  return new RequestAbortedError(request.method, request.path);
1751
- }
1752
1937
  return new NetworkError(request.method, request.path, caught);
1753
1938
  }
1754
- buildHeaders(request, isUpload) {
1755
- const headers = { ...this.headers, ...request.headers };
1756
- if (this.key)
1757
- headers["Authorization"] = `Bearer ${this.key}`;
1758
- if (!isUpload && request.body !== undefined && !("Content-Type" in headers)) {
1759
- headers["Content-Type"] = "application/json";
1760
- }
1761
- return headers;
1762
- }
1763
1939
  static normalizeUrl(url) {
1764
1940
  return url.replace(/\/+$/, "");
1765
1941
  }
1942
+ static resolveFetch(fetchFunction) {
1943
+ if (fetchFunction)
1944
+ return fetchFunction;
1945
+ if (typeof globalThis.fetch !== "function") {
1946
+ throw new Error("this runtime has no global fetch: pass one as SiloOptions.fetch");
1947
+ }
1948
+ return globalThis.fetch.bind(globalThis);
1949
+ }
1766
1950
  }
1767
1951
 
1768
1952
  // src/silo.ts
1769
1953
  class Silo {
1770
1954
  projects;
1771
1955
  media;
1956
+ cache;
1772
1957
  options;
1773
1958
  transport;
1774
1959
  constructor(options) {
1775
- this.options = options;
1776
- this.transport = new Transport({
1960
+ this.cache = options.cache instanceof SiloCache ? options.cache : new SiloCache(options.cache);
1961
+ this.options = { ...options, cache: this.cache };
1962
+ const fetchTransport = new FetchTransport({
1777
1963
  url: options.url,
1778
1964
  key: options.key,
1779
1965
  headers: options.headers,
1780
1966
  timeoutMilliseconds: options.timeoutMilliseconds,
1781
1967
  fetch: options.fetch
1782
1968
  });
1969
+ this.transport = this.cache.wrap(fetchTransport);
1783
1970
  this.projects = new Projects(this.transport);
1784
1971
  this.media = new Media(this.transport);
1785
1972
  }
@@ -1797,7 +1984,8 @@ class Silo {
1797
1984
  method: "GET",
1798
1985
  path: ApiPath.health(),
1799
1986
  signal: options?.signal,
1800
- timeoutMilliseconds: options?.timeoutMilliseconds
1987
+ timeoutMilliseconds: options?.timeoutMilliseconds,
1988
+ cache: "bypass"
1801
1989
  });
1802
1990
  }
1803
1991
  withKey(key) {
@@ -1847,6 +2035,7 @@ class RouteInventory {
1847
2035
  "PATCH /api/media/:id",
1848
2036
  "DELETE /api/media/:id",
1849
2037
  "GET /api/media/:id/usages",
2038
+ "POST /api/media/:id/content",
1850
2039
  "POST /api/media/delete",
1851
2040
  "GET /api/media/folders",
1852
2041
  "POST /api/media/folders",
@@ -1857,6 +2046,7 @@ class RouteInventory {
1857
2046
  "GET /api/session": "key introspection, an operator surface",
1858
2047
  "GET /api/keys": "keys and claims are an operator surface",
1859
2048
  "POST /api/keys": "keys and claims are an operator surface",
2049
+ "PATCH /api/keys/:id": "keys and claims are an operator surface",
1860
2050
  "DELETE /api/keys/:id": "keys and claims are an operator surface",
1861
2051
  "GET /api/audit": "an operator surface",
1862
2052
  "GET /api/observability": "an operator surface",
package/dist/index.d.cts CHANGED
@@ -5,7 +5,10 @@
5
5
  * change rather than a hand-built request.
6
6
  */
7
7
  export { Silo } from "./silo.cjs";
8
+ export { SiloCache } from "./cache/silo-cache.cjs";
8
9
  export type { SiloOptions } from "./silo-options.cjs";
10
+ export type { CacheMode } from "./cache/cache-mode.cjs";
11
+ export type { SiloCacheOptions } from "./cache/silo-cache-options.cjs";
9
12
  export type { RequestOptions } from "./request-options.cjs";
10
13
  export type { FetchFunction } from "./transport/fetch-function.cjs";
11
14
  export { RouteInventory } from "./transport/route-inventory.cjs";
@@ -71,6 +74,7 @@ export type { MediaAssetRecord } from "./media/media-asset.cjs";
71
74
  export type { MediaQuery } from "./media/media-query.cjs";
72
75
  export type { MediaUsage } from "./media/media-usage.cjs";
73
76
  export type { MediaUsageQuery } from "./media/media-usage-page.cjs";
77
+ export type { MediaReplace, MediaReplaceBytes, MediaReplaceOptions } from "./media/media-replace.cjs";
74
78
  export type { MediaUpload, MediaUploadBytes, MediaUploadFileOptions } from "./media/media-upload.cjs";
75
79
  export type { MediaDeleteFailure } from "./media/media-delete-failure.cjs";
76
80
  export type { MediaDeleteOptions } from "./media/media-delete-options.cjs";
package/dist/index.d.ts CHANGED
@@ -5,7 +5,10 @@
5
5
  * change rather than a hand-built request.
6
6
  */
7
7
  export { Silo } from "./silo.js";
8
+ export { SiloCache } from "./cache/silo-cache.js";
8
9
  export type { SiloOptions } from "./silo-options.js";
10
+ export type { CacheMode } from "./cache/cache-mode.js";
11
+ export type { SiloCacheOptions } from "./cache/silo-cache-options.js";
9
12
  export type { RequestOptions } from "./request-options.js";
10
13
  export type { FetchFunction } from "./transport/fetch-function.js";
11
14
  export { RouteInventory } from "./transport/route-inventory.js";
@@ -71,6 +74,7 @@ export type { MediaAssetRecord } from "./media/media-asset.js";
71
74
  export type { MediaQuery } from "./media/media-query.js";
72
75
  export type { MediaUsage } from "./media/media-usage.js";
73
76
  export type { MediaUsageQuery } from "./media/media-usage-page.js";
77
+ export type { MediaReplace, MediaReplaceBytes, MediaReplaceOptions } from "./media/media-replace.js";
74
78
  export type { MediaUpload, MediaUploadBytes, MediaUploadFileOptions } from "./media/media-upload.js";
75
79
  export type { MediaDeleteFailure } from "./media/media-delete-failure.js";
76
80
  export type { MediaDeleteOptions } from "./media/media-delete-options.js";