@blamejs/blamejs-shop 0.0.82 → 0.0.85

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.
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.archive.adapters
4
+ * @nav Tools
5
+ * @title Archive Adapters
6
+ *
7
+ * @intro
8
+ * Source-bytes adapter contract for the `b.archive.read` family.
9
+ * Unifies how bytes flow into the reader regardless of where they
10
+ * live — a local file, an object-store bucket, an HTTP endpoint with
11
+ * Range support, an in-memory Buffer, or a trusted Readable.
12
+ *
13
+ * Two contract shapes, picked by the caller's use case:
14
+ *
15
+ * - **Random-access** — `{ size, range(offset, length) → Buffer }`
16
+ * Required for the read primitive's CD-walk path (the canonical
17
+ * adversarial-safe ZIP read). The reader fetches the EOCD
18
+ * trailer first (last ~64 KiB), walks the central directory,
19
+ * then per-entry seeks the LFH + compressed bytes. Defends the
20
+ * LFH/CD-skew + Zip-Slip + zip-bomb classes by validating every
21
+ * claim before decompressing.
22
+ *
23
+ * - **Trusted sequential** — `{ readable: <Readable> }`
24
+ * Forward-scan-only fallback for operators who control both ends
25
+ * (e.g. piping the framework's own `b.archive.zip().toStream()`
26
+ * back into a reader 30 seconds later). The reader walks local
27
+ * file headers in order; the CD/LFH skew defense + the
28
+ * "entries hidden from LFH but present in CD" attack class are
29
+ * OFF in this mode because there's no central directory to
30
+ * compare against. The trust boundary is in the API surface
31
+ * name — operators reaching for `trustedStream` are declaring
32
+ * they own the producer.
33
+ *
34
+ * AbortSignal is propagated end-to-end: every adapter accepts an
35
+ * `opts.signal` parameter; in-flight `range` calls abort when the
36
+ * caller cancels. Adapters refuse to return short reads silently —
37
+ * a 5-byte request that fulfills 3 bytes throws `adapter/short-read`
38
+ * so the reader can decide whether to refuse the archive or surface
39
+ * the truncation.
40
+ *
41
+ * Shipped adapters:
42
+ *
43
+ * b.archive.adapters.fs(path, opts?) — local file
44
+ * b.archive.adapters.buffer(buf, opts?) — in-memory
45
+ * b.archive.adapters.objectStore(client, key, opts?)
46
+ * — composes b.objectStore
47
+ * Range-GET path
48
+ * b.archive.adapters.http(url, opts?) — composes b.httpClient
49
+ * with Range: bytes= …
50
+ * b.archive.adapters.trustedStream(readable, opts?)
51
+ * — Readable fallback
52
+ *
53
+ * `objectStore` + `http` are composition entry points — operators
54
+ * wire their own `b.objectStore` client / `b.httpClient` instance in
55
+ * so the adapter inherits the framework's SSRF guard / TLS posture /
56
+ * audit chain without duplicating that surface here.
57
+ *
58
+ * @card
59
+ * Source-bytes adapter contract for the b.archive read family — fs / objectStore / http / buffer / trustedStream.
60
+ */
61
+
62
+ var nodeFs = require("node:fs");
63
+ var nodeStream = require("node:stream");
64
+ var lazyRequire = require("./lazy-require");
65
+ var validateOpts = require("./validate-opts");
66
+ var numericBounds = require("./numeric-bounds");
67
+ var safeBuffer = require("./safe-buffer");
68
+ var C = require("./constants");
69
+ var { defineClass } = require("./framework-error");
70
+
71
+ void numericBounds;
72
+ void validateOpts;
73
+
74
+ var AdapterError = defineClass("AdapterError", { alwaysPermanent: true });
75
+
76
+ // Lazy because httpClient + objectStore pull in TLS / SSRF surface
77
+ // the adapter caller may not need (e.g. tests that only use the fs +
78
+ // buffer adapters).
79
+ var httpClient = lazyRequire(function () { return require("./http-client"); });
80
+ void httpClient;
81
+
82
+ // ---- Shared validation helpers --------------------------------------------
83
+
84
+ function _assertNonNegativeInteger(value, label) {
85
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
86
+ throw new AdapterError("adapter/bad-arg",
87
+ label + " must be a non-negative integer (got " + value + ")");
88
+ }
89
+ }
90
+
91
+ function _assertPositiveInteger(value, label) {
92
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
93
+ throw new AdapterError("adapter/bad-arg",
94
+ label + " must be a positive integer (got " + value + ")");
95
+ }
96
+ }
97
+
98
+ function _checkSignal(signal, where) {
99
+ if (signal && signal.aborted) {
100
+ var reason = signal.reason || new AdapterError("adapter/aborted", where + ": adapter aborted by operator");
101
+ throw reason;
102
+ }
103
+ }
104
+
105
+ // ---- fs adapter -----------------------------------------------------------
106
+
107
+ /**
108
+ * @primitive b.archive.adapters.fs
109
+ * @signature b.archive.adapters.fs(path, opts?)
110
+ * @since 0.12.7
111
+ * @status stable
112
+ * @related b.archive.adapters.objectStore, b.archive.adapters.http, b.archive.adapters.buffer
113
+ *
114
+ * Local-file random-access adapter. Opens a read-only file descriptor
115
+ * + fstats the size at adapter-create time so the reader's CD walk
116
+ * can begin with the trailer offset known up-front. Subsequent
117
+ * `range(offset, length)` calls reuse the same fd — operators
118
+ * extracting an archive don't pay a fresh open per range. `close()`
119
+ * is idempotent + safe to call after errors.
120
+ *
121
+ * @opts
122
+ * signal: AbortSignal, // propagates to in-flight read()s
123
+ *
124
+ * @example
125
+ * var adapter = b.archive.adapters.fs("/var/uploads/payload.zip");
126
+ * try {
127
+ * var reader = b.archive.read.zip(adapter);
128
+ * var entries = await reader.inspect();
129
+ * } finally {
130
+ * await adapter.close();
131
+ * }
132
+ */
133
+ function fs(path, opts) {
134
+ if (typeof path !== "string" || path.length === 0) {
135
+ throw new AdapterError("adapter/bad-arg", "fs: path must be a non-empty string");
136
+ }
137
+ opts = opts || {};
138
+ var signal = opts.signal || null;
139
+
140
+ var fd = nodeFs.openSync(path, "r");
141
+ var stat = nodeFs.fstatSync(fd);
142
+ var size = stat.size;
143
+ var closed = false;
144
+
145
+ function close() {
146
+ if (closed) return;
147
+ closed = true;
148
+ try { nodeFs.closeSync(fd); } catch (_e) { /* drop-silent — file already gone */ }
149
+ }
150
+
151
+ function range(offset, length) {
152
+ return new Promise(function (resolve, reject) {
153
+ try {
154
+ _checkSignal(signal, "fs.range");
155
+ _assertNonNegativeInteger(offset, "fs.range: offset");
156
+ _assertPositiveInteger(length, "fs.range: length");
157
+ if (closed) throw new AdapterError("adapter/closed", "fs.range: adapter is closed");
158
+ if (offset + length > size) {
159
+ throw new AdapterError("adapter/out-of-range",
160
+ "fs.range: read past EOF (offset=" + offset + " length=" + length + " size=" + size + ")");
161
+ }
162
+ var buf = Buffer.allocUnsafe(length);
163
+ nodeFs.read(fd, buf, 0, length, offset, function (err, bytesRead) {
164
+ if (err) return reject(err);
165
+ if (bytesRead !== length) {
166
+ return reject(new AdapterError("adapter/short-read",
167
+ "fs.range: short read (requested=" + length + " got=" + bytesRead + ")"));
168
+ }
169
+ resolve(buf);
170
+ });
171
+ } catch (e) { reject(e); }
172
+ });
173
+ }
174
+
175
+ return {
176
+ kind: "random-access",
177
+ name: "fs",
178
+ size: size,
179
+ range: range,
180
+ close: close,
181
+ signal: signal,
182
+ };
183
+ }
184
+
185
+ // ---- Buffer adapter -------------------------------------------------------
186
+
187
+ /**
188
+ * @primitive b.archive.adapters.buffer
189
+ * @signature b.archive.adapters.buffer(buf, opts?)
190
+ * @since 0.12.7
191
+ * @status stable
192
+ * @related b.archive.adapters.fs
193
+ *
194
+ * In-memory random-access adapter — slices a Buffer on `range()`.
195
+ * Useful for tests, small operator-uploaded payloads already in
196
+ * memory, and round-tripping `b.archive.zip().toBuffer()` output
197
+ * back through the reader without touching disk.
198
+ *
199
+ * @opts
200
+ * signal: AbortSignal,
201
+ *
202
+ * @example
203
+ * var produced = b.archive.zip();
204
+ * produced.addFile("readme.txt", "Hello\n");
205
+ * var bytes = produced.toBuffer();
206
+ * var reader = b.archive.read.zip(b.archive.adapters.buffer(bytes));
207
+ * var entries = await reader.inspect();
208
+ */
209
+ function buffer(buf, opts) {
210
+ if (!Buffer.isBuffer(buf)) {
211
+ throw new AdapterError("adapter/bad-arg", "buffer: arg must be a Buffer");
212
+ }
213
+ opts = opts || {};
214
+ var signal = opts.signal || null;
215
+ var size = buf.length;
216
+
217
+ function close() { /* nothing to release */ }
218
+
219
+ function range(offset, length) {
220
+ return new Promise(function (resolve, reject) {
221
+ try {
222
+ _checkSignal(signal, "buffer.range");
223
+ _assertNonNegativeInteger(offset, "buffer.range: offset");
224
+ _assertPositiveInteger(length, "buffer.range: length");
225
+ if (offset + length > size) {
226
+ throw new AdapterError("adapter/out-of-range",
227
+ "buffer.range: read past EOF (offset=" + offset + " length=" + length + " size=" + size + ")");
228
+ }
229
+ // .slice shares the underlying ArrayBuffer; copy so the
230
+ // caller can mutate without surprising the next range() call.
231
+ var out = Buffer.allocUnsafe(length);
232
+ buf.copy(out, 0, offset, offset + length);
233
+ resolve(out);
234
+ } catch (e) { reject(e); }
235
+ });
236
+ }
237
+
238
+ return {
239
+ kind: "random-access",
240
+ name: "buffer",
241
+ size: size,
242
+ range: range,
243
+ close: close,
244
+ signal: signal,
245
+ };
246
+ }
247
+
248
+ // ---- objectStore adapter --------------------------------------------------
249
+
250
+ /**
251
+ * @primitive b.archive.adapters.objectStore
252
+ * @signature b.archive.adapters.objectStore(client, key, opts?)
253
+ * @since 0.12.7
254
+ * @status stable
255
+ * @compliance hipaa, pci-dss, gdpr, soc2
256
+ * @related b.archive.adapters.fs, b.archive.adapters.http, b.objectStore
257
+ *
258
+ * Random-access adapter backed by an operator-supplied
259
+ * `b.objectStore` client. The adapter calls `client.get(key, { range:
260
+ * [start, end] })` for every `range()` request and reads the response
261
+ * body into a Buffer. Composes the framework's existing SSRF guard /
262
+ * TLS posture / audit chain — adapter behaviour follows whatever the
263
+ * client was configured with.
264
+ *
265
+ * The client is expected to expose:
266
+ * client.head(key) → { size: <number> } (or similar size accessor)
267
+ * client.get(key, opts) → AsyncIterable<Buffer> | { body: Readable }
268
+ * (Range opt honored)
269
+ *
270
+ * Operators using bucket implementations that don't expose `.head()`
271
+ * pass `opts.size` explicitly.
272
+ *
273
+ * @opts
274
+ * size: number, // override size (skips head() call)
275
+ * signal: AbortSignal,
276
+ * audit: b.audit, // forwarded to client.get
277
+ *
278
+ * @example
279
+ * var client = { get: async function () { return Buffer.alloc(0); }, head: async function () { return { size: 0 }; } };
280
+ * var adapter = b.archive.adapters.objectStore(client, "incoming/payload.zip");
281
+ * var reader = b.archive.read.zip(adapter);
282
+ * var policy = b.guardArchive.zipBombPolicy({ maxTotalDecompressedBytes: 268435456 });
283
+ * void reader; void policy;
284
+ */
285
+ function objectStore(client, key, opts) {
286
+ if (!client || typeof client.get !== "function") {
287
+ throw new AdapterError("adapter/bad-arg",
288
+ "objectStore: client must expose a .get(key, opts) method");
289
+ }
290
+ if (typeof key !== "string" || key.length === 0) {
291
+ throw new AdapterError("adapter/bad-arg", "objectStore: key must be a non-empty string");
292
+ }
293
+ opts = opts || {};
294
+ var signal = opts.signal || null;
295
+ var sizeOverride = opts.size;
296
+ var size = null;
297
+
298
+ async function _resolveSize() {
299
+ if (size !== null) return size;
300
+ if (typeof sizeOverride === "number") {
301
+ _assertNonNegativeInteger(sizeOverride, "objectStore.size opt");
302
+ size = sizeOverride;
303
+ return size;
304
+ }
305
+ if (typeof client.head !== "function") {
306
+ throw new AdapterError("adapter/no-size",
307
+ "objectStore: client has no .head(key) — pass opts.size explicitly");
308
+ }
309
+ var meta = await client.head(key, { signal: signal });
310
+ if (!meta || typeof meta.size !== "number") {
311
+ throw new AdapterError("adapter/no-size",
312
+ "objectStore: client.head(key) did not return { size: <number> }");
313
+ }
314
+ _assertNonNegativeInteger(meta.size, "objectStore.head(key).size");
315
+ size = meta.size;
316
+ return size;
317
+ }
318
+
319
+ async function range(offset, length) {
320
+ _checkSignal(signal, "objectStore.range");
321
+ _assertNonNegativeInteger(offset, "objectStore.range: offset");
322
+ _assertPositiveInteger(length, "objectStore.range: length");
323
+ var s = await _resolveSize();
324
+ if (offset + length > s) {
325
+ throw new AdapterError("adapter/out-of-range",
326
+ "objectStore.range: read past EOF (offset=" + offset + " length=" + length + " size=" + s + ")");
327
+ }
328
+ // HTTP Range is inclusive on both endpoints.
329
+ var resp = await client.get(key, {
330
+ range: [offset, offset + length - 1],
331
+ signal: signal,
332
+ audit: opts.audit,
333
+ });
334
+ var body = resp && (resp.body || resp);
335
+ if (Buffer.isBuffer(body)) {
336
+ if (body.length !== length) {
337
+ throw new AdapterError("adapter/short-read",
338
+ "objectStore.range: short read (requested=" + length + " got=" + body.length + ")");
339
+ }
340
+ return body;
341
+ }
342
+ if (body && typeof body[Symbol.asyncIterator] === "function") {
343
+ var collector = safeBuffer.boundedChunkCollector({
344
+ maxBytes: length,
345
+ errorClass: AdapterError,
346
+ sizeCode: "adapter/over-read",
347
+ });
348
+ for await (var chunk of body) {
349
+ collector.push(chunk);
350
+ }
351
+ if (collector.bytesCollected() !== length) {
352
+ throw new AdapterError("adapter/short-read",
353
+ "objectStore.range: short read (requested=" + length +
354
+ " got=" + collector.bytesCollected() + ")");
355
+ }
356
+ return collector.result();
357
+ }
358
+ throw new AdapterError("adapter/bad-response",
359
+ "objectStore.range: client.get(key) returned neither Buffer nor AsyncIterable<Buffer>");
360
+ }
361
+
362
+ function close() { /* the client owns its connection pool */ }
363
+
364
+ return {
365
+ kind: "random-access",
366
+ name: "objectStore",
367
+ range: range,
368
+ close: close,
369
+ signal: signal,
370
+ // size is a property accessor — the reader awaits it before
371
+ // the first range() call so the head() round-trip is folded into
372
+ // the first interaction rather than the constructor.
373
+ get size() { return size; },
374
+ resolveSize: _resolveSize,
375
+ };
376
+ }
377
+
378
+ // ---- HTTP adapter ---------------------------------------------------------
379
+
380
+ /**
381
+ * @primitive b.archive.adapters.http
382
+ * @signature b.archive.adapters.http(url, opts?)
383
+ * @since 0.12.7
384
+ * @status stable
385
+ * @compliance gdpr, hipaa, pci-dss
386
+ * @related b.archive.adapters.objectStore, b.httpClient
387
+ *
388
+ * Random-access adapter backed by HTTP Range requests. Composes the
389
+ * framework's `b.httpClient` (SSRF guard + TLS posture + audit chain
390
+ * + PQC-hybrid agent) so the adapter inherits the operator's network
391
+ * surface configuration without duplicating it here.
392
+ *
393
+ * First call issues a HEAD to determine size + verify the server
394
+ * accepts Range requests (`Accept-Ranges: bytes`). Servers without
395
+ * Range support are refused with `adapter/no-range` — operators
396
+ * downloading the full byte stream first and feeding `b.archive.
397
+ * adapters.buffer` is the appropriate fallback in that case.
398
+ *
399
+ * @opts
400
+ * client: b.httpClient, // override the default (must already exist)
401
+ * headers: { ... },
402
+ * timeoutMs: number, // per-request
403
+ * signal: AbortSignal,
404
+ * audit: b.audit,
405
+ *
406
+ * @example
407
+ * var adapter = b.archive.adapters.http("https://artifact-host.example.com/release.zip", {
408
+ * timeoutMs: 60_000,
409
+ * });
410
+ * var reader = b.archive.read.zip(adapter);
411
+ * var entries = await reader.inspect();
412
+ */
413
+ function http(url, opts) {
414
+ if (typeof url !== "string" || url.length === 0) {
415
+ throw new AdapterError("adapter/bad-arg", "http: url must be a non-empty string");
416
+ }
417
+ opts = opts || {};
418
+ var signal = opts.signal || null;
419
+ var client = opts.client || httpClient();
420
+ var headers = Object.assign({}, opts.headers || {});
421
+ var timeoutMs = opts.timeoutMs || C.TIME.seconds(30);
422
+ var size = null;
423
+
424
+ async function _resolveSize() {
425
+ if (size !== null) return size;
426
+ _checkSignal(signal, "http.head");
427
+ var res = await client.request({
428
+ method: "HEAD",
429
+ url: url,
430
+ headers: headers,
431
+ timeoutMs: timeoutMs,
432
+ signal: signal,
433
+ audit: opts.audit,
434
+ });
435
+ if (!res || !res.headers) {
436
+ throw new AdapterError("adapter/bad-response", "http: HEAD returned no headers");
437
+ }
438
+ var acceptRanges = res.headers["accept-ranges"];
439
+ if (!acceptRanges || String(acceptRanges).toLowerCase() !== "bytes") {
440
+ throw new AdapterError("adapter/no-range",
441
+ "http: server does not advertise 'Accept-Ranges: bytes' (got " + JSON.stringify(acceptRanges) + ")");
442
+ }
443
+ var lenHdr = res.headers["content-length"];
444
+ if (!lenHdr) {
445
+ throw new AdapterError("adapter/no-size",
446
+ "http: server did not send Content-Length on HEAD");
447
+ }
448
+ var parsed = Number(lenHdr);
449
+ _assertNonNegativeInteger(parsed, "http: parsed content-length");
450
+ size = parsed;
451
+ return size;
452
+ }
453
+
454
+ async function range(offset, length) {
455
+ _checkSignal(signal, "http.range");
456
+ _assertNonNegativeInteger(offset, "http.range: offset");
457
+ _assertPositiveInteger(length, "http.range: length");
458
+ var s = await _resolveSize();
459
+ if (offset + length > s) {
460
+ throw new AdapterError("adapter/out-of-range",
461
+ "http.range: read past EOF (offset=" + offset + " length=" + length + " size=" + s + ")");
462
+ }
463
+ var rangeHdr = "bytes=" + offset + "-" + (offset + length - 1);
464
+ var hdrs = Object.assign({}, headers, { "Range": rangeHdr });
465
+ var res = await client.request({
466
+ method: "GET",
467
+ url: url,
468
+ headers: hdrs,
469
+ timeoutMs: timeoutMs,
470
+ signal: signal,
471
+ audit: opts.audit,
472
+ });
473
+ if (!res || (res.status !== 206 && res.status !== 200)) {
474
+ throw new AdapterError("adapter/bad-response",
475
+ "http.range: expected 206 Partial Content, got " + (res && res.status));
476
+ }
477
+ var body = res.body;
478
+ if (Buffer.isBuffer(body)) {
479
+ if (body.length !== length) {
480
+ throw new AdapterError("adapter/short-read",
481
+ "http.range: short read (requested=" + length + " got=" + body.length + ")");
482
+ }
483
+ return body;
484
+ }
485
+ if (body && typeof body[Symbol.asyncIterator] === "function") {
486
+ var collector = safeBuffer.boundedChunkCollector({
487
+ maxBytes: length,
488
+ errorClass: AdapterError,
489
+ sizeCode: "adapter/over-read",
490
+ });
491
+ for await (var chunk of body) {
492
+ collector.push(chunk);
493
+ }
494
+ if (collector.bytesCollected() !== length) {
495
+ throw new AdapterError("adapter/short-read",
496
+ "http.range: short read (requested=" + length +
497
+ " got=" + collector.bytesCollected() + ")");
498
+ }
499
+ return collector.result();
500
+ }
501
+ throw new AdapterError("adapter/bad-response",
502
+ "http.range: response body is neither Buffer nor AsyncIterable<Buffer>");
503
+ }
504
+
505
+ function close() { /* httpClient owns its connection pool */ }
506
+
507
+ return {
508
+ kind: "random-access",
509
+ name: "http",
510
+ range: range,
511
+ close: close,
512
+ signal: signal,
513
+ get size() { return size; },
514
+ resolveSize: _resolveSize,
515
+ };
516
+ }
517
+
518
+ // ---- trustedStream adapter ------------------------------------------------
519
+
520
+ /**
521
+ * @primitive b.archive.adapters.trustedStream
522
+ * @signature b.archive.adapters.trustedStream(readable, opts?)
523
+ * @since 0.12.7
524
+ * @status stable
525
+ * @related b.archive.adapters.fs, b.archive.adapters.buffer
526
+ *
527
+ * Forward-scan-only adapter for trusted Readable sources. The reader
528
+ * walks local file headers in order; the CD/LFH skew defense and the
529
+ * "entries hidden from LFH but present in CD" attack class are OFF
530
+ * in this mode because there's no central directory to compare
531
+ * against. Operators reaching for this primitive are declaring they
532
+ * own the producer (e.g. piping their own
533
+ * `b.archive.zip().toStream()` output back into a reader 30 seconds
534
+ * later for round-trip verification).
535
+ *
536
+ * Adversarial input MUST use `b.archive.adapters.fs` / `buffer` /
537
+ * `objectStore` / `http` — the random-access path is the only
538
+ * adversarial-safe one.
539
+ *
540
+ * @opts
541
+ * signal: AbortSignal,
542
+ *
543
+ * @example
544
+ * var produced = fs.createReadStream("./own-export.zip");
545
+ * var reader = b.archive.read.zip.fromTrustedStream(produced);
546
+ * var entries = [];
547
+ * for await (var e of reader.entries()) entries.push(e);
548
+ */
549
+ function trustedStream(readable, opts) {
550
+ if (!readable || typeof readable.pipe !== "function" || typeof readable.on !== "function") {
551
+ throw new AdapterError("adapter/bad-arg",
552
+ "trustedStream: arg must be a Readable (or pipe/on-compatible stream)");
553
+ }
554
+ if (!(readable instanceof nodeStream.Readable) && !readable.readable) {
555
+ // Accept stream-like duck-typed objects; many libraries return
556
+ // Readable-flavored bytes via Symbol.asyncIterator only.
557
+ }
558
+ opts = opts || {};
559
+ var signal = opts.signal || null;
560
+
561
+ function close() {
562
+ if (typeof readable.destroy === "function") {
563
+ try { readable.destroy(); } catch (_e) { /* drop-silent */ }
564
+ }
565
+ }
566
+
567
+ return {
568
+ kind: "trusted-sequential",
569
+ name: "trustedStream",
570
+ readable: readable,
571
+ signal: signal,
572
+ close: close,
573
+ };
574
+ }
575
+
576
+ // ---- Adapter shape predicates --------------------------------------------
577
+
578
+ /**
579
+ * @primitive b.archive.adapters.isRandomAccessAdapter
580
+ * @signature b.archive.adapters.isRandomAccessAdapter(a)
581
+ * @since 0.12.7
582
+ * @status stable
583
+ * @related b.archive.adapters.fs, b.archive.adapters.objectStore
584
+ *
585
+ * Type-predicate: returns `true` when `a` is the random-access shape
586
+ * (`{ kind: "random-access", range, ... }`) produced by `fs` / `buffer`
587
+ * / `objectStore` / `http`. Operators routing through `b.archive.read.zip`
588
+ * compose this to refuse trusted-stream adapters at the wrong entry
589
+ * point.
590
+ *
591
+ * @example
592
+ * var ok = b.archive.adapters.isRandomAccessAdapter(adapter);
593
+ * if (!ok) throw new Error("need random-access adapter");
594
+ */
595
+ function isRandomAccessAdapter(a) {
596
+ return !!(a && a.kind === "random-access" && typeof a.range === "function");
597
+ }
598
+
599
+ /**
600
+ * @primitive b.archive.adapters.isTrustedStreamAdapter
601
+ * @signature b.archive.adapters.isTrustedStreamAdapter(a)
602
+ * @since 0.12.7
603
+ * @status stable
604
+ * @related b.archive.adapters.trustedStream
605
+ *
606
+ * Type-predicate: returns `true` when `a` is the trusted-sequential
607
+ * shape (`{ kind: "trusted-sequential", readable, ... }`) produced by
608
+ * `trustedStream`. Operators routing through `b.archive.read.zip.
609
+ * fromTrustedStream` compose this to refuse random-access adapters
610
+ * at the wrong entry point.
611
+ *
612
+ * @example
613
+ * var ok = b.archive.adapters.isTrustedStreamAdapter(adapter);
614
+ * if (!ok) throw new Error("need trusted-stream adapter");
615
+ */
616
+ function isTrustedStreamAdapter(a) {
617
+ return !!(a && a.kind === "trusted-sequential" && a.readable);
618
+ }
619
+
620
+ module.exports = {
621
+ fs: fs,
622
+ buffer: buffer,
623
+ objectStore: objectStore,
624
+ http: http,
625
+ trustedStream: trustedStream,
626
+ isRandomAccessAdapter: isRandomAccessAdapter,
627
+ isTrustedStreamAdapter: isTrustedStreamAdapter,
628
+ AdapterError: AdapterError,
629
+ };