@dimah-s3/server 0.3.1 → 0.4.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/README.md +13 -3
- package/dist/adapters/adapters.test.d.ts +2 -0
- package/dist/adapters/adapters.test.d.ts.map +1 -0
- package/dist/adapters/elysia.d.ts +22 -0
- package/dist/adapters/elysia.d.ts.map +1 -0
- package/dist/adapters/elysia.js +8 -0
- package/dist/adapters/elysia.js.map +1 -0
- package/dist/adapters/express.d.ts +24 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +125 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.d.ts +30 -0
- package/dist/adapters/fastify.d.ts.map +1 -0
- package/dist/adapters/fastify.js +129 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/hono.d.ts +23 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +8 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/next.d.ts +3 -3
- package/dist/adapters/next.d.ts.map +1 -1
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/node.d.ts +16 -4
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +14 -10
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/svelte-kit.d.ts +23 -0
- package/dist/adapters/svelte-kit.d.ts.map +1 -0
- package/dist/adapters/svelte-kit.js +8 -0
- package/dist/adapters/svelte-kit.js.map +1 -0
- package/dist/adapters/types.d.ts +4 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Presigned S3 handlers with HTTP `handler`, direct `api`, and an optional `plugins` array.
|
|
4
4
|
|
|
5
|
-
Full documentation: [dimah-s3.
|
|
5
|
+
Full documentation: [dimah-s3.vercel.app](https://dimah-s3.vercel.app/docs/server)
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -26,13 +26,23 @@ export const s3 = dimahS3({
|
|
|
26
26
|
});
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Mount with a framework adapter:
|
|
30
|
+
|
|
29
31
|
```ts
|
|
32
|
+
// Next.js
|
|
30
33
|
import { toNextJsHandler } from "@dimah-s3/server/next";
|
|
31
|
-
import { s3 } from "@/lib/s3";
|
|
32
|
-
|
|
33
34
|
export const { GET, POST, DELETE } = toNextJsHandler(s3);
|
|
35
|
+
|
|
36
|
+
// Express — @dimah-s3/server/express → toExpressHandler
|
|
37
|
+
// Hono — @dimah-s3/server/hono → toHonoHandler
|
|
38
|
+
// Fastify — @dimah-s3/server/fastify → toFastifyHandler
|
|
39
|
+
// Elysia — @dimah-s3/server/elysia → toElysiaHandler
|
|
40
|
+
// SvelteKit — @dimah-s3/server/svelte-kit → toSvelteKitHandler
|
|
41
|
+
// Node http — @dimah-s3/server/node → toNodeHandler
|
|
34
42
|
```
|
|
35
43
|
|
|
44
|
+
See [Adapters](https://dimah-s3.vercel.app/docs/server/adapters) for full examples.
|
|
45
|
+
|
|
36
46
|
Server-side (no HTTP):
|
|
37
47
|
|
|
38
48
|
```ts
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.test.d.ts","sourceRoot":"","sources":["../../src/adapters/adapters.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
2
|
+
/** Structural Elysia context — avoids a hard dependency on `elysia`. */
|
|
3
|
+
type ElysiaContext = {
|
|
4
|
+
request: Request;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Adapt a dimah-s3 instance to an Elysia route handler.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Elysia } from "elysia";
|
|
12
|
+
* import { toElysiaHandler } from "@dimah-s3/server/elysia";
|
|
13
|
+
* import { s3 } from "./s3";
|
|
14
|
+
*
|
|
15
|
+
* new Elysia()
|
|
16
|
+
* .all("/api/s3/*", toElysiaHandler(s3))
|
|
17
|
+
* .listen(3000);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function toElysiaHandler(s3: DimahS3HandlerSource): (ctx: ElysiaContext) => Promise<Response>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=elysia.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elysia.d.ts","sourceRoot":"","sources":["../../src/adapters/elysia.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,wEAAwE;AACxE,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,oBAAoB,SACzC,aAAa,uBAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/elysia.ts"],"names":[],"mappings":";AAqBO,SAAS,gBAAgB,EAAA,EAA0B;AACxD,EAAA,OAAO,CAAC,GAAA,KAAuB,EAAA,CAAG,OAAA,CAAQ,IAAI,OAAO,CAAA;AACvD","file":"elysia.js","sourcesContent":["import type { DimahS3HandlerSource } from \"./types\";\n\n/** Structural Elysia context — avoids a hard dependency on `elysia`. */\ntype ElysiaContext = {\n request: Request;\n};\n\n/**\n * Adapt a dimah-s3 instance to an Elysia route handler.\n *\n * @example\n * ```ts\n * import { Elysia } from \"elysia\";\n * import { toElysiaHandler } from \"@dimah-s3/server/elysia\";\n * import { s3 } from \"./s3\";\n *\n * new Elysia()\n * .all(\"/api/s3/*\", toElysiaHandler(s3))\n * .listen(3000);\n * ```\n */\nexport function toElysiaHandler(s3: DimahS3HandlerSource) {\n return (ctx: ElysiaContext) => s3.handler(ctx.request);\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { fromNodeHeaders } from "./node";
|
|
2
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
3
|
+
export { fromNodeHeaders };
|
|
4
|
+
/**
|
|
5
|
+
* Adapt a dimah-s3 instance to an Express / Connect handler.
|
|
6
|
+
*
|
|
7
|
+
* Mount **before** `express.json()` so the request body is not consumed early.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import express from "express";
|
|
12
|
+
* import { toExpressHandler } from "@dimah-s3/server/express";
|
|
13
|
+
* import { s3 } from "./s3";
|
|
14
|
+
*
|
|
15
|
+
* const app = express();
|
|
16
|
+
* // Express 4
|
|
17
|
+
* app.all("/api/s3/*", toExpressHandler(s3));
|
|
18
|
+
* // Express 5 — named wildcard
|
|
19
|
+
* // app.all("/api/s3/*splat", toExpressHandler(s3));
|
|
20
|
+
* app.use(express.json());
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function toExpressHandler(s3: DimahS3HandlerSource): (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse) => Promise<void>;
|
|
24
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAiB,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,oBAAoB,wGAExD"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { DimahS3Error, S3_ERROR_CODES } from '@dimah-s3/core';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var errors = {
|
|
5
|
+
notFound: () => new DimahS3Error("Not Found", 404, { code: S3_ERROR_CODES.NOT_FOUND }),
|
|
6
|
+
invalidJson: () => new DimahS3Error("Invalid JSON payload", 400, {
|
|
7
|
+
code: S3_ERROR_CODES.INVALID_JSON
|
|
8
|
+
}),
|
|
9
|
+
forbidden: () => new DimahS3Error("Forbidden", 403, { code: S3_ERROR_CODES.FORBIDDEN }),
|
|
10
|
+
internalError: () => new DimahS3Error("Internal server error", 500, {
|
|
11
|
+
code: S3_ERROR_CODES.INTERNAL_ERROR
|
|
12
|
+
}),
|
|
13
|
+
objectNotFound: () => new DimahS3Error("Object not found", 404, {
|
|
14
|
+
code: S3_ERROR_CODES.OBJECT_NOT_FOUND
|
|
15
|
+
}),
|
|
16
|
+
s3NetworkError: (code) => new DimahS3Error(
|
|
17
|
+
`S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`,
|
|
18
|
+
502,
|
|
19
|
+
{ code: S3_ERROR_CODES.S3_NETWORK_ERROR, params: { code } }
|
|
20
|
+
),
|
|
21
|
+
fieldRequired: (name) => new DimahS3Error(`${name} is required`, 400, {
|
|
22
|
+
code: S3_ERROR_CODES.FIELD_REQUIRED,
|
|
23
|
+
params: { name }
|
|
24
|
+
}),
|
|
25
|
+
/** Missing `key` on query-string routes (download / delete / list-parts). */
|
|
26
|
+
keyRequired: () => new DimahS3Error("key query parameter is required", 400, {
|
|
27
|
+
code: S3_ERROR_CODES.KEY_REQUIRED
|
|
28
|
+
}),
|
|
29
|
+
uploadIdRequired: () => new DimahS3Error("uploadId is required", 400, {
|
|
30
|
+
code: S3_ERROR_CODES.UPLOAD_ID_REQUIRED
|
|
31
|
+
}),
|
|
32
|
+
partNumberInvalid: () => new DimahS3Error("partNumber must be a positive integer", 400, {
|
|
33
|
+
code: S3_ERROR_CODES.PART_NUMBER_INVALID
|
|
34
|
+
}),
|
|
35
|
+
partsRequired: () => new DimahS3Error("At least one valid part is required", 400, {
|
|
36
|
+
code: S3_ERROR_CODES.PARTS_REQUIRED
|
|
37
|
+
}),
|
|
38
|
+
fileSizeRequiredUpload: () => new DimahS3Error(
|
|
39
|
+
"fileSize is required when upload.requireFileSize is enabled",
|
|
40
|
+
400,
|
|
41
|
+
{ code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_UPLOAD }
|
|
42
|
+
),
|
|
43
|
+
fileSizeRequiredMultipart: () => new DimahS3Error(
|
|
44
|
+
"fileSize is required when multipart.requireFileSize is enabled",
|
|
45
|
+
400,
|
|
46
|
+
{ code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART }
|
|
47
|
+
)
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/adapters/node.ts
|
|
51
|
+
function fromNodeHeaders(nodeHeaders) {
|
|
52
|
+
const headers = new Headers();
|
|
53
|
+
for (const [key, value] of Object.entries(nodeHeaders)) {
|
|
54
|
+
if (value == null) continue;
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
for (const v of value) headers.append(key, v);
|
|
57
|
+
} else {
|
|
58
|
+
headers.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return headers;
|
|
62
|
+
}
|
|
63
|
+
function collectBody(req) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const chunks = [];
|
|
66
|
+
req.on("data", (chunk) => {
|
|
67
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
68
|
+
});
|
|
69
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
70
|
+
req.on("error", reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function toWebRequest(req, body) {
|
|
74
|
+
const host = req.headers.host ?? "localhost";
|
|
75
|
+
const protocol = req.socket.encrypted === true ? "https" : "http";
|
|
76
|
+
const url = new URL(req.url ?? "/", `${protocol}://${host}`);
|
|
77
|
+
const headers = fromNodeHeaders(req.headers);
|
|
78
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
79
|
+
const init = { method, headers };
|
|
80
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
81
|
+
init.body = new Uint8Array(body);
|
|
82
|
+
init.duplex = "half";
|
|
83
|
+
}
|
|
84
|
+
return new Request(url, init);
|
|
85
|
+
}
|
|
86
|
+
async function writeWebResponse(res, response) {
|
|
87
|
+
res.statusCode = response.status;
|
|
88
|
+
response.headers.forEach((value, key) => {
|
|
89
|
+
res.setHeader(key, value);
|
|
90
|
+
});
|
|
91
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
92
|
+
res.end(buffer);
|
|
93
|
+
}
|
|
94
|
+
function toNodeHandler(s3) {
|
|
95
|
+
return async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const body = await collectBody(req);
|
|
98
|
+
const request = toWebRequest(req, body);
|
|
99
|
+
const response = await s3.handler(request);
|
|
100
|
+
await writeWebResponse(res, response);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error("[S3 API]", err);
|
|
103
|
+
if (!res.headersSent) {
|
|
104
|
+
const fallback = errors.internalError();
|
|
105
|
+
res.statusCode = fallback.status;
|
|
106
|
+
res.setHeader("Content-Type", "application/json");
|
|
107
|
+
res.end(
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
message: fallback.message,
|
|
110
|
+
code: fallback.code
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/adapters/express.ts
|
|
119
|
+
function toExpressHandler(s3) {
|
|
120
|
+
return toNodeHandler(s3);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { fromNodeHeaders, toExpressHandler };
|
|
124
|
+
//# sourceMappingURL=express.js.map
|
|
125
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/node.ts","../../src/adapters/express.ts"],"names":[],"mappings":"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","file":"express.js","sourcesContent":["import { DimahS3Error, S3_ERROR_CODES, type S3ErrorCode } from \"@dimah-s3/core\";\n\nexport { DimahS3Error };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () =>\n new DimahS3Error(\"Not Found\", 404, { code: S3_ERROR_CODES.NOT_FOUND }),\n\n invalidJson: () =>\n new DimahS3Error(\"Invalid JSON payload\", 400, {\n code: S3_ERROR_CODES.INVALID_JSON,\n }),\n\n forbidden: () =>\n new DimahS3Error(\"Forbidden\", 403, { code: S3_ERROR_CODES.FORBIDDEN }),\n\n internalError: () =>\n new DimahS3Error(\"Internal server error\", 500, {\n code: S3_ERROR_CODES.INTERNAL_ERROR,\n }),\n\n objectNotFound: () =>\n new DimahS3Error(\"Object not found\", 404, {\n code: S3_ERROR_CODES.OBJECT_NOT_FOUND,\n }),\n\n s3NetworkError: (code: string) =>\n new DimahS3Error(\n `S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`,\n 502,\n { code: S3_ERROR_CODES.S3_NETWORK_ERROR, params: { code } },\n ),\n\n fieldRequired: (name: string) =>\n new DimahS3Error(`${name} is required`, 400, {\n code: S3_ERROR_CODES.FIELD_REQUIRED,\n params: { name },\n }),\n\n /** Missing `key` on query-string routes (download / delete / list-parts). */\n keyRequired: () =>\n new DimahS3Error(\"key query parameter is required\", 400, {\n code: S3_ERROR_CODES.KEY_REQUIRED,\n }),\n\n uploadIdRequired: () =>\n new DimahS3Error(\"uploadId is required\", 400, {\n code: S3_ERROR_CODES.UPLOAD_ID_REQUIRED,\n }),\n\n partNumberInvalid: () =>\n new DimahS3Error(\"partNumber must be a positive integer\", 400, {\n code: S3_ERROR_CODES.PART_NUMBER_INVALID,\n }),\n\n partsRequired: () =>\n new DimahS3Error(\"At least one valid part is required\", 400, {\n code: S3_ERROR_CODES.PARTS_REQUIRED,\n }),\n\n fileSizeRequiredUpload: () =>\n new DimahS3Error(\n \"fileSize is required when upload.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_UPLOAD },\n ),\n\n fileSizeRequiredMultipart: () =>\n new DimahS3Error(\n \"fileSize is required when multipart.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART },\n ),\n} as const;\n\nexport type ServerErrors = typeof errors;\n\n/** Trim and require a non-empty string; throws {@link errors.fieldRequired}. */\nexport function requireString(value: unknown, name: string): string {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) throw errors.fieldRequired(name);\n return trimmed;\n}\n\nfunction statusOf(err: unknown, fallback: number): number {\n return typeof (err as { status?: unknown })?.status === \"number\"\n ? (err as { status: number }).status\n : fallback;\n}\n\n/**\n * Convert hook/S3 throws into {@link DimahS3Error}.\n *\n * - Existing {@link DimahS3Error} instances are preserved (including subclasses).\n * - Plain `Error`s keep their message; `code` is only applied when provided\n * (omit it so clients show the custom English message instead of a mapped\n * library string).\n */\nexport function toDimahS3Error(\n err: unknown,\n fallbackMessage: string,\n fallbackStatus = 403,\n options?: { code?: S3ErrorCode | string },\n): DimahS3Error {\n if (err instanceof DimahS3Error) return err;\n\n const message =\n err instanceof Error && err.message.trim() ? err.message : fallbackMessage;\n const status = statusOf(err, fallbackStatus);\n\n return new DimahS3Error(message, status, {\n cause: err instanceof Error ? err : undefined,\n ...(options?.code !== undefined ? { code: options.code } : {}),\n });\n}\n","import type {\n IncomingHttpHeaders,\n IncomingMessage,\n ServerResponse,\n} from \"node:http\";\nimport { errors } from \"../errors\";\nimport type { DimahS3HandlerSource } from \"./types\";\n\n/**\n * Convert Node.js / Express request headers into a Web {@link Headers} object.\n * Useful when calling `s3.api.*` from a Node handler.\n *\n * @example\n * ```ts\n * import { fromNodeHeaders } from \"@dimah-s3/server/node\";\n *\n * await s3.api.download(key, { headers: fromNodeHeaders(req.headers) });\n * ```\n */\nexport function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(nodeHeaders)) {\n if (value == null) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\nfunction collectBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction toWebRequest(req: IncomingMessage, body: Buffer): Request {\n const host = req.headers.host ?? \"localhost\";\n const protocol =\n (req.socket as { encrypted?: boolean }).encrypted === true\n ? \"https\"\n : \"http\";\n const url = new URL(req.url ?? \"/\", `${protocol}://${host}`);\n\n const headers = fromNodeHeaders(req.headers);\n const method = (req.method ?? \"GET\").toUpperCase();\n const init: RequestInit = { method, headers };\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = new Uint8Array(body);\n // @ts-expect-error Node fetch duplex for streaming bodies\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nasync function writeWebResponse(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n const buffer = Buffer.from(await response.arrayBuffer());\n res.end(buffer);\n}\n\n/**\n * Adapt a dimah-s3 instance to Node.js `http` / Express-style handlers.\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { toNodeHandler } from \"@dimah-s3/server/node\";\n * import { s3 } from \"./s3\";\n *\n * createServer(toNodeHandler(s3)).listen(3000);\n * ```\n */\nexport function toNodeHandler(s3: DimahS3HandlerSource) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = await collectBody(req);\n const request = toWebRequest(req, body);\n const response = await s3.handler(request);\n await writeWebResponse(res, response);\n } catch (err) {\n console.error(\"[S3 API]\", err);\n if (!res.headersSent) {\n const fallback = errors.internalError();\n res.statusCode = fallback.status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n message: fallback.message,\n code: fallback.code,\n }),\n );\n }\n }\n };\n}\n","import { fromNodeHeaders, toNodeHandler } from \"./node\";\nimport type { DimahS3HandlerSource } from \"./types\";\n\nexport { fromNodeHeaders };\n\n/**\n * Adapt a dimah-s3 instance to an Express / Connect handler.\n *\n * Mount **before** `express.json()` so the request body is not consumed early.\n *\n * @example\n * ```ts\n * import express from \"express\";\n * import { toExpressHandler } from \"@dimah-s3/server/express\";\n * import { s3 } from \"./s3\";\n *\n * const app = express();\n * // Express 4\n * app.all(\"/api/s3/*\", toExpressHandler(s3));\n * // Express 5 — named wildcard\n * // app.all(\"/api/s3/*splat\", toExpressHandler(s3));\n * app.use(express.json());\n * ```\n */\nexport function toExpressHandler(s3: DimahS3HandlerSource) {\n return toNodeHandler(s3);\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
3
|
+
/** Structural Fastify request/reply — avoids a hard dependency on `fastify`. */
|
|
4
|
+
type FastifyRequestLike = {
|
|
5
|
+
raw: IncomingMessage;
|
|
6
|
+
};
|
|
7
|
+
type FastifyReplyLike = {
|
|
8
|
+
raw: ServerResponse;
|
|
9
|
+
hijack: () => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Adapt a dimah-s3 instance to a Fastify route handler.
|
|
13
|
+
*
|
|
14
|
+
* Uses `reply.hijack()` and the Node adapter so Fastify does not touch the
|
|
15
|
+
* response stream. Mount this route **before** body parsers (or disable JSON
|
|
16
|
+
* parsing for `/api/s3/*`) so the request body stays readable.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import Fastify from "fastify";
|
|
21
|
+
* import { toFastifyHandler } from "@dimah-s3/server/fastify";
|
|
22
|
+
* import { s3 } from "./s3";
|
|
23
|
+
*
|
|
24
|
+
* const app = Fastify();
|
|
25
|
+
* app.all("/api/s3/*", toFastifyHandler(s3));
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function toFastifyHandler(s3: DimahS3HandlerSource): (req: FastifyRequestLike, reply: FastifyReplyLike) => Promise<void>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=fastify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,gFAAgF;AAChF,KAAK,kBAAkB,GAAG;IACxB,GAAG,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,oBAAoB,SAEpC,kBAAkB,SAAS,gBAAgB,mBAI/D"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { DimahS3Error, S3_ERROR_CODES } from '@dimah-s3/core';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var errors = {
|
|
5
|
+
notFound: () => new DimahS3Error("Not Found", 404, { code: S3_ERROR_CODES.NOT_FOUND }),
|
|
6
|
+
invalidJson: () => new DimahS3Error("Invalid JSON payload", 400, {
|
|
7
|
+
code: S3_ERROR_CODES.INVALID_JSON
|
|
8
|
+
}),
|
|
9
|
+
forbidden: () => new DimahS3Error("Forbidden", 403, { code: S3_ERROR_CODES.FORBIDDEN }),
|
|
10
|
+
internalError: () => new DimahS3Error("Internal server error", 500, {
|
|
11
|
+
code: S3_ERROR_CODES.INTERNAL_ERROR
|
|
12
|
+
}),
|
|
13
|
+
objectNotFound: () => new DimahS3Error("Object not found", 404, {
|
|
14
|
+
code: S3_ERROR_CODES.OBJECT_NOT_FOUND
|
|
15
|
+
}),
|
|
16
|
+
s3NetworkError: (code) => new DimahS3Error(
|
|
17
|
+
`S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`,
|
|
18
|
+
502,
|
|
19
|
+
{ code: S3_ERROR_CODES.S3_NETWORK_ERROR, params: { code } }
|
|
20
|
+
),
|
|
21
|
+
fieldRequired: (name) => new DimahS3Error(`${name} is required`, 400, {
|
|
22
|
+
code: S3_ERROR_CODES.FIELD_REQUIRED,
|
|
23
|
+
params: { name }
|
|
24
|
+
}),
|
|
25
|
+
/** Missing `key` on query-string routes (download / delete / list-parts). */
|
|
26
|
+
keyRequired: () => new DimahS3Error("key query parameter is required", 400, {
|
|
27
|
+
code: S3_ERROR_CODES.KEY_REQUIRED
|
|
28
|
+
}),
|
|
29
|
+
uploadIdRequired: () => new DimahS3Error("uploadId is required", 400, {
|
|
30
|
+
code: S3_ERROR_CODES.UPLOAD_ID_REQUIRED
|
|
31
|
+
}),
|
|
32
|
+
partNumberInvalid: () => new DimahS3Error("partNumber must be a positive integer", 400, {
|
|
33
|
+
code: S3_ERROR_CODES.PART_NUMBER_INVALID
|
|
34
|
+
}),
|
|
35
|
+
partsRequired: () => new DimahS3Error("At least one valid part is required", 400, {
|
|
36
|
+
code: S3_ERROR_CODES.PARTS_REQUIRED
|
|
37
|
+
}),
|
|
38
|
+
fileSizeRequiredUpload: () => new DimahS3Error(
|
|
39
|
+
"fileSize is required when upload.requireFileSize is enabled",
|
|
40
|
+
400,
|
|
41
|
+
{ code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_UPLOAD }
|
|
42
|
+
),
|
|
43
|
+
fileSizeRequiredMultipart: () => new DimahS3Error(
|
|
44
|
+
"fileSize is required when multipart.requireFileSize is enabled",
|
|
45
|
+
400,
|
|
46
|
+
{ code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART }
|
|
47
|
+
)
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/adapters/node.ts
|
|
51
|
+
function fromNodeHeaders(nodeHeaders) {
|
|
52
|
+
const headers = new Headers();
|
|
53
|
+
for (const [key, value] of Object.entries(nodeHeaders)) {
|
|
54
|
+
if (value == null) continue;
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
for (const v of value) headers.append(key, v);
|
|
57
|
+
} else {
|
|
58
|
+
headers.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return headers;
|
|
62
|
+
}
|
|
63
|
+
function collectBody(req) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const chunks = [];
|
|
66
|
+
req.on("data", (chunk) => {
|
|
67
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
68
|
+
});
|
|
69
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
70
|
+
req.on("error", reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function toWebRequest(req, body) {
|
|
74
|
+
const host = req.headers.host ?? "localhost";
|
|
75
|
+
const protocol = req.socket.encrypted === true ? "https" : "http";
|
|
76
|
+
const url = new URL(req.url ?? "/", `${protocol}://${host}`);
|
|
77
|
+
const headers = fromNodeHeaders(req.headers);
|
|
78
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
79
|
+
const init = { method, headers };
|
|
80
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
81
|
+
init.body = new Uint8Array(body);
|
|
82
|
+
init.duplex = "half";
|
|
83
|
+
}
|
|
84
|
+
return new Request(url, init);
|
|
85
|
+
}
|
|
86
|
+
async function writeWebResponse(res, response) {
|
|
87
|
+
res.statusCode = response.status;
|
|
88
|
+
response.headers.forEach((value, key) => {
|
|
89
|
+
res.setHeader(key, value);
|
|
90
|
+
});
|
|
91
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
92
|
+
res.end(buffer);
|
|
93
|
+
}
|
|
94
|
+
function toNodeHandler(s3) {
|
|
95
|
+
return async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const body = await collectBody(req);
|
|
98
|
+
const request = toWebRequest(req, body);
|
|
99
|
+
const response = await s3.handler(request);
|
|
100
|
+
await writeWebResponse(res, response);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error("[S3 API]", err);
|
|
103
|
+
if (!res.headersSent) {
|
|
104
|
+
const fallback = errors.internalError();
|
|
105
|
+
res.statusCode = fallback.status;
|
|
106
|
+
res.setHeader("Content-Type", "application/json");
|
|
107
|
+
res.end(
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
message: fallback.message,
|
|
110
|
+
code: fallback.code
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/adapters/fastify.ts
|
|
119
|
+
function toFastifyHandler(s3) {
|
|
120
|
+
const handler = toNodeHandler(s3);
|
|
121
|
+
return async (req, reply) => {
|
|
122
|
+
reply.hijack();
|
|
123
|
+
await handler(req.raw, reply.raw);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { toFastifyHandler };
|
|
128
|
+
//# sourceMappingURL=fastify.js.map
|
|
129
|
+
//# sourceMappingURL=fastify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/node.ts","../../src/adapters/fastify.ts"],"names":[],"mappings":"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","file":"fastify.js","sourcesContent":["import { DimahS3Error, S3_ERROR_CODES, type S3ErrorCode } from \"@dimah-s3/core\";\n\nexport { DimahS3Error };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () =>\n new DimahS3Error(\"Not Found\", 404, { code: S3_ERROR_CODES.NOT_FOUND }),\n\n invalidJson: () =>\n new DimahS3Error(\"Invalid JSON payload\", 400, {\n code: S3_ERROR_CODES.INVALID_JSON,\n }),\n\n forbidden: () =>\n new DimahS3Error(\"Forbidden\", 403, { code: S3_ERROR_CODES.FORBIDDEN }),\n\n internalError: () =>\n new DimahS3Error(\"Internal server error\", 500, {\n code: S3_ERROR_CODES.INTERNAL_ERROR,\n }),\n\n objectNotFound: () =>\n new DimahS3Error(\"Object not found\", 404, {\n code: S3_ERROR_CODES.OBJECT_NOT_FOUND,\n }),\n\n s3NetworkError: (code: string) =>\n new DimahS3Error(\n `S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`,\n 502,\n { code: S3_ERROR_CODES.S3_NETWORK_ERROR, params: { code } },\n ),\n\n fieldRequired: (name: string) =>\n new DimahS3Error(`${name} is required`, 400, {\n code: S3_ERROR_CODES.FIELD_REQUIRED,\n params: { name },\n }),\n\n /** Missing `key` on query-string routes (download / delete / list-parts). */\n keyRequired: () =>\n new DimahS3Error(\"key query parameter is required\", 400, {\n code: S3_ERROR_CODES.KEY_REQUIRED,\n }),\n\n uploadIdRequired: () =>\n new DimahS3Error(\"uploadId is required\", 400, {\n code: S3_ERROR_CODES.UPLOAD_ID_REQUIRED,\n }),\n\n partNumberInvalid: () =>\n new DimahS3Error(\"partNumber must be a positive integer\", 400, {\n code: S3_ERROR_CODES.PART_NUMBER_INVALID,\n }),\n\n partsRequired: () =>\n new DimahS3Error(\"At least one valid part is required\", 400, {\n code: S3_ERROR_CODES.PARTS_REQUIRED,\n }),\n\n fileSizeRequiredUpload: () =>\n new DimahS3Error(\n \"fileSize is required when upload.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_UPLOAD },\n ),\n\n fileSizeRequiredMultipart: () =>\n new DimahS3Error(\n \"fileSize is required when multipart.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART },\n ),\n} as const;\n\nexport type ServerErrors = typeof errors;\n\n/** Trim and require a non-empty string; throws {@link errors.fieldRequired}. */\nexport function requireString(value: unknown, name: string): string {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) throw errors.fieldRequired(name);\n return trimmed;\n}\n\nfunction statusOf(err: unknown, fallback: number): number {\n return typeof (err as { status?: unknown })?.status === \"number\"\n ? (err as { status: number }).status\n : fallback;\n}\n\n/**\n * Convert hook/S3 throws into {@link DimahS3Error}.\n *\n * - Existing {@link DimahS3Error} instances are preserved (including subclasses).\n * - Plain `Error`s keep their message; `code` is only applied when provided\n * (omit it so clients show the custom English message instead of a mapped\n * library string).\n */\nexport function toDimahS3Error(\n err: unknown,\n fallbackMessage: string,\n fallbackStatus = 403,\n options?: { code?: S3ErrorCode | string },\n): DimahS3Error {\n if (err instanceof DimahS3Error) return err;\n\n const message =\n err instanceof Error && err.message.trim() ? err.message : fallbackMessage;\n const status = statusOf(err, fallbackStatus);\n\n return new DimahS3Error(message, status, {\n cause: err instanceof Error ? err : undefined,\n ...(options?.code !== undefined ? { code: options.code } : {}),\n });\n}\n","import type {\n IncomingHttpHeaders,\n IncomingMessage,\n ServerResponse,\n} from \"node:http\";\nimport { errors } from \"../errors\";\nimport type { DimahS3HandlerSource } from \"./types\";\n\n/**\n * Convert Node.js / Express request headers into a Web {@link Headers} object.\n * Useful when calling `s3.api.*` from a Node handler.\n *\n * @example\n * ```ts\n * import { fromNodeHeaders } from \"@dimah-s3/server/node\";\n *\n * await s3.api.download(key, { headers: fromNodeHeaders(req.headers) });\n * ```\n */\nexport function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(nodeHeaders)) {\n if (value == null) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\nfunction collectBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction toWebRequest(req: IncomingMessage, body: Buffer): Request {\n const host = req.headers.host ?? \"localhost\";\n const protocol =\n (req.socket as { encrypted?: boolean }).encrypted === true\n ? \"https\"\n : \"http\";\n const url = new URL(req.url ?? \"/\", `${protocol}://${host}`);\n\n const headers = fromNodeHeaders(req.headers);\n const method = (req.method ?? \"GET\").toUpperCase();\n const init: RequestInit = { method, headers };\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = new Uint8Array(body);\n // @ts-expect-error Node fetch duplex for streaming bodies\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nasync function writeWebResponse(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n const buffer = Buffer.from(await response.arrayBuffer());\n res.end(buffer);\n}\n\n/**\n * Adapt a dimah-s3 instance to Node.js `http` / Express-style handlers.\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { toNodeHandler } from \"@dimah-s3/server/node\";\n * import { s3 } from \"./s3\";\n *\n * createServer(toNodeHandler(s3)).listen(3000);\n * ```\n */\nexport function toNodeHandler(s3: DimahS3HandlerSource) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = await collectBody(req);\n const request = toWebRequest(req, body);\n const response = await s3.handler(request);\n await writeWebResponse(res, response);\n } catch (err) {\n console.error(\"[S3 API]\", err);\n if (!res.headersSent) {\n const fallback = errors.internalError();\n res.statusCode = fallback.status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n message: fallback.message,\n code: fallback.code,\n }),\n );\n }\n }\n };\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { toNodeHandler } from \"./node\";\nimport type { DimahS3HandlerSource } from \"./types\";\n\n/** Structural Fastify request/reply — avoids a hard dependency on `fastify`. */\ntype FastifyRequestLike = {\n raw: IncomingMessage;\n};\n\ntype FastifyReplyLike = {\n raw: ServerResponse;\n hijack: () => void;\n};\n\n/**\n * Adapt a dimah-s3 instance to a Fastify route handler.\n *\n * Uses `reply.hijack()` and the Node adapter so Fastify does not touch the\n * response stream. Mount this route **before** body parsers (or disable JSON\n * parsing for `/api/s3/*`) so the request body stays readable.\n *\n * @example\n * ```ts\n * import Fastify from \"fastify\";\n * import { toFastifyHandler } from \"@dimah-s3/server/fastify\";\n * import { s3 } from \"./s3\";\n *\n * const app = Fastify();\n * app.all(\"/api/s3/*\", toFastifyHandler(s3));\n * ```\n */\nexport function toFastifyHandler(s3: DimahS3HandlerSource) {\n const handler = toNodeHandler(s3);\n return async (req: FastifyRequestLike, reply: FastifyReplyLike) => {\n reply.hijack();\n await handler(req.raw, reply.raw);\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
2
|
+
/** Structural Hono context — avoids a hard dependency on `hono`. */
|
|
3
|
+
type HonoContext = {
|
|
4
|
+
req: {
|
|
5
|
+
raw: Request;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Adapt a dimah-s3 instance to a Hono route handler.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { Hono } from "hono";
|
|
14
|
+
* import { toHonoHandler } from "@dimah-s3/server/hono";
|
|
15
|
+
* import { s3 } from "./s3";
|
|
16
|
+
*
|
|
17
|
+
* const app = new Hono();
|
|
18
|
+
* app.on(["GET", "POST", "DELETE"], "/api/s3/*", toHonoHandler(s3));
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function toHonoHandler(s3: DimahS3HandlerSource): (c: HonoContext) => Promise<Response>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,oEAAoE;AACpE,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE;QAAE,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,oBAAoB,OACzC,WAAW,uBACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/hono.ts"],"names":[],"mappings":";AAoBO,SAAS,cAAc,EAAA,EAA0B;AACtD,EAAA,OAAO,CAAC,CAAA,KAAmB,EAAA,CAAG,OAAA,CAAQ,CAAA,CAAE,IAAI,GAAG,CAAA;AACjD","file":"hono.js","sourcesContent":["import type { DimahS3HandlerSource } from \"./types\";\n\n/** Structural Hono context — avoids a hard dependency on `hono`. */\ntype HonoContext = {\n req: { raw: Request };\n};\n\n/**\n * Adapt a dimah-s3 instance to a Hono route handler.\n *\n * @example\n * ```ts\n * import { Hono } from \"hono\";\n * import { toHonoHandler } from \"@dimah-s3/server/hono\";\n * import { s3 } from \"./s3\";\n *\n * const app = new Hono();\n * app.on([\"GET\", \"POST\", \"DELETE\"], \"/api/s3/*\", toHonoHandler(s3));\n * ```\n */\nexport function toHonoHandler(s3: DimahS3HandlerSource) {\n return (c: HonoContext) => s3.handler(c.req.raw);\n}\n"]}
|
package/dist/adapters/next.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
2
2
|
/**
|
|
3
|
-
* Adapt a
|
|
3
|
+
* Adapt a dimah-s3 instance to Next.js App Router route handlers.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
6
|
* ```ts
|
|
@@ -10,7 +10,7 @@ import type { DimahS3 } from "../dimah-s3";
|
|
|
10
10
|
* export const { GET, POST, DELETE } = toNextJsHandler(s3);
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
|
-
export declare function toNextJsHandler(s3:
|
|
13
|
+
export declare function toNextJsHandler(s3: DimahS3HandlerSource): {
|
|
14
14
|
GET: (request: Request) => Promise<Response>;
|
|
15
15
|
POST: (request: Request) => Promise<Response>;
|
|
16
16
|
DELETE: (request: Request) => Promise<Response>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,oBAAoB;IAEpD,GAAG;IACH,IAAI;IACJ,MAAM;EAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/next.ts"],"names":[],"mappings":";AAaO,SAAS,
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/next.ts"],"names":[],"mappings":";AAaO,SAAS,gBAAgB,EAAA,EAA0B;AACxD,EAAA,OAAO;AAAA,IACL,KAAK,EAAA,CAAG,OAAA;AAAA,IACR,MAAM,EAAA,CAAG,OAAA;AAAA,IACT,QAAQ,EAAA,CAAG;AAAA,GACb;AACF","file":"next.js","sourcesContent":["import type { DimahS3HandlerSource } from \"./types\";\n\n/**\n * Adapt a dimah-s3 instance to Next.js App Router route handlers.\n *\n * @example\n * ```ts\n * import { toNextJsHandler } from \"@dimah-s3/server/next\";\n * import { s3 } from \"@/lib/s3\";\n *\n * export const { GET, POST, DELETE } = toNextJsHandler(s3);\n * ```\n */\nexport function toNextJsHandler(s3: DimahS3HandlerSource) {\n return {\n GET: s3.handler,\n POST: s3.handler,\n DELETE: s3.handler,\n };\n}\n"]}
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import type {
|
|
1
|
+
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Convert Node.js / Express request headers into a Web {@link Headers} object.
|
|
5
|
+
* Useful when calling `s3.api.*` from a Node handler.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { fromNodeHeaders } from "@dimah-s3/server/node";
|
|
10
|
+
*
|
|
11
|
+
* await s3.api.download(key, { headers: fromNodeHeaders(req.headers) });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers;
|
|
15
|
+
/**
|
|
16
|
+
* Adapt a dimah-s3 instance to Node.js `http` / Express-style handlers.
|
|
5
17
|
*
|
|
6
18
|
* @example
|
|
7
19
|
* ```ts
|
|
@@ -12,5 +24,5 @@ import type { DimahS3 } from "../dimah-s3";
|
|
|
12
24
|
* createServer(toNodeHandler(s3)).listen(3000);
|
|
13
25
|
* ```
|
|
14
26
|
*/
|
|
15
|
-
export declare function toNodeHandler(s3:
|
|
27
|
+
export declare function toNodeHandler(s3: DimahS3HandlerSource): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
16
28
|
//# sourceMappingURL=node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAWzE;AA6CD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,oBAAoB,SACjC,eAAe,OAAO,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CAqBxE"}
|
package/dist/adapters/node.js
CHANGED
|
@@ -48,6 +48,18 @@ var errors = {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
// src/adapters/node.ts
|
|
51
|
+
function fromNodeHeaders(nodeHeaders) {
|
|
52
|
+
const headers = new Headers();
|
|
53
|
+
for (const [key, value] of Object.entries(nodeHeaders)) {
|
|
54
|
+
if (value == null) continue;
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
for (const v of value) headers.append(key, v);
|
|
57
|
+
} else {
|
|
58
|
+
headers.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return headers;
|
|
62
|
+
}
|
|
51
63
|
function collectBody(req) {
|
|
52
64
|
return new Promise((resolve, reject) => {
|
|
53
65
|
const chunks = [];
|
|
@@ -62,15 +74,7 @@ function toWebRequest(req, body) {
|
|
|
62
74
|
const host = req.headers.host ?? "localhost";
|
|
63
75
|
const protocol = req.socket.encrypted === true ? "https" : "http";
|
|
64
76
|
const url = new URL(req.url ?? "/", `${protocol}://${host}`);
|
|
65
|
-
const headers =
|
|
66
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
67
|
-
if (value == null) continue;
|
|
68
|
-
if (Array.isArray(value)) {
|
|
69
|
-
for (const v of value) headers.append(key, v);
|
|
70
|
-
} else {
|
|
71
|
-
headers.set(key, value);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
77
|
+
const headers = fromNodeHeaders(req.headers);
|
|
74
78
|
const method = (req.method ?? "GET").toUpperCase();
|
|
75
79
|
const init = { method, headers };
|
|
76
80
|
if (method !== "GET" && method !== "HEAD") {
|
|
@@ -111,6 +115,6 @@ function toNodeHandler(s3) {
|
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
export { toNodeHandler };
|
|
118
|
+
export { fromNodeHeaders, toNodeHandler };
|
|
115
119
|
//# sourceMappingURL=node.js.map
|
|
116
120
|
//# sourceMappingURL=node.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/node.ts"],"names":[],"mappings":"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","file":"node.js","sourcesContent":["import { DimahS3Error, S3_ERROR_CODES, type S3ErrorCode } from \"@dimah-s3/core\";\n\nexport { DimahS3Error };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () =>\n new DimahS3Error(\"Not Found\", 404, { code: S3_ERROR_CODES.NOT_FOUND }),\n\n invalidJson: () =>\n new DimahS3Error(\"Invalid JSON payload\", 400, {\n code: S3_ERROR_CODES.INVALID_JSON,\n }),\n\n forbidden: () =>\n new DimahS3Error(\"Forbidden\", 403, { code: S3_ERROR_CODES.FORBIDDEN }),\n\n internalError: () =>\n new DimahS3Error(\"Internal server error\", 500, {\n code: S3_ERROR_CODES.INTERNAL_ERROR,\n }),\n\n objectNotFound: () =>\n new DimahS3Error(\"Object not found\", 404, {\n code: S3_ERROR_CODES.OBJECT_NOT_FOUND,\n }),\n\n s3NetworkError: (code: string) =>\n new DimahS3Error(\n `S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`,\n 502,\n { code: S3_ERROR_CODES.S3_NETWORK_ERROR, params: { code } },\n ),\n\n fieldRequired: (name: string) =>\n new DimahS3Error(`${name} is required`, 400, {\n code: S3_ERROR_CODES.FIELD_REQUIRED,\n params: { name },\n }),\n\n /** Missing `key` on query-string routes (download / delete / list-parts). */\n keyRequired: () =>\n new DimahS3Error(\"key query parameter is required\", 400, {\n code: S3_ERROR_CODES.KEY_REQUIRED,\n }),\n\n uploadIdRequired: () =>\n new DimahS3Error(\"uploadId is required\", 400, {\n code: S3_ERROR_CODES.UPLOAD_ID_REQUIRED,\n }),\n\n partNumberInvalid: () =>\n new DimahS3Error(\"partNumber must be a positive integer\", 400, {\n code: S3_ERROR_CODES.PART_NUMBER_INVALID,\n }),\n\n partsRequired: () =>\n new DimahS3Error(\"At least one valid part is required\", 400, {\n code: S3_ERROR_CODES.PARTS_REQUIRED,\n }),\n\n fileSizeRequiredUpload: () =>\n new DimahS3Error(\n \"fileSize is required when upload.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_UPLOAD },\n ),\n\n fileSizeRequiredMultipart: () =>\n new DimahS3Error(\n \"fileSize is required when multipart.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART },\n ),\n} as const;\n\nexport type ServerErrors = typeof errors;\n\n/** Trim and require a non-empty string; throws {@link errors.fieldRequired}. */\nexport function requireString(value: unknown, name: string): string {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) throw errors.fieldRequired(name);\n return trimmed;\n}\n\nfunction statusOf(err: unknown, fallback: number): number {\n return typeof (err as { status?: unknown })?.status === \"number\"\n ? (err as { status: number }).status\n : fallback;\n}\n\n/**\n * Convert hook/S3 throws into {@link DimahS3Error}.\n *\n * - Existing {@link DimahS3Error} instances are preserved (including subclasses).\n * - Plain `Error`s keep their message; `code` is only applied when provided\n * (omit it so clients show the custom English message instead of a mapped\n * library string).\n */\nexport function toDimahS3Error(\n err: unknown,\n fallbackMessage: string,\n fallbackStatus = 403,\n options?: { code?: S3ErrorCode | string },\n): DimahS3Error {\n if (err instanceof DimahS3Error) return err;\n\n const message =\n err instanceof Error && err.message.trim() ? err.message : fallbackMessage;\n const status = statusOf(err, fallbackStatus);\n\n return new DimahS3Error(message, status, {\n cause: err instanceof Error ? err : undefined,\n ...(options?.code !== undefined ? { code: options.code } : {}),\n });\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { DimahS3 } from \"../dimah-s3\";\nimport { errors } from \"../errors\";\n\nfunction collectBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction toWebRequest(req: IncomingMessage, body: Buffer): Request {\n const host = req.headers.host ?? \"localhost\";\n const protocol =\n (req.socket as { encrypted?: boolean }).encrypted === true\n ? \"https\"\n : \"http\";\n const url = new URL(req.url ?? \"/\", `${protocol}://${host}`);\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value == null) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n\n const method = (req.method ?? \"GET\").toUpperCase();\n const init: RequestInit = { method, headers };\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = new Uint8Array(body);\n // @ts-expect-error Node fetch duplex for streaming bodies\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nasync function writeWebResponse(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n const buffer = Buffer.from(await response.arrayBuffer());\n res.end(buffer);\n}\n\n/**\n * Adapt a {@link DimahS3} instance to Node.js `http` / Express-style handlers.\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { toNodeHandler } from \"@dimah-s3/server/node\";\n * import { s3 } from \"./s3\";\n *\n * createServer(toNodeHandler(s3)).listen(3000);\n * ```\n */\nexport function toNodeHandler(\n s3: Pick<DimahS3<Record<string, unknown>>, \"handler\">,\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = await collectBody(req);\n const request = toWebRequest(req, body);\n const response = await s3.handler(request);\n await writeWebResponse(res, response);\n } catch (err) {\n console.error(\"[S3 API]\", err);\n if (!res.headersSent) {\n const fallback = errors.internalError();\n res.statusCode = fallback.status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n message: fallback.message,\n code: fallback.code,\n }),\n );\n }\n }\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/node.ts"],"names":[],"mappings":"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","file":"node.js","sourcesContent":["import { DimahS3Error, S3_ERROR_CODES, type S3ErrorCode } from \"@dimah-s3/core\";\n\nexport { DimahS3Error };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () =>\n new DimahS3Error(\"Not Found\", 404, { code: S3_ERROR_CODES.NOT_FOUND }),\n\n invalidJson: () =>\n new DimahS3Error(\"Invalid JSON payload\", 400, {\n code: S3_ERROR_CODES.INVALID_JSON,\n }),\n\n forbidden: () =>\n new DimahS3Error(\"Forbidden\", 403, { code: S3_ERROR_CODES.FORBIDDEN }),\n\n internalError: () =>\n new DimahS3Error(\"Internal server error\", 500, {\n code: S3_ERROR_CODES.INTERNAL_ERROR,\n }),\n\n objectNotFound: () =>\n new DimahS3Error(\"Object not found\", 404, {\n code: S3_ERROR_CODES.OBJECT_NOT_FOUND,\n }),\n\n s3NetworkError: (code: string) =>\n new DimahS3Error(\n `S3 endpoint unreachable (${code}): check your endpoint URL and network connectivity`,\n 502,\n { code: S3_ERROR_CODES.S3_NETWORK_ERROR, params: { code } },\n ),\n\n fieldRequired: (name: string) =>\n new DimahS3Error(`${name} is required`, 400, {\n code: S3_ERROR_CODES.FIELD_REQUIRED,\n params: { name },\n }),\n\n /** Missing `key` on query-string routes (download / delete / list-parts). */\n keyRequired: () =>\n new DimahS3Error(\"key query parameter is required\", 400, {\n code: S3_ERROR_CODES.KEY_REQUIRED,\n }),\n\n uploadIdRequired: () =>\n new DimahS3Error(\"uploadId is required\", 400, {\n code: S3_ERROR_CODES.UPLOAD_ID_REQUIRED,\n }),\n\n partNumberInvalid: () =>\n new DimahS3Error(\"partNumber must be a positive integer\", 400, {\n code: S3_ERROR_CODES.PART_NUMBER_INVALID,\n }),\n\n partsRequired: () =>\n new DimahS3Error(\"At least one valid part is required\", 400, {\n code: S3_ERROR_CODES.PARTS_REQUIRED,\n }),\n\n fileSizeRequiredUpload: () =>\n new DimahS3Error(\n \"fileSize is required when upload.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_UPLOAD },\n ),\n\n fileSizeRequiredMultipart: () =>\n new DimahS3Error(\n \"fileSize is required when multipart.requireFileSize is enabled\",\n 400,\n { code: S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART },\n ),\n} as const;\n\nexport type ServerErrors = typeof errors;\n\n/** Trim and require a non-empty string; throws {@link errors.fieldRequired}. */\nexport function requireString(value: unknown, name: string): string {\n const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n if (!trimmed) throw errors.fieldRequired(name);\n return trimmed;\n}\n\nfunction statusOf(err: unknown, fallback: number): number {\n return typeof (err as { status?: unknown })?.status === \"number\"\n ? (err as { status: number }).status\n : fallback;\n}\n\n/**\n * Convert hook/S3 throws into {@link DimahS3Error}.\n *\n * - Existing {@link DimahS3Error} instances are preserved (including subclasses).\n * - Plain `Error`s keep their message; `code` is only applied when provided\n * (omit it so clients show the custom English message instead of a mapped\n * library string).\n */\nexport function toDimahS3Error(\n err: unknown,\n fallbackMessage: string,\n fallbackStatus = 403,\n options?: { code?: S3ErrorCode | string },\n): DimahS3Error {\n if (err instanceof DimahS3Error) return err;\n\n const message =\n err instanceof Error && err.message.trim() ? err.message : fallbackMessage;\n const status = statusOf(err, fallbackStatus);\n\n return new DimahS3Error(message, status, {\n cause: err instanceof Error ? err : undefined,\n ...(options?.code !== undefined ? { code: options.code } : {}),\n });\n}\n","import type {\n IncomingHttpHeaders,\n IncomingMessage,\n ServerResponse,\n} from \"node:http\";\nimport { errors } from \"../errors\";\nimport type { DimahS3HandlerSource } from \"./types\";\n\n/**\n * Convert Node.js / Express request headers into a Web {@link Headers} object.\n * Useful when calling `s3.api.*` from a Node handler.\n *\n * @example\n * ```ts\n * import { fromNodeHeaders } from \"@dimah-s3/server/node\";\n *\n * await s3.api.download(key, { headers: fromNodeHeaders(req.headers) });\n * ```\n */\nexport function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(nodeHeaders)) {\n if (value == null) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\nfunction collectBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction toWebRequest(req: IncomingMessage, body: Buffer): Request {\n const host = req.headers.host ?? \"localhost\";\n const protocol =\n (req.socket as { encrypted?: boolean }).encrypted === true\n ? \"https\"\n : \"http\";\n const url = new URL(req.url ?? \"/\", `${protocol}://${host}`);\n\n const headers = fromNodeHeaders(req.headers);\n const method = (req.method ?? \"GET\").toUpperCase();\n const init: RequestInit = { method, headers };\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = new Uint8Array(body);\n // @ts-expect-error Node fetch duplex for streaming bodies\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nasync function writeWebResponse(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n const buffer = Buffer.from(await response.arrayBuffer());\n res.end(buffer);\n}\n\n/**\n * Adapt a dimah-s3 instance to Node.js `http` / Express-style handlers.\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { toNodeHandler } from \"@dimah-s3/server/node\";\n * import { s3 } from \"./s3\";\n *\n * createServer(toNodeHandler(s3)).listen(3000);\n * ```\n */\nexport function toNodeHandler(s3: DimahS3HandlerSource) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = await collectBody(req);\n const request = toWebRequest(req, body);\n const response = await s3.handler(request);\n await writeWebResponse(res, response);\n } catch (err) {\n console.error(\"[S3 API]\", err);\n if (!res.headersSent) {\n const fallback = errors.internalError();\n res.statusCode = fallback.status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n message: fallback.message,\n code: fallback.code,\n }),\n );\n }\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DimahS3HandlerSource } from "./types";
|
|
2
|
+
/** Structural SvelteKit event — avoids a hard dependency on `@sveltejs/kit`. */
|
|
3
|
+
type SvelteKitRequestEvent = {
|
|
4
|
+
request: Request;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Adapt a dimah-s3 instance to a SvelteKit request handler.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // src/routes/api/s3/[...path]/+server.ts
|
|
12
|
+
* import { toSvelteKitHandler } from "@dimah-s3/server/svelte-kit";
|
|
13
|
+
* import { s3 } from "$lib/s3";
|
|
14
|
+
*
|
|
15
|
+
* const handler = toSvelteKitHandler(s3);
|
|
16
|
+
* export const GET = handler;
|
|
17
|
+
* export const POST = handler;
|
|
18
|
+
* export const DELETE = handler;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function toSvelteKitHandler(s3: DimahS3HandlerSource): (event: SvelteKitRequestEvent) => Promise<Response>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=svelte-kit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte-kit.d.ts","sourceRoot":"","sources":["../../src/adapters/svelte-kit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,gFAAgF;AAChF,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,oBAAoB,WAC1C,qBAAqB,uBACrC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/svelte-kit.ts"],"names":[],"mappings":";AAsBO,SAAS,mBAAmB,EAAA,EAA0B;AAC3D,EAAA,OAAO,CAAC,KAAA,KAAiC,EAAA,CAAG,OAAA,CAAQ,MAAM,OAAO,CAAA;AACnE","file":"svelte-kit.js","sourcesContent":["import type { DimahS3HandlerSource } from \"./types\";\n\n/** Structural SvelteKit event — avoids a hard dependency on `@sveltejs/kit`. */\ntype SvelteKitRequestEvent = {\n request: Request;\n};\n\n/**\n * Adapt a dimah-s3 instance to a SvelteKit request handler.\n *\n * @example\n * ```ts\n * // src/routes/api/s3/[...path]/+server.ts\n * import { toSvelteKitHandler } from \"@dimah-s3/server/svelte-kit\";\n * import { s3 } from \"$lib/s3\";\n *\n * const handler = toSvelteKitHandler(s3);\n * export const GET = handler;\n * export const POST = handler;\n * export const DELETE = handler;\n * ```\n */\nexport function toSvelteKitHandler(s3: DimahS3HandlerSource) {\n return (event: SvelteKitRequestEvent) => s3.handler(event.request);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAChC,SAAS,CACV,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimah-s3/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Presigned S3 server — dimahS3() with HTTP handler and direct api for upload, download, delete, and multipart",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"s3",
|
|
@@ -36,6 +36,26 @@
|
|
|
36
36
|
"./node": {
|
|
37
37
|
"types": "./dist/adapters/node.d.ts",
|
|
38
38
|
"default": "./dist/adapters/node.js"
|
|
39
|
+
},
|
|
40
|
+
"./express": {
|
|
41
|
+
"types": "./dist/adapters/express.d.ts",
|
|
42
|
+
"default": "./dist/adapters/express.js"
|
|
43
|
+
},
|
|
44
|
+
"./hono": {
|
|
45
|
+
"types": "./dist/adapters/hono.d.ts",
|
|
46
|
+
"default": "./dist/adapters/hono.js"
|
|
47
|
+
},
|
|
48
|
+
"./fastify": {
|
|
49
|
+
"types": "./dist/adapters/fastify.d.ts",
|
|
50
|
+
"default": "./dist/adapters/fastify.js"
|
|
51
|
+
},
|
|
52
|
+
"./elysia": {
|
|
53
|
+
"types": "./dist/adapters/elysia.d.ts",
|
|
54
|
+
"default": "./dist/adapters/elysia.js"
|
|
55
|
+
},
|
|
56
|
+
"./svelte-kit": {
|
|
57
|
+
"types": "./dist/adapters/svelte-kit.d.ts",
|
|
58
|
+
"default": "./dist/adapters/svelte-kit.js"
|
|
39
59
|
}
|
|
40
60
|
},
|
|
41
61
|
"files": [
|
|
@@ -44,7 +64,7 @@
|
|
|
44
64
|
"dependencies": {
|
|
45
65
|
"@aws-sdk/s3-presigned-post": "^3.1104.0",
|
|
46
66
|
"@aws-sdk/s3-request-presigner": "^3.1104.0",
|
|
47
|
-
"@dimah-s3/core": "0.
|
|
67
|
+
"@dimah-s3/core": "0.4.0"
|
|
48
68
|
},
|
|
49
69
|
"peerDependencies": {
|
|
50
70
|
"@aws-sdk/client-s3": ">=3"
|
|
@@ -57,8 +77,8 @@
|
|
|
57
77
|
"tsup": "^8.5.1",
|
|
58
78
|
"typescript": "7.0.2",
|
|
59
79
|
"vitest": "^4.1.10",
|
|
60
|
-
"@workspace/
|
|
61
|
-
"@workspace/
|
|
80
|
+
"@workspace/typescript-config": "0.0.1",
|
|
81
|
+
"@workspace/eslint-config": "0.0.1"
|
|
62
82
|
},
|
|
63
83
|
"publishConfig": {
|
|
64
84
|
"access": "public"
|