hazo_files 1.6.0 → 2.0.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/CHANGE_LOG.md +24 -0
- package/README.md +161 -0
- package/dist/server/index.d.mts +128 -1
- package/dist/server/index.d.ts +128 -1
- package/dist/server/index.js +296 -0
- package/dist/server/index.mjs +297 -0
- package/dist/testing/index.d.mts +62 -0
- package/dist/testing/index.d.ts +62 -0
- package/dist/testing/index.js +71 -0
- package/dist/testing/index.mjs +44 -0
- package/docs/superpowers/plans/2026-05-23-test-app-v2-providers.md +968 -0
- package/package.json +12 -4
|
@@ -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
|
+
};
|