@dimah-s3/server 1.2.0 → 1.3.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/dist/adapters/express.js +16 -16
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.js +16 -16
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/node.js +16 -16
- package/dist/adapters/node.js.map +1 -1
- package/dist/api/create-s3-endpoint.d.ts +1 -1
- package/dist/api/index.js +18 -18
- package/dist/api/index.js.map +1 -1
- package/dist/errors.d.ts +17 -17
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -19
- package/dist/index.js.map +1 -1
- package/dist/plugins.js +18 -18
- package/dist/plugins.js.map +1 -1
- package/package.json +2 -2
package/dist/adapters/express.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { toResponse } from 'better-call';
|
|
2
|
-
import { S3_ERROR_CODES,
|
|
2
|
+
import { S3_ERROR_CODES, APIError } from '@dimah-s3/core';
|
|
3
3
|
|
|
4
4
|
// src/adapters/node.ts
|
|
5
5
|
var errors = {
|
|
6
|
-
notFound: () =>
|
|
7
|
-
unauthorized: () =>
|
|
8
|
-
forbidden: () =>
|
|
9
|
-
conflict: () =>
|
|
10
|
-
internalError: () =>
|
|
11
|
-
objectNotFound: () =>
|
|
12
|
-
featureDisabled: (feature) =>
|
|
6
|
+
notFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
|
|
7
|
+
unauthorized: () => APIError.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
|
|
8
|
+
forbidden: () => APIError.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
|
|
9
|
+
conflict: () => APIError.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
|
|
10
|
+
internalError: () => APIError.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
|
|
11
|
+
objectNotFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
|
|
12
|
+
featureDisabled: (feature) => APIError.from("NOT_FOUND", {
|
|
13
13
|
code: S3_ERROR_CODES.FEATURE_DISABLED.code,
|
|
14
14
|
message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(
|
|
15
15
|
"{feature}",
|
|
@@ -17,13 +17,13 @@ var errors = {
|
|
|
17
17
|
),
|
|
18
18
|
params: { feature }
|
|
19
19
|
}),
|
|
20
|
-
invalidKey: () =>
|
|
21
|
-
unknownRoute: (route) =>
|
|
20
|
+
invalidKey: () => APIError.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
|
|
21
|
+
unknownRoute: (route) => APIError.from("NOT_FOUND", {
|
|
22
22
|
code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,
|
|
23
23
|
message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,
|
|
24
24
|
params: { route }
|
|
25
25
|
}),
|
|
26
|
-
fileTypeNotAllowed: (fileName, contentType) =>
|
|
26
|
+
fileTypeNotAllowed: (fileName, contentType) => APIError.from("BAD_REQUEST", {
|
|
27
27
|
code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,
|
|
28
28
|
message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,
|
|
29
29
|
params: {
|
|
@@ -31,7 +31,7 @@ var errors = {
|
|
|
31
31
|
...contentType ? { contentType } : {}
|
|
32
32
|
}
|
|
33
33
|
}),
|
|
34
|
-
s3NetworkError: (code) =>
|
|
34
|
+
s3NetworkError: (code) => APIError.from("BAD_GATEWAY", {
|
|
35
35
|
code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,
|
|
36
36
|
message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(
|
|
37
37
|
"{code}",
|
|
@@ -39,7 +39,7 @@ var errors = {
|
|
|
39
39
|
),
|
|
40
40
|
params: { code }
|
|
41
41
|
}),
|
|
42
|
-
multipartPartMissing: (partNumber) =>
|
|
42
|
+
multipartPartMissing: (partNumber) => APIError.from("BAD_REQUEST", {
|
|
43
43
|
code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,
|
|
44
44
|
message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(
|
|
45
45
|
"{partNumber}",
|
|
@@ -47,15 +47,15 @@ var errors = {
|
|
|
47
47
|
),
|
|
48
48
|
params: { partNumber }
|
|
49
49
|
}),
|
|
50
|
-
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) =>
|
|
50
|
+
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
51
51
|
...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
52
52
|
message
|
|
53
53
|
}),
|
|
54
|
-
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>
|
|
54
|
+
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
55
55
|
...S3_ERROR_CODES.QUOTA_EXCEEDED,
|
|
56
56
|
message
|
|
57
57
|
}),
|
|
58
|
-
validationError: (message) =>
|
|
58
|
+
validationError: (message) => APIError.from("BAD_REQUEST", {
|
|
59
59
|
code: S3_ERROR_CODES.VALIDATION_ERROR.code,
|
|
60
60
|
message
|
|
61
61
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/node.ts","../../src/adapters/express.ts"],"names":[],"mappings":";;;;AAkBO,IAAM,MAAA,GAAS;AAAA,EACpB,UAAU,MAAM,YAAA,CAAa,IAAA,CAAK,WAAA,EAAa,eAAe,SAAS,CAAA;AAAA,EAEvE,cAAc,MACZ,YAAA,CAAa,IAAA,CAAK,cAAA,EAAgB,eAAe,YAAY,CAAA;AAAA,EAE/D,WAAW,MAAM,YAAA,CAAa,IAAA,CAAK,WAAA,EAAa,eAAe,SAAS,CAAA;AAAA,EAExE,UAAU,MAAM,YAAA,CAAa,IAAA,CAAK,UAAA,EAAY,eAAe,QAAQ,CAAA;AAAA,EAErE,eAAe,MACb,YAAA,CAAa,IAAA,CAAK,uBAAA,EAAyB,eAAe,cAAc,CAAA;AAAA,EAE1E,gBAAgB,MACd,YAAA,CAAa,IAAA,CAAK,WAAA,EAAa,eAAe,gBAAgB,CAAA;AAAA,EAEhE,eAAA,EAAiB,CAAC,OAAA,KAChB,YAAA,CAAa,KAAK,WAAA,EAAa;AAAA,IAC7B,IAAA,EAAM,eAAe,gBAAA,CAAiB,IAAA;AAAA,IACtC,OAAA,EAAS,cAAA,CAAe,gBAAA,CAAiB,OAAA,CAAQ,UAAA;AAAA,MAC/C,WAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,MAAA,EAAQ,EAAE,OAAA;AAAQ,GACnB,CAAA;AAAA,EAEH,YAAY,MACV,YAAA,CAAa,IAAA,CAAK,aAAA,EAAe,eAAe,WAAW,CAAA;AAAA,EAE7D,YAAA,EAAc,CAAC,KAAA,KACb,YAAA,CAAa,KAAK,WAAA,EAAa;AAAA,IAC7B,IAAA,EAAM,eAAe,aAAA,CAAc,IAAA;AAAA,IACnC,OAAA,EAAS,eAAe,aAAA,CAAc,OAAA;AAAA,IACtC,MAAA,EAAQ,EAAE,KAAA;AAAM,GACjB,CAAA;AAAA,EAEH,oBAAoB,CAAC,QAAA,EAAkB,WAAA,KACrC,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,qBAAA,CAAsB,IAAA;AAAA,IAC3C,OAAA,EAAS,eAAe,qBAAA,CAAsB,OAAA;AAAA,IAC9C,MAAA,EAAQ;AAAA,MACN,QAAA;AAAA,MACA,GAAI,WAAA,GAAc,EAAE,WAAA,KAAgB;AAAC;AACvC,GACD,CAAA;AAAA,EAEH,cAAA,EAAgB,CAAC,IAAA,KACf,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,gBAAA,CAAiB,IAAA;AAAA,IACtC,OAAA,EAAS,cAAA,CAAe,gBAAA,CAAiB,OAAA,CAAQ,UAAA;AAAA,MAC/C,QAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,MAAA,EAAQ,EAAE,IAAA;AAAK,GAChB,CAAA;AAAA,EAEH,oBAAA,EAAsB,CAAC,UAAA,KACrB,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,sBAAA,CAAuB,IAAA;AAAA,IAC5C,OAAA,EAAS,cAAA,CAAe,sBAAA,CAAuB,OAAA,CAAQ,UAAA;AAAA,MACrD,cAAA;AAAA,MACA,OAAO,UAAU;AAAA,KACnB;AAAA,IACA,MAAA,EAAQ,EAAE,UAAA;AAAW,GACtB,CAAA;AAAA,EAEH,eAAA,EAAiB,CACf,OAAA,GAAkB,cAAA,CAAe,kBAAkB,OAAA,KAEnD,YAAA,CAAa,KAAK,mBAAA,EAAqB;AAAA,IACrC,GAAG,cAAA,CAAe,iBAAA;AAAA,IAClB;AAAA,GACD,CAAA;AAAA,EAEH,aAAA,EAAe,CAAC,OAAA,GAAkB,cAAA,CAAe,eAAe,OAAA,KAC9D,YAAA,CAAa,KAAK,mBAAA,EAAqB;AAAA,IACrC,GAAG,cAAA,CAAe,cAAA;AAAA,IAClB;AAAA,GACD,CAAA;AAAA,EAEH,eAAA,EAAiB,CAAC,OAAA,KAChB,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,gBAAA,CAAiB,IAAA;AAAA,IACtC;AAAA,GACD;AACL,CAAA;;;ACnFO,SAAS,gBAAgB,WAAA,EAA2C;AACzE,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,EAAQ;AAC5B,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACtD,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,MAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAC9C,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,IACxB;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,YAAY,GAAA,EAAuC;AAC1D,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,GAAA,CAAI,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAA2B;AACzC,MAAA,MAAA,CAAO,IAAA,CAAK,OAAO,KAAA,KAAU,QAAA,GAAW,OAAO,IAAA,CAAK,KAAK,IAAI,KAAK,CAAA;AAAA,IACpE,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,EAAA,CAAG,OAAO,MAAM,OAAA,CAAQ,OAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAClD,IAAA,GAAA,CAAI,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,EACxB,CAAC,CAAA;AACH;AAEA,SAAS,YAAA,CAAa,KAAsB,IAAA,EAAuB;AACjE,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,IAAA,IAAQ,WAAA;AACjC,EAAA,MAAM,QAAA,GACH,GAAA,CAAI,MAAA,CAAmC,SAAA,KAAc,OAClD,OAAA,GACA,MAAA;AACN,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,GAAA,CAAI,GAAA,IAAO,KAAK,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,IAAI,CAAA,CAAE,CAAA;AAE3D,EAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,GAAA,CAAI,OAAO,CAAA;AAC3C,EAAA,MAAM,MAAA,GAAA,CAAU,GAAA,CAAI,MAAA,IAAU,KAAA,EAAO,WAAA,EAAY;AACjD,EAAA,MAAM,IAAA,GAAoB,EAAE,MAAA,EAAQ,OAAA,EAAQ;AAC5C,EAAA,IAAI,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,MAAA,EAAQ;AACzC,IAAA,IAAA,CAAK,IAAA,GAAO,IAAI,UAAA,CAAW,IAAI,CAAA;AAE/B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAEA,EAAA,OAAO,IAAI,OAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AAC9B;AAEA,eAAe,gBAAA,CACb,KACA,QAAA,EACe;AACf,EAAA,GAAA,CAAI,aAAa,QAAA,CAAS,MAAA;AAC1B,EAAA,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AACvC,IAAA,GAAA,CAAI,SAAA,CAAU,KAAK,KAAK,CAAA;AAAA,EAC1B,CAAC,CAAA;AACD,EAAA,MAAM,SAAS,MAAA,CAAO,IAAA,CAAK,MAAM,QAAA,CAAS,aAAa,CAAA;AACvD,EAAA,GAAA,CAAI,IAAI,MAAM,CAAA;AAChB;AAcO,SAAS,cAAc,EAAA,EAA0B;AACtD,EAAA,OAAO,OAAO,KAAsB,GAAA,KAAuC;AACzE,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,GAAG,CAAA;AAClC,MAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;AACtC,MAAA,MAAM,QAAA,GAAW,MAAM,EAAA,CAAG,OAAA,CAAQ,OAAO,CAAA;AACzC,MAAA,MAAM,gBAAA,CAAiB,KAAK,QAAQ,CAAA;AAAA,IACtC,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,KAAA,CAAM,YAAY,GAAG,CAAA;AAC7B,MAAA,IAAI,CAAC,IAAI,WAAA,EAAa;AACpB,QAAA,MAAM,iBAAiB,GAAA,EAAK,UAAA,CAAW,MAAA,CAAO,aAAA,EAAe,CAAC,CAAA;AAAA,MAChE;AAAA,IACF;AAAA,EACF,CAAA;AACF;;;AC9EO,SAAS,iBAAiB,EAAA,EAA0B;AACzD,EAAA,OAAO,cAAc,EAAE,CAAA;AACzB","file":"express.js","sourcesContent":["import {\n DimahS3Error,\n S3_ERROR_CODES,\n isAPIError,\n isDimahS3Error,\n isS3ErrorCode,\n} from \"@dimah-s3/core\";\nimport type { RouteOperation } from \"@/types/config\";\n\nexport {\n DimahS3Error,\n S3_ERROR_CODES,\n isAPIError,\n isDimahS3Error,\n isS3ErrorCode,\n};\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () => DimahS3Error.from(\"NOT_FOUND\", S3_ERROR_CODES.NOT_FOUND),\n\n unauthorized: () =>\n DimahS3Error.from(\"UNAUTHORIZED\", S3_ERROR_CODES.UNAUTHORIZED),\n\n forbidden: () => DimahS3Error.from(\"FORBIDDEN\", S3_ERROR_CODES.FORBIDDEN),\n\n conflict: () => DimahS3Error.from(\"CONFLICT\", S3_ERROR_CODES.CONFLICT),\n\n internalError: () =>\n DimahS3Error.from(\"INTERNAL_SERVER_ERROR\", S3_ERROR_CODES.INTERNAL_ERROR),\n\n objectNotFound: () =>\n DimahS3Error.from(\"NOT_FOUND\", S3_ERROR_CODES.OBJECT_NOT_FOUND),\n\n featureDisabled: (feature: RouteOperation) =>\n DimahS3Error.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.FEATURE_DISABLED.code,\n message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(\n \"{feature}\",\n feature,\n ),\n params: { feature },\n }),\n\n invalidKey: () =>\n DimahS3Error.from(\"BAD_REQUEST\", S3_ERROR_CODES.INVALID_KEY),\n\n unknownRoute: (route: string) =>\n DimahS3Error.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,\n message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,\n params: { route },\n }),\n\n fileTypeNotAllowed: (fileName: string, contentType?: string) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,\n message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,\n params: {\n fileName,\n ...(contentType ? { contentType } : {}),\n },\n }),\n\n s3NetworkError: (code: string) =>\n DimahS3Error.from(\"BAD_GATEWAY\", {\n code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,\n message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(\n \"{code}\",\n code,\n ),\n params: { code },\n }),\n\n multipartPartMissing: (partNumber: number) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,\n message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(\n \"{partNumber}\",\n String(partNumber),\n ),\n params: { partNumber },\n }),\n\n payloadTooLarge: (\n message: string = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message,\n ) =>\n DimahS3Error.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,\n message,\n }),\n\n quotaExceeded: (message: string = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>\n DimahS3Error.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.QUOTA_EXCEEDED,\n message,\n }),\n\n validationError: (message: string) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.VALIDATION_ERROR.code,\n message,\n }),\n} as const;\n\nexport type ServerErrors = typeof errors;\n","import { toResponse } from \"better-call\";\nimport 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({ query: { route, 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 await writeWebResponse(res, toResponse(errors.internalError()));\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"]}
|
|
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 {\n APIError,\n S3_ERROR_CODES,\n isAPIError,\n isS3ErrorCode,\n} from \"@dimah-s3/core\";\nimport type { RouteOperation } from \"@/types/config\";\n\nexport { APIError, S3_ERROR_CODES, isAPIError, isS3ErrorCode };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () => APIError.from(\"NOT_FOUND\", S3_ERROR_CODES.NOT_FOUND),\n\n unauthorized: () =>\n APIError.from(\"UNAUTHORIZED\", S3_ERROR_CODES.UNAUTHORIZED),\n\n forbidden: () => APIError.from(\"FORBIDDEN\", S3_ERROR_CODES.FORBIDDEN),\n\n conflict: () => APIError.from(\"CONFLICT\", S3_ERROR_CODES.CONFLICT),\n\n internalError: () =>\n APIError.from(\"INTERNAL_SERVER_ERROR\", S3_ERROR_CODES.INTERNAL_ERROR),\n\n objectNotFound: () =>\n APIError.from(\"NOT_FOUND\", S3_ERROR_CODES.OBJECT_NOT_FOUND),\n\n featureDisabled: (feature: RouteOperation) =>\n APIError.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.FEATURE_DISABLED.code,\n message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(\n \"{feature}\",\n feature,\n ),\n params: { feature },\n }),\n\n invalidKey: () => APIError.from(\"BAD_REQUEST\", S3_ERROR_CODES.INVALID_KEY),\n\n unknownRoute: (route: string) =>\n APIError.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,\n message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,\n params: { route },\n }),\n\n fileTypeNotAllowed: (fileName: string, contentType?: string) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,\n message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,\n params: {\n fileName,\n ...(contentType ? { contentType } : {}),\n },\n }),\n\n s3NetworkError: (code: string) =>\n APIError.from(\"BAD_GATEWAY\", {\n code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,\n message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(\n \"{code}\",\n code,\n ),\n params: { code },\n }),\n\n multipartPartMissing: (partNumber: number) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,\n message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(\n \"{partNumber}\",\n String(partNumber),\n ),\n params: { partNumber },\n }),\n\n payloadTooLarge: (\n message: string = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message,\n ) =>\n APIError.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,\n message,\n }),\n\n quotaExceeded: (message: string = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>\n APIError.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.QUOTA_EXCEEDED,\n message,\n }),\n\n validationError: (message: string) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.VALIDATION_ERROR.code,\n message,\n }),\n} as const;\n\nexport type ServerErrors = typeof errors;\n","import { toResponse } from \"better-call\";\nimport 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({ query: { route, 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 await writeWebResponse(res, toResponse(errors.internalError()));\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"]}
|
package/dist/adapters/fastify.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { toResponse } from 'better-call';
|
|
2
|
-
import { S3_ERROR_CODES,
|
|
2
|
+
import { S3_ERROR_CODES, APIError } from '@dimah-s3/core';
|
|
3
3
|
|
|
4
4
|
// src/adapters/node.ts
|
|
5
5
|
var errors = {
|
|
6
|
-
notFound: () =>
|
|
7
|
-
unauthorized: () =>
|
|
8
|
-
forbidden: () =>
|
|
9
|
-
conflict: () =>
|
|
10
|
-
internalError: () =>
|
|
11
|
-
objectNotFound: () =>
|
|
12
|
-
featureDisabled: (feature) =>
|
|
6
|
+
notFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
|
|
7
|
+
unauthorized: () => APIError.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
|
|
8
|
+
forbidden: () => APIError.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
|
|
9
|
+
conflict: () => APIError.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
|
|
10
|
+
internalError: () => APIError.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
|
|
11
|
+
objectNotFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
|
|
12
|
+
featureDisabled: (feature) => APIError.from("NOT_FOUND", {
|
|
13
13
|
code: S3_ERROR_CODES.FEATURE_DISABLED.code,
|
|
14
14
|
message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(
|
|
15
15
|
"{feature}",
|
|
@@ -17,13 +17,13 @@ var errors = {
|
|
|
17
17
|
),
|
|
18
18
|
params: { feature }
|
|
19
19
|
}),
|
|
20
|
-
invalidKey: () =>
|
|
21
|
-
unknownRoute: (route) =>
|
|
20
|
+
invalidKey: () => APIError.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
|
|
21
|
+
unknownRoute: (route) => APIError.from("NOT_FOUND", {
|
|
22
22
|
code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,
|
|
23
23
|
message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,
|
|
24
24
|
params: { route }
|
|
25
25
|
}),
|
|
26
|
-
fileTypeNotAllowed: (fileName, contentType) =>
|
|
26
|
+
fileTypeNotAllowed: (fileName, contentType) => APIError.from("BAD_REQUEST", {
|
|
27
27
|
code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,
|
|
28
28
|
message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,
|
|
29
29
|
params: {
|
|
@@ -31,7 +31,7 @@ var errors = {
|
|
|
31
31
|
...contentType ? { contentType } : {}
|
|
32
32
|
}
|
|
33
33
|
}),
|
|
34
|
-
s3NetworkError: (code) =>
|
|
34
|
+
s3NetworkError: (code) => APIError.from("BAD_GATEWAY", {
|
|
35
35
|
code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,
|
|
36
36
|
message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(
|
|
37
37
|
"{code}",
|
|
@@ -39,7 +39,7 @@ var errors = {
|
|
|
39
39
|
),
|
|
40
40
|
params: { code }
|
|
41
41
|
}),
|
|
42
|
-
multipartPartMissing: (partNumber) =>
|
|
42
|
+
multipartPartMissing: (partNumber) => APIError.from("BAD_REQUEST", {
|
|
43
43
|
code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,
|
|
44
44
|
message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(
|
|
45
45
|
"{partNumber}",
|
|
@@ -47,15 +47,15 @@ var errors = {
|
|
|
47
47
|
),
|
|
48
48
|
params: { partNumber }
|
|
49
49
|
}),
|
|
50
|
-
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) =>
|
|
50
|
+
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
51
51
|
...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
52
52
|
message
|
|
53
53
|
}),
|
|
54
|
-
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>
|
|
54
|
+
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
55
55
|
...S3_ERROR_CODES.QUOTA_EXCEEDED,
|
|
56
56
|
message
|
|
57
57
|
}),
|
|
58
|
-
validationError: (message) =>
|
|
58
|
+
validationError: (message) => APIError.from("BAD_REQUEST", {
|
|
59
59
|
code: S3_ERROR_CODES.VALIDATION_ERROR.code,
|
|
60
60
|
message
|
|
61
61
|
})
|
|
@@ -1 +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 {\n DimahS3Error,\n S3_ERROR_CODES,\n isAPIError,\n isDimahS3Error,\n isS3ErrorCode,\n} from \"@dimah-s3/core\";\nimport type { RouteOperation } from \"@/types/config\";\n\nexport {\n DimahS3Error,\n S3_ERROR_CODES,\n isAPIError,\n isDimahS3Error,\n isS3ErrorCode,\n};\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () => DimahS3Error.from(\"NOT_FOUND\", S3_ERROR_CODES.NOT_FOUND),\n\n unauthorized: () =>\n DimahS3Error.from(\"UNAUTHORIZED\", S3_ERROR_CODES.UNAUTHORIZED),\n\n forbidden: () => DimahS3Error.from(\"FORBIDDEN\", S3_ERROR_CODES.FORBIDDEN),\n\n conflict: () => DimahS3Error.from(\"CONFLICT\", S3_ERROR_CODES.CONFLICT),\n\n internalError: () =>\n DimahS3Error.from(\"INTERNAL_SERVER_ERROR\", S3_ERROR_CODES.INTERNAL_ERROR),\n\n objectNotFound: () =>\n DimahS3Error.from(\"NOT_FOUND\", S3_ERROR_CODES.OBJECT_NOT_FOUND),\n\n featureDisabled: (feature: RouteOperation) =>\n DimahS3Error.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.FEATURE_DISABLED.code,\n message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(\n \"{feature}\",\n feature,\n ),\n params: { feature },\n }),\n\n invalidKey: () =>\n DimahS3Error.from(\"BAD_REQUEST\", S3_ERROR_CODES.INVALID_KEY),\n\n unknownRoute: (route: string) =>\n DimahS3Error.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,\n message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,\n params: { route },\n }),\n\n fileTypeNotAllowed: (fileName: string, contentType?: string) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,\n message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,\n params: {\n fileName,\n ...(contentType ? { contentType } : {}),\n },\n }),\n\n s3NetworkError: (code: string) =>\n DimahS3Error.from(\"BAD_GATEWAY\", {\n code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,\n message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(\n \"{code}\",\n code,\n ),\n params: { code },\n }),\n\n multipartPartMissing: (partNumber: number) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,\n message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(\n \"{partNumber}\",\n String(partNumber),\n ),\n params: { partNumber },\n }),\n\n payloadTooLarge: (\n message: string = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message,\n ) =>\n DimahS3Error.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,\n message,\n }),\n\n quotaExceeded: (message: string = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>\n DimahS3Error.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.QUOTA_EXCEEDED,\n message,\n }),\n\n validationError: (message: string) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.VALIDATION_ERROR.code,\n message,\n }),\n} as const;\n\nexport type ServerErrors = typeof errors;\n","import { toResponse } from \"better-call\";\nimport 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({ query: { route, 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 await writeWebResponse(res, toResponse(errors.internalError()));\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"]}
|
|
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 {\n APIError,\n S3_ERROR_CODES,\n isAPIError,\n isS3ErrorCode,\n} from \"@dimah-s3/core\";\nimport type { RouteOperation } from \"@/types/config\";\n\nexport { APIError, S3_ERROR_CODES, isAPIError, isS3ErrorCode };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () => APIError.from(\"NOT_FOUND\", S3_ERROR_CODES.NOT_FOUND),\n\n unauthorized: () =>\n APIError.from(\"UNAUTHORIZED\", S3_ERROR_CODES.UNAUTHORIZED),\n\n forbidden: () => APIError.from(\"FORBIDDEN\", S3_ERROR_CODES.FORBIDDEN),\n\n conflict: () => APIError.from(\"CONFLICT\", S3_ERROR_CODES.CONFLICT),\n\n internalError: () =>\n APIError.from(\"INTERNAL_SERVER_ERROR\", S3_ERROR_CODES.INTERNAL_ERROR),\n\n objectNotFound: () =>\n APIError.from(\"NOT_FOUND\", S3_ERROR_CODES.OBJECT_NOT_FOUND),\n\n featureDisabled: (feature: RouteOperation) =>\n APIError.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.FEATURE_DISABLED.code,\n message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(\n \"{feature}\",\n feature,\n ),\n params: { feature },\n }),\n\n invalidKey: () => APIError.from(\"BAD_REQUEST\", S3_ERROR_CODES.INVALID_KEY),\n\n unknownRoute: (route: string) =>\n APIError.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,\n message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,\n params: { route },\n }),\n\n fileTypeNotAllowed: (fileName: string, contentType?: string) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,\n message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,\n params: {\n fileName,\n ...(contentType ? { contentType } : {}),\n },\n }),\n\n s3NetworkError: (code: string) =>\n APIError.from(\"BAD_GATEWAY\", {\n code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,\n message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(\n \"{code}\",\n code,\n ),\n params: { code },\n }),\n\n multipartPartMissing: (partNumber: number) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,\n message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(\n \"{partNumber}\",\n String(partNumber),\n ),\n params: { partNumber },\n }),\n\n payloadTooLarge: (\n message: string = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message,\n ) =>\n APIError.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,\n message,\n }),\n\n quotaExceeded: (message: string = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>\n APIError.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.QUOTA_EXCEEDED,\n message,\n }),\n\n validationError: (message: string) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.VALIDATION_ERROR.code,\n message,\n }),\n} as const;\n\nexport type ServerErrors = typeof errors;\n","import { toResponse } from \"better-call\";\nimport 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({ query: { route, 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 await writeWebResponse(res, toResponse(errors.internalError()));\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"]}
|
package/dist/adapters/node.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { toResponse } from 'better-call';
|
|
2
|
-
import { S3_ERROR_CODES,
|
|
2
|
+
import { S3_ERROR_CODES, APIError } from '@dimah-s3/core';
|
|
3
3
|
|
|
4
4
|
// src/adapters/node.ts
|
|
5
5
|
var errors = {
|
|
6
|
-
notFound: () =>
|
|
7
|
-
unauthorized: () =>
|
|
8
|
-
forbidden: () =>
|
|
9
|
-
conflict: () =>
|
|
10
|
-
internalError: () =>
|
|
11
|
-
objectNotFound: () =>
|
|
12
|
-
featureDisabled: (feature) =>
|
|
6
|
+
notFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
|
|
7
|
+
unauthorized: () => APIError.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
|
|
8
|
+
forbidden: () => APIError.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
|
|
9
|
+
conflict: () => APIError.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
|
|
10
|
+
internalError: () => APIError.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
|
|
11
|
+
objectNotFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
|
|
12
|
+
featureDisabled: (feature) => APIError.from("NOT_FOUND", {
|
|
13
13
|
code: S3_ERROR_CODES.FEATURE_DISABLED.code,
|
|
14
14
|
message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(
|
|
15
15
|
"{feature}",
|
|
@@ -17,13 +17,13 @@ var errors = {
|
|
|
17
17
|
),
|
|
18
18
|
params: { feature }
|
|
19
19
|
}),
|
|
20
|
-
invalidKey: () =>
|
|
21
|
-
unknownRoute: (route) =>
|
|
20
|
+
invalidKey: () => APIError.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
|
|
21
|
+
unknownRoute: (route) => APIError.from("NOT_FOUND", {
|
|
22
22
|
code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,
|
|
23
23
|
message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,
|
|
24
24
|
params: { route }
|
|
25
25
|
}),
|
|
26
|
-
fileTypeNotAllowed: (fileName, contentType) =>
|
|
26
|
+
fileTypeNotAllowed: (fileName, contentType) => APIError.from("BAD_REQUEST", {
|
|
27
27
|
code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,
|
|
28
28
|
message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,
|
|
29
29
|
params: {
|
|
@@ -31,7 +31,7 @@ var errors = {
|
|
|
31
31
|
...contentType ? { contentType } : {}
|
|
32
32
|
}
|
|
33
33
|
}),
|
|
34
|
-
s3NetworkError: (code) =>
|
|
34
|
+
s3NetworkError: (code) => APIError.from("BAD_GATEWAY", {
|
|
35
35
|
code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,
|
|
36
36
|
message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(
|
|
37
37
|
"{code}",
|
|
@@ -39,7 +39,7 @@ var errors = {
|
|
|
39
39
|
),
|
|
40
40
|
params: { code }
|
|
41
41
|
}),
|
|
42
|
-
multipartPartMissing: (partNumber) =>
|
|
42
|
+
multipartPartMissing: (partNumber) => APIError.from("BAD_REQUEST", {
|
|
43
43
|
code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,
|
|
44
44
|
message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(
|
|
45
45
|
"{partNumber}",
|
|
@@ -47,15 +47,15 @@ var errors = {
|
|
|
47
47
|
),
|
|
48
48
|
params: { partNumber }
|
|
49
49
|
}),
|
|
50
|
-
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) =>
|
|
50
|
+
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
51
51
|
...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
52
52
|
message
|
|
53
53
|
}),
|
|
54
|
-
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>
|
|
54
|
+
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
55
55
|
...S3_ERROR_CODES.QUOTA_EXCEEDED,
|
|
56
56
|
message
|
|
57
57
|
}),
|
|
58
|
-
validationError: (message) =>
|
|
58
|
+
validationError: (message) => APIError.from("BAD_REQUEST", {
|
|
59
59
|
code: S3_ERROR_CODES.VALIDATION_ERROR.code,
|
|
60
60
|
message
|
|
61
61
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/node.ts"],"names":[],"mappings":";;;;AAkBO,IAAM,MAAA,GAAS;AAAA,EACpB,UAAU,MAAM,YAAA,CAAa,IAAA,CAAK,WAAA,EAAa,eAAe,SAAS,CAAA;AAAA,EAEvE,cAAc,MACZ,YAAA,CAAa,IAAA,CAAK,cAAA,EAAgB,eAAe,YAAY,CAAA;AAAA,EAE/D,WAAW,MAAM,YAAA,CAAa,IAAA,CAAK,WAAA,EAAa,eAAe,SAAS,CAAA;AAAA,EAExE,UAAU,MAAM,YAAA,CAAa,IAAA,CAAK,UAAA,EAAY,eAAe,QAAQ,CAAA;AAAA,EAErE,eAAe,MACb,YAAA,CAAa,IAAA,CAAK,uBAAA,EAAyB,eAAe,cAAc,CAAA;AAAA,EAE1E,gBAAgB,MACd,YAAA,CAAa,IAAA,CAAK,WAAA,EAAa,eAAe,gBAAgB,CAAA;AAAA,EAEhE,eAAA,EAAiB,CAAC,OAAA,KAChB,YAAA,CAAa,KAAK,WAAA,EAAa;AAAA,IAC7B,IAAA,EAAM,eAAe,gBAAA,CAAiB,IAAA;AAAA,IACtC,OAAA,EAAS,cAAA,CAAe,gBAAA,CAAiB,OAAA,CAAQ,UAAA;AAAA,MAC/C,WAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,MAAA,EAAQ,EAAE,OAAA;AAAQ,GACnB,CAAA;AAAA,EAEH,YAAY,MACV,YAAA,CAAa,IAAA,CAAK,aAAA,EAAe,eAAe,WAAW,CAAA;AAAA,EAE7D,YAAA,EAAc,CAAC,KAAA,KACb,YAAA,CAAa,KAAK,WAAA,EAAa;AAAA,IAC7B,IAAA,EAAM,eAAe,aAAA,CAAc,IAAA;AAAA,IACnC,OAAA,EAAS,eAAe,aAAA,CAAc,OAAA;AAAA,IACtC,MAAA,EAAQ,EAAE,KAAA;AAAM,GACjB,CAAA;AAAA,EAEH,oBAAoB,CAAC,QAAA,EAAkB,WAAA,KACrC,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,qBAAA,CAAsB,IAAA;AAAA,IAC3C,OAAA,EAAS,eAAe,qBAAA,CAAsB,OAAA;AAAA,IAC9C,MAAA,EAAQ;AAAA,MACN,QAAA;AAAA,MACA,GAAI,WAAA,GAAc,EAAE,WAAA,KAAgB;AAAC;AACvC,GACD,CAAA;AAAA,EAEH,cAAA,EAAgB,CAAC,IAAA,KACf,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,gBAAA,CAAiB,IAAA;AAAA,IACtC,OAAA,EAAS,cAAA,CAAe,gBAAA,CAAiB,OAAA,CAAQ,UAAA;AAAA,MAC/C,QAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,MAAA,EAAQ,EAAE,IAAA;AAAK,GAChB,CAAA;AAAA,EAEH,oBAAA,EAAsB,CAAC,UAAA,KACrB,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,sBAAA,CAAuB,IAAA;AAAA,IAC5C,OAAA,EAAS,cAAA,CAAe,sBAAA,CAAuB,OAAA,CAAQ,UAAA;AAAA,MACrD,cAAA;AAAA,MACA,OAAO,UAAU;AAAA,KACnB;AAAA,IACA,MAAA,EAAQ,EAAE,UAAA;AAAW,GACtB,CAAA;AAAA,EAEH,eAAA,EAAiB,CACf,OAAA,GAAkB,cAAA,CAAe,kBAAkB,OAAA,KAEnD,YAAA,CAAa,KAAK,mBAAA,EAAqB;AAAA,IACrC,GAAG,cAAA,CAAe,iBAAA;AAAA,IAClB;AAAA,GACD,CAAA;AAAA,EAEH,aAAA,EAAe,CAAC,OAAA,GAAkB,cAAA,CAAe,eAAe,OAAA,KAC9D,YAAA,CAAa,KAAK,mBAAA,EAAqB;AAAA,IACrC,GAAG,cAAA,CAAe,cAAA;AAAA,IAClB;AAAA,GACD,CAAA;AAAA,EAEH,eAAA,EAAiB,CAAC,OAAA,KAChB,YAAA,CAAa,KAAK,aAAA,EAAe;AAAA,IAC/B,IAAA,EAAM,eAAe,gBAAA,CAAiB,IAAA;AAAA,IACtC;AAAA,GACD;AACL,CAAA;;;ACnFO,SAAS,gBAAgB,WAAA,EAA2C;AACzE,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,EAAQ;AAC5B,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACtD,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,MAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAC9C,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,IACxB;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,YAAY,GAAA,EAAuC;AAC1D,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,GAAA,CAAI,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAA2B;AACzC,MAAA,MAAA,CAAO,IAAA,CAAK,OAAO,KAAA,KAAU,QAAA,GAAW,OAAO,IAAA,CAAK,KAAK,IAAI,KAAK,CAAA;AAAA,IACpE,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,EAAA,CAAG,OAAO,MAAM,OAAA,CAAQ,OAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAClD,IAAA,GAAA,CAAI,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,EACxB,CAAC,CAAA;AACH;AAEA,SAAS,YAAA,CAAa,KAAsB,IAAA,EAAuB;AACjE,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,IAAA,IAAQ,WAAA;AACjC,EAAA,MAAM,QAAA,GACH,GAAA,CAAI,MAAA,CAAmC,SAAA,KAAc,OAClD,OAAA,GACA,MAAA;AACN,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,GAAA,CAAI,GAAA,IAAO,KAAK,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,IAAI,CAAA,CAAE,CAAA;AAE3D,EAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,GAAA,CAAI,OAAO,CAAA;AAC3C,EAAA,MAAM,MAAA,GAAA,CAAU,GAAA,CAAI,MAAA,IAAU,KAAA,EAAO,WAAA,EAAY;AACjD,EAAA,MAAM,IAAA,GAAoB,EAAE,MAAA,EAAQ,OAAA,EAAQ;AAC5C,EAAA,IAAI,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,MAAA,EAAQ;AACzC,IAAA,IAAA,CAAK,IAAA,GAAO,IAAI,UAAA,CAAW,IAAI,CAAA;AAE/B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAEA,EAAA,OAAO,IAAI,OAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AAC9B;AAEA,eAAe,gBAAA,CACb,KACA,QAAA,EACe;AACf,EAAA,GAAA,CAAI,aAAa,QAAA,CAAS,MAAA;AAC1B,EAAA,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AACvC,IAAA,GAAA,CAAI,SAAA,CAAU,KAAK,KAAK,CAAA;AAAA,EAC1B,CAAC,CAAA;AACD,EAAA,MAAM,SAAS,MAAA,CAAO,IAAA,CAAK,MAAM,QAAA,CAAS,aAAa,CAAA;AACvD,EAAA,GAAA,CAAI,IAAI,MAAM,CAAA;AAChB;AAcO,SAAS,cAAc,EAAA,EAA0B;AACtD,EAAA,OAAO,OAAO,KAAsB,GAAA,KAAuC;AACzE,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,GAAG,CAAA;AAClC,MAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;AACtC,MAAA,MAAM,QAAA,GAAW,MAAM,EAAA,CAAG,OAAA,CAAQ,OAAO,CAAA;AACzC,MAAA,MAAM,gBAAA,CAAiB,KAAK,QAAQ,CAAA;AAAA,IACtC,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,KAAA,CAAM,YAAY,GAAG,CAAA;AAC7B,MAAA,IAAI,CAAC,IAAI,WAAA,EAAa;AACpB,QAAA,MAAM,iBAAiB,GAAA,EAAK,UAAA,CAAW,MAAA,CAAO,aAAA,EAAe,CAAC,CAAA;AAAA,MAChE;AAAA,IACF;AAAA,EACF,CAAA;AACF","file":"node.js","sourcesContent":["import {\n DimahS3Error,\n S3_ERROR_CODES,\n isAPIError,\n isDimahS3Error,\n isS3ErrorCode,\n} from \"@dimah-s3/core\";\nimport type { RouteOperation } from \"@/types/config\";\n\nexport {\n DimahS3Error,\n S3_ERROR_CODES,\n isAPIError,\n isDimahS3Error,\n isS3ErrorCode,\n};\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () => DimahS3Error.from(\"NOT_FOUND\", S3_ERROR_CODES.NOT_FOUND),\n\n unauthorized: () =>\n DimahS3Error.from(\"UNAUTHORIZED\", S3_ERROR_CODES.UNAUTHORIZED),\n\n forbidden: () => DimahS3Error.from(\"FORBIDDEN\", S3_ERROR_CODES.FORBIDDEN),\n\n conflict: () => DimahS3Error.from(\"CONFLICT\", S3_ERROR_CODES.CONFLICT),\n\n internalError: () =>\n DimahS3Error.from(\"INTERNAL_SERVER_ERROR\", S3_ERROR_CODES.INTERNAL_ERROR),\n\n objectNotFound: () =>\n DimahS3Error.from(\"NOT_FOUND\", S3_ERROR_CODES.OBJECT_NOT_FOUND),\n\n featureDisabled: (feature: RouteOperation) =>\n DimahS3Error.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.FEATURE_DISABLED.code,\n message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(\n \"{feature}\",\n feature,\n ),\n params: { feature },\n }),\n\n invalidKey: () =>\n DimahS3Error.from(\"BAD_REQUEST\", S3_ERROR_CODES.INVALID_KEY),\n\n unknownRoute: (route: string) =>\n DimahS3Error.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,\n message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,\n params: { route },\n }),\n\n fileTypeNotAllowed: (fileName: string, contentType?: string) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,\n message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,\n params: {\n fileName,\n ...(contentType ? { contentType } : {}),\n },\n }),\n\n s3NetworkError: (code: string) =>\n DimahS3Error.from(\"BAD_GATEWAY\", {\n code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,\n message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(\n \"{code}\",\n code,\n ),\n params: { code },\n }),\n\n multipartPartMissing: (partNumber: number) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,\n message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(\n \"{partNumber}\",\n String(partNumber),\n ),\n params: { partNumber },\n }),\n\n payloadTooLarge: (\n message: string = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message,\n ) =>\n DimahS3Error.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,\n message,\n }),\n\n quotaExceeded: (message: string = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>\n DimahS3Error.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.QUOTA_EXCEEDED,\n message,\n }),\n\n validationError: (message: string) =>\n DimahS3Error.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.VALIDATION_ERROR.code,\n message,\n }),\n} as const;\n\nexport type ServerErrors = typeof errors;\n","import { toResponse } from \"better-call\";\nimport 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({ query: { route, 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 await writeWebResponse(res, toResponse(errors.internalError()));\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 {\n APIError,\n S3_ERROR_CODES,\n isAPIError,\n isS3ErrorCode,\n} from \"@dimah-s3/core\";\nimport type { RouteOperation } from \"@/types/config\";\n\nexport { APIError, S3_ERROR_CODES, isAPIError, isS3ErrorCode };\n\n/** English API errors with stable codes for client-side localization. */\nexport const errors = {\n notFound: () => APIError.from(\"NOT_FOUND\", S3_ERROR_CODES.NOT_FOUND),\n\n unauthorized: () =>\n APIError.from(\"UNAUTHORIZED\", S3_ERROR_CODES.UNAUTHORIZED),\n\n forbidden: () => APIError.from(\"FORBIDDEN\", S3_ERROR_CODES.FORBIDDEN),\n\n conflict: () => APIError.from(\"CONFLICT\", S3_ERROR_CODES.CONFLICT),\n\n internalError: () =>\n APIError.from(\"INTERNAL_SERVER_ERROR\", S3_ERROR_CODES.INTERNAL_ERROR),\n\n objectNotFound: () =>\n APIError.from(\"NOT_FOUND\", S3_ERROR_CODES.OBJECT_NOT_FOUND),\n\n featureDisabled: (feature: RouteOperation) =>\n APIError.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.FEATURE_DISABLED.code,\n message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(\n \"{feature}\",\n feature,\n ),\n params: { feature },\n }),\n\n invalidKey: () => APIError.from(\"BAD_REQUEST\", S3_ERROR_CODES.INVALID_KEY),\n\n unknownRoute: (route: string) =>\n APIError.from(\"NOT_FOUND\", {\n code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,\n message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,\n params: { route },\n }),\n\n fileTypeNotAllowed: (fileName: string, contentType?: string) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,\n message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,\n params: {\n fileName,\n ...(contentType ? { contentType } : {}),\n },\n }),\n\n s3NetworkError: (code: string) =>\n APIError.from(\"BAD_GATEWAY\", {\n code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,\n message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(\n \"{code}\",\n code,\n ),\n params: { code },\n }),\n\n multipartPartMissing: (partNumber: number) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,\n message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(\n \"{partNumber}\",\n String(partNumber),\n ),\n params: { partNumber },\n }),\n\n payloadTooLarge: (\n message: string = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message,\n ) =>\n APIError.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,\n message,\n }),\n\n quotaExceeded: (message: string = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>\n APIError.from(\"PAYLOAD_TOO_LARGE\", {\n ...S3_ERROR_CODES.QUOTA_EXCEEDED,\n message,\n }),\n\n validationError: (message: string) =>\n APIError.from(\"BAD_REQUEST\", {\n code: S3_ERROR_CODES.VALIDATION_ERROR.code,\n message,\n }),\n} as const;\n\nexport type ServerErrors = typeof errors;\n","import { toResponse } from \"better-call\";\nimport 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({ query: { route, 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 await writeWebResponse(res, toResponse(errors.internalError()));\n }\n }\n };\n}\n"]}
|
|
@@ -125,7 +125,7 @@ type CreateS3Endpoint = typeof createEndpointWithContext;
|
|
|
125
125
|
*
|
|
126
126
|
* Paths are absolute under `basePath` (e.g. `/db/objects`). Context
|
|
127
127
|
* (`config`, `request`) is injected by the router / `s3.api`.
|
|
128
|
-
* Zod failures throw `
|
|
128
|
+
* Zod failures throw `APIError` (`VALIDATION_ERROR`) via
|
|
129
129
|
* better-call `onValidationError`. Zod `body` / `query` schemas are
|
|
130
130
|
* compiled with `z.compile()` (Zod 4.5) so valid requests skip the
|
|
131
131
|
* interpreter; unsupported schemas fall back to the runtime parser.
|
package/dist/api/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { createMiddleware, createEndpoint } from 'better-call';
|
|
2
2
|
import * as z from 'zod';
|
|
3
|
-
import { S3_API_ROUTES, isAPIError,
|
|
3
|
+
import { S3_API_ROUTES, isAPIError, APIError, S3_ERROR_CODES, confirmBodySchema, deleteQuerySchema, deleteBatchBodySchema, downloadQuerySchema, fileQuerySchema, multipartAbortBodySchema, multipartCompleteBodySchema, multipartInitBodySchema, multipartListPartsQuerySchema, multipartSignPartBodySchema, uploadBodySchema, S3_MAX_EXPIRES_IN, buildContentDisposition, S3_MAX_POST_OBJECT_BYTES, resolveStoredFileName, S3_DEFAULT_EXPIRES_IN, matchesFileTypes, normalizeObjectKey, sanitizeFileName } from '@dimah-s3/core';
|
|
4
4
|
import { DeleteObjectCommand, GetObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, PutObjectCommand, ListPartsCommand, AbortMultipartUploadCommand, CompleteMultipartUploadCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
|
|
5
5
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
6
6
|
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
7
7
|
|
|
8
8
|
// src/api/create-s3-endpoint.ts
|
|
9
9
|
var errors = {
|
|
10
|
-
notFound: () =>
|
|
11
|
-
unauthorized: () =>
|
|
12
|
-
forbidden: () =>
|
|
13
|
-
conflict: () =>
|
|
14
|
-
internalError: () =>
|
|
15
|
-
objectNotFound: () =>
|
|
16
|
-
featureDisabled: (feature) =>
|
|
10
|
+
notFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
|
|
11
|
+
unauthorized: () => APIError.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
|
|
12
|
+
forbidden: () => APIError.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
|
|
13
|
+
conflict: () => APIError.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
|
|
14
|
+
internalError: () => APIError.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
|
|
15
|
+
objectNotFound: () => APIError.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
|
|
16
|
+
featureDisabled: (feature) => APIError.from("NOT_FOUND", {
|
|
17
17
|
code: S3_ERROR_CODES.FEATURE_DISABLED.code,
|
|
18
18
|
message: S3_ERROR_CODES.FEATURE_DISABLED.message.replaceAll(
|
|
19
19
|
"{feature}",
|
|
@@ -21,13 +21,13 @@ var errors = {
|
|
|
21
21
|
),
|
|
22
22
|
params: { feature }
|
|
23
23
|
}),
|
|
24
|
-
invalidKey: () =>
|
|
25
|
-
unknownRoute: (route) =>
|
|
24
|
+
invalidKey: () => APIError.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
|
|
25
|
+
unknownRoute: (route) => APIError.from("NOT_FOUND", {
|
|
26
26
|
code: S3_ERROR_CODES.UNKNOWN_ROUTE.code,
|
|
27
27
|
message: S3_ERROR_CODES.UNKNOWN_ROUTE.message,
|
|
28
28
|
params: { route }
|
|
29
29
|
}),
|
|
30
|
-
fileTypeNotAllowed: (fileName, contentType) =>
|
|
30
|
+
fileTypeNotAllowed: (fileName, contentType) => APIError.from("BAD_REQUEST", {
|
|
31
31
|
code: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.code,
|
|
32
32
|
message: S3_ERROR_CODES.FILE_TYPE_NOT_ALLOWED.message,
|
|
33
33
|
params: {
|
|
@@ -35,7 +35,7 @@ var errors = {
|
|
|
35
35
|
...contentType ? { contentType } : {}
|
|
36
36
|
}
|
|
37
37
|
}),
|
|
38
|
-
s3NetworkError: (code) =>
|
|
38
|
+
s3NetworkError: (code) => APIError.from("BAD_GATEWAY", {
|
|
39
39
|
code: S3_ERROR_CODES.S3_NETWORK_ERROR.code,
|
|
40
40
|
message: S3_ERROR_CODES.S3_NETWORK_ERROR.message.replaceAll(
|
|
41
41
|
"{code}",
|
|
@@ -43,7 +43,7 @@ var errors = {
|
|
|
43
43
|
),
|
|
44
44
|
params: { code }
|
|
45
45
|
}),
|
|
46
|
-
multipartPartMissing: (partNumber) =>
|
|
46
|
+
multipartPartMissing: (partNumber) => APIError.from("BAD_REQUEST", {
|
|
47
47
|
code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,
|
|
48
48
|
message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(
|
|
49
49
|
"{partNumber}",
|
|
@@ -51,15 +51,15 @@ var errors = {
|
|
|
51
51
|
),
|
|
52
52
|
params: { partNumber }
|
|
53
53
|
}),
|
|
54
|
-
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) =>
|
|
54
|
+
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
55
55
|
...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
56
56
|
message
|
|
57
57
|
}),
|
|
58
|
-
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) =>
|
|
58
|
+
quotaExceeded: (message = S3_ERROR_CODES.QUOTA_EXCEEDED.message) => APIError.from("PAYLOAD_TOO_LARGE", {
|
|
59
59
|
...S3_ERROR_CODES.QUOTA_EXCEEDED,
|
|
60
60
|
message
|
|
61
61
|
}),
|
|
62
|
-
validationError: (message) =>
|
|
62
|
+
validationError: (message) => APIError.from("BAD_REQUEST", {
|
|
63
63
|
code: S3_ERROR_CODES.VALIDATION_ERROR.code,
|
|
64
64
|
message
|
|
65
65
|
})
|
|
@@ -122,7 +122,7 @@ function resolveLogger(logger) {
|
|
|
122
122
|
function mapGuardError(err) {
|
|
123
123
|
if (isAPIError(err)) throw err;
|
|
124
124
|
if (err instanceof Error && err.message.trim()) {
|
|
125
|
-
throw new
|
|
125
|
+
throw new APIError("FORBIDDEN", {
|
|
126
126
|
message: err.message,
|
|
127
127
|
cause: err
|
|
128
128
|
});
|
|
@@ -568,7 +568,7 @@ var deleteBatch = createS3Endpoint(
|
|
|
568
568
|
results.push({
|
|
569
569
|
key,
|
|
570
570
|
success: false,
|
|
571
|
-
error:
|
|
571
|
+
error: isAPIError(err) ? { code: err.code ?? "INTERNAL_ERROR", message: err.message } : { code: "INTERNAL_ERROR", message: "Internal server error" }
|
|
572
572
|
});
|
|
573
573
|
}
|
|
574
574
|
}
|