hazo_files 2.0.0 → 2.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.
@@ -47,6 +47,7 @@ __export(index_exports, {
47
47
  FileTooLargeError: () => FileTooLargeError,
48
48
  GoogleDriveAuth: () => GoogleDriveAuth,
49
49
  GoogleDriveModule: () => GoogleDriveModule,
50
+ GoogleDriveProvider: () => GoogleDriveProvider,
50
51
  HAZO_FILES_DEFAULT_TABLE_NAME: () => HAZO_FILES_DEFAULT_TABLE_NAME,
51
52
  HAZO_FILES_JOB_TYPES: () => HAZO_FILES_JOB_TYPES,
52
53
  HAZO_FILES_MIGRATION_V2: () => HAZO_FILES_MIGRATION_V2,
@@ -59,6 +60,7 @@ __export(index_exports, {
59
60
  HAZO_FILE_QUOTAS_TABLE_SCHEMA: () => HAZO_FILE_QUOTAS_TABLE_SCHEMA,
60
61
  HazoFilesError: () => HazoFilesError,
61
62
  ImportSizeCapError: () => ImportSizeCapError,
63
+ InMemoryProvider: () => InMemoryProvider,
62
64
  InvalidExtensionError: () => InvalidExtensionError,
63
65
  InvalidPathError: () => InvalidPathError,
64
66
  LLMExtractionService: () => LLMExtractionService,
@@ -6554,6 +6556,194 @@ async function streamToBuffer(s) {
6554
6556
  return Buffer.concat(chunks);
6555
6557
  }
6556
6558
 
6559
+ // src/providers/in-memory.ts
6560
+ var InMemoryProvider = class {
6561
+ constructor() {
6562
+ this.provider_tag = "in_memory";
6563
+ this.store = /* @__PURE__ */ new Map();
6564
+ }
6565
+ async put(path4, body, opts) {
6566
+ if (opts?.ifNotExists && this.store.has(path4)) throw new Error(`File exists: ${path4}`);
6567
+ const buf = Buffer.isBuffer(body) ? body : await streamToBuffer2(body);
6568
+ this.store.set(path4, buf);
6569
+ return { provider: this.provider_tag, native_id: path4, size: buf.length };
6570
+ }
6571
+ async get(path4) {
6572
+ const buf = this.store.get(path4);
6573
+ if (!buf) throw new Error(`Not found: ${path4}`);
6574
+ return buf;
6575
+ }
6576
+ async delete(path4) {
6577
+ this.store.delete(path4);
6578
+ }
6579
+ async exists(path4) {
6580
+ return this.store.has(path4);
6581
+ }
6582
+ async getSignedUrl(path4, _opts) {
6583
+ const buf = this.store.get(path4);
6584
+ if (!buf) throw new Error(`Not found: ${path4}`);
6585
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
6586
+ }
6587
+ async probe() {
6588
+ return { ok: true };
6589
+ }
6590
+ /** Test-only escape hatch — exposes the internal store for assertions. */
6591
+ snapshot() {
6592
+ return new Map(this.store);
6593
+ }
6594
+ };
6595
+ async function streamToBuffer2(s) {
6596
+ const chunks = [];
6597
+ for await (const c of s) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
6598
+ return Buffer.concat(chunks);
6599
+ }
6600
+
6601
+ // src/providers/google-drive.ts
6602
+ var import_googleapis3 = require("googleapis");
6603
+ var GoogleDriveProvider = class {
6604
+ constructor(opts) {
6605
+ this.provider_tag = "gdrive";
6606
+ let credentials = {};
6607
+ try {
6608
+ credentials = JSON.parse(opts.service_account_json);
6609
+ } catch {
6610
+ }
6611
+ const auth = new import_googleapis3.google.auth.GoogleAuth({
6612
+ credentials,
6613
+ scopes: ["https://www.googleapis.com/auth/drive"]
6614
+ });
6615
+ this.drive = import_googleapis3.google.drive({ version: "v3", auth });
6616
+ this.driveId = opts.shared_drive_id;
6617
+ this.cache = opts.path_cache;
6618
+ }
6619
+ async probe() {
6620
+ try {
6621
+ await this.drive.drives.get({
6622
+ driveId: this.driveId,
6623
+ supportsAllDrives: true
6624
+ });
6625
+ return { ok: true };
6626
+ } catch (err) {
6627
+ const e = err;
6628
+ if (e?.code === 404) return { ok: false, error: "drive_not_shared", message: "SA cannot see Shared Drive" };
6629
+ if (e?.code === 403) return { ok: false, error: "write_denied", message: "SA lacks permission" };
6630
+ return { ok: false, error: "transient", message: String(err) };
6631
+ }
6632
+ }
6633
+ async put(path4, body, opts) {
6634
+ const segments = path4.split("/").filter(Boolean);
6635
+ const filename = segments.pop();
6636
+ const parentId = await this.resolvePath(segments, { create: true });
6637
+ if (opts?.ifNotExists) {
6638
+ const existing = await this.findChild(filename, parentId);
6639
+ if (existing) throw new Error(`File exists: ${path4}`);
6640
+ }
6641
+ const buf = Buffer.isBuffer(body) ? body : await streamToBuffer3(body);
6642
+ const res = await this.drive.files.create({
6643
+ requestBody: {
6644
+ name: filename,
6645
+ parents: [parentId],
6646
+ mimeType: opts?.contentType ?? "application/octet-stream"
6647
+ },
6648
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6649
+ media: { body: buf },
6650
+ fields: "id, name"
6651
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6652
+ });
6653
+ return { provider: this.provider_tag, native_id: res.data.id, size: buf.length };
6654
+ }
6655
+ async get(path4) {
6656
+ const id = await this.lookupFileId(path4);
6657
+ const res = await this.drive.files.get(
6658
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6659
+ { fileId: id, alt: "media", supportsAllDrives: true },
6660
+ { responseType: "arraybuffer" }
6661
+ );
6662
+ return Buffer.from(res.data);
6663
+ }
6664
+ async delete(path4) {
6665
+ try {
6666
+ const id = await this.lookupFileId(path4);
6667
+ await this.drive.files.delete({ fileId: id, supportsAllDrives: true });
6668
+ } catch (err) {
6669
+ const e = err;
6670
+ if (e?.code !== 404) throw err;
6671
+ }
6672
+ }
6673
+ async exists(path4) {
6674
+ try {
6675
+ await this.lookupFileId(path4);
6676
+ return true;
6677
+ } catch {
6678
+ return false;
6679
+ }
6680
+ }
6681
+ async getSignedUrl(path4, _opts) {
6682
+ const id = await this.lookupFileId(path4);
6683
+ return `https://drive.google.com/uc?id=${id}&export=download`;
6684
+ }
6685
+ async resolvePath(segments, opts) {
6686
+ let parentId = this.driveId;
6687
+ const accumulated = [];
6688
+ for (const segment of segments) {
6689
+ accumulated.push(segment);
6690
+ const cacheKey = accumulated.join("/");
6691
+ const cached = await this.cache.lookup(cacheKey);
6692
+ if (cached) {
6693
+ parentId = cached;
6694
+ continue;
6695
+ }
6696
+ let found = await this.findChild(segment, parentId, "application/vnd.google-apps.folder");
6697
+ if (!found && opts.create) {
6698
+ const created = await this.drive.files.create({
6699
+ requestBody: {
6700
+ name: segment,
6701
+ parents: [parentId],
6702
+ mimeType: "application/vnd.google-apps.folder"
6703
+ },
6704
+ fields: "id"
6705
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6706
+ });
6707
+ found = created.data.id;
6708
+ }
6709
+ if (!found) throw new Error(`Folder not found and create=false: ${cacheKey}`);
6710
+ await this.cache.write(cacheKey, found);
6711
+ parentId = found;
6712
+ }
6713
+ return parentId;
6714
+ }
6715
+ async findChild(name, parentId, mimeType) {
6716
+ const q = [
6717
+ `name='${name.replace(/'/g, "\\'")}'`,
6718
+ `'${parentId}' in parents`,
6719
+ `trashed=false`
6720
+ ];
6721
+ if (mimeType) q.push(`mimeType='${mimeType}'`);
6722
+ const res = await this.drive.files.list({
6723
+ q: q.join(" and "),
6724
+ driveId: this.driveId,
6725
+ corpora: "drive",
6726
+ includeItemsFromAllDrives: true,
6727
+ fields: "files(id, name)"
6728
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6729
+ });
6730
+ return res.data.files?.[0]?.id ?? null;
6731
+ }
6732
+ async lookupFileId(path4) {
6733
+ const segments = path4.split("/").filter(Boolean);
6734
+ const filename = segments.pop();
6735
+ const parentId = await this.resolvePath(segments, { create: false });
6736
+ const id = await this.findChild(filename, parentId);
6737
+ if (!id) throw Object.assign(new Error(`Not found: ${path4}`), { code: 404 });
6738
+ return id;
6739
+ }
6740
+ };
6741
+ async function streamToBuffer3(s) {
6742
+ const chunks = [];
6743
+ for await (const c of s) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
6744
+ return Buffer.concat(chunks);
6745
+ }
6746
+
6557
6747
  // src/providers/types.ts
6558
6748
  var StorageCollisionExhausted = class extends Error {
6559
6749
  constructor(attempts, lastPath) {
@@ -6636,6 +6826,7 @@ try {
6636
6826
  FileTooLargeError,
6637
6827
  GoogleDriveAuth,
6638
6828
  GoogleDriveModule,
6829
+ GoogleDriveProvider,
6639
6830
  HAZO_FILES_DEFAULT_TABLE_NAME,
6640
6831
  HAZO_FILES_JOB_TYPES,
6641
6832
  HAZO_FILES_MIGRATION_V2,
@@ -6648,6 +6839,7 @@ try {
6648
6839
  HAZO_FILE_QUOTAS_TABLE_SCHEMA,
6649
6840
  HazoFilesError,
6650
6841
  ImportSizeCapError,
6842
+ InMemoryProvider,
6651
6843
  InvalidExtensionError,
6652
6844
  InvalidPathError,
6653
6845
  LLMExtractionService,
@@ -6368,6 +6368,194 @@ async function streamToBuffer(s) {
6368
6368
  return Buffer.concat(chunks);
6369
6369
  }
6370
6370
 
6371
+ // src/providers/in-memory.ts
6372
+ var InMemoryProvider = class {
6373
+ constructor() {
6374
+ this.provider_tag = "in_memory";
6375
+ this.store = /* @__PURE__ */ new Map();
6376
+ }
6377
+ async put(path4, body, opts) {
6378
+ if (opts?.ifNotExists && this.store.has(path4)) throw new Error(`File exists: ${path4}`);
6379
+ const buf = Buffer.isBuffer(body) ? body : await streamToBuffer2(body);
6380
+ this.store.set(path4, buf);
6381
+ return { provider: this.provider_tag, native_id: path4, size: buf.length };
6382
+ }
6383
+ async get(path4) {
6384
+ const buf = this.store.get(path4);
6385
+ if (!buf) throw new Error(`Not found: ${path4}`);
6386
+ return buf;
6387
+ }
6388
+ async delete(path4) {
6389
+ this.store.delete(path4);
6390
+ }
6391
+ async exists(path4) {
6392
+ return this.store.has(path4);
6393
+ }
6394
+ async getSignedUrl(path4, _opts) {
6395
+ const buf = this.store.get(path4);
6396
+ if (!buf) throw new Error(`Not found: ${path4}`);
6397
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
6398
+ }
6399
+ async probe() {
6400
+ return { ok: true };
6401
+ }
6402
+ /** Test-only escape hatch — exposes the internal store for assertions. */
6403
+ snapshot() {
6404
+ return new Map(this.store);
6405
+ }
6406
+ };
6407
+ async function streamToBuffer2(s) {
6408
+ const chunks = [];
6409
+ for await (const c of s) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
6410
+ return Buffer.concat(chunks);
6411
+ }
6412
+
6413
+ // src/providers/google-drive.ts
6414
+ import { google as google3 } from "googleapis";
6415
+ var GoogleDriveProvider = class {
6416
+ constructor(opts) {
6417
+ this.provider_tag = "gdrive";
6418
+ let credentials = {};
6419
+ try {
6420
+ credentials = JSON.parse(opts.service_account_json);
6421
+ } catch {
6422
+ }
6423
+ const auth = new google3.auth.GoogleAuth({
6424
+ credentials,
6425
+ scopes: ["https://www.googleapis.com/auth/drive"]
6426
+ });
6427
+ this.drive = google3.drive({ version: "v3", auth });
6428
+ this.driveId = opts.shared_drive_id;
6429
+ this.cache = opts.path_cache;
6430
+ }
6431
+ async probe() {
6432
+ try {
6433
+ await this.drive.drives.get({
6434
+ driveId: this.driveId,
6435
+ supportsAllDrives: true
6436
+ });
6437
+ return { ok: true };
6438
+ } catch (err) {
6439
+ const e = err;
6440
+ if (e?.code === 404) return { ok: false, error: "drive_not_shared", message: "SA cannot see Shared Drive" };
6441
+ if (e?.code === 403) return { ok: false, error: "write_denied", message: "SA lacks permission" };
6442
+ return { ok: false, error: "transient", message: String(err) };
6443
+ }
6444
+ }
6445
+ async put(path4, body, opts) {
6446
+ const segments = path4.split("/").filter(Boolean);
6447
+ const filename = segments.pop();
6448
+ const parentId = await this.resolvePath(segments, { create: true });
6449
+ if (opts?.ifNotExists) {
6450
+ const existing = await this.findChild(filename, parentId);
6451
+ if (existing) throw new Error(`File exists: ${path4}`);
6452
+ }
6453
+ const buf = Buffer.isBuffer(body) ? body : await streamToBuffer3(body);
6454
+ const res = await this.drive.files.create({
6455
+ requestBody: {
6456
+ name: filename,
6457
+ parents: [parentId],
6458
+ mimeType: opts?.contentType ?? "application/octet-stream"
6459
+ },
6460
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6461
+ media: { body: buf },
6462
+ fields: "id, name"
6463
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6464
+ });
6465
+ return { provider: this.provider_tag, native_id: res.data.id, size: buf.length };
6466
+ }
6467
+ async get(path4) {
6468
+ const id = await this.lookupFileId(path4);
6469
+ const res = await this.drive.files.get(
6470
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6471
+ { fileId: id, alt: "media", supportsAllDrives: true },
6472
+ { responseType: "arraybuffer" }
6473
+ );
6474
+ return Buffer.from(res.data);
6475
+ }
6476
+ async delete(path4) {
6477
+ try {
6478
+ const id = await this.lookupFileId(path4);
6479
+ await this.drive.files.delete({ fileId: id, supportsAllDrives: true });
6480
+ } catch (err) {
6481
+ const e = err;
6482
+ if (e?.code !== 404) throw err;
6483
+ }
6484
+ }
6485
+ async exists(path4) {
6486
+ try {
6487
+ await this.lookupFileId(path4);
6488
+ return true;
6489
+ } catch {
6490
+ return false;
6491
+ }
6492
+ }
6493
+ async getSignedUrl(path4, _opts) {
6494
+ const id = await this.lookupFileId(path4);
6495
+ return `https://drive.google.com/uc?id=${id}&export=download`;
6496
+ }
6497
+ async resolvePath(segments, opts) {
6498
+ let parentId = this.driveId;
6499
+ const accumulated = [];
6500
+ for (const segment of segments) {
6501
+ accumulated.push(segment);
6502
+ const cacheKey = accumulated.join("/");
6503
+ const cached = await this.cache.lookup(cacheKey);
6504
+ if (cached) {
6505
+ parentId = cached;
6506
+ continue;
6507
+ }
6508
+ let found = await this.findChild(segment, parentId, "application/vnd.google-apps.folder");
6509
+ if (!found && opts.create) {
6510
+ const created = await this.drive.files.create({
6511
+ requestBody: {
6512
+ name: segment,
6513
+ parents: [parentId],
6514
+ mimeType: "application/vnd.google-apps.folder"
6515
+ },
6516
+ fields: "id"
6517
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6518
+ });
6519
+ found = created.data.id;
6520
+ }
6521
+ if (!found) throw new Error(`Folder not found and create=false: ${cacheKey}`);
6522
+ await this.cache.write(cacheKey, found);
6523
+ parentId = found;
6524
+ }
6525
+ return parentId;
6526
+ }
6527
+ async findChild(name, parentId, mimeType) {
6528
+ const q = [
6529
+ `name='${name.replace(/'/g, "\\'")}'`,
6530
+ `'${parentId}' in parents`,
6531
+ `trashed=false`
6532
+ ];
6533
+ if (mimeType) q.push(`mimeType='${mimeType}'`);
6534
+ const res = await this.drive.files.list({
6535
+ q: q.join(" and "),
6536
+ driveId: this.driveId,
6537
+ corpora: "drive",
6538
+ includeItemsFromAllDrives: true,
6539
+ fields: "files(id, name)"
6540
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6541
+ });
6542
+ return res.data.files?.[0]?.id ?? null;
6543
+ }
6544
+ async lookupFileId(path4) {
6545
+ const segments = path4.split("/").filter(Boolean);
6546
+ const filename = segments.pop();
6547
+ const parentId = await this.resolvePath(segments, { create: false });
6548
+ const id = await this.findChild(filename, parentId);
6549
+ if (!id) throw Object.assign(new Error(`Not found: ${path4}`), { code: 404 });
6550
+ return id;
6551
+ }
6552
+ };
6553
+ async function streamToBuffer3(s) {
6554
+ const chunks = [];
6555
+ for await (const c of s) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
6556
+ return Buffer.concat(chunks);
6557
+ }
6558
+
6371
6559
  // src/providers/types.ts
6372
6560
  var StorageCollisionExhausted = class extends Error {
6373
6561
  constructor(attempts, lastPath) {
@@ -6449,6 +6637,7 @@ export {
6449
6637
  FileTooLargeError,
6450
6638
  GoogleDriveAuth,
6451
6639
  GoogleDriveModule,
6640
+ GoogleDriveProvider,
6452
6641
  HAZO_FILES_DEFAULT_TABLE_NAME,
6453
6642
  HAZO_FILES_JOB_TYPES,
6454
6643
  HAZO_FILES_MIGRATION_V2,
@@ -6461,6 +6650,7 @@ export {
6461
6650
  HAZO_FILE_QUOTAS_TABLE_SCHEMA,
6462
6651
  HazoFilesError,
6463
6652
  ImportSizeCapError,
6653
+ InMemoryProvider,
6464
6654
  InvalidExtensionError,
6465
6655
  InvalidPathError,
6466
6656
  LLMExtractionService,
@@ -0,0 +1,62 @@
1
+ import { Readable } from 'node:stream';
2
+
3
+ type StoragePath = string;
4
+ interface PutOpts {
5
+ /** Reject if the target path already exists (atomic). */
6
+ ifNotExists?: boolean;
7
+ /** Hint for content-type; provider may sniff if absent. */
8
+ contentType?: string;
9
+ /** Free-form key/value metadata persisted with the file when supported. */
10
+ metadata?: Record<string, string>;
11
+ }
12
+ interface PutResult {
13
+ /** Provider tag — `"app_file_server"`, `"gdrive"`, `"in_memory"`. */
14
+ provider: string;
15
+ /** Provider-native identifier (path for app-server; file ID for GDrive). */
16
+ native_id: string;
17
+ /** Size in bytes of the persisted body. */
18
+ size: number;
19
+ }
20
+ interface SignedUrlOpts {
21
+ /** Seconds the URL is valid for. */
22
+ ttl_seconds?: number;
23
+ /** Suggested download filename (Content-Disposition). */
24
+ filename_hint?: string;
25
+ }
26
+ interface ProbeResult {
27
+ ok: boolean;
28
+ /** Machine-readable error tag when ok=false. */
29
+ error?: "drive_not_shared" | "write_denied" | "invalid_id" | "transient" | "config_missing";
30
+ /** Free-form detail for logging. */
31
+ message?: string;
32
+ }
33
+ /**
34
+ * Storage provider abstraction. Every method MUST be idempotent at the
35
+ * data-content level — re-invoking put with identical body is allowed.
36
+ *
37
+ * Paths are logical; providers translate to native identifiers internally.
38
+ */
39
+ interface FileStorageProvider {
40
+ put(path: StoragePath, body: Buffer | Readable, opts?: PutOpts): Promise<PutResult>;
41
+ get(path: StoragePath): Promise<Buffer | Readable>;
42
+ delete(path: StoragePath): Promise<void>;
43
+ exists(path: StoragePath): Promise<boolean>;
44
+ getSignedUrl(path: StoragePath, opts?: SignedUrlOpts): Promise<string>;
45
+ /** Used by validation cron + onboarding step 2. */
46
+ probe(): Promise<ProbeResult>;
47
+ }
48
+
49
+ declare class InMemoryProvider implements FileStorageProvider {
50
+ readonly provider_tag: "in_memory";
51
+ private readonly store;
52
+ put(path: string, body: Buffer | Readable, opts?: PutOpts): Promise<PutResult>;
53
+ get(path: string): Promise<Buffer>;
54
+ delete(path: string): Promise<void>;
55
+ exists(path: string): Promise<boolean>;
56
+ getSignedUrl(path: string, _opts?: SignedUrlOpts): Promise<string>;
57
+ probe(): Promise<ProbeResult>;
58
+ /** Test-only escape hatch — exposes the internal store for assertions. */
59
+ snapshot(): Map<string, Buffer>;
60
+ }
61
+
62
+ export { InMemoryProvider };
@@ -0,0 +1,62 @@
1
+ import { Readable } from 'node:stream';
2
+
3
+ type StoragePath = string;
4
+ interface PutOpts {
5
+ /** Reject if the target path already exists (atomic). */
6
+ ifNotExists?: boolean;
7
+ /** Hint for content-type; provider may sniff if absent. */
8
+ contentType?: string;
9
+ /** Free-form key/value metadata persisted with the file when supported. */
10
+ metadata?: Record<string, string>;
11
+ }
12
+ interface PutResult {
13
+ /** Provider tag — `"app_file_server"`, `"gdrive"`, `"in_memory"`. */
14
+ provider: string;
15
+ /** Provider-native identifier (path for app-server; file ID for GDrive). */
16
+ native_id: string;
17
+ /** Size in bytes of the persisted body. */
18
+ size: number;
19
+ }
20
+ interface SignedUrlOpts {
21
+ /** Seconds the URL is valid for. */
22
+ ttl_seconds?: number;
23
+ /** Suggested download filename (Content-Disposition). */
24
+ filename_hint?: string;
25
+ }
26
+ interface ProbeResult {
27
+ ok: boolean;
28
+ /** Machine-readable error tag when ok=false. */
29
+ error?: "drive_not_shared" | "write_denied" | "invalid_id" | "transient" | "config_missing";
30
+ /** Free-form detail for logging. */
31
+ message?: string;
32
+ }
33
+ /**
34
+ * Storage provider abstraction. Every method MUST be idempotent at the
35
+ * data-content level — re-invoking put with identical body is allowed.
36
+ *
37
+ * Paths are logical; providers translate to native identifiers internally.
38
+ */
39
+ interface FileStorageProvider {
40
+ put(path: StoragePath, body: Buffer | Readable, opts?: PutOpts): Promise<PutResult>;
41
+ get(path: StoragePath): Promise<Buffer | Readable>;
42
+ delete(path: StoragePath): Promise<void>;
43
+ exists(path: StoragePath): Promise<boolean>;
44
+ getSignedUrl(path: StoragePath, opts?: SignedUrlOpts): Promise<string>;
45
+ /** Used by validation cron + onboarding step 2. */
46
+ probe(): Promise<ProbeResult>;
47
+ }
48
+
49
+ declare class InMemoryProvider implements FileStorageProvider {
50
+ readonly provider_tag: "in_memory";
51
+ private readonly store;
52
+ put(path: string, body: Buffer | Readable, opts?: PutOpts): Promise<PutResult>;
53
+ get(path: string): Promise<Buffer>;
54
+ delete(path: string): Promise<void>;
55
+ exists(path: string): Promise<boolean>;
56
+ getSignedUrl(path: string, _opts?: SignedUrlOpts): Promise<string>;
57
+ probe(): Promise<ProbeResult>;
58
+ /** Test-only escape hatch — exposes the internal store for assertions. */
59
+ snapshot(): Map<string, Buffer>;
60
+ }
61
+
62
+ export { InMemoryProvider };
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/testing/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ InMemoryProvider: () => InMemoryProvider
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/providers/in-memory.ts
28
+ var InMemoryProvider = class {
29
+ constructor() {
30
+ this.provider_tag = "in_memory";
31
+ this.store = /* @__PURE__ */ new Map();
32
+ }
33
+ async put(path, body, opts) {
34
+ if (opts?.ifNotExists && this.store.has(path)) throw new Error(`File exists: ${path}`);
35
+ const buf = Buffer.isBuffer(body) ? body : await streamToBuffer(body);
36
+ this.store.set(path, buf);
37
+ return { provider: this.provider_tag, native_id: path, size: buf.length };
38
+ }
39
+ async get(path) {
40
+ const buf = this.store.get(path);
41
+ if (!buf) throw new Error(`Not found: ${path}`);
42
+ return buf;
43
+ }
44
+ async delete(path) {
45
+ this.store.delete(path);
46
+ }
47
+ async exists(path) {
48
+ return this.store.has(path);
49
+ }
50
+ async getSignedUrl(path, _opts) {
51
+ const buf = this.store.get(path);
52
+ if (!buf) throw new Error(`Not found: ${path}`);
53
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
54
+ }
55
+ async probe() {
56
+ return { ok: true };
57
+ }
58
+ /** Test-only escape hatch — exposes the internal store for assertions. */
59
+ snapshot() {
60
+ return new Map(this.store);
61
+ }
62
+ };
63
+ async function streamToBuffer(s) {
64
+ const chunks = [];
65
+ for await (const c of s) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
66
+ return Buffer.concat(chunks);
67
+ }
68
+ // Annotate the CommonJS export names for ESM import in node:
69
+ 0 && (module.exports = {
70
+ InMemoryProvider
71
+ });
@@ -0,0 +1,44 @@
1
+ // src/providers/in-memory.ts
2
+ var InMemoryProvider = class {
3
+ constructor() {
4
+ this.provider_tag = "in_memory";
5
+ this.store = /* @__PURE__ */ new Map();
6
+ }
7
+ async put(path, body, opts) {
8
+ if (opts?.ifNotExists && this.store.has(path)) throw new Error(`File exists: ${path}`);
9
+ const buf = Buffer.isBuffer(body) ? body : await streamToBuffer(body);
10
+ this.store.set(path, buf);
11
+ return { provider: this.provider_tag, native_id: path, size: buf.length };
12
+ }
13
+ async get(path) {
14
+ const buf = this.store.get(path);
15
+ if (!buf) throw new Error(`Not found: ${path}`);
16
+ return buf;
17
+ }
18
+ async delete(path) {
19
+ this.store.delete(path);
20
+ }
21
+ async exists(path) {
22
+ return this.store.has(path);
23
+ }
24
+ async getSignedUrl(path, _opts) {
25
+ const buf = this.store.get(path);
26
+ if (!buf) throw new Error(`Not found: ${path}`);
27
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
28
+ }
29
+ async probe() {
30
+ return { ok: true };
31
+ }
32
+ /** Test-only escape hatch — exposes the internal store for assertions. */
33
+ snapshot() {
34
+ return new Map(this.store);
35
+ }
36
+ };
37
+ async function streamToBuffer(s) {
38
+ const chunks = [];
39
+ for await (const c of s) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
40
+ return Buffer.concat(chunks);
41
+ }
42
+ export {
43
+ InMemoryProvider
44
+ };
@@ -8,7 +8,7 @@
8
8
  - [x] Task 4: Signed-URL serve route — 0983dfa (2026-05-23)
9
9
  - [x] Task 5: AppFileServerProvider scenario page — fdff8ec (2026-05-23)
10
10
  - [x] Task 6: Stub pages (InMemory + GDrive) — d815df6 (2026-05-23)
11
- - [ ] Task 7: Smoke test — manual browser verification needed
11
+ - [x] Task 7: Smoke test — passed, published to npm (2026-05-23)
12
12
 
13
13
  > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
14
14