@ayepi/files 0.1.0
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/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/fs.cjs +168 -0
- package/dist/fs.d.cts +23 -0
- package/dist/fs.d.ts +23 -0
- package/dist/fs.js +167 -0
- package/dist/index.cjs +48 -0
- package/dist/index.d.cts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.js +45 -0
- package/dist/server.cjs +4125 -0
- package/dist/server.d.cts +51 -0
- package/dist/server.d.ts +51 -0
- package/dist/server.js +4123 -0
- package/package.json +86 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* # @ayepi/files
|
|
4
|
+
*
|
|
5
|
+
* A generic, **S3-like** key-based file store: stream bytes in under a key, stream them
|
|
6
|
+
* back out, list by prefix, and hand out **presigned** upload/download URLs that expire.
|
|
7
|
+
* The interface is tiny and storage-agnostic; the bundled {@link fsFiles | filesystem
|
|
8
|
+
* store} (`@ayepi/files/fs`) is the default, and `@ayepi/aws`'s `s3Files` implements the
|
|
9
|
+
* same {@link FileStore}. Presigned URLs for a store that can't self-serve (the filesystem
|
|
10
|
+
* one) are wired with `@ayepi/files/server` ({@link mountFiles} / `createFilesHandler`).
|
|
11
|
+
*
|
|
12
|
+
* Everything is **stream-first** — `put` takes a `ReadableStream` (or any {@link FileBody}),
|
|
13
|
+
* `get` returns a {@link FileObject} you read as a stream — with helpers
|
|
14
|
+
* ({@link toStream}, {@link collect}, {@link transfer}) for the common piping/transfer needs.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { fsFiles } from '@ayepi/files/fs';
|
|
18
|
+
* const files = fsFiles({ dir: './uploads' });
|
|
19
|
+
* await files.put('reports/2026.csv', someReadableStream, { contentType: 'text/csv' });
|
|
20
|
+
* const obj = await files.get('reports/2026.csv');
|
|
21
|
+
* await obj?.stream().pipeTo(destination);
|
|
22
|
+
* for (const f of (await files.list('reports/')).files) console.log(f.key, f.size);
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
/** Metadata about a stored object (no body) — the S3 `HeadObject` shape. */
|
|
28
|
+
interface FileInfo {
|
|
29
|
+
/** The object's key. */
|
|
30
|
+
readonly key: string;
|
|
31
|
+
/** Size in bytes. */
|
|
32
|
+
readonly size: number;
|
|
33
|
+
/** MIME type, if known/stored. */
|
|
34
|
+
readonly contentType?: string;
|
|
35
|
+
/** An opaque content tag (e.g. a hash) when the backend supplies one. */
|
|
36
|
+
readonly etag?: string;
|
|
37
|
+
/** Last-modified time (ms epoch). */
|
|
38
|
+
readonly modifiedAt: number;
|
|
39
|
+
/** Arbitrary user metadata stored alongside the object. */
|
|
40
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
41
|
+
}
|
|
42
|
+
/** Anything you can hand to {@link FileStore.put} as the body — a stream is preferred. */
|
|
43
|
+
type FileBody = ReadableStream<Uint8Array> | Uint8Array | Blob | string;
|
|
44
|
+
/** A stored object's metadata plus lazy accessors for its bytes. */
|
|
45
|
+
interface FileObject {
|
|
46
|
+
/** The object's metadata. */
|
|
47
|
+
readonly info: FileInfo;
|
|
48
|
+
/** The body as a byte stream (read it once). */
|
|
49
|
+
stream(): ReadableStream<Uint8Array>;
|
|
50
|
+
/** Read the whole body into memory. */
|
|
51
|
+
bytes(): Promise<Uint8Array>;
|
|
52
|
+
/** Read the whole body as a UTF-8 string. */
|
|
53
|
+
text(): Promise<string>;
|
|
54
|
+
}
|
|
55
|
+
/** Options for {@link FileStore.put}. */
|
|
56
|
+
interface PutOptions {
|
|
57
|
+
/** MIME type to record (and serve). */
|
|
58
|
+
readonly contentType?: string;
|
|
59
|
+
/** Arbitrary user metadata to store. */
|
|
60
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
61
|
+
}
|
|
62
|
+
/** Options for {@link FileStore.list}. */
|
|
63
|
+
interface ListOptions {
|
|
64
|
+
/** Max keys to return in this page (the backend may return fewer). */
|
|
65
|
+
readonly limit?: number;
|
|
66
|
+
/** Continuation cursor from a previous page's {@link ListResult.cursor}. */
|
|
67
|
+
readonly cursor?: string;
|
|
68
|
+
}
|
|
69
|
+
/** A page of {@link FileStore.list} results. */
|
|
70
|
+
interface ListResult {
|
|
71
|
+
/** The objects in this page (metadata only), key-sorted. */
|
|
72
|
+
readonly files: readonly FileInfo[];
|
|
73
|
+
/** Pass to a follow-up `list({ cursor })` to continue; absent when the listing is complete. */
|
|
74
|
+
readonly cursor?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The storage contract — a small, S3-like, key-based interface. Implementations:
|
|
78
|
+
* {@link fsFiles} (`@ayepi/files/fs`) and `s3Files` (`@ayepi/aws`).
|
|
79
|
+
*/
|
|
80
|
+
interface FileStore {
|
|
81
|
+
/** Store `body` under `key` (overwriting any existing object); returns the resulting metadata. */
|
|
82
|
+
put(key: string, body: FileBody, opts?: PutOptions): Promise<FileInfo>;
|
|
83
|
+
/** Fetch an object (metadata + lazy body), or `undefined` if the key doesn't exist. */
|
|
84
|
+
get(key: string): Promise<FileObject | undefined>;
|
|
85
|
+
/** Fetch just the metadata, or `undefined` if the key doesn't exist. */
|
|
86
|
+
head(key: string): Promise<FileInfo | undefined>;
|
|
87
|
+
/** Delete an object; resolves `true` if it existed. */
|
|
88
|
+
delete(key: string): Promise<boolean>;
|
|
89
|
+
/** List objects whose key starts with `prefix`, paginated. */
|
|
90
|
+
list(prefix: string, opts?: ListOptions): Promise<ListResult>;
|
|
91
|
+
}
|
|
92
|
+
/** Options for {@link Presigner.presignDownload}. */
|
|
93
|
+
interface PresignDownloadOptions {
|
|
94
|
+
/** Seconds until the URL expires (default chosen by the presigner). */
|
|
95
|
+
readonly expiresIn?: number;
|
|
96
|
+
}
|
|
97
|
+
/** Options for {@link Presigner.presignUpload}. */
|
|
98
|
+
interface PresignUploadOptions {
|
|
99
|
+
/** Seconds until the URL expires (default chosen by the presigner). */
|
|
100
|
+
readonly expiresIn?: number;
|
|
101
|
+
/** Pin the `Content-Type` the upload must use. */
|
|
102
|
+
readonly contentType?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The presign capability — kept separate from {@link FileStore} because not every store can
|
|
106
|
+
* self-serve. `s3Files` implements it natively; the filesystem store gets it from
|
|
107
|
+
* `@ayepi/files/server` ({@link mountFiles}).
|
|
108
|
+
*/
|
|
109
|
+
interface Presigner {
|
|
110
|
+
/** A time-limited URL to GET `key`. */
|
|
111
|
+
presignDownload(key: string, opts?: PresignDownloadOptions): Promise<string>;
|
|
112
|
+
/** A time-limited URL to PUT `key`. */
|
|
113
|
+
presignUpload(key: string, opts?: PresignUploadOptions): Promise<string>;
|
|
114
|
+
}
|
|
115
|
+
/** Normalize any {@link FileBody} into a byte stream. */
|
|
116
|
+
declare function toStream(body: FileBody): ReadableStream<Uint8Array>;
|
|
117
|
+
/** Read a byte stream fully into a single `Uint8Array`. */
|
|
118
|
+
declare function collect(stream: ReadableStream<Uint8Array>): Promise<Uint8Array>;
|
|
119
|
+
/**
|
|
120
|
+
* Stream an object from one store to another (`src[srcKey] → dst[dstKey]`) without buffering
|
|
121
|
+
* it all in memory. Carries the source's `contentType`/`metadata` unless overridden. Throws
|
|
122
|
+
* if the source key is missing.
|
|
123
|
+
*/
|
|
124
|
+
declare function transfer(src: FileStore, srcKey: string, dst: FileStore, dstKey: string, opts?: PutOptions): Promise<FileInfo>;
|
|
125
|
+
//#endregion
|
|
126
|
+
export { FileBody, FileInfo, FileObject, FileStore, ListOptions, ListResult, PresignDownloadOptions, PresignUploadOptions, Presigner, PutOptions, collect, toStream, transfer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
/** Normalize any {@link FileBody} into a byte stream. */
|
|
3
|
+
function toStream(body) {
|
|
4
|
+
if (body instanceof ReadableStream) return body;
|
|
5
|
+
if (body instanceof Blob) return body.stream();
|
|
6
|
+
const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
|
|
7
|
+
return new ReadableStream({ start(controller) {
|
|
8
|
+
controller.enqueue(bytes);
|
|
9
|
+
controller.close();
|
|
10
|
+
} });
|
|
11
|
+
}
|
|
12
|
+
/** Read a byte stream fully into a single `Uint8Array`. */
|
|
13
|
+
async function collect(stream) {
|
|
14
|
+
const reader = stream.getReader();
|
|
15
|
+
const chunks = [];
|
|
16
|
+
let total = 0;
|
|
17
|
+
for (;;) {
|
|
18
|
+
const { done, value } = await reader.read();
|
|
19
|
+
if (done) break;
|
|
20
|
+
chunks.push(value);
|
|
21
|
+
total += value.length;
|
|
22
|
+
}
|
|
23
|
+
const out = new Uint8Array(total);
|
|
24
|
+
let offset = 0;
|
|
25
|
+
for (const c of chunks) {
|
|
26
|
+
out.set(c, offset);
|
|
27
|
+
offset += c.length;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Stream an object from one store to another (`src[srcKey] → dst[dstKey]`) without buffering
|
|
33
|
+
* it all in memory. Carries the source's `contentType`/`metadata` unless overridden. Throws
|
|
34
|
+
* if the source key is missing.
|
|
35
|
+
*/
|
|
36
|
+
async function transfer(src, srcKey, dst, dstKey, opts) {
|
|
37
|
+
const obj = await src.get(srcKey);
|
|
38
|
+
if (!obj) throw new Error(`transfer: source key "${srcKey}" not found`);
|
|
39
|
+
return dst.put(dstKey, obj.stream(), {
|
|
40
|
+
contentType: opts?.contentType ?? obj.info.contentType,
|
|
41
|
+
metadata: opts?.metadata ?? obj.info.metadata
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { collect, toStream, transfer };
|