uploaderkit 1.0.0 → 2.0.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/NOTICE +16 -0
- package/README.es.md +1140 -0
- package/README.md +1135 -0
- package/dist/FileViewer-ChTf7-uY.d.cts +49 -0
- package/dist/FileViewer-nuD-GEdJ.d.ts +49 -0
- package/dist/adapters/gcs.cjs +83 -0
- package/dist/adapters/gcs.cjs.map +1 -0
- package/dist/adapters/gcs.d.cts +31 -0
- package/dist/adapters/gcs.d.ts +31 -0
- package/dist/adapters/gcs.js +81 -0
- package/dist/adapters/gcs.js.map +1 -0
- package/dist/adapters/memory.cjs +27 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.d.cts +18 -0
- package/dist/adapters/memory.d.ts +18 -0
- package/dist/adapters/memory.js +25 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/s3.cjs +75 -0
- package/dist/adapters/s3.cjs.map +1 -0
- package/dist/adapters/s3.d.cts +27 -0
- package/dist/adapters/s3.d.ts +27 -0
- package/dist/adapters/s3.js +73 -0
- package/dist/adapters/s3.js.map +1 -0
- package/dist/chunk-3FI44IOW.js +150 -0
- package/dist/chunk-3FI44IOW.js.map +1 -0
- package/dist/chunk-H7BRW5IY.js +308 -0
- package/dist/chunk-H7BRW5IY.js.map +1 -0
- package/dist/chunk-PDKAF4GX.js +707 -0
- package/dist/chunk-PDKAF4GX.js.map +1 -0
- package/dist/chunk-PTEX7F4R.js +309 -0
- package/dist/chunk-PTEX7F4R.js.map +1 -0
- package/dist/chunk-T5YZG6JW.js +511 -0
- package/dist/chunk-T5YZG6JW.js.map +1 -0
- package/dist/index.cjs +220 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -170
- package/dist/index.d.ts +57 -170
- package/dist/index.js +1 -324
- package/dist/index.js.map +1 -1
- package/dist/presets.cjs +1665 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +100 -0
- package/dist/presets.d.ts +100 -0
- package/dist/presets.js +405 -0
- package/dist/presets.js.map +1 -0
- package/dist/react.cjs +981 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +106 -0
- package/dist/react.d.ts +106 -0
- package/dist/react.js +5 -0
- package/dist/react.js.map +1 -0
- package/dist/server/express.cjs +179 -0
- package/dist/server/express.cjs.map +1 -0
- package/dist/server/express.d.cts +64 -0
- package/dist/server/express.d.ts +64 -0
- package/dist/server/express.js +111 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/next.cjs +165 -0
- package/dist/server/next.cjs.map +1 -0
- package/dist/server/next.d.cts +38 -0
- package/dist/server/next.d.ts +38 -0
- package/dist/server/next.js +107 -0
- package/dist/server/next.js.map +1 -0
- package/dist/server.cjs +550 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +19 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.js +80 -0
- package/dist/server.js.map +1 -0
- package/dist/storage-CYkSHWZX.d.cts +133 -0
- package/dist/storage-Qc9epG0G.d.ts +133 -0
- package/dist/types-BSlJJwti.d.cts +341 -0
- package/dist/types-BSlJJwti.d.ts +341 -0
- package/dist/ui.cjs +2325 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +331 -0
- package/dist/ui.d.ts +331 -0
- package/dist/ui.js +749 -0
- package/dist/ui.js.map +1 -0
- package/dist/useSlottedUploader-BzT5jV8c.d.cts +139 -0
- package/dist/useSlottedUploader-IkG5RhEO.d.ts +139 -0
- package/dist/useUploader-BiBdS-7y.d.cts +117 -0
- package/dist/useUploader-CQHpj_oI.d.ts +117 -0
- package/package.json +189 -3
- package/tailwind.css +80 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { U as UploaderLabels } from './types-BSlJJwti.cjs';
|
|
4
|
+
|
|
5
|
+
/** What the viewer needs to render a file — a StoredFile satisfies it. */
|
|
6
|
+
type ViewableFile = {
|
|
7
|
+
url: string;
|
|
8
|
+
fileName?: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
};
|
|
11
|
+
type FileViewerProps = {
|
|
12
|
+
/** File to show; `null` keeps the viewer closed. */
|
|
13
|
+
file: ViewableFile | null;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* The collection to browse. With more than one entry the viewer gains side
|
|
17
|
+
* arrows and ←/→ keyboard navigation, starting at `file`'s position.
|
|
18
|
+
*/
|
|
19
|
+
files?: ViewableFile[];
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a fresh URL right before rendering — the hook for private scopes,
|
|
22
|
+
* where the persisted signed URL may have expired and the app re-signs via
|
|
23
|
+
* its /storage signed-url endpoint.
|
|
24
|
+
*/
|
|
25
|
+
resolveUrl?: (file: ViewableFile) => Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Replaces the built-in "could not load" panel. Receives the file that
|
|
28
|
+
* failed and a `retry` that re-resolves and re-renders it, so a custom
|
|
29
|
+
* panel keeps the recovery the default one offers.
|
|
30
|
+
*/
|
|
31
|
+
renderError?: (context: {
|
|
32
|
+
file: ViewableFile;
|
|
33
|
+
retry: () => void;
|
|
34
|
+
}) => ReactNode;
|
|
35
|
+
labels?: Partial<UploaderLabels>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Full-screen file preview: images zoomed to fit, PDFs embedded, anything
|
|
39
|
+
* else offered as a download — no more blind new-tab jumps. Pass `files` and
|
|
40
|
+
* it becomes a gallery: side arrows, ←/→ on the keyboard, a position counter.
|
|
41
|
+
* Used internally by `Uploader` and `SlottedUploader`, and exported standalone
|
|
42
|
+
* so any screen can preview the `StoredFile`s it persisted.
|
|
43
|
+
*
|
|
44
|
+
* Portals to `<body>` so an ancestor's stacking context can never trap it,
|
|
45
|
+
* and traps focus while open.
|
|
46
|
+
*/
|
|
47
|
+
declare const FileViewer: ({ file, onClose, files, resolveUrl, renderError, labels, }: FileViewerProps) => react.ReactPortal | null;
|
|
48
|
+
|
|
49
|
+
export { type FileViewerProps as F, type ViewableFile as V, FileViewer as a };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { U as UploaderLabels } from './types-BSlJJwti.js';
|
|
4
|
+
|
|
5
|
+
/** What the viewer needs to render a file — a StoredFile satisfies it. */
|
|
6
|
+
type ViewableFile = {
|
|
7
|
+
url: string;
|
|
8
|
+
fileName?: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
};
|
|
11
|
+
type FileViewerProps = {
|
|
12
|
+
/** File to show; `null` keeps the viewer closed. */
|
|
13
|
+
file: ViewableFile | null;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* The collection to browse. With more than one entry the viewer gains side
|
|
17
|
+
* arrows and ←/→ keyboard navigation, starting at `file`'s position.
|
|
18
|
+
*/
|
|
19
|
+
files?: ViewableFile[];
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a fresh URL right before rendering — the hook for private scopes,
|
|
22
|
+
* where the persisted signed URL may have expired and the app re-signs via
|
|
23
|
+
* its /storage signed-url endpoint.
|
|
24
|
+
*/
|
|
25
|
+
resolveUrl?: (file: ViewableFile) => Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Replaces the built-in "could not load" panel. Receives the file that
|
|
28
|
+
* failed and a `retry` that re-resolves and re-renders it, so a custom
|
|
29
|
+
* panel keeps the recovery the default one offers.
|
|
30
|
+
*/
|
|
31
|
+
renderError?: (context: {
|
|
32
|
+
file: ViewableFile;
|
|
33
|
+
retry: () => void;
|
|
34
|
+
}) => ReactNode;
|
|
35
|
+
labels?: Partial<UploaderLabels>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Full-screen file preview: images zoomed to fit, PDFs embedded, anything
|
|
39
|
+
* else offered as a download — no more blind new-tab jumps. Pass `files` and
|
|
40
|
+
* it becomes a gallery: side arrows, ←/→ on the keyboard, a position counter.
|
|
41
|
+
* Used internally by `Uploader` and `SlottedUploader`, and exported standalone
|
|
42
|
+
* so any screen can preview the `StoredFile`s it persisted.
|
|
43
|
+
*
|
|
44
|
+
* Portals to `<body>` so an ancestor's stacking context can never trap it,
|
|
45
|
+
* and traps focus while open.
|
|
46
|
+
*/
|
|
47
|
+
declare const FileViewer: ({ file, onClose, files, resolveUrl, renderError, labels, }: FileViewerProps) => react.ReactPortal | null;
|
|
48
|
+
|
|
49
|
+
export { type FileViewerProps as F, type ViewableFile as V, FileViewer as a };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/gcs.ts
|
|
4
|
+
var defaultPublicUrl = (bucketName, key) => `https://storage.googleapis.com/${bucketName}/${encodeURI(key)}`;
|
|
5
|
+
var createGcsProvider = ({
|
|
6
|
+
publicBucket,
|
|
7
|
+
privateBucket,
|
|
8
|
+
publicUrl = defaultPublicUrl
|
|
9
|
+
}) => {
|
|
10
|
+
let publicAccessChecked = false;
|
|
11
|
+
const checkPublicAccess = (url, bucketName) => {
|
|
12
|
+
if (publicAccessChecked) return;
|
|
13
|
+
publicAccessChecked = true;
|
|
14
|
+
void fetch(url, { method: "HEAD" }).then((response) => {
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
console.error(
|
|
17
|
+
`[uploaderkit] gcs: public objects in "${bucketName}" answer ${response.status} \u2014 the bucket is not publicly readable. Grant it once with:
|
|
18
|
+
gcloud storage buckets add-iam-policy-binding gs://${bucketName} --member=allUsers --role=roles/storage.objectViewer`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}).catch(() => void 0);
|
|
22
|
+
};
|
|
23
|
+
const locate = async (key) => {
|
|
24
|
+
const [inPrivate] = await privateBucket.file(key).exists();
|
|
25
|
+
return inPrivate ? privateBucket : publicBucket;
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
name: "gcs",
|
|
29
|
+
capabilities: { signedUrl: true, resumable: false, rangeRead: true },
|
|
30
|
+
put: async ({ key, body, contentType, visibility, metadata }) => {
|
|
31
|
+
const bucket = visibility === "private" ? privateBucket : publicBucket;
|
|
32
|
+
const file = bucket.file(key);
|
|
33
|
+
await file.save(Buffer.from(body), {
|
|
34
|
+
contentType,
|
|
35
|
+
resumable: false,
|
|
36
|
+
metadata: { metadata }
|
|
37
|
+
});
|
|
38
|
+
if (visibility === "public") {
|
|
39
|
+
await file.makePublic().catch(() => void 0);
|
|
40
|
+
}
|
|
41
|
+
const url = publicUrl(bucket.name, key);
|
|
42
|
+
if (visibility === "public") checkPublicAccess(url, bucket.name);
|
|
43
|
+
return { key, url };
|
|
44
|
+
},
|
|
45
|
+
get: async (key) => {
|
|
46
|
+
const bucket = await locate(key);
|
|
47
|
+
const [data] = await bucket.file(key).download();
|
|
48
|
+
return new Uint8Array(data);
|
|
49
|
+
},
|
|
50
|
+
// `download()` buffers the whole object; a 20MB document costs that per
|
|
51
|
+
// concurrent request, and materialization serves many at once.
|
|
52
|
+
getStream: async (key) => {
|
|
53
|
+
const bucket = await locate(key);
|
|
54
|
+
return bucket.file(key).createReadStream();
|
|
55
|
+
},
|
|
56
|
+
delete: async (key) => {
|
|
57
|
+
const bucket = await locate(key);
|
|
58
|
+
return bucket.file(key).delete().then(() => true).catch(() => false);
|
|
59
|
+
},
|
|
60
|
+
signedUrl: async (key, { expiresIn, download }) => {
|
|
61
|
+
const bucket = await locate(key);
|
|
62
|
+
const [url] = await bucket.file(key).getSignedUrl({
|
|
63
|
+
version: "v4",
|
|
64
|
+
action: "read",
|
|
65
|
+
expires: Date.now() + expiresIn * 1e3,
|
|
66
|
+
...download ? { responseDisposition: "attachment" } : {}
|
|
67
|
+
});
|
|
68
|
+
return url;
|
|
69
|
+
},
|
|
70
|
+
list: async (prefix) => {
|
|
71
|
+
const [privateFiles] = await privateBucket.getFiles({ prefix });
|
|
72
|
+
const [publicFiles] = await publicBucket.getFiles({ prefix });
|
|
73
|
+
return [...privateFiles, ...publicFiles].map((file) => ({
|
|
74
|
+
key: file.name,
|
|
75
|
+
size: Number(file.metadata.size ?? 0)
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
exports.createGcsProvider = createGcsProvider;
|
|
82
|
+
//# sourceMappingURL=gcs.cjs.map
|
|
83
|
+
//# sourceMappingURL=gcs.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/gcs.ts"],"names":[],"mappings":"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","file":"gcs.cjs","sourcesContent":["import type { Bucket } from '@google-cloud/storage'\n\nimport type { StreamingStorageProvider } from '../server/storage'\n\nexport type GcsProviderOptions = {\n\t/** Objects with `visibility: 'public'` land here and get a direct URL. */\n\tpublicBucket: Bucket\n\t/** Objects with `visibility: 'private'` land here; access is signed-only. */\n\tprivateBucket: Bucket\n\t/**\n\t * Direct URL for a public object.\n\t * @defaultValue `https://storage.googleapis.com/{bucket}/{key}`\n\t */\n\tpublicUrl?: (bucketName: string, key: string) => string\n}\n\nconst defaultPublicUrl = (bucketName: string, key: string): string =>\n\t`https://storage.googleapis.com/${bucketName}/${encodeURI(key)}`\n\n/**\n * Google Cloud Storage provider over a two-bucket layout: public assets on\n * one bucket, sensitive documents on another that is never world-readable.\n *\n * `@google-cloud/storage` is an optional peer — importing this module without\n * it installed fails, but only for the app that chose GCS.\n *\n * Reads probe the private bucket first and fall back to the public one; keys\n * are unique per scope path, so a hit in both cannot happen.\n *\n * @see AGENTS.md §6 — why the probe exists\n */\nexport const createGcsProvider = ({\n\tpublicBucket,\n\tprivateBucket,\n\tpublicUrl = defaultPublicUrl,\n}: GcsProviderOptions): StreamingStorageProvider => {\n\t// Under uniform bucket-level access, per-object `makePublic()` fails\n\t// silently and a \"public\" upload 403s in every <img> — a bug that only\n\t// shows as broken avatars. Probe the first public upload's URL once and\n\t// say exactly how to fix the bucket.\n\tlet publicAccessChecked = false\n\tconst checkPublicAccess = (url: string, bucketName: string): void => {\n\t\tif (publicAccessChecked) return\n\t\tpublicAccessChecked = true\n\t\tvoid fetch(url, { method: 'HEAD' })\n\t\t\t.then(response => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[uploaderkit] gcs: public objects in \"${bucketName}\" answer ${response.status} — the bucket is not publicly readable. Grant it once with:\\n` +\n\t\t\t\t\t\t\t` gcloud storage buckets add-iam-policy-binding gs://${bucketName} --member=allUsers --role=roles/storage.objectViewer`\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => undefined)\n\t}\n\n\tconst locate = async (key: string): Promise<Bucket> => {\n\t\tconst [inPrivate] = await privateBucket.file(key).exists()\n\t\treturn inPrivate ? privateBucket : publicBucket\n\t}\n\n\treturn {\n\t\tname: 'gcs',\n\t\tcapabilities: { signedUrl: true, resumable: false, rangeRead: true },\n\n\t\tput: async ({ key, body, contentType, visibility, metadata }) => {\n\t\t\tconst bucket = visibility === 'private' ? privateBucket : publicBucket\n\t\t\tconst file = bucket.file(key)\n\n\t\t\tawait file.save(Buffer.from(body), {\n\t\t\t\tcontentType,\n\t\t\t\tresumable: false,\n\t\t\t\tmetadata: { metadata },\n\t\t\t})\n\n\t\t\tif (visibility === 'public') {\n\t\t\t\t// Buckets with uniform public access reject per-object ACLs; when\n\t\t\t\t// the bucket itself grants allUsers read, this failure is benign —\n\t\t\t\t// and when it does not, the probe below reports it loudly.\n\t\t\t\tawait file.makePublic().catch(() => undefined)\n\t\t\t}\n\n\t\t\tconst url = publicUrl(bucket.name, key)\n\t\t\tif (visibility === 'public') checkPublicAccess(url, bucket.name)\n\t\t\treturn { key, url }\n\t\t},\n\n\t\tget: async key => {\n\t\t\tconst bucket = await locate(key)\n\t\t\tconst [data] = await bucket.file(key).download()\n\t\t\treturn new Uint8Array(data)\n\t\t},\n\n\t\t// `download()` buffers the whole object; a 20MB document costs that per\n\t\t// concurrent request, and materialization serves many at once.\n\t\tgetStream: async key => {\n\t\t\tconst bucket = await locate(key)\n\t\t\treturn bucket.file(key).createReadStream()\n\t\t},\n\n\t\tdelete: async key => {\n\t\t\tconst bucket = await locate(key)\n\t\t\treturn bucket\n\t\t\t\t.file(key)\n\t\t\t\t.delete()\n\t\t\t\t.then(() => true)\n\t\t\t\t.catch(() => false)\n\t\t},\n\n\t\tsignedUrl: async (key, { expiresIn, download }) => {\n\t\t\tconst bucket = await locate(key)\n\t\t\tconst [url] = await bucket.file(key).getSignedUrl({\n\t\t\t\tversion: 'v4',\n\t\t\t\taction: 'read',\n\t\t\t\texpires: Date.now() + expiresIn * 1000,\n\t\t\t\t...(download ? { responseDisposition: 'attachment' } : {}),\n\t\t\t})\n\t\t\treturn url\n\t\t},\n\n\t\tlist: async prefix => {\n\t\t\tconst [privateFiles] = await privateBucket.getFiles({ prefix })\n\t\t\tconst [publicFiles] = await publicBucket.getFiles({ prefix })\n\t\t\treturn [...privateFiles, ...publicFiles].map(file => ({\n\t\t\t\tkey: file.name,\n\t\t\t\tsize: Number(file.metadata.size ?? 0),\n\t\t\t}))\n\t\t},\n\t}\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Bucket } from '@google-cloud/storage';
|
|
2
|
+
import { S as StreamingStorageProvider } from '../storage-CYkSHWZX.cjs';
|
|
3
|
+
import 'node:stream';
|
|
4
|
+
import '../types-BSlJJwti.cjs';
|
|
5
|
+
|
|
6
|
+
type GcsProviderOptions = {
|
|
7
|
+
/** Objects with `visibility: 'public'` land here and get a direct URL. */
|
|
8
|
+
publicBucket: Bucket;
|
|
9
|
+
/** Objects with `visibility: 'private'` land here; access is signed-only. */
|
|
10
|
+
privateBucket: Bucket;
|
|
11
|
+
/**
|
|
12
|
+
* Direct URL for a public object.
|
|
13
|
+
* @defaultValue `https://storage.googleapis.com/{bucket}/{key}`
|
|
14
|
+
*/
|
|
15
|
+
publicUrl?: (bucketName: string, key: string) => string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Google Cloud Storage provider over a two-bucket layout: public assets on
|
|
19
|
+
* one bucket, sensitive documents on another that is never world-readable.
|
|
20
|
+
*
|
|
21
|
+
* `@google-cloud/storage` is an optional peer — importing this module without
|
|
22
|
+
* it installed fails, but only for the app that chose GCS.
|
|
23
|
+
*
|
|
24
|
+
* Reads probe the private bucket first and fall back to the public one; keys
|
|
25
|
+
* are unique per scope path, so a hit in both cannot happen.
|
|
26
|
+
*
|
|
27
|
+
* @see AGENTS.md §6 — why the probe exists
|
|
28
|
+
*/
|
|
29
|
+
declare const createGcsProvider: ({ publicBucket, privateBucket, publicUrl, }: GcsProviderOptions) => StreamingStorageProvider;
|
|
30
|
+
|
|
31
|
+
export { type GcsProviderOptions, createGcsProvider };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Bucket } from '@google-cloud/storage';
|
|
2
|
+
import { S as StreamingStorageProvider } from '../storage-Qc9epG0G.js';
|
|
3
|
+
import 'node:stream';
|
|
4
|
+
import '../types-BSlJJwti.js';
|
|
5
|
+
|
|
6
|
+
type GcsProviderOptions = {
|
|
7
|
+
/** Objects with `visibility: 'public'` land here and get a direct URL. */
|
|
8
|
+
publicBucket: Bucket;
|
|
9
|
+
/** Objects with `visibility: 'private'` land here; access is signed-only. */
|
|
10
|
+
privateBucket: Bucket;
|
|
11
|
+
/**
|
|
12
|
+
* Direct URL for a public object.
|
|
13
|
+
* @defaultValue `https://storage.googleapis.com/{bucket}/{key}`
|
|
14
|
+
*/
|
|
15
|
+
publicUrl?: (bucketName: string, key: string) => string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Google Cloud Storage provider over a two-bucket layout: public assets on
|
|
19
|
+
* one bucket, sensitive documents on another that is never world-readable.
|
|
20
|
+
*
|
|
21
|
+
* `@google-cloud/storage` is an optional peer — importing this module without
|
|
22
|
+
* it installed fails, but only for the app that chose GCS.
|
|
23
|
+
*
|
|
24
|
+
* Reads probe the private bucket first and fall back to the public one; keys
|
|
25
|
+
* are unique per scope path, so a hit in both cannot happen.
|
|
26
|
+
*
|
|
27
|
+
* @see AGENTS.md §6 — why the probe exists
|
|
28
|
+
*/
|
|
29
|
+
declare const createGcsProvider: ({ publicBucket, privateBucket, publicUrl, }: GcsProviderOptions) => StreamingStorageProvider;
|
|
30
|
+
|
|
31
|
+
export { type GcsProviderOptions, createGcsProvider };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/adapters/gcs.ts
|
|
2
|
+
var defaultPublicUrl = (bucketName, key) => `https://storage.googleapis.com/${bucketName}/${encodeURI(key)}`;
|
|
3
|
+
var createGcsProvider = ({
|
|
4
|
+
publicBucket,
|
|
5
|
+
privateBucket,
|
|
6
|
+
publicUrl = defaultPublicUrl
|
|
7
|
+
}) => {
|
|
8
|
+
let publicAccessChecked = false;
|
|
9
|
+
const checkPublicAccess = (url, bucketName) => {
|
|
10
|
+
if (publicAccessChecked) return;
|
|
11
|
+
publicAccessChecked = true;
|
|
12
|
+
void fetch(url, { method: "HEAD" }).then((response) => {
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
console.error(
|
|
15
|
+
`[uploaderkit] gcs: public objects in "${bucketName}" answer ${response.status} \u2014 the bucket is not publicly readable. Grant it once with:
|
|
16
|
+
gcloud storage buckets add-iam-policy-binding gs://${bucketName} --member=allUsers --role=roles/storage.objectViewer`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}).catch(() => void 0);
|
|
20
|
+
};
|
|
21
|
+
const locate = async (key) => {
|
|
22
|
+
const [inPrivate] = await privateBucket.file(key).exists();
|
|
23
|
+
return inPrivate ? privateBucket : publicBucket;
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
name: "gcs",
|
|
27
|
+
capabilities: { signedUrl: true, resumable: false, rangeRead: true },
|
|
28
|
+
put: async ({ key, body, contentType, visibility, metadata }) => {
|
|
29
|
+
const bucket = visibility === "private" ? privateBucket : publicBucket;
|
|
30
|
+
const file = bucket.file(key);
|
|
31
|
+
await file.save(Buffer.from(body), {
|
|
32
|
+
contentType,
|
|
33
|
+
resumable: false,
|
|
34
|
+
metadata: { metadata }
|
|
35
|
+
});
|
|
36
|
+
if (visibility === "public") {
|
|
37
|
+
await file.makePublic().catch(() => void 0);
|
|
38
|
+
}
|
|
39
|
+
const url = publicUrl(bucket.name, key);
|
|
40
|
+
if (visibility === "public") checkPublicAccess(url, bucket.name);
|
|
41
|
+
return { key, url };
|
|
42
|
+
},
|
|
43
|
+
get: async (key) => {
|
|
44
|
+
const bucket = await locate(key);
|
|
45
|
+
const [data] = await bucket.file(key).download();
|
|
46
|
+
return new Uint8Array(data);
|
|
47
|
+
},
|
|
48
|
+
// `download()` buffers the whole object; a 20MB document costs that per
|
|
49
|
+
// concurrent request, and materialization serves many at once.
|
|
50
|
+
getStream: async (key) => {
|
|
51
|
+
const bucket = await locate(key);
|
|
52
|
+
return bucket.file(key).createReadStream();
|
|
53
|
+
},
|
|
54
|
+
delete: async (key) => {
|
|
55
|
+
const bucket = await locate(key);
|
|
56
|
+
return bucket.file(key).delete().then(() => true).catch(() => false);
|
|
57
|
+
},
|
|
58
|
+
signedUrl: async (key, { expiresIn, download }) => {
|
|
59
|
+
const bucket = await locate(key);
|
|
60
|
+
const [url] = await bucket.file(key).getSignedUrl({
|
|
61
|
+
version: "v4",
|
|
62
|
+
action: "read",
|
|
63
|
+
expires: Date.now() + expiresIn * 1e3,
|
|
64
|
+
...download ? { responseDisposition: "attachment" } : {}
|
|
65
|
+
});
|
|
66
|
+
return url;
|
|
67
|
+
},
|
|
68
|
+
list: async (prefix) => {
|
|
69
|
+
const [privateFiles] = await privateBucket.getFiles({ prefix });
|
|
70
|
+
const [publicFiles] = await publicBucket.getFiles({ prefix });
|
|
71
|
+
return [...privateFiles, ...publicFiles].map((file) => ({
|
|
72
|
+
key: file.name,
|
|
73
|
+
size: Number(file.metadata.size ?? 0)
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export { createGcsProvider };
|
|
80
|
+
//# sourceMappingURL=gcs.js.map
|
|
81
|
+
//# sourceMappingURL=gcs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/gcs.ts"],"names":[],"mappings":"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","file":"gcs.js","sourcesContent":["import type { Bucket } from '@google-cloud/storage'\n\nimport type { StreamingStorageProvider } from '../server/storage'\n\nexport type GcsProviderOptions = {\n\t/** Objects with `visibility: 'public'` land here and get a direct URL. */\n\tpublicBucket: Bucket\n\t/** Objects with `visibility: 'private'` land here; access is signed-only. */\n\tprivateBucket: Bucket\n\t/**\n\t * Direct URL for a public object.\n\t * @defaultValue `https://storage.googleapis.com/{bucket}/{key}`\n\t */\n\tpublicUrl?: (bucketName: string, key: string) => string\n}\n\nconst defaultPublicUrl = (bucketName: string, key: string): string =>\n\t`https://storage.googleapis.com/${bucketName}/${encodeURI(key)}`\n\n/**\n * Google Cloud Storage provider over a two-bucket layout: public assets on\n * one bucket, sensitive documents on another that is never world-readable.\n *\n * `@google-cloud/storage` is an optional peer — importing this module without\n * it installed fails, but only for the app that chose GCS.\n *\n * Reads probe the private bucket first and fall back to the public one; keys\n * are unique per scope path, so a hit in both cannot happen.\n *\n * @see AGENTS.md §6 — why the probe exists\n */\nexport const createGcsProvider = ({\n\tpublicBucket,\n\tprivateBucket,\n\tpublicUrl = defaultPublicUrl,\n}: GcsProviderOptions): StreamingStorageProvider => {\n\t// Under uniform bucket-level access, per-object `makePublic()` fails\n\t// silently and a \"public\" upload 403s in every <img> — a bug that only\n\t// shows as broken avatars. Probe the first public upload's URL once and\n\t// say exactly how to fix the bucket.\n\tlet publicAccessChecked = false\n\tconst checkPublicAccess = (url: string, bucketName: string): void => {\n\t\tif (publicAccessChecked) return\n\t\tpublicAccessChecked = true\n\t\tvoid fetch(url, { method: 'HEAD' })\n\t\t\t.then(response => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[uploaderkit] gcs: public objects in \"${bucketName}\" answer ${response.status} — the bucket is not publicly readable. Grant it once with:\\n` +\n\t\t\t\t\t\t\t` gcloud storage buckets add-iam-policy-binding gs://${bucketName} --member=allUsers --role=roles/storage.objectViewer`\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => undefined)\n\t}\n\n\tconst locate = async (key: string): Promise<Bucket> => {\n\t\tconst [inPrivate] = await privateBucket.file(key).exists()\n\t\treturn inPrivate ? privateBucket : publicBucket\n\t}\n\n\treturn {\n\t\tname: 'gcs',\n\t\tcapabilities: { signedUrl: true, resumable: false, rangeRead: true },\n\n\t\tput: async ({ key, body, contentType, visibility, metadata }) => {\n\t\t\tconst bucket = visibility === 'private' ? privateBucket : publicBucket\n\t\t\tconst file = bucket.file(key)\n\n\t\t\tawait file.save(Buffer.from(body), {\n\t\t\t\tcontentType,\n\t\t\t\tresumable: false,\n\t\t\t\tmetadata: { metadata },\n\t\t\t})\n\n\t\t\tif (visibility === 'public') {\n\t\t\t\t// Buckets with uniform public access reject per-object ACLs; when\n\t\t\t\t// the bucket itself grants allUsers read, this failure is benign —\n\t\t\t\t// and when it does not, the probe below reports it loudly.\n\t\t\t\tawait file.makePublic().catch(() => undefined)\n\t\t\t}\n\n\t\t\tconst url = publicUrl(bucket.name, key)\n\t\t\tif (visibility === 'public') checkPublicAccess(url, bucket.name)\n\t\t\treturn { key, url }\n\t\t},\n\n\t\tget: async key => {\n\t\t\tconst bucket = await locate(key)\n\t\t\tconst [data] = await bucket.file(key).download()\n\t\t\treturn new Uint8Array(data)\n\t\t},\n\n\t\t// `download()` buffers the whole object; a 20MB document costs that per\n\t\t// concurrent request, and materialization serves many at once.\n\t\tgetStream: async key => {\n\t\t\tconst bucket = await locate(key)\n\t\t\treturn bucket.file(key).createReadStream()\n\t\t},\n\n\t\tdelete: async key => {\n\t\t\tconst bucket = await locate(key)\n\t\t\treturn bucket\n\t\t\t\t.file(key)\n\t\t\t\t.delete()\n\t\t\t\t.then(() => true)\n\t\t\t\t.catch(() => false)\n\t\t},\n\n\t\tsignedUrl: async (key, { expiresIn, download }) => {\n\t\t\tconst bucket = await locate(key)\n\t\t\tconst [url] = await bucket.file(key).getSignedUrl({\n\t\t\t\tversion: 'v4',\n\t\t\t\taction: 'read',\n\t\t\t\texpires: Date.now() + expiresIn * 1000,\n\t\t\t\t...(download ? { responseDisposition: 'attachment' } : {}),\n\t\t\t})\n\t\t\treturn url\n\t\t},\n\n\t\tlist: async prefix => {\n\t\t\tconst [privateFiles] = await privateBucket.getFiles({ prefix })\n\t\t\tconst [publicFiles] = await publicBucket.getFiles({ prefix })\n\t\t\treturn [...privateFiles, ...publicFiles].map(file => ({\n\t\t\t\tkey: file.name,\n\t\t\t\tsize: Number(file.metadata.size ?? 0),\n\t\t\t}))\n\t\t},\n\t}\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/memory.ts
|
|
4
|
+
var createMemoryProvider = () => {
|
|
5
|
+
const objects = /* @__PURE__ */ new Map();
|
|
6
|
+
return {
|
|
7
|
+
name: "memory",
|
|
8
|
+
capabilities: { signedUrl: true, resumable: false, rangeRead: true },
|
|
9
|
+
objects,
|
|
10
|
+
put: async ({ key, body, contentType, visibility }) => {
|
|
11
|
+
objects.set(key, { body, contentType, visibility });
|
|
12
|
+
return { key, url: `memory://${key}` };
|
|
13
|
+
},
|
|
14
|
+
get: async (key) => {
|
|
15
|
+
const object = objects.get(key);
|
|
16
|
+
if (!object) throw new Error(`memory provider: no object at "${key}"`);
|
|
17
|
+
return object.body;
|
|
18
|
+
},
|
|
19
|
+
delete: async (key) => objects.delete(key),
|
|
20
|
+
signedUrl: async (key, { expiresIn }) => `memory://signed/${key}?expires=${expiresIn}`,
|
|
21
|
+
list: async (prefix) => [...objects.entries()].filter(([key]) => key.startsWith(prefix)).map(([key, object]) => ({ key, size: object.body.byteLength }))
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
exports.createMemoryProvider = createMemoryProvider;
|
|
26
|
+
//# sourceMappingURL=memory.cjs.map
|
|
27
|
+
//# sourceMappingURL=memory.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/memory.ts"],"names":[],"mappings":";;;AAYO,IAAM,uBAAuB,MAG/B;AACJ,EAAA,MAAM,OAAA,uBAAc,GAAA,EAA0B;AAE9C,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,QAAA;AAAA,IACN,cAAc,EAAE,SAAA,EAAW,MAAM,SAAA,EAAW,KAAA,EAAO,WAAW,IAAA,EAAK;AAAA,IACnE,OAAA;AAAA,IAEA,KAAK,OAAO,EAAE,KAAK,IAAA,EAAM,WAAA,EAAa,YAAW,KAAM;AACtD,MAAA,OAAA,CAAQ,IAAI,GAAA,EAAK,EAAE,IAAA,EAAM,WAAA,EAAa,YAAY,CAAA;AAClD,MAAA,OAAO,EAAE,GAAA,EAAK,GAAA,EAAK,CAAA,SAAA,EAAY,GAAG,CAAA,CAAA,EAAG;AAAA,IACtC,CAAA;AAAA,IAEA,GAAA,EAAK,OAAM,GAAA,KAAO;AACjB,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAC9B,MAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,GAAG,CAAA,CAAA,CAAG,CAAA;AACrE,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IACf,CAAA;AAAA,IAEA,MAAA,EAAQ,OAAM,GAAA,KAAO,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,IAEvC,SAAA,EAAW,OAAO,GAAA,EAAK,EAAE,WAAU,KAClC,CAAA,gBAAA,EAAmB,GAAG,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA;AAAA,IAE5C,IAAA,EAAM,OAAM,MAAA,KACX,CAAC,GAAG,OAAA,CAAQ,OAAA,EAAS,CAAA,CACnB,MAAA,CAAO,CAAC,CAAC,GAAG,CAAA,KAAM,GAAA,CAAI,UAAA,CAAW,MAAM,CAAC,CAAA,CACxC,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,MAAM,CAAA,MAAO,EAAE,GAAA,EAAK,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,YAAW,CAAE;AAAA,GAClE;AACD","file":"memory.cjs","sourcesContent":["import type { StorageProvider, Visibility } from '../types'\ntype StoredObject = {\n\tbody: Uint8Array\n\tcontentType: string\n\tvisibility: Visibility\n}\n\n/**\n * In-memory provider for tests and local development. Signed URLs are fake but\n * carry the expiry, so an assertion can check TTL wiring without a cloud\n * account.\n */\nexport const createMemoryProvider = (): StorageProvider & {\n\t/** Test hook: inspect what was stored without going through `get`. */\n\tobjects: Map<string, StoredObject>\n} => {\n\tconst objects = new Map<string, StoredObject>()\n\n\treturn {\n\t\tname: 'memory',\n\t\tcapabilities: { signedUrl: true, resumable: false, rangeRead: true },\n\t\tobjects,\n\n\t\tput: async ({ key, body, contentType, visibility }) => {\n\t\t\tobjects.set(key, { body, contentType, visibility })\n\t\t\treturn { key, url: `memory://${key}` }\n\t\t},\n\n\t\tget: async key => {\n\t\t\tconst object = objects.get(key)\n\t\t\tif (!object) throw new Error(`memory provider: no object at \"${key}\"`)\n\t\t\treturn object.body\n\t\t},\n\n\t\tdelete: async key => objects.delete(key),\n\n\t\tsignedUrl: async (key, { expiresIn }) =>\n\t\t\t`memory://signed/${key}?expires=${expiresIn}`,\n\n\t\tlist: async prefix =>\n\t\t\t[...objects.entries()]\n\t\t\t\t.filter(([key]) => key.startsWith(prefix))\n\t\t\t\t.map(([key, object]) => ({ key, size: object.body.byteLength })),\n\t}\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { S as StorageProvider, V as Visibility } from '../types-BSlJJwti.cjs';
|
|
2
|
+
|
|
3
|
+
type StoredObject = {
|
|
4
|
+
body: Uint8Array;
|
|
5
|
+
contentType: string;
|
|
6
|
+
visibility: Visibility;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* In-memory provider for tests and local development. Signed URLs are fake but
|
|
10
|
+
* carry the expiry, so an assertion can check TTL wiring without a cloud
|
|
11
|
+
* account.
|
|
12
|
+
*/
|
|
13
|
+
declare const createMemoryProvider: () => StorageProvider & {
|
|
14
|
+
/** Test hook: inspect what was stored without going through `get`. */
|
|
15
|
+
objects: Map<string, StoredObject>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { createMemoryProvider };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { S as StorageProvider, V as Visibility } from '../types-BSlJJwti.js';
|
|
2
|
+
|
|
3
|
+
type StoredObject = {
|
|
4
|
+
body: Uint8Array;
|
|
5
|
+
contentType: string;
|
|
6
|
+
visibility: Visibility;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* In-memory provider for tests and local development. Signed URLs are fake but
|
|
10
|
+
* carry the expiry, so an assertion can check TTL wiring without a cloud
|
|
11
|
+
* account.
|
|
12
|
+
*/
|
|
13
|
+
declare const createMemoryProvider: () => StorageProvider & {
|
|
14
|
+
/** Test hook: inspect what was stored without going through `get`. */
|
|
15
|
+
objects: Map<string, StoredObject>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { createMemoryProvider };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/adapters/memory.ts
|
|
2
|
+
var createMemoryProvider = () => {
|
|
3
|
+
const objects = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
name: "memory",
|
|
6
|
+
capabilities: { signedUrl: true, resumable: false, rangeRead: true },
|
|
7
|
+
objects,
|
|
8
|
+
put: async ({ key, body, contentType, visibility }) => {
|
|
9
|
+
objects.set(key, { body, contentType, visibility });
|
|
10
|
+
return { key, url: `memory://${key}` };
|
|
11
|
+
},
|
|
12
|
+
get: async (key) => {
|
|
13
|
+
const object = objects.get(key);
|
|
14
|
+
if (!object) throw new Error(`memory provider: no object at "${key}"`);
|
|
15
|
+
return object.body;
|
|
16
|
+
},
|
|
17
|
+
delete: async (key) => objects.delete(key),
|
|
18
|
+
signedUrl: async (key, { expiresIn }) => `memory://signed/${key}?expires=${expiresIn}`,
|
|
19
|
+
list: async (prefix) => [...objects.entries()].filter(([key]) => key.startsWith(prefix)).map(([key, object]) => ({ key, size: object.body.byteLength }))
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { createMemoryProvider };
|
|
24
|
+
//# sourceMappingURL=memory.js.map
|
|
25
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/memory.ts"],"names":[],"mappings":";AAYO,IAAM,uBAAuB,MAG/B;AACJ,EAAA,MAAM,OAAA,uBAAc,GAAA,EAA0B;AAE9C,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,QAAA;AAAA,IACN,cAAc,EAAE,SAAA,EAAW,MAAM,SAAA,EAAW,KAAA,EAAO,WAAW,IAAA,EAAK;AAAA,IACnE,OAAA;AAAA,IAEA,KAAK,OAAO,EAAE,KAAK,IAAA,EAAM,WAAA,EAAa,YAAW,KAAM;AACtD,MAAA,OAAA,CAAQ,IAAI,GAAA,EAAK,EAAE,IAAA,EAAM,WAAA,EAAa,YAAY,CAAA;AAClD,MAAA,OAAO,EAAE,GAAA,EAAK,GAAA,EAAK,CAAA,SAAA,EAAY,GAAG,CAAA,CAAA,EAAG;AAAA,IACtC,CAAA;AAAA,IAEA,GAAA,EAAK,OAAM,GAAA,KAAO;AACjB,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAC9B,MAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,GAAG,CAAA,CAAA,CAAG,CAAA;AACrE,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IACf,CAAA;AAAA,IAEA,MAAA,EAAQ,OAAM,GAAA,KAAO,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,IAEvC,SAAA,EAAW,OAAO,GAAA,EAAK,EAAE,WAAU,KAClC,CAAA,gBAAA,EAAmB,GAAG,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA;AAAA,IAE5C,IAAA,EAAM,OAAM,MAAA,KACX,CAAC,GAAG,OAAA,CAAQ,OAAA,EAAS,CAAA,CACnB,MAAA,CAAO,CAAC,CAAC,GAAG,CAAA,KAAM,GAAA,CAAI,UAAA,CAAW,MAAM,CAAC,CAAA,CACxC,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,MAAM,CAAA,MAAO,EAAE,GAAA,EAAK,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,YAAW,CAAE;AAAA,GAClE;AACD","file":"memory.js","sourcesContent":["import type { StorageProvider, Visibility } from '../types'\ntype StoredObject = {\n\tbody: Uint8Array\n\tcontentType: string\n\tvisibility: Visibility\n}\n\n/**\n * In-memory provider for tests and local development. Signed URLs are fake but\n * carry the expiry, so an assertion can check TTL wiring without a cloud\n * account.\n */\nexport const createMemoryProvider = (): StorageProvider & {\n\t/** Test hook: inspect what was stored without going through `get`. */\n\tobjects: Map<string, StoredObject>\n} => {\n\tconst objects = new Map<string, StoredObject>()\n\n\treturn {\n\t\tname: 'memory',\n\t\tcapabilities: { signedUrl: true, resumable: false, rangeRead: true },\n\t\tobjects,\n\n\t\tput: async ({ key, body, contentType, visibility }) => {\n\t\t\tobjects.set(key, { body, contentType, visibility })\n\t\t\treturn { key, url: `memory://${key}` }\n\t\t},\n\n\t\tget: async key => {\n\t\t\tconst object = objects.get(key)\n\t\t\tif (!object) throw new Error(`memory provider: no object at \"${key}\"`)\n\t\t\treturn object.body\n\t\t},\n\n\t\tdelete: async key => objects.delete(key),\n\n\t\tsignedUrl: async (key, { expiresIn }) =>\n\t\t\t`memory://signed/${key}?expires=${expiresIn}`,\n\n\t\tlist: async prefix =>\n\t\t\t[...objects.entries()]\n\t\t\t\t.filter(([key]) => key.startsWith(prefix))\n\t\t\t\t.map(([key, object]) => ({ key, size: object.body.byteLength })),\n\t}\n}\n"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clientS3 = require('@aws-sdk/client-s3');
|
|
4
|
+
var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
|
|
5
|
+
|
|
6
|
+
// src/adapters/s3.ts
|
|
7
|
+
var createS3Provider = ({
|
|
8
|
+
client,
|
|
9
|
+
bucket,
|
|
10
|
+
publicUrl
|
|
11
|
+
}) => ({
|
|
12
|
+
name: "s3",
|
|
13
|
+
capabilities: { signedUrl: true, resumable: false, rangeRead: true },
|
|
14
|
+
put: async ({ key, body, contentType, visibility, metadata }) => {
|
|
15
|
+
if (visibility === "public" && !publicUrl) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
's3 provider: a public scope needs the "publicUrl" option \u2014 per-object ACLs are blocked on modern buckets, so pass the CDN/public-domain mapping'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
await client.send(
|
|
21
|
+
new clientS3.PutObjectCommand({
|
|
22
|
+
Bucket: bucket,
|
|
23
|
+
Key: key,
|
|
24
|
+
Body: body,
|
|
25
|
+
ContentType: contentType,
|
|
26
|
+
Metadata: metadata
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
return {
|
|
30
|
+
key,
|
|
31
|
+
url: visibility === "public" ? publicUrl(key) : await s3RequestPresigner.getSignedUrl(
|
|
32
|
+
client,
|
|
33
|
+
new clientS3.GetObjectCommand({ Bucket: bucket, Key: key }),
|
|
34
|
+
{ expiresIn: 300 }
|
|
35
|
+
)
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
get: async (key) => {
|
|
39
|
+
const result = await client.send(
|
|
40
|
+
new clientS3.GetObjectCommand({ Bucket: bucket, Key: key })
|
|
41
|
+
);
|
|
42
|
+
if (!result.Body) throw new Error(`s3 provider: no object at "${key}"`);
|
|
43
|
+
return result.Body.transformToByteArray();
|
|
44
|
+
},
|
|
45
|
+
delete: async (key) => {
|
|
46
|
+
try {
|
|
47
|
+
await client.send(new clientS3.DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
signedUrl: (key, { expiresIn, download }) => s3RequestPresigner.getSignedUrl(
|
|
54
|
+
client,
|
|
55
|
+
new clientS3.GetObjectCommand({
|
|
56
|
+
Bucket: bucket,
|
|
57
|
+
Key: key,
|
|
58
|
+
...download ? { ResponseContentDisposition: "attachment" } : {}
|
|
59
|
+
}),
|
|
60
|
+
{ expiresIn }
|
|
61
|
+
),
|
|
62
|
+
list: async (prefix) => {
|
|
63
|
+
const result = await client.send(
|
|
64
|
+
new clientS3.ListObjectsV2Command({ Bucket: bucket, Prefix: prefix })
|
|
65
|
+
);
|
|
66
|
+
return (result.Contents ?? []).map((object) => ({
|
|
67
|
+
key: object.Key ?? "",
|
|
68
|
+
size: object.Size ?? 0
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
exports.createS3Provider = createS3Provider;
|
|
74
|
+
//# sourceMappingURL=s3.cjs.map
|
|
75
|
+
//# sourceMappingURL=s3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/s3.ts"],"names":["PutObjectCommand","getSignedUrl","GetObjectCommand","DeleteObjectCommand","ListObjectsV2Command"],"mappings":";;;;;;AAgCO,IAAM,mBAAmB,CAAC;AAAA,EAChC,MAAA;AAAA,EACA,MAAA;AAAA,EACA;AACD,CAAA,MAA2C;AAAA,EAC1C,IAAA,EAAM,IAAA;AAAA,EACN,cAAc,EAAE,SAAA,EAAW,MAAM,SAAA,EAAW,KAAA,EAAO,WAAW,IAAA,EAAK;AAAA,EAEnE,GAAA,EAAK,OAAO,EAAE,GAAA,EAAK,MAAM,WAAA,EAAa,UAAA,EAAY,UAAS,KAAM;AAChE,IAAA,IAAI,UAAA,KAAe,QAAA,IAAY,CAAC,SAAA,EAAW;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACT;AAAA,OACD;AAAA,IACD;AAEA,IAAA,MAAM,MAAA,CAAO,IAAA;AAAA,MACZ,IAAIA,yBAAA,CAAiB;AAAA,QACpB,MAAA,EAAQ,MAAA;AAAA,QACR,GAAA,EAAK,GAAA;AAAA,QACL,IAAA,EAAM,IAAA;AAAA,QACN,WAAA,EAAa,WAAA;AAAA,QACb,QAAA,EAAU;AAAA,OACV;AAAA,KACF;AAEA,IAAA,OAAO;AAAA,MACN,GAAA;AAAA,MACA,KACC,UAAA,KAAe,QAAA,GACZ,SAAA,CAAW,GAAG,IACd,MAAMC,+BAAA;AAAA,QACN,MAAA;AAAA,QACA,IAAIC,yBAAA,CAAiB,EAAE,QAAQ,MAAA,EAAQ,GAAA,EAAK,KAAK,CAAA;AAAA,QACjD,EAAE,WAAW,GAAA;AAAI;AAClB,KACJ;AAAA,EACD,CAAA;AAAA,EAEA,GAAA,EAAK,OAAM,GAAA,KAAO;AACjB,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,IAAA;AAAA,MAC3B,IAAIA,yBAAA,CAAiB,EAAE,QAAQ,MAAA,EAAQ,GAAA,EAAK,KAAK;AAAA,KAClD;AACA,IAAA,IAAI,CAAC,OAAO,IAAA,EAAM,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,GAAG,CAAA,CAAA,CAAG,CAAA;AACtE,IAAA,OAAO,MAAA,CAAO,KAAK,oBAAA,EAAqB;AAAA,EACzC,CAAA;AAAA,EAEA,MAAA,EAAQ,OAAM,GAAA,KAAO;AACpB,IAAA,IAAI;AACH,MAAA,MAAM,MAAA,CAAO,IAAA,CAAK,IAAIC,4BAAA,CAAoB,EAAE,QAAQ,MAAA,EAAQ,GAAA,EAAK,GAAA,EAAK,CAAC,CAAA;AACvE,MAAA,OAAO,IAAA;AAAA,IACR,CAAA,CAAA,MAAQ;AACP,MAAA,OAAO,KAAA;AAAA,IACR;AAAA,EACD,CAAA;AAAA,EAEA,WAAW,CAAC,GAAA,EAAK,EAAE,SAAA,EAAW,UAAS,KACtCF,+BAAA;AAAA,IACC,MAAA;AAAA,IACA,IAAIC,yBAAA,CAAiB;AAAA,MACpB,MAAA,EAAQ,MAAA;AAAA,MACR,GAAA,EAAK,GAAA;AAAA,MACL,GAAI,QAAA,GAAW,EAAE,0BAAA,EAA4B,YAAA,KAAiB;AAAC,KAC/D,CAAA;AAAA,IACD,EAAE,SAAA;AAAU,GACb;AAAA,EAED,IAAA,EAAM,OAAM,MAAA,KAAU;AACrB,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,IAAA;AAAA,MAC3B,IAAIE,6BAAA,CAAqB,EAAE,QAAQ,MAAA,EAAQ,MAAA,EAAQ,QAAQ;AAAA,KAC5D;AACA,IAAA,OAAA,CAAQ,MAAA,CAAO,QAAA,IAAY,EAAC,EAAG,IAAI,CAAA,MAAA,MAAW;AAAA,MAC7C,GAAA,EAAK,OAAO,GAAA,IAAO,EAAA;AAAA,MACnB,IAAA,EAAM,OAAO,IAAA,IAAQ;AAAA,KACtB,CAAE,CAAA;AAAA,EACH;AACD,CAAA","file":"s3.cjs","sourcesContent":["import {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tListObjectsV2Command,\n\tPutObjectCommand,\n\ttype S3Client,\n} from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\n\nimport type { StorageProvider } from '../types'\nexport type S3ProviderOptions = {\n\tclient: S3Client\n\tbucket: string\n\t/**\n\t * Direct URL for a public object — your CDN, an R2 public domain, or the\n\t * bucket website. Required before the first `visibility: 'public'` upload:\n\t * modern buckets block per-object ACLs, so the adapter cannot invent a\n\t * stable public URL on its own.\n\t */\n\tpublicUrl?: (key: string) => string\n}\n\n/**\n * S3-compatible provider. One adapter covers AWS S3, Cloudflare R2, Backblaze\n * B2, MinIO and Wasabi — they all speak this protocol; only the client's\n * `endpoint`/credentials change.\n *\n * The bucket is treated as private (objects readable through signed URLs);\n * `publicUrl` maps the keys a CDN or public domain exposes. Both `@aws-sdk`\n * packages are optional peers — importing this module without them installed\n * fails, but only for the app that chose S3.\n */\nexport const createS3Provider = ({\n\tclient,\n\tbucket,\n\tpublicUrl,\n}: S3ProviderOptions): StorageProvider => ({\n\tname: 's3',\n\tcapabilities: { signedUrl: true, resumable: false, rangeRead: true },\n\n\tput: async ({ key, body, contentType, visibility, metadata }) => {\n\t\tif (visibility === 'public' && !publicUrl) {\n\t\t\tthrow new Error(\n\t\t\t\t's3 provider: a public scope needs the \"publicUrl\" option — per-object ACLs are blocked on modern buckets, so pass the CDN/public-domain mapping'\n\t\t\t)\n\t\t}\n\n\t\tawait client.send(\n\t\t\tnew PutObjectCommand({\n\t\t\t\tBucket: bucket,\n\t\t\t\tKey: key,\n\t\t\t\tBody: body,\n\t\t\t\tContentType: contentType,\n\t\t\t\tMetadata: metadata,\n\t\t\t})\n\t\t)\n\n\t\treturn {\n\t\t\tkey,\n\t\t\turl:\n\t\t\t\tvisibility === 'public'\n\t\t\t\t\t? publicUrl!(key)\n\t\t\t\t\t: await getSignedUrl(\n\t\t\t\t\t\t\tclient,\n\t\t\t\t\t\t\tnew GetObjectCommand({ Bucket: bucket, Key: key }),\n\t\t\t\t\t\t\t{ expiresIn: 300 }\n\t\t\t\t\t\t),\n\t\t}\n\t},\n\n\tget: async key => {\n\t\tconst result = await client.send(\n\t\t\tnew GetObjectCommand({ Bucket: bucket, Key: key })\n\t\t)\n\t\tif (!result.Body) throw new Error(`s3 provider: no object at \"${key}\"`)\n\t\treturn result.Body.transformToByteArray()\n\t},\n\n\tdelete: async key => {\n\t\ttry {\n\t\t\tawait client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))\n\t\t\treturn true\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t},\n\n\tsignedUrl: (key, { expiresIn, download }) =>\n\t\tgetSignedUrl(\n\t\t\tclient,\n\t\t\tnew GetObjectCommand({\n\t\t\t\tBucket: bucket,\n\t\t\t\tKey: key,\n\t\t\t\t...(download ? { ResponseContentDisposition: 'attachment' } : {}),\n\t\t\t}),\n\t\t\t{ expiresIn }\n\t\t),\n\n\tlist: async prefix => {\n\t\tconst result = await client.send(\n\t\t\tnew ListObjectsV2Command({ Bucket: bucket, Prefix: prefix })\n\t\t)\n\t\treturn (result.Contents ?? []).map(object => ({\n\t\t\tkey: object.Key ?? '',\n\t\t\tsize: object.Size ?? 0,\n\t\t}))\n\t},\n})\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { S as StorageProvider } from '../types-BSlJJwti.cjs';
|
|
3
|
+
|
|
4
|
+
type S3ProviderOptions = {
|
|
5
|
+
client: S3Client;
|
|
6
|
+
bucket: string;
|
|
7
|
+
/**
|
|
8
|
+
* Direct URL for a public object — your CDN, an R2 public domain, or the
|
|
9
|
+
* bucket website. Required before the first `visibility: 'public'` upload:
|
|
10
|
+
* modern buckets block per-object ACLs, so the adapter cannot invent a
|
|
11
|
+
* stable public URL on its own.
|
|
12
|
+
*/
|
|
13
|
+
publicUrl?: (key: string) => string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* S3-compatible provider. One adapter covers AWS S3, Cloudflare R2, Backblaze
|
|
17
|
+
* B2, MinIO and Wasabi — they all speak this protocol; only the client's
|
|
18
|
+
* `endpoint`/credentials change.
|
|
19
|
+
*
|
|
20
|
+
* The bucket is treated as private (objects readable through signed URLs);
|
|
21
|
+
* `publicUrl` maps the keys a CDN or public domain exposes. Both `@aws-sdk`
|
|
22
|
+
* packages are optional peers — importing this module without them installed
|
|
23
|
+
* fails, but only for the app that chose S3.
|
|
24
|
+
*/
|
|
25
|
+
declare const createS3Provider: ({ client, bucket, publicUrl, }: S3ProviderOptions) => StorageProvider;
|
|
26
|
+
|
|
27
|
+
export { type S3ProviderOptions, createS3Provider };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { S as StorageProvider } from '../types-BSlJJwti.js';
|
|
3
|
+
|
|
4
|
+
type S3ProviderOptions = {
|
|
5
|
+
client: S3Client;
|
|
6
|
+
bucket: string;
|
|
7
|
+
/**
|
|
8
|
+
* Direct URL for a public object — your CDN, an R2 public domain, or the
|
|
9
|
+
* bucket website. Required before the first `visibility: 'public'` upload:
|
|
10
|
+
* modern buckets block per-object ACLs, so the adapter cannot invent a
|
|
11
|
+
* stable public URL on its own.
|
|
12
|
+
*/
|
|
13
|
+
publicUrl?: (key: string) => string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* S3-compatible provider. One adapter covers AWS S3, Cloudflare R2, Backblaze
|
|
17
|
+
* B2, MinIO and Wasabi — they all speak this protocol; only the client's
|
|
18
|
+
* `endpoint`/credentials change.
|
|
19
|
+
*
|
|
20
|
+
* The bucket is treated as private (objects readable through signed URLs);
|
|
21
|
+
* `publicUrl` maps the keys a CDN or public domain exposes. Both `@aws-sdk`
|
|
22
|
+
* packages are optional peers — importing this module without them installed
|
|
23
|
+
* fails, but only for the app that chose S3.
|
|
24
|
+
*/
|
|
25
|
+
declare const createS3Provider: ({ client, bucket, publicUrl, }: S3ProviderOptions) => StorageProvider;
|
|
26
|
+
|
|
27
|
+
export { type S3ProviderOptions, createS3Provider };
|