@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.
@@ -1,15 +1,15 @@
1
1
  import { toResponse } from 'better-call';
2
- import { S3_ERROR_CODES, DimahS3Error } from '@dimah-s3/core';
2
+ import { S3_ERROR_CODES, APIError } from '@dimah-s3/core';
3
3
 
4
4
  // src/adapters/node.ts
5
5
  var errors = {
6
- notFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
7
- unauthorized: () => DimahS3Error.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
8
- forbidden: () => DimahS3Error.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
9
- conflict: () => DimahS3Error.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
10
- internalError: () => DimahS3Error.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
11
- objectNotFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
12
- featureDisabled: (feature) => DimahS3Error.from("NOT_FOUND", {
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: () => DimahS3Error.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
21
- unknownRoute: (route) => DimahS3Error.from("NOT_FOUND", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("BAD_GATEWAY", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("BAD_REQUEST", {
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":"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","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"]}
@@ -1,15 +1,15 @@
1
1
  import { toResponse } from 'better-call';
2
- import { S3_ERROR_CODES, DimahS3Error } from '@dimah-s3/core';
2
+ import { S3_ERROR_CODES, APIError } from '@dimah-s3/core';
3
3
 
4
4
  // src/adapters/node.ts
5
5
  var errors = {
6
- notFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
7
- unauthorized: () => DimahS3Error.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
8
- forbidden: () => DimahS3Error.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
9
- conflict: () => DimahS3Error.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
10
- internalError: () => DimahS3Error.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
11
- objectNotFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
12
- featureDisabled: (feature) => DimahS3Error.from("NOT_FOUND", {
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: () => DimahS3Error.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
21
- unknownRoute: (route) => DimahS3Error.from("NOT_FOUND", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("BAD_GATEWAY", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("BAD_REQUEST", {
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"]}
@@ -1,15 +1,15 @@
1
1
  import { toResponse } from 'better-call';
2
- import { S3_ERROR_CODES, DimahS3Error } from '@dimah-s3/core';
2
+ import { S3_ERROR_CODES, APIError } from '@dimah-s3/core';
3
3
 
4
4
  // src/adapters/node.ts
5
5
  var errors = {
6
- notFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
7
- unauthorized: () => DimahS3Error.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
8
- forbidden: () => DimahS3Error.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
9
- conflict: () => DimahS3Error.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
10
- internalError: () => DimahS3Error.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
11
- objectNotFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
12
- featureDisabled: (feature) => DimahS3Error.from("NOT_FOUND", {
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: () => DimahS3Error.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
21
- unknownRoute: (route) => DimahS3Error.from("NOT_FOUND", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("BAD_GATEWAY", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("BAD_REQUEST", {
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":"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","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 `DimahS3Error` (`VALIDATION_ERROR`) via
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, DimahS3Error, S3_ERROR_CODES, confirmBodySchema, deleteQuerySchema, isDimahS3Error, 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';
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: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.NOT_FOUND),
11
- unauthorized: () => DimahS3Error.from("UNAUTHORIZED", S3_ERROR_CODES.UNAUTHORIZED),
12
- forbidden: () => DimahS3Error.from("FORBIDDEN", S3_ERROR_CODES.FORBIDDEN),
13
- conflict: () => DimahS3Error.from("CONFLICT", S3_ERROR_CODES.CONFLICT),
14
- internalError: () => DimahS3Error.from("INTERNAL_SERVER_ERROR", S3_ERROR_CODES.INTERNAL_ERROR),
15
- objectNotFound: () => DimahS3Error.from("NOT_FOUND", S3_ERROR_CODES.OBJECT_NOT_FOUND),
16
- featureDisabled: (feature) => DimahS3Error.from("NOT_FOUND", {
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: () => DimahS3Error.from("BAD_REQUEST", S3_ERROR_CODES.INVALID_KEY),
25
- unknownRoute: (route) => DimahS3Error.from("NOT_FOUND", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("BAD_GATEWAY", {
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) => DimahS3Error.from("BAD_REQUEST", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
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) => DimahS3Error.from("BAD_REQUEST", {
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 DimahS3Error("FORBIDDEN", {
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: isDimahS3Error(err) ? { code: err.code ?? "INTERNAL_ERROR", message: err.message } : { code: "INTERNAL_ERROR", message: "Internal server 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
  }