@org-quicko/silo-client 1.1.0 → 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.
package/dist/index.js CHANGED
@@ -208,12 +208,14 @@ var DefaultUsageLimit = 50;
208
208
  class MediaUsagePage extends Page {
209
209
  transport;
210
210
  assetId;
211
+ requestOptions;
211
212
  visible;
212
213
  visibleCapped;
213
- constructor(rows, total, visible, visibleCapped, window, transport, assetId) {
214
+ constructor(rows, total, visible, visibleCapped, window, transport, assetId, requestOptions) {
214
215
  super(rows, total, window);
215
216
  this.transport = transport;
216
217
  this.assetId = assetId;
218
+ this.requestOptions = requestOptions;
217
219
  this.visible = visible;
218
220
  this.visibleCapped = visibleCapped;
219
221
  }
@@ -228,11 +230,11 @@ class MediaUsagePage extends Page {
228
230
  }
229
231
  async next(options) {
230
232
  const window = this.windowForNext();
231
- return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, options) : null;
233
+ return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, { ...this.requestOptions, ...options }) : null;
232
234
  }
233
235
  async previous(options) {
234
236
  const window = this.window.previous();
235
- return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, options) : null;
237
+ return window ? MediaUsagePage.loadWindow(this.transport, this.assetId, window, { ...this.requestOptions, ...options }) : null;
236
238
  }
237
239
  static load(transport, assetId, query, options) {
238
240
  const window = new PageWindow(query.limit ?? DefaultUsageLimit, query.offset ?? 0);
@@ -244,10 +246,11 @@ class MediaUsagePage extends Page {
244
246
  path: ApiPath.mediaAssetUsages(assetId),
245
247
  query: { limit: window.limit, offset: window.offset },
246
248
  signal: options?.signal,
247
- timeoutMilliseconds: options?.timeoutMilliseconds
249
+ timeoutMilliseconds: options?.timeoutMilliseconds,
250
+ cache: options?.cache
248
251
  });
249
252
  const mapped = MediaAssetMapper.toUsagePage(body);
250
- return new MediaUsagePage(mapped.items, mapped.total, mapped.visible, mapped.visibleCapped, window, transport, assetId);
253
+ return new MediaUsagePage(mapped.items, mapped.total, mapped.visible, mapped.visibleCapped, window, transport, assetId, options);
251
254
  }
252
255
  }
253
256
 
@@ -343,7 +346,8 @@ class MediaAsset {
343
346
  method: "GET",
344
347
  path: ApiPath.mediaAsset(this.id),
345
348
  signal: options?.signal,
346
- timeoutMilliseconds: options?.timeoutMilliseconds
349
+ timeoutMilliseconds: options?.timeoutMilliseconds,
350
+ cache: options?.cache ?? "refresh"
347
351
  });
348
352
  this.record = MediaAssetMapper.toRecord(payload);
349
353
  return this;
@@ -405,7 +409,8 @@ class MediaFolders {
405
409
  method: "GET",
406
410
  path: ApiPath.mediaFolders(),
407
411
  signal: options?.signal,
408
- timeoutMilliseconds: options?.timeoutMilliseconds
412
+ timeoutMilliseconds: options?.timeoutMilliseconds,
413
+ cache: options?.cache
409
414
  });
410
415
  return body.items;
411
416
  }
@@ -447,21 +452,23 @@ class MediaFolders {
447
452
  class MediaPage extends Page {
448
453
  transport;
449
454
  wireQuery;
450
- constructor(rows, total, window, transport, wireQuery) {
455
+ requestOptions;
456
+ constructor(rows, total, window, transport, wireQuery, requestOptions) {
451
457
  super(rows, total, window);
452
458
  this.transport = transport;
453
459
  this.wireQuery = wireQuery;
460
+ this.requestOptions = requestOptions;
454
461
  }
455
462
  get files() {
456
463
  return this.rows;
457
464
  }
458
465
  async next(options) {
459
466
  const window = this.windowForNext();
460
- return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, options) : null;
467
+ return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, { ...this.requestOptions, ...options }) : null;
461
468
  }
462
469
  async previous(options) {
463
470
  const window = this.window.previous();
464
- return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, options) : null;
471
+ return window ? MediaPage.loadWindow(this.transport, this.wireQuery, window, { ...this.requestOptions, ...options }) : null;
465
472
  }
