@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.
- package/README.md +49 -1
- package/dist/cache/cache-mode.d.cts +2 -0
- package/dist/cache/cache-mode.d.ts +2 -0
- package/dist/cache/response-cache.d.cts +16 -0
- package/dist/cache/response-cache.d.ts +16 -0
- package/dist/cache/silo-cache-options.d.cts +7 -0
- package/dist/cache/silo-cache-options.d.ts +7 -0
- package/dist/cache/silo-cache.d.cts +11 -0
- package/dist/cache/silo-cache.d.ts +11 -0
- package/dist/errors/network-error.d.cts +5 -0
- package/dist/errors/network-error.d.ts +5 -0
- package/dist/index.cjs +264 -74
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +264 -74
- package/dist/media/media-asset.d.cts +17 -0
- package/dist/media/media-asset.d.ts +17 -0
- package/dist/media/media-file.d.cts +16 -0
- package/dist/media/media-file.d.ts +16 -0
- package/dist/media/media-page.d.cts +1 -0
- package/dist/media/media-page.d.ts +1 -0
- package/dist/media/media-replace.d.cts +17 -0
- package/dist/media/media-replace.d.ts +17 -0
- package/dist/media/media-usage-page.d.cts +1 -0
- package/dist/media/media-usage-page.d.ts +1 -0
- package/dist/media/media.d.cts +0 -4
- package/dist/media/media.d.ts +0 -4
- package/dist/request-options.d.cts +3 -0
- package/dist/request-options.d.ts +3 -0
- package/dist/silo-options.d.cts +4 -0
- package/dist/silo-options.d.ts +4 -0
- package/dist/silo.d.cts +3 -0
- package/dist/silo.d.ts +3 -0
- package/dist/transport/api-path.d.cts +1 -0
- package/dist/transport/api-path.d.ts +1 -0
- package/dist/transport/caching-transport.d.cts +14 -0
- package/dist/transport/caching-transport.d.ts +14 -0
- package/dist/transport/fetch-transport.d.cts +31 -0
- package/dist/transport/fetch-transport.d.ts +31 -0
- package/dist/transport/prepared-transport-request.d.cts +6 -0
- package/dist/transport/prepared-transport-request.d.ts +6 -0
- package/dist/transport/transport-request.d.cts +2 -0
- package/dist/transport/transport-request.d.ts +2 -0
- package/dist/transport/transport.d.cts +2 -39
- package/dist/transport/transport.d.ts +2 -39
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -84,6 +84,9 @@ class ApiPath {
|
|
|
84
84
|
static mediaAssetUsages(id) {
|
|
85
85
|
return `${ApiPath.mediaAsset(id)}/usages`;
|
|
86
86
|
}
|
|
87
|
+
static mediaAssetContent(id) {
|
|
88
|
+
return `${ApiPath.mediaAsset(id)}/content`;
|
|
89
|
+
}
|
|
87
90
|
static mediaBulkDelete() {
|
|
88
91
|
return "/api/media/delete";
|
|
89
92
|
}
|
|
@@ -130,6 +133,22 @@ class MediaAssetMapper {
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
// src/media/media-file.ts
|
|
137
|
+
class MediaFile {
|
|
138
|
+
static toBlob(bytes, contentType) {
|
|
139
|
+
if (bytes instanceof Blob)
|
|
140
|
+
return bytes;
|
|
141
|
+
return new Blob([bytes], contentType ? { type: contentType } : undefined);
|
|
142
|
+
}
|
|
143
|
+
static nameOf(input, explicit) {
|
|
144
|
+
if (explicit)
|
|
145
|
+
return explicit;
|
|
146
|
+
if (input instanceof File && input.name)
|
|
147
|
+
return input.name;
|
|
148
|
+
throw new Error("media: a Blob has no filename — pass { filename } explicitly");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
// src/media/media-reference.ts
|
|
134
153
|
class MediaReference {
|
|
135
154
|
static Scheme = "silo://media/";
|
|
@@ -189,12 +208,14 @@ var DefaultUsageLimit = 50;
|
|
|
189
208
|
class MediaUsagePage extends Page {
|
|
190
209
|
transport;
|
|
191
210
|
assetId;
|
|
211
|
+
requestOptions;
|
|
192
212
|
visible;
|
|
193
213
|
visibleCapped;
|
|
194
|
-
constructor(rows, total, visible, visibleCapped, window, transport, assetId) {
|
|
214
|
+
constructor(rows, total, visible, visibleCapped, window, transport, assetId, requestOptions) {
|
|
195
215
|
super(rows, total, window);
|
|
196
216
|
this.transport = transport;
|
|
197
217
|
this.assetId = assetId;
|
|
218
|
+
this.requestOptions = requestOptions;
|
|
198
219
|
this.visible = visible;
|
|
199
220
|
this.visibleCapped = visibleCapped;
|
|
200
221
|
}
|
|
@@ -209,11 +230,11 @@ class MediaUsagePage extends Page {
|
|
|
209
230
|
}
|
|
210
231
|
async next(options) {
|
|
211
232
|
const window = this.windowForNext();
|
|
212
|
-
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;
|
|
213
234
|
}
|
|
214
235
|
async previous(options) {
|
|
215
236
|
const window = this.window.previous();
|
|
216
|
-
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;
|
|
217
238
|
}
|
|
218
239
|
static load(transport, assetId, query, options) {
|
|
219
240
|
const window = new PageWindow(query.limit ?? DefaultUsageLimit, query.offset ?? 0);
|
|
@@ -225,10 +246,11 @@ class MediaUsagePage extends Page {
|
|
|
225
246
|
path: ApiPath.mediaAssetUsages(assetId),
|
|
226
247
|
query: { limit: window.limit, offset: window.offset },
|
|
227
248
|
signal: options?.signal,
|
|
228
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
249
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
250
|
+
cache: options?.cache
|
|
229
251
|
});
|
|
230
252
|
const mapped = MediaAssetMapper.toUsagePage(body);
|
|
231
|
-
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);
|
|
232
254
|
}
|
|
233
255
|
}
|
|
234
256
|
|
|
@@ -291,6 +313,22 @@ class MediaAsset {
|
|
|
291
313
|
async setTags(tags, options) {
|
|
292
314
|
return this.patch({ tags: [...tags] }, options);
|
|
293
315
|
}
|
|
316
|
+
async replace(input, options) {
|
|
317
|
+
const form = new FormData;
|
|
318
|
+
if (input instanceof Blob) {
|
|
319
|
+
form.set("file", input, MediaFile.nameOf(input, options?.filename));
|
|
320
|
+
} else {
|
|
321
|
+
form.set("file", MediaFile.toBlob(input.bytes, input.contentType), input.filename);
|
|
322
|
+
}
|
|
323
|
+
const payload = await this.transport.upload({
|
|
324
|
+
method: "POST",
|
|
325
|
+
path: ApiPath.mediaAssetContent(this.id),
|
|
326
|
+
signal: options?.signal,
|
|
327
|
+
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
328
|
+
}, form);
|
|
329
|
+
this.record = MediaAssetMapper.toRecord(payload);
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
294
332
|
async delete(options) {
|
|
295
333
|
await this.transport.empty({
|
|
296
334
|
method: "DELETE",
|
|
@@ -308,7 +346,8 @@ class MediaAsset {
|
|
|
308
346
|
method: "GET",
|
|
309
347
|
path: ApiPath.mediaAsset(this.id),
|
|
310
348
|
signal: options?.signal,
|
|
311
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
349
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
350
|
+
cache: options?.cache ?? "refresh"
|
|
312
351
|
});
|
|
313
352
|
this.record = MediaAssetMapper.toRecord(payload);
|
|
314
353
|
return this;
|
|
@@ -370,7 +409,8 @@ class MediaFolders {
|
|
|
370
409
|
method: "GET",
|
|
371
410
|
path: ApiPath.mediaFolders(),
|
|
372
411
|
signal: options?.signal,
|
|
373
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
412
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
413
|
+
cache: options?.cache
|
|
374
414
|
});
|
|
375
415
|
return body.items;
|
|
376
416
|
}
|
|
@@ -412,21 +452,23 @@ class MediaFolders {
|
|
|
412
452
|
class MediaPage extends Page {
|
|
413
453
|
transport;
|
|
414
454
|
wireQuery;
|
|
415
|
-
|
|
455
|
+
requestOptions;
|
|
456
|
+
constructor(rows, total, window, transport, wireQuery, requestOptions) {
|
|
416
457
|
super(rows, total, window);
|
|
417
458
|
this.transport = transport;
|
|
418
459
|
this.wireQuery = wireQuery;
|
|
460
|
+
this.requestOptions = requestOptions;
|
|
419
461
|
}
|
|
420
462
|
get files() {
|
|
421
463
|
return this.rows;
|
|
422
464
|
}
|
|
423
465
|
async next(options) {
|
|
424
466
|
const window = this.windowForNext();
|
|
425
|
-
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;
|
|
426
468
|
}
|
|
427
469
|
async previous(options) {
|
|
428
470
|
const window = this.window.previous();
|
|
429
|
-
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;
|
|
430
472
|
}
|
|
431
473
|
static async loadWindow(transport, wireQuery, window, options) {
|
|
432
474
|
const body = await transport.json({
|
|
@@ -434,10 +476,11 @@ class MediaPage extends Page {
|
|
|
434
476
|
path: ApiPath.media(),
|
|
435
477
|
query: { ...wireQuery, limit: window.limit, offset: window.offset },
|
|
436
478
|
signal: options?.signal,
|
|
437
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
479
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
480
|
+
cache: options?.cache
|
|
438
481
|
});
|
|
439
482
|
const rows = body.items.map((payload) => new MediaAsset(transport, MediaAssetMapper.toRecord(payload)));
|
|
440
|
-
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);
|
|
441
484
|
}
|
|
442
485
|
}
|
|
443
486
|
|
|
@@ -524,11 +567,11 @@ class Media {
|
|
|
524
567
|
let options;
|
|
525
568
|
if (input instanceof Blob) {
|
|
526
569
|
const fileOptions = second ?? {};
|
|
527
|
-
form.set("file", input,
|
|
570
|
+
form.set("file", input, MediaFile.nameOf(input, fileOptions.filename));
|
|
528
571
|
folder = fileOptions.folder;
|
|
529
572
|
options = third ?? fileOptions;
|
|
530
573
|
} else {
|
|
531
|
-
form.set("file",
|
|
574
|
+
form.set("file", MediaFile.toBlob(input.bytes, input.contentType), input.filename);
|
|
532
575
|
folder = input.folder;
|
|
533
576
|
options = second;
|
|
534
577
|
}
|
|
@@ -557,7 +600,8 @@ class Media {
|
|
|
557
600
|
method: "GET",
|
|
558
601
|
path: ApiPath.mediaAsset(id),
|
|
559
602
|
signal: options?.signal,
|
|
560
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
603
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
604
|
+
cache: options?.cache
|
|
561
605
|
});
|
|
562
606
|
return new MediaAsset(this.transport, MediaAssetMapper.toRecord(payload));
|
|
563
607
|
}
|
|
@@ -566,7 +610,8 @@ class Media {
|
|
|
566
610
|
method: "GET",
|
|
567
611
|
path: ApiPath.mediaExtensions(),
|
|
568
612
|
signal: options?.signal,
|
|
569
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
613
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
614
|
+
cache: options?.cache
|
|
570
615
|
});
|
|
571
616
|
return body.items;
|
|
572
617
|
}
|
|
@@ -596,18 +641,6 @@ class Media {
|
|
|
596
641
|
sort: query.sort
|
|
597
642
|
};
|
|
598
643
|
}
|
|
599
|
-
static toBlob(bytes, contentType) {
|
|
600
|
-
if (bytes instanceof Blob)
|
|
601
|
-
return bytes;
|
|
602
|
-
return new Blob([bytes], contentType ? { type: contentType } : undefined);
|
|
603
|
-
}
|
|
604
|
-
static filenameOf(input, explicit) {
|
|
605
|
-
if (explicit)
|
|
606
|
-
return explicit;
|
|
607
|
-
if (input instanceof File && input.name)
|
|
608
|
-
return input.name;
|
|
609
|
-
throw new Error("media upload: a Blob has no filename — pass { filename } explicitly");
|
|
610
|
-
}
|
|
611
644
|
}
|
|
612
645
|
|
|
613
646
|
// src/variables/variable.ts
|
|
@@ -1291,6 +1324,132 @@ class Projects {
|
|
|
1291
1324
|
}
|
|
1292
1325
|
}
|
|
1293
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
|
+
|
|
1294
1453
|
// src/errors/silo-error.ts
|
|
1295
1454
|
class SiloError extends Error {
|
|
1296
1455
|
status;
|
|
@@ -1471,12 +1630,19 @@ class NetworkError extends Error {
|
|
|
1471
1630
|
method;
|
|
1472
1631
|
path;
|
|
1473
1632
|
constructor(method, path, cause) {
|
|
1474
|
-
super(`network error on ${method} ${path}: the request never reached the server`, { cause });
|
|
1633
|
+
super(`network error on ${method} ${path}: the request never reached the server${NetworkError.reason(cause)}`, { cause });
|
|
1475
1634
|
this.name = "NetworkError";
|
|
1476
1635
|
this.method = method;
|
|
1477
1636
|
this.path = path;
|
|
1478
1637
|
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
1479
1638
|
}
|
|
1639
|
+
static reason(cause) {
|
|
1640
|
+
if (cause instanceof Error && cause.message)
|
|
1641
|
+
return ` (${cause.message})`;
|
|
1642
|
+
if (typeof cause === "string" && cause)
|
|
1643
|
+
return ` (${cause})`;
|
|
1644
|
+
return "";
|
|
1645
|
+
}
|
|
1480
1646
|
}
|
|
1481
1647
|
|
|
1482
1648
|
// src/errors/timeout-error.ts
|
|
@@ -1578,60 +1744,80 @@ class ResponseDecoder {
|
|
|
1578
1744
|
}
|
|
1579
1745
|
}
|
|
1580
1746
|
|
|
1581
|
-
// src/transport/transport.ts
|
|
1582
|
-
class
|
|
1583
|
-
url;
|
|
1584
|
-
key;
|
|
1585
|
-
headers;
|
|
1586
|
-
timeoutMilliseconds;
|
|
1587
|
-
fetchFunction;
|
|
1747
|
+
// src/transport/fetch-transport.ts
|
|
1748
|
+
class FetchTransport {
|
|
1749
|
+
#url;
|
|
1750
|
+
#key;
|
|
1751
|
+
#headers;
|
|
1752
|
+
#timeoutMilliseconds;
|
|
1753
|
+
#fetchFunction;
|
|
1588
1754
|
constructor(options) {
|
|
1589
|
-
this
|
|
1590
|
-
this
|
|
1591
|
-
this
|
|
1592
|
-
this
|
|
1593
|
-
this
|
|
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);
|
|
1594
1760
|
}
|
|
1595
1761
|
async json(request) {
|
|
1596
1762
|
const response = await this.execute(request);
|
|
1597
1763
|
return await ResponseDecoder.decode(response, request, true);
|
|
1598
1764
|
}
|
|
1765
|
+
async jsonPrepared(request, prepared) {
|
|
1766
|
+
const response = await this.execute(request, prepared);
|
|
1767
|
+
return await ResponseDecoder.decode(response, request, true);
|
|
1768
|
+
}
|
|
1599
1769
|
async empty(request) {
|
|
1600
1770
|
const response = await this.execute(request);
|
|
1601
1771
|
await ResponseDecoder.decode(response, request, false);
|
|
1602
1772
|
}
|
|
1603
1773
|
async stream(request) {
|
|
1604
|
-
|
|
1605
|
-
return response.body;
|
|
1774
|
+
return (await this.execute(request)).body;
|
|
1606
1775
|
}
|
|
1607
1776
|
async upload(request, form) {
|
|
1608
|
-
const response = await this.execute(request, form);
|
|
1777
|
+
const response = await this.execute(request, undefined, form);
|
|
1609
1778
|
return await ResponseDecoder.decode(response, request, true);
|
|
1610
1779
|
}
|
|
1611
1780
|
withKey(key) {
|
|
1612
|
-
return new
|
|
1781
|
+
return new FetchTransport({ ...this.snapshot(), key });
|
|
1613
1782
|
}
|
|
1614
1783
|
withUrl(url) {
|
|
1615
|
-
return new
|
|
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);
|
|
1616
1801
|
}
|
|
1617
1802
|
snapshot() {
|
|
1618
1803
|
return {
|
|
1619
|
-
url: this
|
|
1620
|
-
key: this
|
|
1621
|
-
headers: this
|
|
1622
|
-
timeoutMilliseconds: this
|
|
1623
|
-
fetch: this
|
|
1804
|
+
url: this.#url,
|
|
1805
|
+
key: this.#key,
|
|
1806
|
+
headers: this.#headers,
|
|
1807
|
+
timeoutMilliseconds: this.#timeoutMilliseconds,
|
|
1808
|
+
fetch: this.#fetchFunction
|
|
1624
1809
|
};
|
|
1625
1810
|
}
|
|
1626
|
-
async execute(request, form) {
|
|
1627
|
-
const abortSignals = new AbortSignals(request.signal, request.timeoutMilliseconds ?? this
|
|
1628
|
-
const
|
|
1811
|
+
async execute(request, prepared, form) {
|
|
1812
|
+
const abortSignals = new AbortSignals(request.signal, request.timeoutMilliseconds ?? this.#timeoutMilliseconds);
|
|
1813
|
+
const sendRequest = this.#fetchFunction;
|
|
1629
1814
|
let response;
|
|
1630
1815
|
try {
|
|
1631
|
-
|
|
1816
|
+
const outbound = prepared ?? this.prepare(request, form);
|
|
1817
|
+
response = await sendRequest(outbound.url, {
|
|
1632
1818
|
method: request.method,
|
|
1633
|
-
headers:
|
|
1634
|
-
body:
|
|
1819
|
+
headers: outbound.headers,
|
|
1820
|
+
body: outbound.body,
|
|
1635
1821
|
signal: abortSignals.signal
|
|
1636
1822
|
});
|
|
1637
1823
|
} catch (caught) {
|
|
@@ -1645,44 +1831,44 @@ class Transport {
|
|
|
1645
1831
|
return response;
|
|
1646
1832
|
}
|
|
1647
1833
|
transportFailure(request, abortSignals, caught) {
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
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);
|
|
1651
1836
|
}
|
|
1652
|
-
if (firedBy === "caller")
|
|
1837
|
+
if (abortSignals.firedBy() === "caller")
|
|
1653
1838
|
return new RequestAbortedError(request.method, request.path);
|
|
1654
|
-
}
|
|
1655
1839
|
return new NetworkError(request.method, request.path, caught);
|
|
1656
1840
|
}
|
|
1657
|
-
buildHeaders(request, isUpload) {
|
|
1658
|
-
const headers = { ...this.headers, ...request.headers };
|
|
1659
|
-
if (this.key)
|
|
1660
|
-
headers["Authorization"] = `Bearer ${this.key}`;
|
|
1661
|
-
if (!isUpload && request.body !== undefined && !("Content-Type" in headers)) {
|
|
1662
|
-
headers["Content-Type"] = "application/json";
|
|
1663
|
-
}
|
|
1664
|
-
return headers;
|
|
1665
|
-
}
|
|
1666
1841
|
static normalizeUrl(url) {
|
|
1667
1842
|
return url.replace(/\/+$/, "");
|
|
1668
1843
|
}
|
|
1844
|
+
static resolveFetch(fetchFunction) {
|
|
1845
|
+
if (fetchFunction)
|
|
1846
|
+
return fetchFunction;
|
|
1847
|
+
if (typeof globalThis.fetch !== "function") {
|
|
1848
|
+
throw new Error("this runtime has no global fetch: pass one as SiloOptions.fetch");
|
|
1849
|
+
}
|
|
1850
|
+
return globalThis.fetch.bind(globalThis);
|
|
1851
|
+
}
|
|
1669
1852
|
}
|
|
1670
1853
|
|
|
1671
1854
|
// src/silo.ts
|
|
1672
1855
|
class Silo {
|
|
1673
1856
|
projects;
|
|
1674
1857
|
media;
|
|
1858
|
+
cache;
|
|
1675
1859
|
options;
|
|
1676
1860
|
transport;
|
|
1677
1861
|
constructor(options) {
|
|
1678
|
-
this.
|
|
1679
|
-
this.
|
|
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({
|
|
1680
1865
|
url: options.url,
|
|
1681
1866
|
key: options.key,
|
|
1682
1867
|
headers: options.headers,
|
|
1683
1868
|
timeoutMilliseconds: options.timeoutMilliseconds,
|
|
1684
1869
|
fetch: options.fetch
|
|
1685
1870
|
});
|
|
1871
|
+
this.transport = this.cache.wrap(fetchTransport);
|
|
1686
1872
|
this.projects = new Projects(this.transport);
|
|
1687
1873
|
this.media = new Media(this.transport);
|
|
1688
1874
|
}
|
|
@@ -1700,7 +1886,8 @@ class Silo {
|
|
|
1700
1886
|
method: "GET",
|
|
1701
1887
|
path: ApiPath.health(),
|
|
1702
1888
|
signal: options?.signal,
|
|
1703
|
-
timeoutMilliseconds: options?.timeoutMilliseconds
|
|
1889
|
+
timeoutMilliseconds: options?.timeoutMilliseconds,
|
|
1890
|
+
cache: "bypass"
|
|
1704
1891
|
});
|
|
1705
1892
|
}
|
|
1706
1893
|
withKey(key) {
|
|
@@ -1750,6 +1937,7 @@ class RouteInventory {
|
|
|
1750
1937
|
"PATCH /api/media/:id",
|
|
1751
1938
|
"DELETE /api/media/:id",
|
|
1752
1939
|
"GET /api/media/:id/usages",
|
|
1940
|
+
"POST /api/media/:id/content",
|
|
1753
1941
|
"POST /api/media/delete",
|
|
1754
1942
|
"GET /api/media/folders",
|
|
1755
1943
|
"POST /api/media/folders",
|
|
@@ -1760,6 +1948,7 @@ class RouteInventory {
|
|
|
1760
1948
|
"GET /api/session": "key introspection, an operator surface",
|
|
1761
1949
|
"GET /api/keys": "keys and claims are an operator surface",
|
|
1762
1950
|
"POST /api/keys": "keys and claims are an operator surface",
|
|
1951
|
+
"PATCH /api/keys/:id": "keys and claims are an operator surface",
|
|
1763
1952
|
"DELETE /api/keys/:id": "keys and claims are an operator surface",
|
|
1764
1953
|
"GET /api/audit": "an operator surface",
|
|
1765
1954
|
"GET /api/observability": "an operator surface",
|
|
@@ -1881,6 +2070,7 @@ export {
|
|
|
1881
2070
|
SortTerm,
|
|
1882
2071
|
Sort,
|
|
1883
2072
|
SiloError,
|
|
2073
|
+
SiloCache,
|
|
1884
2074
|
Silo,
|
|
1885
2075
|
SearchReach,
|
|
1886
2076
|
SearchPage,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestOptions } from "../request-options.cjs";
|
|
2
2
|
import type { Transport } from "../transport/transport.cjs";
|
|
3
3
|
import type { MediaDeleteOptions } from "./media-delete-options.cjs";
|
|
4
|
+
import type { MediaReplace, MediaReplaceOptions } from "./media-replace.cjs";
|
|
4
5
|
import type { MediaUsageQuery } from "./media-usage-page.cjs";
|
|
5
6
|
import { MediaUsagePage } from "./media-usage-page.cjs";
|
|
6
7
|
/** `MediaAsset`'s mapped state — `MediaAssetMapper.toRecord`'s output. */
|
|
@@ -53,6 +54,22 @@ export declare class MediaAsset {
|
|
|
53
54
|
moveTo(folder: string, options?: RequestOptions): Promise<this>;
|
|
54
55
|
/** REPLACES the tag list — `PATCH` replaces, it does not append. */
|
|
55
56
|
setTags(tags: readonly string[], options?: RequestOptions): Promise<this>;
|
|
57
|
+
/**
|
|
58
|
+
* Swaps the bytes behind this asset (D67). `id`, `reference`, `url`,
|
|
59
|
+
* `filename` and `folder` all survive; `hash`, `sizeInBytes` and
|
|
60
|
+
* `contentType` are re-read from the server's answer.
|
|
61
|
+
*
|
|
62
|
+
* Every entry referencing it now resolves to the new file, without any of
|
|
63
|
+
* them being rewritten — which is the point, and the reason the server asks
|
|
64
|
+
* for `media:replace` **and** `entries:update` at every scope that refers
|
|
65
|
+
* to it.
|
|
66
|
+
*
|
|
67
|
+
* The new file must keep the current extension: the blob key's suffix is
|
|
68
|
+
* derived from the filename at upload and is the visible tail of the URL on
|
|
69
|
+
* a bucket-backed instance, so a `.png` asset takes a `.png` replacement.
|
|
70
|
+
* Converting a file is a new upload, not a replacement of this one.
|
|
71
|
+
*/
|
|
72
|
+
replace(input: MediaReplace, options?: MediaReplaceOptions): Promise<this>;
|
|
56
73
|
/** Refused while an entry references it; `{ force: true }` also needs
|
|
57
74
|
* `entries:update` on every scope this asset reaches. */
|
|
58
75
|
delete(options?: MediaDeleteOptions): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestOptions } from "../request-options.js";
|
|
2
2
|
import type { Transport } from "../transport/transport.js";
|
|
3
3
|
import type { MediaDeleteOptions } from "./media-delete-options.js";
|
|
4
|
+
import type { MediaReplace, MediaReplaceOptions } from "./media-replace.js";
|
|
4
5
|
import type { MediaUsageQuery } from "./media-usage-page.js";
|
|
5
6
|
import { MediaUsagePage } from "./media-usage-page.js";
|
|
6
7
|
/** `MediaAsset`'s mapped state — `MediaAssetMapper.toRecord`'s output. */
|
|
@@ -53,6 +54,22 @@ export declare class MediaAsset {
|
|
|
53
54
|
moveTo(folder: string, options?: RequestOptions): Promise<this>;
|
|
54
55
|
/** REPLACES the tag list — `PATCH` replaces, it does not append. */
|
|
55
56
|
setTags(tags: readonly string[], options?: RequestOptions): Promise<this>;
|
|
57
|
+
/**
|
|
58
|
+
* Swaps the bytes behind this asset (D67). `id`, `reference`, `url`,
|
|
59
|
+
* `filename` and `folder` all survive; `hash`, `sizeInBytes` and
|
|
60
|
+
* `contentType` are re-read from the server's answer.
|
|
61
|
+
*
|
|
62
|
+
* Every entry referencing it now resolves to the new file, without any of
|
|
63
|
+
* them being rewritten — which is the point, and the reason the server asks
|
|
64
|
+
* for `media:replace` **and** `entries:update` at every scope that refers
|
|
65
|
+
* to it.
|
|
66
|
+
*
|
|
67
|
+
* The new file must keep the current extension: the blob key's suffix is
|
|
68
|
+
* derived from the filename at upload and is the visible tail of the URL on
|
|
69
|
+
* a bucket-backed instance, so a `.png` asset takes a `.png` replacement.
|
|
70
|
+
* Converting a file is a new upload, not a replacement of this one.
|
|
71
|
+
*/
|
|
72
|
+
replace(input: MediaReplace, options?: MediaReplaceOptions): Promise<this>;
|
|
56
73
|
/** Refused while an entry references it; `{ force: true }` also needs
|
|
57
74
|
* `entries:update` on every scope this asset reaches. */
|
|
58
75
|
delete(options?: MediaDeleteOptions): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning what a caller passed into the two things a multipart part needs: a
|
|
3
|
+
* `Blob` and a filename.
|
|
4
|
+
*
|
|
5
|
+
* Shared by `Media.upload` and `MediaAsset.replace` (D67) rather than copied
|
|
6
|
+
* into the second. The filename is not cosmetic on either path — the server
|
|
7
|
+
* reads the extension off it to decide what the library accepts, and on a
|
|
8
|
+
* replace to decide whether the file's type is changing at all — so the two
|
|
9
|
+
* entry points must derive it the same way.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MediaFile {
|
|
12
|
+
static toBlob(bytes: Uint8Array | ArrayBuffer | Blob, contentType?: string): Blob;
|
|
13
|
+
/** A `File` carries its own name; a bare `Blob` does not, and sending it
|
|
14
|
+
* as "blob" is worse than refusing outright. */
|
|
15
|
+
static nameOf(input: Blob, explicit?: string): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning what a caller passed into the two things a multipart part needs: a
|
|
3
|
+
* `Blob` and a filename.
|
|
4
|
+
*
|
|
5
|
+
* Shared by `Media.upload` and `MediaAsset.replace` (D67) rather than copied
|
|
6
|
+
* into the second. The filename is not cosmetic on either path — the server
|
|
7
|
+
* reads the extension off it to decide what the library accepts, and on a
|
|
8
|
+
* replace to decide whether the file's type is changing at all — so the two
|
|
9
|
+
* entry points must derive it the same way.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MediaFile {
|
|
12
|
+
static toBlob(bytes: Uint8Array | ArrayBuffer | Blob, contentType?: string): Blob;
|
|
13
|
+
/** A `File` carries its own name; a bare `Blob` does not, and sending it
|
|
14
|
+
* as "blob" is worse than refusing outright. */
|
|
15
|
+
static nameOf(input: Blob, explicit?: string): string;
|
|
16
|
+
}
|
|
@@ -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>;
|