@storyshelf/storage-s3 0.1.3 → 0.3.2
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/dist/index.mjs +53 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
5
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
5
7
|
//#region src/index.ts
|
|
6
8
|
/** Join an optional key prefix with a storage path. */
|
|
7
9
|
function s3Key(prefix, path) {
|
|
@@ -13,6 +15,28 @@ function s3Rel(prefix, key) {
|
|
|
13
15
|
function isNotFound(error) {
|
|
14
16
|
return error instanceof S3ServiceException && error.$metadata.httpStatusCode === 404;
|
|
15
17
|
}
|
|
18
|
+
function isWebStream(body) {
|
|
19
|
+
return typeof body === "object" && body !== null && typeof body.getReader === "function";
|
|
20
|
+
}
|
|
21
|
+
async function s3WriteStream(ctx, path, stream) {
|
|
22
|
+
await new Upload({
|
|
23
|
+
client: ctx.client,
|
|
24
|
+
params: {
|
|
25
|
+
Bucket: ctx.bucket,
|
|
26
|
+
Key: s3Key(ctx.prefix, path),
|
|
27
|
+
Body: stream
|
|
28
|
+
}
|
|
29
|
+
}).done();
|
|
30
|
+
}
|
|
31
|
+
async function s3ReadStream(ctx, path) {
|
|
32
|
+
const body = (await ctx.client.send(new GetObjectCommand({
|
|
33
|
+
Bucket: ctx.bucket,
|
|
34
|
+
Key: s3Key(ctx.prefix, path)
|
|
35
|
+
}))).Body;
|
|
36
|
+
if (body instanceof Readable) return body;
|
|
37
|
+
if (isWebStream(body)) return Readable.fromWeb(body);
|
|
38
|
+
throw new Error(`No object at "${path}"`);
|
|
39
|
+
}
|
|
16
40
|
async function s3Read(ctx, path) {
|
|
17
41
|
const body = (await ctx.client.send(new GetObjectCommand({
|
|
18
42
|
Bucket: ctx.bucket,
|
|
@@ -62,7 +86,29 @@ function createS3Storage(options) {
|
|
|
62
86
|
name: "S3 Storage",
|
|
63
87
|
version: globalThis.__PKG_VERSION__ ?? "0.0.0",
|
|
64
88
|
description: "S3-compatible storage adapter",
|
|
65
|
-
kind: "s3"
|
|
89
|
+
kind: "s3",
|
|
90
|
+
category: "storage"
|
|
91
|
+
},
|
|
92
|
+
lifecycle: {
|
|
93
|
+
init: async () => {
|
|
94
|
+
await client.send(new ListObjectsV2Command({
|
|
95
|
+
Bucket: bucket,
|
|
96
|
+
Prefix: prefix,
|
|
97
|
+
MaxKeys: 1
|
|
98
|
+
}));
|
|
99
|
+
},
|
|
100
|
+
health: async () => {
|
|
101
|
+
const started = Date.now();
|
|
102
|
+
await client.send(new ListObjectsV2Command({
|
|
103
|
+
Bucket: bucket,
|
|
104
|
+
Prefix: prefix,
|
|
105
|
+
MaxKeys: 1
|
|
106
|
+
}));
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
latencyMs: Date.now() - started
|
|
110
|
+
};
|
|
111
|
+
}
|
|
66
112
|
},
|
|
67
113
|
async read(path) {
|
|
68
114
|
return await s3Read(ctx, path);
|
|
@@ -85,6 +131,12 @@ function createS3Storage(options) {
|
|
|
85
131
|
},
|
|
86
132
|
async list(listPrefix) {
|
|
87
133
|
return await s3List(ctx, listPrefix);
|
|
134
|
+
},
|
|
135
|
+
async writeStream(path, stream) {
|
|
136
|
+
await s3WriteStream(ctx, path, stream);
|
|
137
|
+
},
|
|
138
|
+
async readStream(path) {
|
|
139
|
+
return await s3ReadStream(ctx, path);
|
|
88
140
|
}
|
|
89
141
|
};
|
|
90
142
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n S3ServiceException,\n} from \"@aws-sdk/client-s3\";\
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n S3ServiceException,\n} from \"@aws-sdk/client-s3\";\nimport { Upload } from \"@aws-sdk/lib-storage\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeWebStream } from \"node:stream/web\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/** Options for configuring an S3-compatible storage adapter. */\nexport interface S3StorageOptions {\n /** S3 bucket name. */\n bucket: string;\n /** Optional key prefix applied to all stored objects. */\n prefix?: string;\n /** Custom endpoint for S3-compatible services (e.g. MinIO, R2). */\n endpoint?: string;\n /** AWS region. Defaults to `us-east-1`. */\n region?: string;\n /** Pre-configured S3 client. Defaults to a client built from the other options. */\n client?: S3Client;\n}\n\n/** Join an optional key prefix with a storage path. */\nexport function s3Key(prefix: string, path: string): string {\n return prefix === \"\" ? path : `${prefix}/${path}`;\n}\n\nfunction s3Rel(prefix: string, key: string): string {\n return prefix === \"\" ? key : key.slice(prefix.length + 1);\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof S3ServiceException && error.$metadata.httpStatusCode === 404;\n}\n\nfunction isWebStream(body: unknown): body is NodeWebStream {\n return (\n typeof body === \"object\" &&\n body !== null &&\n typeof (body as { getReader?: unknown }).getReader === \"function\"\n );\n}\n\nasync function s3WriteStream(ctx: S3Context, path: string, stream: Readable): Promise<void> {\n const upload = new Upload({\n client: ctx.client,\n params: { Bucket: ctx.bucket, Key: s3Key(ctx.prefix, path), Body: stream },\n });\n await upload.done();\n}\n\nasync function s3ReadStream(ctx: S3Context, path: string): Promise<Readable> {\n const response = await ctx.client.send(\n new GetObjectCommand({ Bucket: ctx.bucket, Key: s3Key(ctx.prefix, path) }),\n );\n const body: unknown = response.Body;\n if (body instanceof Readable) {\n return body;\n }\n if (isWebStream(body)) {\n return Readable.fromWeb(body);\n }\n throw new Error(`No object at \"${path}\"`);\n}\n\ninterface S3Context {\n client: S3Client;\n bucket: string;\n prefix: string;\n}\n\nasync function s3Read(ctx: S3Context, path: string): Promise<Buffer> {\n const response = await ctx.client.send(\n new GetObjectCommand({ Bucket: ctx.bucket, Key: s3Key(ctx.prefix, path) }),\n );\n const body = response.Body;\n if (body === undefined) {\n return Buffer.alloc(0);\n }\n return Buffer.from(await body.transformToByteArray());\n}\n\nasync function s3Exists(ctx: S3Context, path: string): Promise<boolean> {\n try {\n await ctx.client.send(\n new HeadObjectCommand({ Bucket: ctx.bucket, Key: s3Key(ctx.prefix, path) }),\n );\n return true;\n } catch (error) {\n if (isNotFound(error)) {\n return false;\n }\n throw error;\n }\n}\n\nasync function s3List(ctx: S3Context, listPrefix: string): Promise<string[]> {\n const response = await ctx.client.send(\n new ListObjectsV2Command({ Bucket: ctx.bucket, Prefix: s3Key(ctx.prefix, listPrefix) }),\n );\n return (response.Contents ?? [])\n .map((item) => item.Key)\n .filter((key): key is string => key !== undefined)\n .map((key) => s3Rel(ctx.prefix, key));\n}\n\n/**\n * Create an S3-compatible StorageAdapter (AWS S3, R2, MinIO, etc.).\n *\n * @param options - S3 configuration options.\n * @returns A StorageAdapter backed by the configured S3 bucket.\n */\nexport function createS3Storage(options: S3StorageOptions): StorageAdapter {\n const { bucket, prefix = \"\", endpoint, region = \"us-east-1\", client: injectedClient } = options;\n const client = injectedClient ?? new S3Client({ endpoint, region, forcePathStyle: true });\n const ctx: S3Context = { client, bucket, prefix };\n\n return {\n metadata: {\n name: \"S3 Storage\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"S3-compatible storage adapter\",\n kind: \"s3\",\n category: \"storage\",\n },\n lifecycle: {\n init: async () => {\n await client.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix, MaxKeys: 1 }));\n },\n health: async () => {\n const started = Date.now();\n await client.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix, MaxKeys: 1 }));\n return { ok: true, latencyMs: Date.now() - started };\n },\n },\n async read(path) {\n return await s3Read(ctx, path);\n },\n async write(path, data) {\n await client.send(\n new PutObjectCommand({ Bucket: bucket, Key: s3Key(prefix, path), Body: data }),\n );\n },\n async delete(path) {\n await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: s3Key(prefix, path) }));\n },\n async exists(path) {\n return await s3Exists(ctx, path);\n },\n async list(listPrefix) {\n return await s3List(ctx, listPrefix);\n },\n async writeStream(path, stream) {\n await s3WriteStream(ctx, path, stream);\n },\n async readStream(path) {\n return await s3ReadStream(ctx, path);\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyshelf/storage-s3",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "S3-compatible storage adapter for StoryShelf (AWS SDK v3).",
|
|
5
5
|
"homepage": "https://github.com/GuptaSiddhant/StoryShelf#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@aws-sdk/client-s3": "^3.700.0",
|
|
49
|
-
"@
|
|
49
|
+
"@aws-sdk/lib-storage": "3.700.0",
|
|
50
|
+
"@storyshelf/core": "^0.3.2"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "catalog:",
|