466
473
  static async loadWindow(transport, wireQuery, window, options) {
467
474
  const body = await transport.json({
@@ -469,10 +476,11 @@ class MediaPage extends Page {
469
476
  path: ApiPath.media(),
470
477
  query: { ...wireQuery, limit: window.limit, offset: window.offset },
471
478
  signal: options?.signal,
472
- timeoutMilliseconds: options?.timeoutMilliseconds
479
+ timeoutMilliseconds: options?.timeoutMilliseconds,
480
+ cache: options?.cache
473
481
  });
474
482
  const rows = body.items.map((payload) => new MediaAsset(transport, MediaAssetMapper.toRecord(payload)));
475
- return new MediaPage(rows, body.total, new PageWindow(body.limit, body.offset), transport, wireQuery);
483
+ return new MediaPage(rows, body.total, new PageWindow(body.limit, body.offset), transport, wireQuery, options);
476
484
  }
477
485
  }
478
486
 
@@ -592,7 +600,8 @@ class Media {
592
600
  method: "GET",
593
601
  path: ApiPath.mediaAsset(id),
594
602
  signal: options?.signal,
595
- timeoutMilliseconds: options?.timeoutMilliseconds
603
+ timeoutMilliseconds: options?.timeoutMilliseconds,
604
+ cache: options?.cache
596
605
  });
597
606
  return new MediaAsset(this.transport, MediaAssetMapper.toRecord(payload));
598
607
  }
@@ -601,7 +610,8 @@ class Media {
601
610
  method: "GET",
602
611
  path: ApiPath.mediaExtensions(),
603
612
  signal: options?.signal,
604
- timeoutMilliseconds: options?.timeoutMilliseconds
613
+ timeoutMilliseconds: options?.timeoutMilliseconds,
614
+ cache: options?.cache
605
615
  });
606
616
  return body.items;
607
617
  }
@@ -1314,6 +1324,132 @@ class Projects {
1314
1324
  }
1315
1325
  }
1316
1326
 
1327
+ // src/transport/caching-transport.ts
1328
+ class CachingTransport {
1329
+ #fetchTransport;
1330
+ #cache;
1331
+ constructor(fetchTransport, cache) {
1332
+ this.#fetchTransport = fetchTransport;
1333
+ this.#cache = cache;
1334
+ }
1335
+ async json(request) {
1336
+ if (request.method !== "GET")
1337
+ return this.write(() => this.#fetchTransport.json(request));
1338
+ if (request.cache === "bypass")
1339
+ return this.#fetchTransport.json(request);
1340
+ if (request.signal?.aborted)
1341
+ throw new RequestAbortedError(request.method, request.path);
1342
+ let prepared;
1343
+ try {
1344
+ prepared = this.#fetchTransport.prepare(request);
1345
+ } catch (caught) {
1346
+ throw this.#fetchTransport.preparationFailure(request, caught);
1347
+ }
1348
+ const key = JSON.stringify([prepared.url, [...prepared.headers]]);
1349
+ if (request.cache !== "refresh") {
1350
+ const cached = this.#cache.get(key);
1351
+ if (cached)
1352
+ return cached.body;
1353
+ }
1354
+ const ticket = this.#cache.start(key);
1355
+ try {
1356
+ const body = await this.#fetchTransport.jsonPrepared(request, prepared);
1357
+ this.#cache.set(key, ticket, body);
1358
+ return body;
1359
+ } finally {
1360
+ this.#cache.finish(key, ticket);
1361
+ }
1362
+ }
1363
+ empty(request) {
1364
+ return request.method === "GET" ? this.#fetchTransport.empty(request) : this.write(() => this.#fetchTransport.empty(request));
1365
+ }
1366
+ stream(request) {
1367
+ return request.method === "GET" ? this.#fetchTransport.stream(request) : this.write(() => this.#fetchTransport.stream(request));
1368
+ }
1369
+ upload(request, form) {
1370
+ return request.method === "GET" ? this.#fetchTransport.upload(request, form) : this.write(() => this.#fetchTransport.upload(request, form));
1371
+ }
1372
+ async write(dispatch) {
1373
+ this.#cache.clear();
1374
+ try {
1375
+ return await dispatch();
1376
+ } finally {
1377
+ this.#cache.clear();
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ // src/cache/response-cache.ts
1383
+ import { TTLCache } from "@isaacs/ttlcache";
1384
+
1385
+ class ResponseCache {
1386
+ #store;
1387
+ #pending = new Map;
1388
+ constructor(options) {
1389
+ this.#store = new TTLCache({
1390
+ ttl: options.ttlMilliseconds,
1391
+ ...options.maxEntries === undefined || options.maxEntries === Infinity ? {} : { max: options.maxEntries },
1392
+ checkAgeOnGet: true,
1393
+ updateAgeOnGet: false
1394
+ });
1395
+ }
1396
+ get size() {
1397
+ return this.#store.size;
1398
+ }
1399
+ get(key) {
1400
+ const cached = this.#store.get(key);
1401
+ return cached === undefined ? undefined : { body: structuredClone(cached.body) };
1402
+ }
1403
+ set(key, ticket, body) {
1404
+ if (this.#pending.get(key) !== ticket)
1405
+ return;
1406
+ this.#store.set(key, { body: structuredClone(body) });
1407
+ }
1408
+ start(key) {
1409
+ const ticket = Symbol();
1410
+ this.#pending.set(key, ticket);
1411
+ return ticket;
1412
+ }
1413
+ finish(key, ticket) {
1414
+ if (this.#pending.get(key) === ticket)
1415
+ this.#pending.delete(key);
1416
+ }
1417
+ clear() {
1418
+ this.#pending.clear();
1419
+ this.#store.clear();
1420
+ }
1421
+ }
1422
+
1423
+ // src/cache/silo-cache.ts
1424
+ class SiloCache {
1425
+ #responseCache;
1426
+ constructor(options) {
1427
+ if (options !== undefined)
1428
+ SiloCache.validate(options);
1429
+ this.#responseCache = options === undefined ? undefined : new ResponseCache(options);
1430
+ }
1431
+ get enabled() {
1432
+ return this.#responseCache !== undefined;
1433
+ }
1434
+ get size() {
1435
+ return this.#responseCache?.size ?? 0;
1436
+ }
1437
+ clear() {
1438
+ this.#responseCache?.clear();
1439
+ }
1440
+ wrap(fetchTransport) {
1441
+ return this.#responseCache ? new CachingTransport(fetchTransport, this.#responseCache) : fetchTransport;
1442
+ }
1443
+ static validate(options) {
1444
+ if (!Number.isSafeInteger(options.ttlMilliseconds) || options.ttlMilliseconds <= 0) {
1445
+ throw new TypeError("SiloOptions.cache.ttlMilliseconds must be a positive finite safe integer");
1446
+ }
1447
+ if (options.maxEntries !== undefined && options.maxEntries !== Infinity && (!Number.isSafeInteger(options.maxEntries) || options.maxEntries <= 0)) {
1448
+ throw new TypeError("SiloOptions.cache.maxEntries must be a positive safe integer or Infinity");
1449
+ }
1450
+ }
1451
+ }
1452
+
1317
1453
  // src/errors/silo-error.ts
1318
1454
  class SiloError extends Error {
1319
1455
  status;
@@ -1608,61 +1744,80 @@ class ResponseDecoder {
1608
1744
  }
1609
1745
  }
1610
1746
 
1611
- // src/transport/transport.ts
1612
- class Transport {
1613
- url;
1614
- key;
1615
- headers;
1616
- timeoutMilliseconds;
1617
- fetchFunction;
1747
+ // src/transport/fetch-transport.ts
1748
+ class FetchTransport {
1749
+ #url;
1750
+ #key;
1751
+ #headers;
1752
+ #timeoutMilliseconds;
1753
+ #fetchFunction;
1618
1754
  constructor(options) {
1619
- this.url = Transport.normalizeUrl(options.url);
1620
- this.key = options.key;
1621
- this.headers = options.headers ?? {};
1622
- this.timeoutMilliseconds = options.timeoutMilliseconds;
1623
- this.fetchFunction = Transport.resolveFetch(options.fetch);
1755
+ this.#url = FetchTransport.normalizeUrl(options.url);
1756
+ this.#key = options.key;
1757
+ this.#headers = options.headers ?? {};
1758
+ this.#timeoutMilliseconds = options.timeoutMilliseconds;
1759
+ this.#fetchFunction = FetchTransport.resolveFetch(options.fetch);
1624
1760
  }
1625
1761
  async json(request) {
1626
1762
  const response = await this.execute(request);
1627
1763
  return await ResponseDecoder.decode(response, request, true);
1628
1764
  }
1765
+ async jsonPrepared(request, prepared) {
1766
+ const response = await this.execute(request, prepared);
1767
+ return await ResponseDecoder.decode(response, request, true);
1768
+ }
1629
1769
  async empty(request) {
1630
1770
  const response = await this.execute(request);
1631
1771
  await ResponseDecoder.decode(response, request, false);
1632
1772
  }
1633
1773
  async stream(request) {
1634
- const response = await this.execute(request);
1635
- return response.body;
1774
+ return (await this.execute(request)).body;
1636
1775
  }
1637
1776
  async upload(request, form) {
1638
- const response = await this.execute(request, form);
1777
+ const response = await this.execute(request, undefined, form);
1639
1778
  return await ResponseDecoder.decode(response, request, true);
1640
1779
  }
1641
1780
  withKey(key) {
1642
- return new Transport({ ...this.snapshot(), key });
1781
+ return new FetchTransport({ ...this.snapshot(), key });
1643
1782
  }
1644
1783
  withUrl(url) {
1645
- return new Transport({ ...this.snapshot(), url });
1784
+ return new FetchTransport({ ...this.snapshot(), url });
1785
+ }
1786
+ prepare(request, form) {
1787
+ const rawHeaders = { ...this.#headers, ...request.headers };
1788
+ if (this.#key)
1789
+ rawHeaders.Authorization = `Bearer ${this.#key}`;
1790
+ if (!form && request.body !== undefined && !("Content-Type" in rawHeaders)) {
1791
+ rawHeaders["Content-Type"] = "application/json";
1792
+ }
1793
+ return {
1794
+ url: `${this.#url}${request.path}${QueryString.build(request.query)}`,
1795
+ headers: new Headers(rawHeaders),
1796
+ body: form ?? (request.body === undefined ? undefined : JSON.stringify(request.body))
1797
+ };
1798
+ }
1799
+ preparationFailure(request, caught) {
1800
+ return new NetworkError(request.method, request.path, caught);
1646
1801
  }
1647
1802
  snapshot() {
1648
1803
  return {
1649
- url: this.url,
1650
- key: this.key,
1651
- headers: this.headers,
1652
- timeoutMilliseconds: this.timeoutMilliseconds,
1653
- fetch: this.fetchFunction
1804
+ url: this.#url,
1805
+ key: this.#key,
1806
+ headers: this.#headers,
1807
+ timeoutMilliseconds: this.#timeoutMilliseconds,
1808
+ fetch: this.#fetchFunction
1654
1809
  };
1655
1810
  }
1656
- async execute(request, form) {
1657
- const abortSignals = new AbortSignals(request.signal, request.timeoutMilliseconds ?? this.timeoutMilliseconds);
1658
- const url = `${this.url}${request.path}${QueryString.build(request.query)}`;
1659
- const sendRequest = this.fetchFunction;
1811
+ async execute(request, prepared, form) {
1812
+ const abortSignals = new AbortSignals(request.signal, request.timeoutMilliseconds ?? this.#timeoutMilliseconds);
1813
+ const sendRequest = this.#fetchFunction;
1660
1814
  let response;
1661
1815
  try {
1662
- response = await sendRequest(url, {
1816
+ const outbound = prepared ?? this.prepare(request, form);
1817
+ response = await sendRequest(outbound.url, {
1663
1818
  method: request.method,
1664
- headers: this.buildHeaders(request, Boolean(form)),
1665
- body: form ?? (request.body === undefined ? undefined : JSON.stringify(request.body)),
1819
+ headers: outbound.headers,
1820
+ body: outbound.body,
1666
1821
  signal: abortSignals.signal
1667
1822
  });
1668
1823
  } catch (caught) {
@@ -1676,24 +1831,13 @@ class Transport {
1676
1831
  return response;
1677
1832
  }
1678
1833
  transportFailure(request, abortSignals, caught) {
1679
- const firedBy = abortSignals.firedBy();
1680
- if (firedBy === "timeout") {
1681
- return new TimeoutError(request.method, request.path, request.timeoutMilliseconds ?? this.timeoutMilliseconds ?? 0);
1834
+ if (abortSignals.firedBy() === "timeout") {
1835
+ return new TimeoutError(request.method, request.path, request.timeoutMilliseconds ?? this.#timeoutMilliseconds ?? 0);
1682
1836
  }
1683
- if (firedBy === "caller") {
1837
+ if (abortSignals.firedBy() === "caller")
1684
1838
  return new RequestAbortedError(request.method, request.path);
1685
- }
1686
1839
  return new NetworkError(request.method, request.path, caught);
1687
1840
  }
1688
- buildHeaders(request, isUpload) {
1689
- const headers = { ...this.headers, ...request.headers };
1690
- if (this.key)
1691
- headers["Authorization"] = `Bearer ${this.key}`;
1692
- if (!isUpload && request.body !== undefined && !("Content-Type" in headers)) {
1693
- headers["Content-Type"] = "application/json";
1694
- }
1695
- return headers;
1696
- }
1697
1841
  static normalizeUrl(url) {
1698
1842
  return url.replace(/\/+$/, "");
1699
1843
  }
@@ -1711,17 +1855,20 @@ class Transport {
1711
1855
  class Silo {
1712
1856
  projects;
1713
1857
  media;
1858
+ cache;
1714
1859
  options;
1715
1860
  transport;
1716
1861
  constructor(options) {
1717
- this.options = options;
1718
- this.transport = new Transport({
1862
+ this.cache = options.cache instanceof SiloCache ? options.cache : new SiloCache(options.cache);
1863
+ this.options = { ...options, cache: this.cache };
1864
+ const fetchTransport = new FetchTransport({
1719
1865
  url: options.url,
1720
1866
  key: options.key,
1721
1867
  headers: options.headers,
1722
1868
  timeoutMilliseconds: options.timeoutMilliseconds,
1723
1869
  fetch: options.fetch
1724
1870
  });
1871
+ this.transport = this.cache.wrap(fetchTransport);
1725
1872
  this.projects = new Projects(this.transport);
1726
1873
  this.media = new Media(this.transport);
1727
1874
  }
@@ -1739,7 +1886,8 @@ class Silo {
1739
1886
  method: "GET",
1740
1887
  path: ApiPath.health(),
1741
1888
  signal: options?.signal,
1742
- timeoutMilliseconds: options?.timeoutMilliseconds
1889
+ timeoutMilliseconds: options?.timeoutMilliseconds,
1890
+ cache: "bypass"
1743
1891
  });
1744
1892
  }
1745
1893
  withKey(key) {
@@ -1922,6 +2070,7 @@ export {
1922
2070
  SortTerm,
1923
2071
  Sort,
1924
2072
  SiloError,
2073
+ SiloCache,
1925
2074
  Silo,
1926
2075
  SearchReach,
1927
2076
  SearchPage,
@@ -9,6 +9,7 @@ import { MediaAsset } from "./media-asset.cjs";
9
9
  export declare class MediaPage extends Page<MediaAsset> {
10
10
  private readonly transport;
11
11
  private readonly wireQuery;
12
+ private readonly requestOptions;
12
13
  private constructor();
13
14
  get files(): readonly MediaAsset[];
14
15
  next(options?: RequestOptions): Promise<MediaPage | null>;
@@ -9,6 +9,7 @@ import { MediaAsset } from "./media-asset.js";
9
9
  export declare class MediaPage extends Page<MediaAsset> {
10
10
  private readonly transport;
11
11
  private readonly wireQuery;
12
+ private readonly requestOptions;
12
13
  private constructor();
13
14
  get files(): readonly MediaAsset[];
14
15
  next(options?: RequestOptions): Promise<MediaPage | null>;
@@ -20,6 +20,7 @@ export interface MediaUsageQuery {
20
20
  export declare class MediaUsagePage extends Page<MediaUsage> {
21
21
  private readonly transport;
22
22
  private readonly assetId;
23
+ private readonly requestOptions;
23
24
  readonly visible: number;
24
25
  readonly visibleCapped: boolean;
25
26
  private constructor();
@@ -20,6 +20,7 @@ export interface MediaUsageQuery {
20
20
  export declare class MediaUsagePage extends Page<MediaUsage> {
21
21
  private readonly transport;
22
22
  private readonly assetId;
23
+ private readonly requestOptions;
23
24
  readonly visible: number;
24
25
  readonly visibleCapped: boolean;
25
26
  private constructor();
@@ -1,3 +1,4 @@
1
+ import type { CacheMode } from "./cache/cache-mode.cjs";
1
2
  /**
2
3
  * The one cancellation convention on every call: the last parameter of
3
4
  * every method in the client. A per-call value wins over the client's own
@@ -6,4 +7,6 @@
6
7
  export interface RequestOptions {
7
8
  signal?: AbortSignal;
8
9
  timeoutMilliseconds?: number;
10
+ /** `bypass` skips lookup and storage; `refresh` fetches and stores. Ignored on writes and disabled caches. */
11
+ cache?: CacheMode;
9
12
  }
@@ -1,3 +1,4 @@
1
+ import type { CacheMode } from "./cache/cache-mode.js";
1
2
  /**
2
3
  * The one cancellation convention on every call: the last parameter of
3
4
  * every method in the client. A per-call value wins over the client's own
@@ -6,4 +7,6 @@
6
7
  export interface RequestOptions {
7
8
  signal?: AbortSignal;
8
9
  timeoutMilliseconds?: number;
10
+ /** `bypass` skips lookup and storage; `refresh` fetches and stores. Ignored on writes and disabled caches. */
11
+ cache?: CacheMode;
9
12
  }
@@ -1,4 +1,6 @@
1
1
  import type { FetchFunction } from "./transport/fetch-function.cjs";
2
+ import type { SiloCache } from "./cache/silo-cache.cjs";
3
+ import type { SiloCacheOptions } from "./cache/silo-cache-options.cjs";
2
4
  /**
3
5
  * Everything `new Silo(...)` accepts. Only `url` is required: a key is
4
6
  * optional because anonymous reads are legal.
@@ -9,4 +11,6 @@ export interface SiloOptions {
9
11
  timeoutMilliseconds?: number;
10
12
  headers?: Record<string, string>;
11
13
  fetch?: FetchFunction;
14
+ /** Enable read caching or share an existing cache. Omit to disable. */
15
+ cache?: SiloCacheOptions | SiloCache;
12
16
  }
@@ -1,4 +1,6 @@
1
1
  import type { FetchFunction } from "./transport/fetch-function.js";
2
+ import type { SiloCache } from "./cache/silo-cache.js";
3
+ import type { SiloCacheOptions } from "./cache/silo-cache-options.js";
2
4
  /**
3
5
  * Everything `new Silo(...)` accepts. Only `url` is required: a key is
4
6
  * optional because anonymous reads are legal.
@@ -9,4 +11,6 @@ export interface SiloOptions {
9
11
  timeoutMilliseconds?: number;
10
12
  headers?: Record<string, string>;
11
13
  fetch?: FetchFunction;
14
+ /** Enable read caching or share an existing cache. Omit to disable. */
15
+ cache?: SiloCacheOptions | SiloCache;
12
16
  }
package/dist/silo.d.cts CHANGED
@@ -7,6 +7,7 @@ import type { EnvironmentHandle } from "./scope/environment-handle.cjs";
7
7
  import type { SearchPage } from "./search/search-page.cjs";
8
8
  import type { SearchQuery } from "./search/search-query.cjs";
9
9
  import type { SiloOptions } from "./silo-options.cjs";
10
+ import { SiloCache } from "./cache/silo-cache.cjs";
10
11
  /**
11
12
  * One silo instance. Navigate it the way the API is shaped:
12
13
  * `silo.project("acme").environment("prod").collection<Post>("posts")`.
@@ -20,6 +21,8 @@ export declare class Silo {
20
21
  readonly projects: Projects;
21
22
  /** The media catalog. Media is instance-global, so it takes no scope. */
22
23
  readonly media: Media;
24
+ /** This instance's optional read cache. */
25
+ readonly cache: SiloCache;
23
26
  private readonly options;
24
27
  private readonly transport;
25
28
  constructor(options: SiloOptions);
package/dist/silo.d.ts CHANGED
@@ -7,6 +7,7 @@ import type { EnvironmentHandle } from "./scope/environment-handle.js";
7
7
  import type { SearchPage } from "./search/search-page.js";
8
8
  import type { SearchQuery } from "./search/search-query.js";
9
9
  import type { SiloOptions } from "./silo-options.js";
10
+ import { SiloCache } from "./cache/silo-cache.js";
10
11
  /**
11
12
  * One silo instance. Navigate it the way the API is shaped:
12
13
  * `silo.project("acme").environment("prod").collection<Post>("posts")`.
@@ -20,6 +21,8 @@ export declare class Silo {
20
21
  readonly projects: Projects;
21
22
  /** The media catalog. Media is instance-global, so it takes no scope. */
22
23
  readonly media: Media;
24
+ /** This instance's optional read cache. */
25
+ readonly cache: SiloCache;
23
26
  private readonly options;
24
27
  private readonly transport;
25
28
  constructor(options: SiloOptions);
@@ -0,0 +1,14 @@
1
+ import { ResponseCache } from "../cache/response-cache.cjs";
2
+ import type { FetchTransport } from "./fetch-transport.cjs";
3
+ import type { TransportRequest } from "./transport-request.cjs";
4
+ import type { Transport } from "./transport.cjs";
5
+ /** Adds opt-in GET JSON caching to the fetch transport. */
6
+ export declare class CachingTransport implements Transport {
7
+ #private;
8
+ constructor(fetchTransport: FetchTransport, cache: ResponseCache);
9
+ json<T>(request: TransportRequest): Promise<T>;
10
+ empty(request: TransportRequest): Promise<void>;
11
+ stream(request: TransportRequest): Promise<ReadableStream<Uint8Array> | null>;
12
+ upload<T>(request: TransportRequest, form: FormData): Promise<T>;
13
+ private write;
14
+ }
@@ -0,0 +1,14 @@
1
+ import { ResponseCache } from "../cache/response-cache.js";
2
+ import type { FetchTransport } from "./fetch-transport.js";
3
+ import type { TransportRequest } from "./transport-request.js";
4
+ import type { Transport } from "./transport.js";
5
+ /** Adds opt-in GET JSON caching to the fetch transport. */
6
+ export declare class CachingTransport implements Transport {
7
+ #private;
8
+ constructor(fetchTransport: FetchTransport, cache: ResponseCache);
9
+ json<T>(request: TransportRequest): Promise<T>;
10
+ empty(request: TransportRequest): Promise<void>;
11
+ stream(request: TransportRequest): Promise<ReadableStream<Uint8Array> | null>;
12
+ upload<T>(request: TransportRequest, form: FormData): Promise<T>;
13
+ private write;
14
+ }
@@ -0,0 +1,31 @@
1
+ import { NetworkError } from "../errors/network-error.cjs";
2
+ import type { FetchFunction } from "./fetch-function.cjs";
3
+ import type { PreparedTransportRequest } from "./prepared-transport-request.cjs";
4
+ import type { TransportRequest } from "./transport-request.cjs";
5
+ import type { Transport } from "./transport.cjs";
6
+ export interface FetchTransportOptions {
7
+ url: string;
8
+ key?: string;
9
+ headers?: Record<string, string>;
10
+ timeoutMilliseconds?: number;
11
+ fetch?: FetchFunction;
12
+ }
13
+ /** The fetch-backed implementation behind the internal transport seam. */
14
+ export declare class FetchTransport implements Transport {
15
+ #private;
16
+ constructor(options: FetchTransportOptions);
17
+ json<T>(request: TransportRequest): Promise<T>;
18
+ jsonPrepared<T>(request: TransportRequest, prepared: PreparedTransportRequest): Promise<T>;
19
+ empty(request: TransportRequest): Promise<void>;
20
+ stream(request: TransportRequest): Promise<ReadableStream<Uint8Array> | null>;
21
+ upload<T>(request: TransportRequest, form: FormData): Promise<T>;
22
+ withKey(key: string | undefined): FetchTransport;
23
+ withUrl(url: string): FetchTransport;
24
+ prepare(request: TransportRequest, form?: FormData): PreparedTransportRequest;
25
+ preparationFailure(request: TransportRequest, caught: unknown): NetworkError;
26
+ private snapshot;
27
+ private execute;
28
+ private transportFailure;
29
+ private static normalizeUrl;
30
+ private static resolveFetch;
31
+ }
@@ -0,0 +1,31 @@
1
+ import { NetworkError } from "../errors/network-error.js";
2
+ import type { FetchFunction } from "./fetch-function.js";
3
+ import type { PreparedTransportRequest } from "./prepared-transport-request.js";
4
+ import type { TransportRequest } from "./transport-request.js";
5
+ import type { Transport } from "./transport.js";
6
+ export interface FetchTransportOptions {
7
+ url: string;
8
+ key?: string;
9
+ headers?: Record<string, string>;
10
+ timeoutMilliseconds?: number;
11
+ fetch?: FetchFunction;
12
+ }
13
+ /** The fetch-backed implementation behind the internal transport seam. */
14
+ export declare class FetchTransport implements Transport {
15
+ #private;
16
+ constructor(options: FetchTransportOptions);
17
+ json<T>(request: TransportRequest): Promise<T>;
18
+ jsonPrepared<T>(request: TransportRequest, prepared: PreparedTransportRequest): Promise<T>;
19
+ empty(request: TransportRequest): Promise<void>;
20
+ stream(request: TransportRequest): Promise<ReadableStream<Uint8Array> | null>;
21
+ upload<T>(request: TransportRequest, form: FormData): Promise<T>;
22
+ withKey(key: string | undefined): FetchTransport;
23
+ withUrl(url: string): FetchTransport;
24
+ prepare(request: TransportRequest, form?: FormData): PreparedTransportRequest;
25
+ preparationFailure(request: TransportRequest, caught: unknown): NetworkError;
26
+ private snapshot;
27
+ private execute;
28
+ private transportFailure;
29
+ private static normalizeUrl;
30
+ private static resolveFetch;
31
+ }
@@ -0,0 +1,6 @@
1
+ /** One request after its final URL, headers and body have been prepared. */
2
+ export interface PreparedTransportRequest {
3
+ url: string;
4
+ headers: Headers;
5
+ body: BodyInit | undefined;
6
+ }
@@ -0,0 +1,6 @@
1
+ /** One request after its final URL, headers and body have been prepared. */
2
+ export interface PreparedTransportRequest {
3
+ url: string;
4
+ headers: Headers;
5
+ body: BodyInit | undefined;
6
+ }