@nextlyhq/storage-s3 0.0.2-alpha.64 → 0.0.2-alpha.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +10 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -6,7 +6,7 @@ var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
|
|
|
6
6
|
|
|
7
7
|
// src/adapter.ts
|
|
8
8
|
|
|
9
|
-
// ../nextly/dist/chunk-
|
|
9
|
+
// ../nextly/dist/chunk-IAZJNA2U.mjs
|
|
10
10
|
function isDbError(err) {
|
|
11
11
|
if (!err || typeof err !== "object") return false;
|
|
12
12
|
const obj = err;
|
|
@@ -109,6 +109,13 @@ var NEXTLY_ERROR_STATUS = {
|
|
|
109
109
|
// Plugin platform (P4) — contributes.routes collection (D25).
|
|
110
110
|
NEXTLY_ROUTE_COLLISION: 409,
|
|
111
111
|
NEXTLY_ROUTE_INVALID_PATH: 400,
|
|
112
|
+
// A root-mounted route declared where the request pipeline never reaches the
|
|
113
|
+
// root pass. 400 like an invalid path and not 409: nothing else claims the
|
|
114
|
+
// address, the declaration simply cannot be served from there.
|
|
115
|
+
NEXTLY_ROUTE_UNREACHABLE_ROOT: 400,
|
|
116
|
+
// A `mount` outside the declared union, which only an untyped caller can
|
|
117
|
+
// produce. 400 for the same reason: the declaration itself is unserveable.
|
|
118
|
+
NEXTLY_ROUTE_INVALID_MOUNT: 400,
|
|
112
119
|
// An email transport whose library is an optional peer dependency the host
|
|
113
120
|
// has not installed. 503 rather than 500: the request is not malformed and
|
|
114
121
|
// nothing is broken, the install simply cannot carry it out yet, and the
|
|
@@ -446,7 +453,7 @@ var NextlyError = class _NextlyError extends Error {
|
|
|
446
453
|
}
|
|
447
454
|
};
|
|
448
455
|
|
|
449
|
-
// ../nextly/dist/chunk-
|
|
456
|
+
// ../nextly/dist/chunk-T3OVIJZM.mjs
|
|
450
457
|
var StorageReadTooLargeError = class extends NextlyError {
|
|
451
458
|
constructor(path, maxBytes, size) {
|
|
452
459
|
super({
|
|
@@ -464,7 +471,7 @@ var StorageReadTooLargeError = class extends NextlyError {
|
|
|
464
471
|
}
|
|
465
472
|
};
|
|
466
473
|
|
|
467
|
-
// ../nextly/dist/chunk-
|
|
474
|
+
// ../nextly/dist/chunk-YGE6AGZG.mjs
|
|
468
475
|
var DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
469
476
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
470
477
|
var DEFAULT_READ_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../nextly/dist/chunk-HJ5IQPOY.mjs","../../nextly/dist/chunk-W77AQUUQ.mjs","../../nextly/dist/chunk-C2VHLQKK.mjs","../src/adapter.ts","../src/plugin.ts","../src/index.ts"],"names":["S3Client","Upload","DeleteObjectCommand","DeleteObjectsCommand","HeadObjectCommand","GetObjectCommand","getSignedUrl","PutObjectCommand"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/database/errors.ts\nvar DbError = class extends Error {\n kind;\n dialect;\n code;\n meta;\n cause;\n constructor(args) {\n super(args.message);\n this.name = \"DbError\";\n this.kind = args.kind;\n this.dialect = args.dialect;\n this.code = args.code;\n this.meta = args.meta;\n this.cause = args.cause;\n }\n};\nfunction isDbError(err) {\n if (!err || typeof err !== \"object\") return false;\n const obj = err;\n return obj.name === \"DbError\" && typeof obj.kind === \"string\";\n}\nfunction toDbError(dialect, error, meta) {\n if (isDbError(error)) return error;\n const code = safeCode(error);\n const rawMsg = error != null && typeof error === \"object\" && \"message\" in error ? error.message : void 0;\n const message = typeof rawMsg === \"string\" ? rawMsg : \"Database error\";\n let kind = \"internal\";\n switch (dialect) {\n case \"postgresql\":\n kind = mapPostgresCodeToKind(code);\n break;\n case \"mysql\":\n kind = mapMySqlCodeToKind(code);\n break;\n case \"sqlite\":\n kind = mapSqliteCodeToKind(code);\n break;\n }\n if ((dialect === \"mysql\" || dialect === \"sqlite\") && (kind === \"constraint\" || kind === \"internal\") && isLikelyUniqueViolationMessage(dialect, message)) {\n kind = \"unique-violation\";\n }\n if (kind === \"internal\" && code) {\n const c = code.toUpperCase();\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n kind = \"connection-lost\";\n else if (c === \"ETIMEDOUT\") kind = \"timeout\";\n }\n return new DbError({ message, kind, dialect, code, meta, cause: error });\n}\nfunction safeCode(error) {\n if (!error || typeof error !== \"object\") return void 0;\n const e = error;\n let c = e.code ?? e.sqlState ?? e.state ?? e.errno ?? void 0;\n if (!c && typeof e.originalError?.code === \"string\") c = e.originalError.code;\n if (!c && typeof e.cause?.code === \"string\") c = e.cause.code;\n if (!c && typeof e.severity === \"string\" && typeof e.code === \"string\")\n c = e.code;\n if (typeof c === \"number\") return String(c);\n if (typeof c === \"string\" && c.length > 0) return c;\n return void 0;\n}\nfunction mapPostgresCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"40P01\") return \"deadlock\";\n if (c === \"40001\") return \"serialization-failure\";\n if (c === \"57014\") return \"timeout\";\n if (c === \"23505\") return \"unique-violation\";\n if (c === \"23503\") return \"fk-violation\";\n if (c === \"23502\") return \"not-null-violation\";\n if (c === \"42601\") return \"syntax\";\n if (c.startsWith(\"08\") || c === \"57P01\") return \"connection-lost\";\n if (c.startsWith(\"23\")) return \"constraint\";\n return \"internal\";\n}\nfunction mapMySqlCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"1213\" || c === \"ER_LOCK_DEADLOCK\") return \"deadlock\";\n if (c === \"1205\" || c === \"ER_LOCK_WAIT_TIMEOUT\") return \"timeout\";\n if (c === \"1062\" || c === \"ER_DUP_ENTRY\") return \"unique-violation\";\n if (c === \"23000\") return \"constraint\";\n if (c === \"1064\" || c === \"ER_PARSE_ERROR\") return \"syntax\";\n if (c === \"1452\" || c === \"ER_NO_REFERENCED_ROW_2\") return \"fk-violation\";\n if (c === \"1451\" || c === \"ER_ROW_IS_REFERENCED_2\") return \"fk-violation\";\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n return \"connection-lost\";\n if (c === \"ETIMEDOUT\") return \"timeout\";\n return \"internal\";\n}\nfunction mapSqliteCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"SQLITE_BUSY\" || c === \"SQLITE_LOCKED\") return \"deadlock\";\n if (c === \"SQLITE_CONSTRAINT_UNIQUE\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_PRIMARYKEY\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_FOREIGNKEY\") return \"fk-violation\";\n if (c === \"SQLITE_CONSTRAINT_NOTNULL\") return \"not-null-violation\";\n if (c === \"SQLITE_INTERRUPT\") return \"timeout\";\n if (c === \"SQLITE_ERROR\") return \"syntax\";\n if (c.startsWith(\"SQLITE_CONSTRAINT\")) return \"constraint\";\n return \"internal\";\n}\nfunction isLikelyUniqueViolationMessage(dialect, message) {\n const m = message.toLowerCase();\n if (dialect === \"mysql\") {\n return m.includes(\"duplicate entry\") || m.includes(\"duplicate key\");\n }\n if (dialect === \"sqlite\") {\n return m.includes(\"unique constraint failed\");\n }\n return false;\n}\n\n// src/errors/error-codes.ts\nvar NEXTLY_ERROR_STATUS = {\n VALIDATION_ERROR: 400,\n INVALID_INPUT: 400,\n AUTH_REQUIRED: 401,\n AUTH_INVALID_CREDENTIALS: 401,\n TOKEN_EXPIRED: 401,\n FORBIDDEN: 403,\n // The schema builder is off in this environment (production by default).\n // Separate from FORBIDDEN: the caller's permissions are not the problem.\n BUILDER_DISABLED: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n DUPLICATE: 409,\n RATE_LIMITED: 429,\n PAYLOAD_TOO_LARGE: 413,\n UNSUPPORTED_MEDIA_TYPE: 415,\n // 422: understood, well-formed, and refused on a rule the caller can act on.\n // Deliberately NOT added to CANONICAL_CODE_FOR_STATUS -- that list drives the\n // status -> code direction, where 422 stays mapped to INVALID_INPUT.\n BUSINESS_RULE_VIOLATION: 422,\n INTERNAL_ERROR: 500,\n DATABASE_ERROR: 500,\n EXTERNAL_SERVICE_ERROR: 502,\n SERVICE_UNAVAILABLE: 503,\n // Outbound-fetch safety (utils/validate-external-url): a URL refused for SSRF\n // safety, and a fetch that timed out / exceeded the size cap / failed to decode.\n EXTERNAL_URL_BLOCKED: 400,\n EXTERNAL_REQUEST_FAILED: 502,\n FILENAME_INVALID: 400,\n EXTENSION_BLOCKED: 400,\n MIME_BLOCKED: 415,\n MIME_NOT_ALLOWED: 415,\n SIZE_EXCEEDED: 413,\n // A stored object exceeded the cap a READ was given, which is a different\n // question from SIZE_EXCEEDED above: that one refuses an upload the caller\n // is sending, this one refuses to buffer an object already stored. Kept\n // apart so a caller discriminating on the code cannot match both.\n STORAGE_READ_TOO_LARGE: 413,\n // A stored object did not answer within the deadline the read was given. 504\n // rather than 500 because the failure is the BACKEND not answering, and a\n // caller can act on that difference: a gateway timeout is worth retrying, an\n // internal error is not.\n STORAGE_READ_TIMEOUT: 504,\n // The store answered, and answered badly. 502 rather than 500 because the\n // fault is UPSTREAM of this process: a caller can retry it, and an operator\n // reading the log needs to look at the bucket rather than at this service.\n // Distinct from the timeout above, which never got an answer at all.\n STORAGE_READ_UNREACHABLE: 502,\n MAGIC_BYTE_MISMATCH: 400,\n SVG_SANITIZATION_FAILED: 400,\n UNSUPPORTED_FOR_BACKEND: 415,\n // Plan B — schema bookkeeping consolidation.\n NEXTLY_LEGACY_BOOKKEEPING_DETECTED: 409,\n NEXTLY_UPGRADE_TABLE_NAME_COLLISION: 409,\n NEXTLY_UPGRADE_IN_PROGRESS: 409,\n // Plan C2 — nextly migrate phases.\n NEXTLY_MIGRATE_LOCK_BUSY: 409,\n NEXTLY_BASELINE_LOCK_NOT_HELD: 409,\n NEXTLY_RESOLVE_LOCK_NOT_HELD: 409,\n // Boot refused to serve: the migrate lock stayed held past the wait deadline,\n // so this process never established whether the schema matches the code. 503\n // rather than 409 — a load balancer should take the instance out of rotation\n // and retry it, which is exactly the recovery this refusal wants.\n NEXTLY_BOOT_MIGRATIONS_NOT_RUN: 503,\n // Still running rather than refused. 503 for the same reason: retry shortly.\n NEXTLY_BOOT_MIGRATIONS_PENDING: 503,\n NEXTLY_CORE_DESTRUCTIVE_REFUSED: 409,\n NEXTLY_MIGRATION_DRIFT: 409,\n NEXTLY_MIGRATION_APPLY_FAILED: 500,\n // Plan C3 — migrate:resolve recovery command.\n NEXTLY_MIGRATION_FILE_MISSING: 404,\n NEXTLY_MIGRATION_SNAPSHOT_MISSING: 404,\n NEXTLY_MIGRATION_RESOLVE_DRIFT: 409,\n NEXTLY_MIGRATION_RESOLVE_PRECONDITION: 409,\n // Plan D — UI schema support.\n NEXTLY_UI_SCHEMA_INVALID: 400,\n NEXTLY_SCHEMA_SLUG_COLLISION: 409,\n NEXTLY_SCHEMA_RELATION_TARGET_MISSING: 400,\n // Plugin platform (P2b) — schema extend (contributes.extend) + relations (D15).\n NEXTLY_SCHEMA_EXTEND_TARGET_UNKNOWN: 400,\n NEXTLY_SCHEMA_EXTEND_FIELD_DUPLICATE: 409,\n NEXTLY_SCHEMA_CROSS_PLUGIN_RELATION: 409,\n // Plugin platform (P2c) — framework remap (.rename()).\n NEXTLY_SCHEMA_RENAME_UNKNOWN_TARGET: 400,\n // Plugin platform — a declared admin.clientConfig that cannot be delivered\n // to the browser, refused at boot rather than serialized mangled.\n NEXTLY_PLUGIN_CLIENT_CONFIG_INVALID: 500,\n // Plugin platform — a contributed admin widget that cannot be delivered to\n // the browser. Refused at boot because it is serialized into the ONE\n // `/api/admin-meta/workspace` payload: a value `JSON.stringify` throws on\n // fails that request for every admin, not just the widget's own card.\n NEXTLY_PLUGIN_ADMIN_WIDGET_INVALID: 500,\n // Plugin platform (P0) — boot-time plugin dependency/version resolution.\n PLUGIN_RESOLUTION_ERROR: 500,\n // Plugin platform (P4) — contributes.routes collection (D25).\n NEXTLY_ROUTE_COLLISION: 409,\n NEXTLY_ROUTE_INVALID_PATH: 400,\n // An email transport whose library is an optional peer dependency the host\n // has not installed. 503 rather than 500: the request is not malformed and\n // nothing is broken, the install simply cannot carry it out yet, and the\n // remedy is one command on the server rather than a change by the caller.\n NEXTLY_EMAIL_TRANSPORT_UNAVAILABLE: 503,\n // The tooling that compiles `nextly.config.ts` is an optional peer the host\n // has not installed. 503 rather than 500 for the same reason as the mail\n // transport above: nothing is broken and the request is not malformed, the\n // install simply cannot carry it out until one command is run.\n NEXTLY_CONFIG_TOOLING_UNAVAILABLE: 503\n};\nvar CANONICAL_CODE_FOR_STATUS = [\n \"VALIDATION_ERROR\",\n \"AUTH_REQUIRED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"PAYLOAD_TOO_LARGE\",\n \"UNSUPPORTED_MEDIA_TYPE\",\n \"RATE_LIMITED\",\n \"EXTERNAL_SERVICE_ERROR\",\n \"SERVICE_UNAVAILABLE\"\n];\nvar LEGACY_UNPROCESSABLE_STATUS = 422;\nvar STATUS_TO_CODE = {\n ...Object.fromEntries(\n CANONICAL_CODE_FOR_STATUS.map((code) => [NEXTLY_ERROR_STATUS[code], code])\n ),\n [LEGACY_UNPROCESSABLE_STATUS]: \"INVALID_INPUT\"\n};\nfunction statusToErrorCode(statusCode) {\n return STATUS_TO_CODE[statusCode] ?? \"INTERNAL_ERROR\";\n}\nvar GENERIC_PUBLIC_MESSAGE = {\n VALIDATION_ERROR: \"Validation failed.\",\n INVALID_INPUT: \"The request could not be processed.\",\n AUTH_REQUIRED: \"Authentication required.\",\n FORBIDDEN: \"You don't have permission to perform this action.\",\n NOT_FOUND: \"Not found.\",\n CONFLICT: \"The resource has changed since you last loaded it. Please refresh and try again.\",\n DUPLICATE: \"Resource already exists.\",\n PAYLOAD_TOO_LARGE: \"The request is too large.\",\n UNSUPPORTED_MEDIA_TYPE: \"That file type is not supported.\",\n RATE_LIMITED: \"Too many requests. Please try again later.\",\n EXTERNAL_SERVICE_ERROR: \"An upstream service failed.\",\n SERVICE_UNAVAILABLE: \"The service is temporarily unavailable.\",\n INTERNAL_ERROR: \"An unexpected error occurred.\"\n};\nfunction genericPublicMessage(code) {\n return GENERIC_PUBLIC_MESSAGE[code] ?? GENERIC_PUBLIC_MESSAGE.INTERNAL_ERROR;\n}\n\n// src/errors/nextly-error.ts\nvar DB_ERROR_MAPPING = {\n \"unique-violation\": {\n code: \"DUPLICATE\",\n statusCode: 409,\n publicMessage: \"Resource already exists.\"\n },\n \"fk-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"Referenced record does not exist.\"\n },\n \"not-null-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"A required field is missing.\"\n },\n constraint: {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"The provided data violates a constraint.\"\n },\n deadlock: {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n \"serialization-failure\": {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n timeout: {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"The operation timed out. Please try again.\"\n },\n \"connection-lost\": {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"A temporary database error occurred. Please try again.\"\n },\n syntax: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n },\n internal: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n }\n};\nvar NEXTLY_ERROR_BRAND = Symbol.for(\"nextly/NextlyError\");\nfunction hasBrand(value) {\n if (value === null || typeof value !== \"object\" && typeof value !== \"function\") {\n return false;\n }\n return value[NEXTLY_ERROR_BRAND] === true;\n}\nvar NextlyError = class _NextlyError extends Error {\n code;\n statusCode;\n publicMessage;\n publicData;\n messageKey;\n logMessage;\n logContext;\n cause;\n timestamp;\n constructor(opts) {\n super(opts.publicMessage);\n this.name = \"NextlyError\";\n this.code = opts.code;\n this.publicMessage = opts.publicMessage;\n this.publicData = opts.publicData;\n this.messageKey = opts.messageKey;\n this.logMessage = opts.logMessage;\n this.logContext = opts.logContext;\n this.cause = opts.cause;\n this.timestamp = /* @__PURE__ */ new Date();\n this.statusCode = _NextlyError.resolveStatusCode(opts);\n Error.captureStackTrace?.(this, _NextlyError);\n }\n static {\n _NextlyError.prototype[NEXTLY_ERROR_BRAND] = true;\n }\n static resolveStatusCode(opts) {\n if (typeof opts.statusCode === \"number\") return opts.statusCode;\n if (opts.code in NEXTLY_ERROR_STATUS) {\n return NEXTLY_ERROR_STATUS[opts.code];\n }\n return 500;\n }\n /** HTTP-safe JSON. Strips logMessage / logContext / cause / stack. */\n toResponseJSON(requestId) {\n const json = {\n code: String(this.code),\n message: this.publicMessage,\n requestId\n };\n if (this.messageKey) json.messageKey = this.messageKey;\n if (this.publicData !== void 0) json.data = this.publicData;\n return json;\n }\n /** Operator-facing JSON for log lines. Includes everything. */\n toLogJSON(requestId) {\n return {\n code: this.code,\n statusCode: this.statusCode,\n publicMessage: this.publicMessage,\n messageKey: this.messageKey,\n logMessage: this.logMessage,\n logContext: this.logContext,\n cause: this.cause ? {\n name: this.cause.name,\n message: this.cause.message,\n stack: this.cause.stack\n } : void 0,\n timestamp: this.timestamp.toISOString(),\n requestId\n };\n }\n // ────────────────────────────────────────────────────────────────────\n // Static factories — the recommended throw site for every common case.\n // Public messages here follow the §13.8 rubric: complete sentence,\n // generic, no identifiers, no value echoing, no policy hints.\n // ────────────────────────────────────────────────────────────────────\n static invalidCredentials(opts) {\n return new _NextlyError({\n code: \"AUTH_INVALID_CREDENTIALS\",\n publicMessage: \"Invalid email or password.\",\n logMessage: \"Login failed\",\n logContext: opts?.logContext\n });\n }\n static authRequired(opts) {\n return new _NextlyError({\n code: \"AUTH_REQUIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n /**\n * Distinct from `authRequired`: the caller was authenticated but the\n * session token expired. Clients key on the TOKEN_EXPIRED *code* to\n * silently refresh and retry rather than redirecting to login; the public\n * message stays the generic spec §13.6 string (\"Authentication required.\")\n * so the wire never reveals the session state — same as `authRequired`.\n */\n static tokenExpired(opts) {\n return new _NextlyError({\n code: \"TOKEN_EXPIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n static notFound(opts) {\n return new _NextlyError({\n code: \"NOT_FOUND\",\n publicMessage: opts?.message ?? \"Not found.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n static forbidden(opts) {\n return new _NextlyError({\n code: \"FORBIDDEN\",\n publicMessage: \"You don't have permission to perform this action.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * A call the caller got wrong, where naming the mistake IS the value of the\n * error.\n *\n * The only factory that takes its public message from the caller. The\n * generic messages elsewhere exist so an HTTP response cannot leak internal\n * detail; this one is for arguments and configuration a developer controls\n * and must be told about — a missing option, an unusable combination — where\n * `internal()` would reduce the one useful sentence to \"An unexpected error\n * occurred.\" Do not pass user-supplied data through it.\n */\n static invalidInput(opts) {\n return new _NextlyError({\n code: \"INVALID_INPUT\",\n publicMessage: opts.message,\n logContext: opts.logContext\n });\n }\n static validation(opts) {\n return new _NextlyError({\n code: \"VALIDATION_ERROR\",\n publicMessage: \"Validation failed.\",\n publicData: { errors: opts.errors },\n cause: opts.cause,\n logContext: opts.logContext\n });\n }\n static conflict(opts) {\n return new _NextlyError({\n code: \"CONFLICT\",\n publicMessage: opts?.message ?? \"The resource has changed since you last loaded it. Please refresh and try again.\",\n cause: opts?.cause,\n logContext: { reason: opts?.reason, ...opts?.logContext }\n });\n }\n static duplicate(opts) {\n return new _NextlyError({\n code: \"DUPLICATE\",\n publicMessage: \"Resource already exists.\",\n logContext: opts?.logContext\n });\n }\n static rateLimited(opts) {\n return new _NextlyError({\n code: \"RATE_LIMITED\",\n publicMessage: \"Too many requests. Please try again later.\",\n publicData: opts?.retryAfterSeconds !== void 0 ? { retryAfterSeconds: opts.retryAfterSeconds } : void 0,\n logContext: opts?.logContext\n });\n }\n static internal(opts) {\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n publicMessage: \"An unexpected error occurred.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n // Accepts an optional `logMessage` override so callers (e.g. the health\n // route) can record a specific operator narrative (\"Health check failed\")\n // while the public message stays canonical per spec §13.8.5.\n static serviceUnavailable(opts) {\n return new _NextlyError({\n code: \"SERVICE_UNAVAILABLE\",\n publicMessage: opts?.publicMessage ?? \"Service unavailable. Please try again later.\",\n logMessage: opts?.logMessage,\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * Convert a DbError (or arbitrary unknown thrown by the DB layer) to a\n * NextlyError with a generic public message and rich logContext. Used by\n * `withDbErrors` for auto-conversion (Pattern A) and by services that\n * catch DB errors at boundaries (Pattern B). Spec §8.2 mapping table.\n *\n * Never leaks DB driver text, constraint names, or table names into\n * `publicMessage`. All DB context goes into `logContext`. The original\n * DbError is preserved as `cause`.\n */\n static fromDatabaseError(error) {\n if (isDbError(error)) {\n const mapping = DB_ERROR_MAPPING[error.kind];\n const logContext = {\n dbKind: error.kind,\n dialect: error.dialect\n };\n if (error.code !== void 0) logContext.dbCode = error.code;\n if (error.meta !== void 0) logContext.meta = error.meta;\n return new _NextlyError({\n code: mapping.code,\n statusCode: mapping.statusCode,\n publicMessage: mapping.publicMessage,\n logMessage: \"Database error\",\n logContext,\n cause: error\n });\n }\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\",\n logMessage: \"Non-DbError passed to fromDatabaseError\",\n cause: error instanceof Error ? error : void 0,\n logContext: error instanceof Error ? void 0 : { value: String(error) }\n });\n }\n // ────────────────────────────────────────────────────────────────────\n // Type guards. Structural rather than `instanceof` so they survive\n // package-boundary mismatches (one consumer's NextlyError is a\n // different module instance from another's).\n // ────────────────────────────────────────────────────────────────────\n static is(err) {\n return hasBrand(err);\n }\n static isCode(err, code) {\n return hasBrand(err) && err.code === code;\n }\n static isNotFound(err) {\n return _NextlyError.isCode(err, \"NOT_FOUND\");\n }\n static isValidation(err) {\n return _NextlyError.isCode(err, \"VALIDATION_ERROR\");\n }\n static isAuthRequired(err) {\n return _NextlyError.isCode(err, \"AUTH_REQUIRED\");\n }\n static isForbidden(err) {\n return _NextlyError.isCode(err, \"FORBIDDEN\");\n }\n static isConflict(err) {\n return _NextlyError.isCode(err, \"CONFLICT\");\n }\n static isRateLimited(err) {\n return _NextlyError.isCode(err, \"RATE_LIMITED\");\n }\n};\n\nexport {\n DbError,\n isDbError,\n toDbError,\n safeCode,\n NEXTLY_ERROR_STATUS,\n statusToErrorCode,\n genericPublicMessage,\n NextlyError\n};\n","import {\n NextlyError\n} from \"./chunk-HJ5IQPOY.mjs\";\n\n// src/storage/read-errors.ts\nvar StorageReadTooLargeError = class extends NextlyError {\n constructor(path, maxBytes, size) {\n super({\n code: \"STORAGE_READ_TOO_LARGE\",\n publicMessage: \"The stored file is larger than the limit for this read.\",\n logContext: {\n path,\n maxBytes,\n ...size === void 0 ? {} : { size }\n }\n });\n this.path = path;\n this.maxBytes = maxBytes;\n this.size = size;\n }\n};\nfunction isStorageReadTooLarge(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TOO_LARGE\";\n}\nvar StorageReadTimeoutError = class extends NextlyError {\n constructor(path, timeoutMs) {\n super({\n code: \"STORAGE_READ_TIMEOUT\",\n publicMessage: \"The stored file could not be read in time.\",\n logContext: { path, timeoutMs }\n });\n this.path = path;\n this.timeoutMs = timeoutMs;\n }\n};\nfunction isStorageReadTimeout(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TIMEOUT\";\n}\n\nexport {\n StorageReadTooLargeError,\n isStorageReadTooLarge,\n StorageReadTimeoutError,\n isStorageReadTimeout\n};\n","import {\n StorageReadTimeoutError,\n StorageReadTooLargeError\n} from \"./chunk-W77AQUUQ.mjs\";\nimport {\n NextlyError\n} from \"./chunk-HJ5IQPOY.mjs\";\n\n// src/utils/validate-external-url.ts\nvar IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$/;\nvar PRIVATE_IPV4_CIDRS = [\n \"0.0.0.0/8\",\n // RFC1122 \"this network\" / wildcard\n \"10.0.0.0/8\",\n // RFC1918\n \"100.64.0.0/10\",\n // RFC6598 CGNAT\n \"127.0.0.0/8\",\n // RFC1122 loopback\n \"169.254.0.0/16\",\n // RFC3927 link-local + AWS/GCP metadata\n \"172.16.0.0/12\",\n // RFC1918\n \"192.0.0.0/24\",\n // RFC6890 IETF protocol assignments\n \"192.0.2.0/24\",\n // RFC5737 documentation\n \"192.168.0.0/16\",\n // RFC1918\n \"198.18.0.0/15\",\n // RFC2544 benchmarking\n \"198.51.100.0/24\",\n // RFC5737 documentation\n \"203.0.113.0/24\",\n // RFC5737 documentation\n \"224.0.0.0/4\",\n // RFC5771 multicast\n \"240.0.0.0/4\"\n // RFC1112 reserved\n];\nvar CLOUD_METADATA_HOSTS = /* @__PURE__ */ new Set([\n \"metadata.google.internal\",\n \"metadata.googleapis.com\",\n \"metadata\"\n // GCP short form\n]);\nvar ExternalUrlError = class extends NextlyError {\n constructor(message, url) {\n super({\n code: \"EXTERNAL_URL_BLOCKED\",\n publicMessage: message,\n logContext: { url }\n });\n this.url = url;\n this.name = \"ExternalUrlError\";\n }\n};\nasync function validateExternalUrl(rawUrl, options = {}) {\n let parsed;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new ExternalUrlError(\"Invalid URL\", rawUrl);\n }\n const hostname = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n const isLocalhostHostname = hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n const baseProtocols = options.allowedProtocols ?? [\"https:\"];\n const protocols = options.allowLocalhost && isLocalhostHostname ? [...baseProtocols, \"http:\"] : baseProtocols;\n if (!protocols.includes(parsed.protocol)) {\n throw new ExternalUrlError(\n `Protocol ${parsed.protocol} not allowed (allowed: ${protocols.join(\", \")})`,\n rawUrl\n );\n }\n if (CLOUD_METADATA_HOSTS.has(hostname)) {\n throw new ExternalUrlError(\n `Cloud-metadata hostname rejected: ${parsed.hostname}`,\n rawUrl\n );\n }\n if (IPV4_RE.test(hostname)) {\n if (!isPublicIpv4(\n hostname,\n options.allowLocalhost === true && hostname === \"127.0.0.1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 4 };\n }\n if (hostname.includes(\":\")) {\n if (!isPublicIpv6(\n hostname,\n options.allowLocalhost === true && hostname === \"::1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 6 };\n }\n const { lookup } = await import(\"dns/promises\");\n let addresses;\n try {\n addresses = await lookup(hostname, { all: true });\n } catch (err) {\n throw new ExternalUrlError(\n `DNS lookup failed: ${err.message}`,\n rawUrl\n );\n }\n if (addresses.length === 0) {\n throw new ExternalUrlError(\"No IPs returned for host\", rawUrl);\n }\n for (const { address, family } of addresses) {\n const allow = options.allowLocalhost === true && isLocalhostHostname && family === 4;\n const ok = family === 4 ? isPublicIpv4(address, allow) : isPublicIpv6(address, allow);\n if (!ok) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${address}`,\n rawUrl\n );\n }\n }\n const first = addresses[0];\n return {\n url: parsed,\n pinnedIp: first.address,\n family: first.family === 6 ? 6 : 4\n };\n}\nvar DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;\nvar DEFAULT_TIMEOUT_MS = 3e4;\nvar SafeFetchError = class extends NextlyError {\n constructor(message, url, reason, status) {\n super({\n code: \"EXTERNAL_REQUEST_FAILED\",\n publicMessage: message,\n logContext: { url, reason }\n });\n this.url = url;\n this.reason = reason;\n this.status = status;\n this.name = \"SafeFetchError\";\n }\n};\nfunction createPinnedLookup(ip, family) {\n return (_hostname, options, callback) => {\n if (options && options.all) {\n callback(null, [{ address: ip, family }]);\n } else {\n callback(null, ip, family);\n }\n };\n}\nasync function safeFetch(rawUrl, options = {}) {\n const {\n allowLocalhost,\n allowedProtocols,\n method,\n headers,\n body,\n signal,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n timeoutMs = DEFAULT_TIMEOUT_MS\n } = options;\n const controller = new AbortController();\n const forwardAbort = () => controller.abort(signal?.reason);\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", forwardAbort, { once: true });\n }\n const timer = setTimeout(\n () => controller.abort(\n new SafeFetchError(`Request exceeded ${timeoutMs}ms`, rawUrl, \"timeout\")\n ),\n timeoutMs\n );\n try {\n const validated = await abortable(\n validateExternalUrl(rawUrl, { allowLocalhost, allowedProtocols }),\n controller.signal\n );\n return await pinnedFetch(validated, {\n method,\n headers,\n body,\n signal: controller.signal,\n maxResponseBytes\n });\n } finally {\n clearTimeout(timer);\n if (signal) signal.removeEventListener(\"abort\", forwardAbort);\n }\n}\nfunction abortError(signal) {\n const reason = signal.reason;\n if (reason instanceof Error) return reason;\n return new DOMException(\"The operation was aborted\", \"AbortError\");\n}\nfunction abortable(promise, signal) {\n if (signal.aborted) return Promise.reject(abortError(signal));\n let onAbort;\n const aborted = new Promise((_resolve, reject) => {\n onAbort = () => reject(abortError(signal));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n return Promise.race([promise, aborted]).finally(\n () => signal.removeEventListener(\"abort\", onAbort)\n );\n}\nasync function pinnedFetch(validated, init) {\n const { url, pinnedIp, family } = validated;\n const httpMod = url.protocol === \"https:\" ? await import(\"https\") : await import(\"http\");\n const zlib = await import(\"zlib\");\n const lookup = createPinnedLookup(pinnedIp, family);\n const outHeaders = toOutgoingHeaders(init.headers);\n if (init.body != null) {\n delete outHeaders[\"transfer-encoding\"];\n outHeaders[\"content-length\"] = String(\n typeof init.body === \"string\" ? Buffer.byteLength(init.body) : init.body.byteLength\n );\n }\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn) => {\n if (settled) return;\n settled = true;\n fn();\n };\n const failure = (err) => init.signal.aborted ? abortError(init.signal) : err;\n const req = httpMod.request(\n url,\n {\n method: init.method ?? \"GET\",\n headers: outHeaders,\n lookup,\n // Fresh socket per request so a pooled connection can't reuse a\n // differently-resolved address, and so the pinned lookup always runs.\n agent: false,\n // The combined deadline/caller signal aborts the request in flight.\n signal: init.signal\n },\n (res) => {\n const chunks = [];\n let received = 0;\n res.on(\"data\", (chunk) => {\n received += chunk.length;\n if (received > init.maxResponseBytes) {\n res.destroy();\n req.destroy();\n settle(\n () => reject(\n new SafeFetchError(\n `Response body exceeded ${init.maxResponseBytes} bytes`,\n url.href,\n \"response-too-large\",\n res.statusCode\n )\n )\n );\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n const decoded = decodeBody(\n Buffer.concat(chunks),\n res.headers[\"content-encoding\"],\n zlib,\n init.maxResponseBytes,\n url.href,\n res.statusCode\n );\n if (decoded instanceof SafeFetchError) {\n settle(() => reject(decoded));\n return;\n }\n settle(\n () => resolve(toWhatwgResponse(res, decoded.body, decoded.decoded))\n );\n });\n res.on(\"error\", (err) => settle(() => reject(failure(err))));\n }\n );\n req.on(\"error\", (err) => settle(() => reject(failure(err))));\n if (!req.destroyed) {\n if (init.body != null) req.write(init.body);\n req.end();\n }\n });\n}\nfunction toOutgoingHeaders(headers) {\n const out = {};\n if (!headers) return out;\n const add = (rawKey, value) => {\n const key = rawKey.toLowerCase();\n if (key === \"host\") return;\n const existing = out[key];\n out[key] = existing === void 0 ? value : [].concat(existing, value);\n };\n if (headers instanceof Headers) {\n headers.forEach((value, key) => add(key, value));\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) add(key, value);\n } else {\n for (const [key, value] of Object.entries(headers)) add(key, value);\n }\n return out;\n}\nfunction toWhatwgResponse(res, body, stripContentHeaders) {\n const headers = new Headers();\n for (const [key, value] of Object.entries(res.headers)) {\n if (value == null) continue;\n const lower = key.toLowerCase();\n if (stripContentHeaders && (lower === \"content-encoding\" || lower === \"content-length\")) {\n continue;\n }\n const values = Array.isArray(value) ? value : [value];\n for (const v of values) {\n try {\n headers.append(key, v);\n } catch {\n }\n }\n }\n const raw = res.statusCode ?? 502;\n const status = raw >= 200 && raw <= 599 ? raw : 502;\n const nullBody = status === 204 || status === 205 || status === 304;\n const bytes = new Uint8Array(body.byteLength);\n bytes.set(body);\n return new Response(nullBody ? null : bytes, {\n status,\n statusText: res.statusMessage ?? \"\",\n headers\n });\n}\nfunction decodeBody(buf, encoding, zlib, cap, url, status) {\n if (!encoding) return { body: buf, decoded: false };\n if (buf.length === 0) return { body: buf, decoded: true };\n const layers = encoding.split(\",\").map((e) => e.trim().toLowerCase()).filter(Boolean);\n const opts = { maxOutputLength: cap };\n let current = buf;\n let decoded = false;\n try {\n for (let i = layers.length - 1; i >= 0; i--) {\n const enc = layers[i];\n if (enc === \"identity\") continue;\n if (enc === \"gzip\" || enc === \"x-gzip\") {\n current = zlib.gunzipSync(current, opts);\n } else if (enc === \"br\") {\n current = zlib.brotliDecompressSync(current, opts);\n } else if (enc === \"deflate\") {\n try {\n current = zlib.inflateSync(current, opts);\n } catch (inflateErr) {\n if (errorCode(inflateErr) === \"ERR_BUFFER_TOO_LARGE\")\n throw inflateErr;\n current = zlib.inflateRawSync(current, opts);\n }\n } else {\n if (decoded) {\n return new SafeFetchError(\n `Cannot fully decode content-encoding \"${encoding}\"`,\n url,\n \"decode-failed\",\n status\n );\n }\n return { body: buf, decoded: false };\n }\n decoded = true;\n }\n return { body: current, decoded };\n } catch (err) {\n const tooLarge = errorCode(err) === \"ERR_BUFFER_TOO_LARGE\";\n return new SafeFetchError(\n tooLarge ? `Decoded ${encoding} response body exceeded the size cap` : `Failed to decode ${encoding} response body`,\n url,\n tooLarge ? \"response-too-large\" : \"decode-failed\",\n status\n );\n }\n}\nfunction errorCode(err) {\n if (err != null && typeof err === \"object\" && \"code\" in err) {\n const code = err.code;\n return typeof code === \"string\" ? code : void 0;\n }\n return void 0;\n}\nfunction ipv4ToInt(ip) {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n let acc = 0;\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 0 || n > 255) return null;\n acc = acc << 8 | n;\n }\n return acc >>> 0;\n}\nfunction isIpv4InCidr(intIp, cidr) {\n const [addr, prefixRaw] = cidr.split(\"/\");\n const intCidr = ipv4ToInt(addr);\n if (intCidr === null) return false;\n const prefix = Number(prefixRaw);\n if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;\n const mask = prefix === 0 ? 0 : -1 >>> 32 - prefix << 32 - prefix;\n return (intIp & mask) >>> 0 === (intCidr & mask) >>> 0;\n}\nfunction isPublicIpv4(addr, allowLoopback) {\n const intIp = ipv4ToInt(addr);\n if (intIp === null) return false;\n if (allowLoopback && addr === \"127.0.0.1\") return true;\n for (const cidr of PRIVATE_IPV4_CIDRS) {\n if (isIpv4InCidr(intIp, cidr)) return false;\n }\n return true;\n}\nfunction mappedIpv4(lower) {\n const dotted = lower.match(/^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n if (dotted) return dotted[1];\n const hex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);\n if (hex) {\n const hi = parseInt(hex[1], 16);\n const lo = parseInt(hex[2], 16);\n return [hi >> 8, hi & 255, lo >> 8, lo & 255].join(\".\");\n }\n return null;\n}\nfunction isPublicIpv6(addr, allowLoopback) {\n const lower = addr.toLowerCase();\n if (allowLoopback && lower === \"::1\") return true;\n const mapped = mappedIpv4(lower);\n if (mapped) {\n return isPublicIpv4(mapped, allowLoopback);\n }\n if (lower === \"::\" || lower === \"::1\") return false;\n const firstHextet = lower.split(\":\")[0] || \"\";\n if (firstHextet.startsWith(\"fc\") || firstHextet.startsWith(\"fd\"))\n return false;\n if (firstHextet.startsWith(\"fe8\") || firstHextet.startsWith(\"fe9\"))\n return false;\n if (firstHextet.startsWith(\"fea\") || firstHextet.startsWith(\"feb\"))\n return false;\n if (firstHextet.startsWith(\"ff\")) return false;\n return true;\n}\n\n// src/storage/fetch-stored-bytes.ts\nasync function fetchStoredBytes(url, context, label, options, signal) {\n let response;\n try {\n response = await safeFetch(url, {\n ...options?.maxBytes === void 0 ? {} : { maxResponseBytes: options.maxBytes },\n /*\n * A caller's signal REPLACES the deadline rather than joining it.\n *\n * These adapters look the object's address up before fetching it, and\n * that lookup can stall too. Starting a fresh timer here would give the\n * fetch its own full budget on top of however long the lookup took, so a\n * read could outlive the deadline the caller was told applied — by\n * roughly double. One signal begun before the lookup governs both phases.\n */\n ...signal !== void 0 ? { signal } : options?.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }\n });\n } catch (error) {\n const verdict = classifyFetchFailure(error);\n if (verdict === \"absent\") return null;\n if (verdict === \"oversized\") {\n throw new StorageReadTooLargeError(\n context,\n resolveReadBounds(options).maxBytes\n );\n }\n throw error;\n }\n if (response.status === 404) return null;\n if (!response.ok) {\n throw NextlyError.internal({\n logContext: {\n service: label,\n path: context,\n status: response.status,\n statusText: response.statusText\n }\n });\n }\n return Buffer.from(await response.arrayBuffer());\n}\nfunction classifyFetchFailure(error) {\n if (!(error instanceof SafeFetchError)) return \"unknown\";\n if (error.status === 404) return \"absent\";\n const successful = error.status !== void 0 && error.status >= 200 && error.status < 300;\n return error.reason === \"response-too-large\" && successful ? \"oversized\" : \"unknown\";\n}\nvar DEFAULT_READ_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;\nvar DEFAULT_READ_MAX_BYTES = DEFAULT_MAX_RESPONSE_BYTES;\nfunction resolveReadBounds(options) {\n return {\n maxBytes: options?.maxBytes ?? DEFAULT_READ_MAX_BYTES,\n timeoutMs: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS\n };\n}\nfunction deadlineSignal(timeoutMs, context) {\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new StorageReadTimeoutError(context, timeoutMs));\n }, timeoutMs);\n timer.unref?.();\n return {\n signal: controller.signal,\n /*\n * Called once the read has settled, whichever way it went. `unref` only\n * stops a pending timer holding the process open; it still sits on the\n * timer heap retaining the controller and the path, so a server doing a\n * thousand reads a second keeps thirty seconds of them alive and then runs\n * a thousand aborts that answer nobody.\n */\n cancel: () => {\n clearTimeout(timer);\n }\n };\n}\nasync function withDeadline(work, signal) {\n if (signal === void 0) return await work;\n return await Promise.race([\n work,\n new Promise((_, reject) => {\n if (signal.aborted) {\n reject(signal.reason);\n return;\n }\n signal.addEventListener(\"abort\", () => reject(signal.reason), {\n once: true\n });\n })\n ]);\n}\n\nexport {\n ExternalUrlError,\n validateExternalUrl,\n SafeFetchError,\n safeFetch,\n fetchStoredBytes,\n classifyFetchFailure,\n DEFAULT_READ_TIMEOUT_MS,\n DEFAULT_READ_MAX_BYTES,\n resolveReadBounds,\n deadlineSignal,\n withDeadline\n};\n","/**\n * S3 Storage Adapter\n *\n * Stores files on AWS S3 or S3-compatible services (Cloudflare R2, MinIO, DigitalOcean Spaces).\n * Uses AWS SDK v3 with automatic multipart upload for large files (>5MB).\n *\n * Features:\n * - Automatic multipart upload via @aws-sdk/lib-storage\n * - Pre-signed URL generation for client-side uploads\n * - Signed URL generation for private file access\n * - Cloudflare R2 support (S3-compatible API)\n * - Custom endpoint support (MinIO, DigitalOcean Spaces)\n * - CDN URL override support\n * - File metadata retrieval\n *\n * @example AWS S3\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: { media: true }\n * });\n * ```\n *\n * @example Cloudflare R2\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media',\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n * publicUrl: 'https://pub-abc.r2.dev',\n * collections: { media: true }\n * });\n * ```\n *\n * @example MinIO (self-hosted)\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * });\n * ```\n */\n\nimport {\n S3Client,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n type PutObjectCommandInput,\n type GetObjectCommandOutput,\n} from \"@aws-sdk/client-s3\";\nimport { Upload } from \"@aws-sdk/lib-storage\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type {\n StorageReadOptions,\n IStorageAdapter,\n UploadOptions,\n UploadResult,\n StorageAdapterInfo,\n ClientUploadData,\n FileMetadata,\n BulkDeleteResult,\n} from \"nextly/storage\";\nimport { resolveReadBounds } from \"nextly/storage/fetch-stored-bytes\";\nimport { StorageReadTooLargeError } from \"nextly/storage/read-errors\";\n\nimport type { S3StorageConfig, ResolvedS3Config } from \"./types\";\n\n// ============================================================\n// S3 Storage Adapter\n// ============================================================\n\n/**\n * S3 Storage Adapter\n *\n * Implements the IStorageAdapter interface for AWS S3 and S3-compatible services.\n * Provides full support for uploads, downloads, signed URLs, and client-side uploads.\n */\n/**\n * The SDK request options carrying the caller's deadline, or none.\n *\n * A free function rather than a branch inside `read`, because it is the only\n * part of that method with nothing to do with telling an absent key apart from\n * an unreachable bucket — which is the distinction a reader has to hold in\n * their head there.\n *\n * `AbortSignal.timeout` rather than a controller and a `setTimeout`: it needs no\n * clearing, so it cannot leak a timer when the read resolves first. The signal\n * covers the BODY as well as the request, because the SDK threads it through\n * the response stream — and a backend that answers headers promptly and then\n * stalls mid-transfer is exactly the case a request-only deadline misses.\n */\nfunction abortAfter(options?: StorageReadOptions): {\n abortSignal: AbortSignal;\n} {\n /*\n * ALWAYS a signal, defaulted through the shared resolver.\n * `StorageReadOptions` promises the deadline whether or not a caller names\n * one, and the URL-backed adapters keep that promise by inheriting\n * `safeFetch`'s. Returning nothing here left the no-options read — which is\n * what the media pipeline does — able to hang on a stalled bucket forever.\n */\n return {\n abortSignal: AbortSignal.timeout(resolveReadBounds(options).timeoutMs),\n };\n}\n\nexport class S3StorageAdapter implements IStorageAdapter {\n private client: S3Client;\n private resolvedConfig: ResolvedS3Config;\n private isR2: boolean;\n\n /**\n * Create a new S3 storage adapter.\n *\n * @param config - S3 storage configuration\n * @throws Error if bucket or region is not provided\n */\n constructor(private config: S3StorageConfig) {\n // Validate required config\n if (!config.bucket) {\n throw new Error(\"@nextly/storage-s3: bucket is required\");\n }\n if (!config.region) {\n throw new Error(\"@nextly/storage-s3: region is required\");\n }\n\n // Detect if this is Cloudflare R2\n this.isR2 = config.endpoint?.includes(\"r2.cloudflarestorage.com\") ?? false;\n\n // Resolve config with defaults\n this.resolvedConfig = {\n bucket: config.bucket,\n region: config.region,\n endpoint: config.endpoint,\n forcePathStyle: config.forcePathStyle ?? false,\n acl: config.acl ?? \"private\",\n publicUrl: config.publicUrl,\n cacheControl: config.cacheControl ?? \"public, max-age=31536000\",\n contentDisposition: config.contentDisposition,\n signedUrlExpiresIn: config.signedUrlExpiresIn ?? 3600,\n };\n\n // Build credentials\n const credentials = this.buildCredentials();\n\n // Initialize S3 client\n this.client = new S3Client({\n region: config.region,\n endpoint: config.endpoint,\n credentials,\n forcePathStyle: this.resolvedConfig.forcePathStyle,\n ...config.config,\n });\n }\n\n /**\n * Build AWS credentials from config.\n * Supports explicit credentials or falls back to SDK default chain.\n */\n private buildCredentials():\n | { accessKeyId: string; secretAccessKey: string }\n | undefined {\n if (this.config.accessKeyId && this.config.secretAccessKey) {\n return {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey,\n };\n }\n\n // Check if credentials are in the config object\n if (this.config.config?.credentials) {\n return undefined; // Let SDK use the provided config.credentials\n }\n\n // Fall back to SDK default credential chain (env vars, IAM roles, etc.)\n return undefined;\n }\n\n // ============================================================\n // Core IStorageAdapter Methods\n // ============================================================\n\n /**\n * Upload file to S3.\n *\n * Uses AWS SDK v3's Upload class from @aws-sdk/lib-storage which:\n * - Automatically handles multipart upload for large files (>5MB)\n * - Provides progress tracking capability\n * - Includes retry logic\n * - Optimizes upload performance\n *\n * @param buffer - File content as Buffer\n * @param options - Upload options (filename, mimeType, folder, collection)\n * @returns Upload result with URL and storage path\n */\n async upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult> {\n const key = this.generateKey(options.filename, options.folder);\n\n // Build upload parameters\n const uploadParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n Body: buffer,\n ContentType: options.contentType || options.mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2 (R2 ignores ACL settings)\n if (!this.isR2) {\n uploadParams.ACL = this.resolvedConfig.acl;\n }\n\n // Add Content-Disposition: per-file override takes priority, then global config\n const disposition =\n options.contentDisposition ?? this.resolvedConfig.contentDisposition;\n if (disposition) {\n const filename = this.sanitizeFilename(options.filename);\n uploadParams.ContentDisposition =\n disposition === \"attachment\"\n ? `attachment; filename=\"${filename}\"`\n : \"inline\";\n }\n\n // Store original filename in metadata for later retrieval\n uploadParams.Metadata = {\n \"original-filename\": options.filename,\n };\n\n // Use Upload for automatic multipart handling\n const upload = new Upload({\n client: this.client,\n params: uploadParams,\n });\n\n await upload.done();\n\n return {\n url: this.getPublicUrl(key),\n path: key,\n };\n }\n\n /**\n * Delete file from S3.\n *\n * @param filePath - Storage path/key to delete\n */\n async delete(filePath: string): Promise<void> {\n const command = new DeleteObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n }\n\n /**\n * Bulk delete files from S3 using a single API call per 1000 keys.\n *\n * Uses AWS SDK v3's DeleteObjectsCommand which supports up to 1000 keys per\n * request. Automatically batches larger arrays and collects per-key results.\n *\n * @param filePaths - Storage paths/keys to delete\n * @returns Object with arrays of successful and failed deletions\n */\n async bulkDelete(filePaths: string[]): Promise<BulkDeleteResult> {\n const successful: string[] = [];\n const failed: Array<{ filePath: string; error: string }> = [];\n const maxBatchSize = 1000; // AWS limit\n\n for (let i = 0; i < filePaths.length; i += maxBatchSize) {\n const batch = filePaths.slice(i, i + maxBatchSize);\n const command = new DeleteObjectsCommand({\n Bucket: this.resolvedConfig.bucket,\n Delete: {\n Objects: batch.map(key => ({ Key: key })),\n Quiet: false,\n },\n });\n\n const response = await this.client.send(command);\n\n if (response.Errors && response.Errors.length > 0) {\n for (const err of response.Errors) {\n if (err.Key) {\n failed.push({\n filePath: err.Key,\n error: err.Message ?? \"Unknown S3 delete error\",\n });\n }\n }\n }\n\n if (response.Deleted) {\n for (const del of response.Deleted) {\n if (del.Key) {\n successful.push(del.Key);\n }\n }\n }\n }\n\n return { successful, failed };\n }\n\n /**\n * Check if file exists in S3.\n *\n * Uses HeadObject command which is more efficient than GetObject\n * for existence checks (doesn't download the file).\n *\n * @param filePath - Storage path/key to check\n * @returns true if file exists, false otherwise\n */\n async exists(filePath: string): Promise<boolean> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n return true;\n } catch (error: unknown) {\n // HeadObject returns 404 if object doesn't exist\n if (this.isNotFoundError(error)) {\n return false;\n }\n // Re-throw other errors (permissions, network, etc.)\n throw error;\n }\n }\n\n /**\n * Get public URL for S3 file.\n *\n * Priority order:\n * 1. Custom publicUrl (CDN or custom domain) if configured\n * 2. Standard S3 URL based on region and bucket\n *\n * For R2: Requires publicUrl configuration (R2 has no default public URLs).\n *\n * @param filePath - Storage path/key\n * @returns Public URL to access the file\n * @throws Error if R2 is used without publicUrl configuration\n */\n getPublicUrl(filePath: string): string {\n // Use custom CDN/public URL if configured\n if (this.resolvedConfig.publicUrl) {\n const baseUrl = this.resolvedConfig.publicUrl.replace(/\\/$/, \"\");\n return `${baseUrl}/${filePath}`;\n }\n\n // R2 requires custom domain or public bucket URL\n if (this.isR2) {\n throw new Error(\n \"@nextly/storage-s3: Cloudflare R2 requires publicUrl configuration.\\n\\n\" +\n \"R2 does not have default public URLs like AWS S3. Configure one of:\\n\" +\n \"1. Public bucket URL: publicUrl: 'https://pub-xxx.r2.dev'\\n\" +\n \"2. Custom domain: publicUrl: 'https://cdn.example.com'\\n\\n\" +\n \"Set up public access in the Cloudflare R2 dashboard.\"\n );\n }\n\n // Standard S3 URL format\n // https://bucket.s3.region.amazonaws.com/key\n return `https://${this.resolvedConfig.bucket}.s3.${this.resolvedConfig.region}.amazonaws.com/${filePath}`;\n }\n\n /**\n * Get storage type identifier.\n *\n * Returns \"s3\" for all S3-compatible services (AWS S3, R2, MinIO, etc.)\n * as they all use the S3 API.\n */\n getType(): \"s3\" {\n return \"s3\";\n }\n\n // ============================================================\n // Optional IStorageAdapter Methods\n // ============================================================\n\n /**\n * Get adapter info including capabilities.\n *\n * @returns Adapter info with type, name, and capability flags\n */\n getInfo(): StorageAdapterInfo {\n return {\n type: \"s3\",\n name: \"S3StorageAdapter\",\n supportsSignedUrls: true,\n supportsClientUploads: true,\n };\n }\n\n /**\n * Get file metadata from S3.\n *\n * Retrieves file information including size, content type, and timestamps\n * using the HeadObject command.\n *\n * @param filePath - Storage path/key\n * @returns File metadata or null if file not found\n */\n async getMetadata(filePath: string): Promise<FileMetadata | null> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n const response = await this.client.send(command);\n const filename = filePath.split(\"/\").pop() || filePath;\n\n return {\n id: filePath,\n filename,\n originalFilename: response.Metadata?.[\"original-filename\"] || filename,\n mimeType: response.ContentType || \"application/octet-stream\",\n size: response.ContentLength || 0,\n url: this.getPublicUrl(filePath),\n createdAt:\n response.LastModified?.toISOString() || new Date().toISOString(),\n };\n } catch (error: unknown) {\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Read a stored object back as bytes, or `null` when it is not there.\n *\n * The contract declares this OPTIONAL and, until now, no adapter supplied\n * it — so a caller reaching for `adapter.read` got `undefined` and fell\n * through whatever branch followed. An optional member nothing implements is\n * a contract that reads as a capability and behaves as an absence.\n *\n * BUFFERS rather than streams, because that is the declared return type and\n * widening it here would make this adapter answer a different question from\n * its siblings. That bounds what it should be used for: an asset the server\n * must serve from its own origin — a font file, a small document — rather\n * than arbitrarily large media, which belongs behind {@link getSignedUrl} or\n * a public URL. A streaming variant is a separate contract member, not a\n * quiet change to this one.\n *\n * `null` for a missing key rather than a throw, matching\n * {@link getMetadata}: absence is an ordinary answer about the bucket, while\n * a credential or network failure is not, so only the first is folded into\n * the return value.\n *\n * @param filePath - Storage path/key\n * @returns The object's bytes, or `null` when no such key exists\n */\n async read(\n filePath: string,\n options?: StorageReadOptions\n ): Promise<Buffer | null> {\n try {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n /*\n * The caller's deadline reaches the SDK, or it is advertised and inert.\n * `StorageReadOptions` promises a bound, and the URL-backed adapters keep\n * it by handing `timeoutMs` to `safeFetch`; without this the same option\n * does nothing here, so a stalled bucket holds the read open past a\n * deadline the caller was told applied — the worst kind of option, one\n * that validates and then has no effect.\n *\n * Aborting covers the BODY as well as the request. `transformToByteArray`\n * pulls the stream after `send` resolves, and a backend that answers\n * headers promptly and then stalls mid-transfer is exactly the case a\n * request-only timeout misses.\n */\n const response = await this.client.send(command, abortAfter(options));\n /*\n * An empty body is not a missing object, and the two must not collapse.\n * `GetObject` on a zero-byte key succeeds with no stream to transform,\n * so returning `null` here would report a stored empty file as absent —\n * and a caller deleting \"missing\" keys would then delete a real one.\n */\n if (response.Body === undefined) return Buffer.alloc(0);\n\n /*\n * Refused on the LENGTH the store reports, before the body is pulled\n * down. `transformToByteArray` buffers the whole object, so a caller that\n * knows its own limit has to be able to stop an oversized read rather\n * than discover the size after paying for it — which is what an\n * attachment path does when it checks a size only after buffering.\n */\n const cap = resolveReadBounds(options).maxBytes;\n // Cheap refusal first, where the store told us the size up front.\n this.refuseOverCap(filePath, response.ContentLength, cap);\n return await this.readCapped(filePath, response.Body, cap);\n } catch (error: unknown) {\n /*\n * Only an OBJECT-level absence becomes `null`. `isNotFoundError` treats\n * any 404 as missing, and `NoSuchBucket` is a 404 — so a bucket that was\n * deleted, renamed or mistyped would report every key in it as absent,\n * which is the configuration failure this method's contract exists to\n * tell apart from an ordinary miss. Named explicitly rather than by\n * status, because the status cannot separate them.\n */\n if (error instanceof Error && error.name === \"NoSuchBucket\") throw error;\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Generate signed URL for temporary private file access.\n *\n * Creates a pre-signed GetObject URL that grants temporary read access\n * to private files. Useful for serving files from private buckets.\n *\n * @param filePath - Storage path/key\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Pre-signed URL for downloading the file\n */\n async getSignedUrl(filePath: string, expiresIn?: number): Promise<string> {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: expiresIn ?? this.resolvedConfig.signedUrlExpiresIn,\n });\n }\n\n /**\n * Generate pre-signed URL for client-side uploads.\n *\n * Creates a pre-signed PutObject URL that allows direct uploads from\n * the browser to S3, bypassing server-side upload limits (e.g., Vercel's 4.5MB).\n *\n * @param key - Storage path/key for the upload\n * @param mimeType - MIME type of the file being uploaded\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Client upload data with URL, method, and headers\n */\n async getPresignedUploadUrl(\n key: string,\n mimeType: string,\n expiresIn?: number\n ): Promise<ClientUploadData> {\n const expiration = expiresIn ?? this.resolvedConfig.signedUrlExpiresIn;\n\n // Build PutObject command parameters\n const commandParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n ContentType: mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2\n if (!this.isR2) {\n commandParams.ACL = this.resolvedConfig.acl;\n }\n\n const command = new PutObjectCommand(commandParams);\n\n const uploadUrl = await getSignedUrl(this.client, command, {\n expiresIn: expiration,\n });\n\n return {\n uploadUrl,\n path: key,\n method: \"PUT\",\n headers: {\n \"Content-Type\": mimeType,\n },\n expiresAt: new Date(Date.now() + expiration * 1000),\n };\n }\n\n // ============================================================\n // Helper Methods\n // ============================================================\n\n /**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}\n *\n * @param filename - Original filename (will be sanitized)\n * @param folder - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\n private generateKey(filename: string, folder?: string): string {\n const sanitized = this.sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const prefix = folder ? `${folder}/${year}/${month}` : `${year}/${month}`;\n\n return `${prefix}/${uuid}-${sanitized}`;\n }\n\n /**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\n private sanitizeFilename(filename: string): string {\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n }\n\n /**\n * Check if an error is a \"not found\" error from S3.\n *\n * @param error - Error to check\n * @returns true if error indicates file not found\n */\n /**\n * The object's bytes, refusing once more than `cap` have actually arrived.\n *\n * `ContentLength` is OPTIONAL in the SDK's response type, and this adapter\n * explicitly supports MinIO, R2 and other compatible services — any of which\n * may omit it or report it wrongly. A preflight check against metadata is\n * therefore a courtesy that fails OPEN: trusting it alone leaves the cap\n * unenforced exactly where an unusual backend is involved, which is where a\n * caller most needs it.\n *\n * Counted while consuming, so an oversized body is abandoned partway rather\n * than after it has all been buffered — the cap exists to bound memory, and\n * one applied after `transformToByteArray` has already spent it.\n *\n * Falls back to buffering when the body is not async-iterable, which the SDK\n * types permit. Stated rather than hidden: on that path the cap bounds what a\n * caller RECEIVES rather than what the process allocates.\n */\n private async readCapped(\n filePath: string,\n body: NonNullable<GetObjectCommandOutput[\"Body\"]>,\n cap: number\n ): Promise<Buffer> {\n /*\n * Probed rather than assumed. The SDK types the body as a union whose Node\n * member is async-iterable and whose browser members are not, and this\n * package supports compatible services whose transport may differ — so the\n * capability is read off the value in hand instead of from the type.\n */\n const iterable = body as Partial<AsyncIterable<Uint8Array>>;\n if (typeof iterable[Symbol.asyncIterator] !== \"function\") {\n const whole = Buffer.from(await body.transformToByteArray());\n if (whole.byteLength > cap) {\n throw new StorageReadTooLargeError(filePath, cap, whole.byteLength);\n }\n return whole;\n }\n\n const chunks: Buffer[] = [];\n let received = 0;\n for await (const chunk of body as AsyncIterable<Uint8Array>) {\n received += chunk.byteLength;\n if (received > cap) {\n throw new StorageReadTooLargeError(filePath, cap, received);\n }\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks);\n }\n\n /**\n * Refuse a read whose object is already known to exceed the caller's cap.\n *\n * Its own method rather than three conditions inside `read`, because the\n * decision has nothing to do with the surrounding error handling and reads\n * as noise there — and because `read`'s branching is the part a reviewer has\n * to hold in their head to check the absent-versus-unavailable distinction.\n *\n * Both `undefined` cases mean \"no opinion\" and pass: a caller that named no\n * cap has none, and a store that reported no length gives nothing to compare.\n * The second is deliberately permissive — refusing an unmeasured object would\n * reject valid reads whenever S3 omits the header.\n */\n private refuseOverCap(\n filePath: string,\n declared: number | undefined,\n cap: number | undefined\n ): void {\n if (cap === undefined || declared === undefined || declared <= cap) return;\n /*\n * The SHARED refusal rather than a generic internal error, because callers\n * act on the difference. The email attachment path answers an over-cap read\n * with a size error the author can do something about; anything else is\n * wrapped as an opaque storage failure, so throwing `internal` here would\n * turn a fixable refusal into one that tells them nothing.\n */\n throw new StorageReadTooLargeError(filePath, cap, declared);\n }\n\n private isNotFoundError(error: unknown): boolean {\n if (error && typeof error === \"object\") {\n const e = error as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n return e.name === \"NotFound\" || e.$metadata?.httpStatusCode === 404;\n }\n return false;\n }\n\n // ============================================================\n // Public Accessors\n // ============================================================\n\n /**\n * Get the S3 client instance.\n * Useful for advanced operations not covered by the adapter interface.\n */\n getClient(): S3Client {\n return this.client;\n }\n\n /**\n * Get the bucket name.\n */\n getBucket(): string {\n return this.resolvedConfig.bucket;\n }\n\n /**\n * Get the AWS region.\n */\n getRegion(): string {\n return this.resolvedConfig.region;\n }\n\n /**\n * Check if this adapter is configured for Cloudflare R2.\n */\n isCloudflareR2(): boolean {\n return this.isR2;\n }\n}\n","/**\n * S3 Storage Plugin\n *\n * Factory function that creates a storage plugin for AWS S3 and S3-compatible services.\n * Returns a StoragePlugin that can be registered with MediaStorage.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With collection-specific configuration\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * collections: {\n * // Simple enable with defaults\n * media: true,\n *\n * // Full configuration for private documents\n * 'private-docs': {\n * prefix: 'private/',\n * clientUploads: true,\n * signedDownloads: true,\n * signedUrlExpiresIn: 3600\n * }\n * }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { StoragePlugin, ClientUploadData } from \"nextly/storage\";\n\nimport { S3StorageAdapter } from \"./adapter\";\nimport type { S3StorageConfig } from \"./types\";\n\n// ============================================================\n// Plugin Factory Function\n// ============================================================\n\n/**\n * Create an S3 storage plugin for Nextly.\n *\n * This factory function creates a StoragePlugin that can be added to\n * the `storage` array in `nextly.config.ts`. It supports:\n *\n * - AWS S3 (standard)\n * - Cloudflare R2 (S3-compatible)\n * - MinIO (S3-compatible, self-hosted)\n * - DigitalOcean Spaces (S3-compatible)\n * - Any other S3-compatible service\n *\n * @param config - S3 storage configuration\n * @returns A StoragePlugin that MediaStorage can register\n *\n * @throws Error if bucket is not provided (via adapter)\n * @throws Error if region is not provided (via adapter)\n */\nexport function s3Storage(config: S3StorageConfig): StoragePlugin {\n // Handle disabled plugin\n // When disabled, return a plugin with no collections and null adapter\n // MediaStorage.registerPlugin() checks for null adapter and skips registration\n if (config.enabled === false) {\n return {\n name: \"s3-storage\",\n type: \"s3\",\n collections: {},\n adapter: null as unknown as StoragePlugin[\"adapter\"],\n };\n }\n\n // Create the S3 adapter\n // Adapter constructor validates required config (bucket, region)\n const adapter = new S3StorageAdapter(config);\n\n // Build and return the plugin\n const plugin: StoragePlugin = {\n name: \"s3-storage\",\n type: \"s3\",\n collections: config.collections,\n adapter,\n\n /**\n * Generate a pre-signed URL for client-side uploads.\n *\n * This allows files to be uploaded directly from the browser to S3,\n * bypassing server-side upload limits (e.g., Vercel's 4.5MB limit).\n *\n * @param filename - Original filename from the client\n * @param mimeType - MIME type of the file\n * @param collection - Collection slug this upload belongs to\n * @returns Client upload data with pre-signed URL\n */\n async getClientUploadUrl(\n filename: string,\n mimeType: string,\n collection: string\n ): Promise<ClientUploadData> {\n // Get collection-specific configuration\n const collectionConfig = config.collections[collection];\n const prefix =\n typeof collectionConfig === \"object\"\n ? collectionConfig.prefix\n : undefined;\n\n // Generate storage key matching adapter's format\n // Format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n const key = generateStorageKey(filename, prefix);\n\n // Get pre-signed upload URL from adapter\n return adapter.getPresignedUploadUrl(key, mimeType);\n },\n\n /**\n * Generate a signed URL for private file downloads.\n *\n * Creates a time-limited URL for accessing files in private buckets.\n * Only works when collection has `signedDownloads: true`.\n *\n * @param path - Storage path/key of the file\n * @param expiresIn - URL validity duration in seconds\n * @returns Signed URL for downloading the file\n */\n async getSignedDownloadUrl(\n path: string,\n expiresIn?: number\n ): Promise<string> {\n return adapter.getSignedUrl(\n path,\n expiresIn ?? config.signedUrlExpiresIn ?? 3600\n );\n },\n };\n\n return plugin;\n}\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n * This matches the format used by S3StorageAdapter for consistency.\n *\n * @param filename - Original filename (will be sanitized)\n * @param prefix - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\nfunction generateStorageKey(filename: string, prefix?: string): string {\n const sanitized = sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const keyPrefix = prefix ? `${prefix}${year}/${month}` : `${year}/${month}`;\n\n return `${keyPrefix}/${uuid}-${sanitized}`;\n}\n\n/**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * Removes path separators and replaces unsafe characters with hyphens.\n * Keeps only: a-z, A-Z, 0-9, dot, underscore, hyphen\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\nfunction sanitizeFilename(filename: string): string {\n // Extract basename (remove any path components)\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n // Replace unsafe characters with hyphens\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n}\n","/**\n * @nextly/storage-s3\n *\n * AWS S3 storage adapter for Nextly CMS.\n * Also works with S3-compatible services like Cloudflare R2, MinIO, and DigitalOcean Spaces.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With MinIO\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\n// ============================================================\n// S3 Storage Plugin Export (Primary API)\n// ============================================================\n\nexport { s3Storage } from \"./plugin\";\n\n// ============================================================\n// S3 Storage Adapter Export\n// ============================================================\n\nexport { S3StorageAdapter } from \"./adapter\";\n\n// ============================================================\n// S3 Types Export\n// ============================================================\n\nexport type {\n S3StorageConfig,\n S3ObjectACL,\n S3CollectionConfig,\n S3CollectionStorageMap,\n ResolvedS3Config,\n} from \"./types\";\n\n// ============================================================\n// Package Metadata\n// ============================================================\n\nexport const PACKAGE_NAME = \"@nextly/storage-s3\";\nexport const PACKAGE_VERSION = \"0.1.0\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../../nextly/dist/chunk-IAZJNA2U.mjs","../../nextly/dist/chunk-T3OVIJZM.mjs","../../nextly/dist/chunk-YGE6AGZG.mjs","../src/adapter.ts","../src/plugin.ts","../src/index.ts"],"names":["S3Client","Upload","DeleteObjectCommand","DeleteObjectsCommand","HeadObjectCommand","GetObjectCommand","getSignedUrl","PutObjectCommand"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/database/errors.ts\nvar DbError = class extends Error {\n kind;\n dialect;\n code;\n meta;\n cause;\n constructor(args) {\n super(args.message);\n this.name = \"DbError\";\n this.kind = args.kind;\n this.dialect = args.dialect;\n this.code = args.code;\n this.meta = args.meta;\n this.cause = args.cause;\n }\n};\nfunction isDbError(err) {\n if (!err || typeof err !== \"object\") return false;\n const obj = err;\n return obj.name === \"DbError\" && typeof obj.kind === \"string\";\n}\nfunction toDbError(dialect, error, meta) {\n if (isDbError(error)) return error;\n const code = safeCode(error);\n const rawMsg = error != null && typeof error === \"object\" && \"message\" in error ? error.message : void 0;\n const message = typeof rawMsg === \"string\" ? rawMsg : \"Database error\";\n let kind = \"internal\";\n switch (dialect) {\n case \"postgresql\":\n kind = mapPostgresCodeToKind(code);\n break;\n case \"mysql\":\n kind = mapMySqlCodeToKind(code);\n break;\n case \"sqlite\":\n kind = mapSqliteCodeToKind(code);\n break;\n }\n if ((dialect === \"mysql\" || dialect === \"sqlite\") && (kind === \"constraint\" || kind === \"internal\") && isLikelyUniqueViolationMessage(dialect, message)) {\n kind = \"unique-violation\";\n }\n if (kind === \"internal\" && code) {\n const c = code.toUpperCase();\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n kind = \"connection-lost\";\n else if (c === \"ETIMEDOUT\") kind = \"timeout\";\n }\n return new DbError({ message, kind, dialect, code, meta, cause: error });\n}\nfunction safeCode(error) {\n if (!error || typeof error !== \"object\") return void 0;\n const e = error;\n let c = e.code ?? e.sqlState ?? e.state ?? e.errno ?? void 0;\n if (!c && typeof e.originalError?.code === \"string\") c = e.originalError.code;\n if (!c && typeof e.cause?.code === \"string\") c = e.cause.code;\n if (!c && typeof e.severity === \"string\" && typeof e.code === \"string\")\n c = e.code;\n if (typeof c === \"number\") return String(c);\n if (typeof c === \"string\" && c.length > 0) return c;\n return void 0;\n}\nfunction mapPostgresCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"40P01\") return \"deadlock\";\n if (c === \"40001\") return \"serialization-failure\";\n if (c === \"57014\") return \"timeout\";\n if (c === \"23505\") return \"unique-violation\";\n if (c === \"23503\") return \"fk-violation\";\n if (c === \"23502\") return \"not-null-violation\";\n if (c === \"42601\") return \"syntax\";\n if (c.startsWith(\"08\") || c === \"57P01\") return \"connection-lost\";\n if (c.startsWith(\"23\")) return \"constraint\";\n return \"internal\";\n}\nfunction mapMySqlCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"1213\" || c === \"ER_LOCK_DEADLOCK\") return \"deadlock\";\n if (c === \"1205\" || c === \"ER_LOCK_WAIT_TIMEOUT\") return \"timeout\";\n if (c === \"1062\" || c === \"ER_DUP_ENTRY\") return \"unique-violation\";\n if (c === \"23000\") return \"constraint\";\n if (c === \"1064\" || c === \"ER_PARSE_ERROR\") return \"syntax\";\n if (c === \"1452\" || c === \"ER_NO_REFERENCED_ROW_2\") return \"fk-violation\";\n if (c === \"1451\" || c === \"ER_ROW_IS_REFERENCED_2\") return \"fk-violation\";\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n return \"connection-lost\";\n if (c === \"ETIMEDOUT\") return \"timeout\";\n return \"internal\";\n}\nfunction mapSqliteCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"SQLITE_BUSY\" || c === \"SQLITE_LOCKED\") return \"deadlock\";\n if (c === \"SQLITE_CONSTRAINT_UNIQUE\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_PRIMARYKEY\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_FOREIGNKEY\") return \"fk-violation\";\n if (c === \"SQLITE_CONSTRAINT_NOTNULL\") return \"not-null-violation\";\n if (c === \"SQLITE_INTERRUPT\") return \"timeout\";\n if (c === \"SQLITE_ERROR\") return \"syntax\";\n if (c.startsWith(\"SQLITE_CONSTRAINT\")) return \"constraint\";\n return \"internal\";\n}\nfunction isLikelyUniqueViolationMessage(dialect, message) {\n const m = message.toLowerCase();\n if (dialect === \"mysql\") {\n return m.includes(\"duplicate entry\") || m.includes(\"duplicate key\");\n }\n if (dialect === \"sqlite\") {\n return m.includes(\"unique constraint failed\");\n }\n return false;\n}\n\n// src/errors/error-codes.ts\nvar NEXTLY_ERROR_STATUS = {\n VALIDATION_ERROR: 400,\n INVALID_INPUT: 400,\n AUTH_REQUIRED: 401,\n AUTH_INVALID_CREDENTIALS: 401,\n TOKEN_EXPIRED: 401,\n FORBIDDEN: 403,\n // The schema builder is off in this environment (production by default).\n // Separate from FORBIDDEN: the caller's permissions are not the problem.\n BUILDER_DISABLED: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n DUPLICATE: 409,\n RATE_LIMITED: 429,\n PAYLOAD_TOO_LARGE: 413,\n UNSUPPORTED_MEDIA_TYPE: 415,\n // 422: understood, well-formed, and refused on a rule the caller can act on.\n // Deliberately NOT added to CANONICAL_CODE_FOR_STATUS -- that list drives the\n // status -> code direction, where 422 stays mapped to INVALID_INPUT.\n BUSINESS_RULE_VIOLATION: 422,\n INTERNAL_ERROR: 500,\n DATABASE_ERROR: 500,\n EXTERNAL_SERVICE_ERROR: 502,\n SERVICE_UNAVAILABLE: 503,\n // Outbound-fetch safety (utils/validate-external-url): a URL refused for SSRF\n // safety, and a fetch that timed out / exceeded the size cap / failed to decode.\n EXTERNAL_URL_BLOCKED: 400,\n EXTERNAL_REQUEST_FAILED: 502,\n FILENAME_INVALID: 400,\n EXTENSION_BLOCKED: 400,\n MIME_BLOCKED: 415,\n MIME_NOT_ALLOWED: 415,\n SIZE_EXCEEDED: 413,\n // A stored object exceeded the cap a READ was given, which is a different\n // question from SIZE_EXCEEDED above: that one refuses an upload the caller\n // is sending, this one refuses to buffer an object already stored. Kept\n // apart so a caller discriminating on the code cannot match both.\n STORAGE_READ_TOO_LARGE: 413,\n // A stored object did not answer within the deadline the read was given. 504\n // rather than 500 because the failure is the BACKEND not answering, and a\n // caller can act on that difference: a gateway timeout is worth retrying, an\n // internal error is not.\n STORAGE_READ_TIMEOUT: 504,\n // The store answered, and answered badly. 502 rather than 500 because the\n // fault is UPSTREAM of this process: a caller can retry it, and an operator\n // reading the log needs to look at the bucket rather than at this service.\n // Distinct from the timeout above, which never got an answer at all.\n STORAGE_READ_UNREACHABLE: 502,\n MAGIC_BYTE_MISMATCH: 400,\n SVG_SANITIZATION_FAILED: 400,\n UNSUPPORTED_FOR_BACKEND: 415,\n // Plan B — schema bookkeeping consolidation.\n NEXTLY_LEGACY_BOOKKEEPING_DETECTED: 409,\n NEXTLY_UPGRADE_TABLE_NAME_COLLISION: 409,\n NEXTLY_UPGRADE_IN_PROGRESS: 409,\n // Plan C2 — nextly migrate phases.\n NEXTLY_MIGRATE_LOCK_BUSY: 409,\n NEXTLY_BASELINE_LOCK_NOT_HELD: 409,\n NEXTLY_RESOLVE_LOCK_NOT_HELD: 409,\n // Boot refused to serve: the migrate lock stayed held past the wait deadline,\n // so this process never established whether the schema matches the code. 503\n // rather than 409 — a load balancer should take the instance out of rotation\n // and retry it, which is exactly the recovery this refusal wants.\n NEXTLY_BOOT_MIGRATIONS_NOT_RUN: 503,\n // Still running rather than refused. 503 for the same reason: retry shortly.\n NEXTLY_BOOT_MIGRATIONS_PENDING: 503,\n NEXTLY_CORE_DESTRUCTIVE_REFUSED: 409,\n NEXTLY_MIGRATION_DRIFT: 409,\n NEXTLY_MIGRATION_APPLY_FAILED: 500,\n // Plan C3 — migrate:resolve recovery command.\n NEXTLY_MIGRATION_FILE_MISSING: 404,\n NEXTLY_MIGRATION_SNAPSHOT_MISSING: 404,\n NEXTLY_MIGRATION_RESOLVE_DRIFT: 409,\n NEXTLY_MIGRATION_RESOLVE_PRECONDITION: 409,\n // Plan D — UI schema support.\n NEXTLY_UI_SCHEMA_INVALID: 400,\n NEXTLY_SCHEMA_SLUG_COLLISION: 409,\n NEXTLY_SCHEMA_RELATION_TARGET_MISSING: 400,\n // Plugin platform (P2b) — schema extend (contributes.extend) + relations (D15).\n NEXTLY_SCHEMA_EXTEND_TARGET_UNKNOWN: 400,\n NEXTLY_SCHEMA_EXTEND_FIELD_DUPLICATE: 409,\n NEXTLY_SCHEMA_CROSS_PLUGIN_RELATION: 409,\n // Plugin platform (P2c) — framework remap (.rename()).\n NEXTLY_SCHEMA_RENAME_UNKNOWN_TARGET: 400,\n // Plugin platform — a declared admin.clientConfig that cannot be delivered\n // to the browser, refused at boot rather than serialized mangled.\n NEXTLY_PLUGIN_CLIENT_CONFIG_INVALID: 500,\n // Plugin platform — a contributed admin widget that cannot be delivered to\n // the browser. Refused at boot because it is serialized into the ONE\n // `/api/admin-meta/workspace` payload: a value `JSON.stringify` throws on\n // fails that request for every admin, not just the widget's own card.\n NEXTLY_PLUGIN_ADMIN_WIDGET_INVALID: 500,\n // Plugin platform (P0) — boot-time plugin dependency/version resolution.\n PLUGIN_RESOLUTION_ERROR: 500,\n // Plugin platform (P4) — contributes.routes collection (D25).\n NEXTLY_ROUTE_COLLISION: 409,\n NEXTLY_ROUTE_INVALID_PATH: 400,\n // A root-mounted route declared where the request pipeline never reaches the\n // root pass. 400 like an invalid path and not 409: nothing else claims the\n // address, the declaration simply cannot be served from there.\n NEXTLY_ROUTE_UNREACHABLE_ROOT: 400,\n // A `mount` outside the declared union, which only an untyped caller can\n // produce. 400 for the same reason: the declaration itself is unserveable.\n NEXTLY_ROUTE_INVALID_MOUNT: 400,\n // An email transport whose library is an optional peer dependency the host\n // has not installed. 503 rather than 500: the request is not malformed and\n // nothing is broken, the install simply cannot carry it out yet, and the\n // remedy is one command on the server rather than a change by the caller.\n NEXTLY_EMAIL_TRANSPORT_UNAVAILABLE: 503,\n // The tooling that compiles `nextly.config.ts` is an optional peer the host\n // has not installed. 503 rather than 500 for the same reason as the mail\n // transport above: nothing is broken and the request is not malformed, the\n // install simply cannot carry it out until one command is run.\n NEXTLY_CONFIG_TOOLING_UNAVAILABLE: 503\n};\nvar CANONICAL_CODE_FOR_STATUS = [\n \"VALIDATION_ERROR\",\n \"AUTH_REQUIRED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"PAYLOAD_TOO_LARGE\",\n \"UNSUPPORTED_MEDIA_TYPE\",\n \"RATE_LIMITED\",\n \"EXTERNAL_SERVICE_ERROR\",\n \"SERVICE_UNAVAILABLE\"\n];\nvar LEGACY_UNPROCESSABLE_STATUS = 422;\nvar STATUS_TO_CODE = {\n ...Object.fromEntries(\n CANONICAL_CODE_FOR_STATUS.map((code) => [NEXTLY_ERROR_STATUS[code], code])\n ),\n [LEGACY_UNPROCESSABLE_STATUS]: \"INVALID_INPUT\"\n};\nfunction statusToErrorCode(statusCode) {\n return STATUS_TO_CODE[statusCode] ?? \"INTERNAL_ERROR\";\n}\nvar GENERIC_PUBLIC_MESSAGE = {\n VALIDATION_ERROR: \"Validation failed.\",\n INVALID_INPUT: \"The request could not be processed.\",\n AUTH_REQUIRED: \"Authentication required.\",\n FORBIDDEN: \"You don't have permission to perform this action.\",\n NOT_FOUND: \"Not found.\",\n CONFLICT: \"The resource has changed since you last loaded it. Please refresh and try again.\",\n DUPLICATE: \"Resource already exists.\",\n PAYLOAD_TOO_LARGE: \"The request is too large.\",\n UNSUPPORTED_MEDIA_TYPE: \"That file type is not supported.\",\n RATE_LIMITED: \"Too many requests. Please try again later.\",\n EXTERNAL_SERVICE_ERROR: \"An upstream service failed.\",\n SERVICE_UNAVAILABLE: \"The service is temporarily unavailable.\",\n INTERNAL_ERROR: \"An unexpected error occurred.\"\n};\nfunction genericPublicMessage(code) {\n return GENERIC_PUBLIC_MESSAGE[code] ?? GENERIC_PUBLIC_MESSAGE.INTERNAL_ERROR;\n}\n\n// src/errors/nextly-error.ts\nvar DB_ERROR_MAPPING = {\n \"unique-violation\": {\n code: \"DUPLICATE\",\n statusCode: 409,\n publicMessage: \"Resource already exists.\"\n },\n \"fk-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"Referenced record does not exist.\"\n },\n \"not-null-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"A required field is missing.\"\n },\n constraint: {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"The provided data violates a constraint.\"\n },\n deadlock: {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n \"serialization-failure\": {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n timeout: {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"The operation timed out. Please try again.\"\n },\n \"connection-lost\": {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"A temporary database error occurred. Please try again.\"\n },\n syntax: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n },\n internal: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n }\n};\nvar NEXTLY_ERROR_BRAND = Symbol.for(\"nextly/NextlyError\");\nfunction hasBrand(value) {\n if (value === null || typeof value !== \"object\" && typeof value !== \"function\") {\n return false;\n }\n return value[NEXTLY_ERROR_BRAND] === true;\n}\nvar NextlyError = class _NextlyError extends Error {\n code;\n statusCode;\n publicMessage;\n publicData;\n messageKey;\n logMessage;\n logContext;\n cause;\n timestamp;\n constructor(opts) {\n super(opts.publicMessage);\n this.name = \"NextlyError\";\n this.code = opts.code;\n this.publicMessage = opts.publicMessage;\n this.publicData = opts.publicData;\n this.messageKey = opts.messageKey;\n this.logMessage = opts.logMessage;\n this.logContext = opts.logContext;\n this.cause = opts.cause;\n this.timestamp = /* @__PURE__ */ new Date();\n this.statusCode = _NextlyError.resolveStatusCode(opts);\n Error.captureStackTrace?.(this, _NextlyError);\n }\n static {\n _NextlyError.prototype[NEXTLY_ERROR_BRAND] = true;\n }\n static resolveStatusCode(opts) {\n if (typeof opts.statusCode === \"number\") return opts.statusCode;\n if (opts.code in NEXTLY_ERROR_STATUS) {\n return NEXTLY_ERROR_STATUS[opts.code];\n }\n return 500;\n }\n /** HTTP-safe JSON. Strips logMessage / logContext / cause / stack. */\n toResponseJSON(requestId) {\n const json = {\n code: String(this.code),\n message: this.publicMessage,\n requestId\n };\n if (this.messageKey) json.messageKey = this.messageKey;\n if (this.publicData !== void 0) json.data = this.publicData;\n return json;\n }\n /** Operator-facing JSON for log lines. Includes everything. */\n toLogJSON(requestId) {\n return {\n code: this.code,\n statusCode: this.statusCode,\n publicMessage: this.publicMessage,\n messageKey: this.messageKey,\n logMessage: this.logMessage,\n logContext: this.logContext,\n cause: this.cause ? {\n name: this.cause.name,\n message: this.cause.message,\n stack: this.cause.stack\n } : void 0,\n timestamp: this.timestamp.toISOString(),\n requestId\n };\n }\n // ────────────────────────────────────────────────────────────────────\n // Static factories — the recommended throw site for every common case.\n // Public messages here follow the §13.8 rubric: complete sentence,\n // generic, no identifiers, no value echoing, no policy hints.\n // ────────────────────────────────────────────────────────────────────\n static invalidCredentials(opts) {\n return new _NextlyError({\n code: \"AUTH_INVALID_CREDENTIALS\",\n publicMessage: \"Invalid email or password.\",\n logMessage: \"Login failed\",\n logContext: opts?.logContext\n });\n }\n static authRequired(opts) {\n return new _NextlyError({\n code: \"AUTH_REQUIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n /**\n * Distinct from `authRequired`: the caller was authenticated but the\n * session token expired. Clients key on the TOKEN_EXPIRED *code* to\n * silently refresh and retry rather than redirecting to login; the public\n * message stays the generic spec §13.6 string (\"Authentication required.\")\n * so the wire never reveals the session state — same as `authRequired`.\n */\n static tokenExpired(opts) {\n return new _NextlyError({\n code: \"TOKEN_EXPIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n static notFound(opts) {\n return new _NextlyError({\n code: \"NOT_FOUND\",\n publicMessage: opts?.message ?? \"Not found.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n static forbidden(opts) {\n return new _NextlyError({\n code: \"FORBIDDEN\",\n publicMessage: \"You don't have permission to perform this action.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * A call the caller got wrong, where naming the mistake IS the value of the\n * error.\n *\n * The only factory that takes its public message from the caller. The\n * generic messages elsewhere exist so an HTTP response cannot leak internal\n * detail; this one is for arguments and configuration a developer controls\n * and must be told about — a missing option, an unusable combination — where\n * `internal()` would reduce the one useful sentence to \"An unexpected error\n * occurred.\" Do not pass user-supplied data through it.\n */\n static invalidInput(opts) {\n return new _NextlyError({\n code: \"INVALID_INPUT\",\n publicMessage: opts.message,\n logContext: opts.logContext\n });\n }\n static validation(opts) {\n return new _NextlyError({\n code: \"VALIDATION_ERROR\",\n publicMessage: \"Validation failed.\",\n publicData: { errors: opts.errors },\n cause: opts.cause,\n logContext: opts.logContext\n });\n }\n static conflict(opts) {\n return new _NextlyError({\n code: \"CONFLICT\",\n publicMessage: opts?.message ?? \"The resource has changed since you last loaded it. Please refresh and try again.\",\n cause: opts?.cause,\n logContext: { reason: opts?.reason, ...opts?.logContext }\n });\n }\n static duplicate(opts) {\n return new _NextlyError({\n code: \"DUPLICATE\",\n publicMessage: \"Resource already exists.\",\n logContext: opts?.logContext\n });\n }\n static rateLimited(opts) {\n return new _NextlyError({\n code: \"RATE_LIMITED\",\n publicMessage: \"Too many requests. Please try again later.\",\n publicData: opts?.retryAfterSeconds !== void 0 ? { retryAfterSeconds: opts.retryAfterSeconds } : void 0,\n logContext: opts?.logContext\n });\n }\n static internal(opts) {\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n publicMessage: \"An unexpected error occurred.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n // Accepts an optional `logMessage` override so callers (e.g. the health\n // route) can record a specific operator narrative (\"Health check failed\")\n // while the public message stays canonical per spec §13.8.5.\n static serviceUnavailable(opts) {\n return new _NextlyError({\n code: \"SERVICE_UNAVAILABLE\",\n publicMessage: opts?.publicMessage ?? \"Service unavailable. Please try again later.\",\n logMessage: opts?.logMessage,\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * Convert a DbError (or arbitrary unknown thrown by the DB layer) to a\n * NextlyError with a generic public message and rich logContext. Used by\n * `withDbErrors` for auto-conversion (Pattern A) and by services that\n * catch DB errors at boundaries (Pattern B). Spec §8.2 mapping table.\n *\n * Never leaks DB driver text, constraint names, or table names into\n * `publicMessage`. All DB context goes into `logContext`. The original\n * DbError is preserved as `cause`.\n */\n static fromDatabaseError(error) {\n if (isDbError(error)) {\n const mapping = DB_ERROR_MAPPING[error.kind];\n const logContext = {\n dbKind: error.kind,\n dialect: error.dialect\n };\n if (error.code !== void 0) logContext.dbCode = error.code;\n if (error.meta !== void 0) logContext.meta = error.meta;\n return new _NextlyError({\n code: mapping.code,\n statusCode: mapping.statusCode,\n publicMessage: mapping.publicMessage,\n logMessage: \"Database error\",\n logContext,\n cause: error\n });\n }\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\",\n logMessage: \"Non-DbError passed to fromDatabaseError\",\n cause: error instanceof Error ? error : void 0,\n logContext: error instanceof Error ? void 0 : { value: String(error) }\n });\n }\n // ────────────────────────────────────────────────────────────────────\n // Type guards. Structural rather than `instanceof` so they survive\n // package-boundary mismatches (one consumer's NextlyError is a\n // different module instance from another's).\n // ────────────────────────────────────────────────────────────────────\n static is(err) {\n return hasBrand(err);\n }\n static isCode(err, code) {\n return hasBrand(err) && err.code === code;\n }\n static isNotFound(err) {\n return _NextlyError.isCode(err, \"NOT_FOUND\");\n }\n static isValidation(err) {\n return _NextlyError.isCode(err, \"VALIDATION_ERROR\");\n }\n static isAuthRequired(err) {\n return _NextlyError.isCode(err, \"AUTH_REQUIRED\");\n }\n static isForbidden(err) {\n return _NextlyError.isCode(err, \"FORBIDDEN\");\n }\n static isConflict(err) {\n return _NextlyError.isCode(err, \"CONFLICT\");\n }\n static isRateLimited(err) {\n return _NextlyError.isCode(err, \"RATE_LIMITED\");\n }\n};\n\nexport {\n DbError,\n isDbError,\n toDbError,\n safeCode,\n NEXTLY_ERROR_STATUS,\n statusToErrorCode,\n genericPublicMessage,\n NextlyError\n};\n","import {\n NextlyError\n} from \"./chunk-IAZJNA2U.mjs\";\n\n// src/storage/read-errors.ts\nvar StorageReadTooLargeError = class extends NextlyError {\n constructor(path, maxBytes, size) {\n super({\n code: \"STORAGE_READ_TOO_LARGE\",\n publicMessage: \"The stored file is larger than the limit for this read.\",\n logContext: {\n path,\n maxBytes,\n ...size === void 0 ? {} : { size }\n }\n });\n this.path = path;\n this.maxBytes = maxBytes;\n this.size = size;\n }\n};\nfunction isStorageReadTooLarge(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TOO_LARGE\";\n}\nvar StorageReadTimeoutError = class extends NextlyError {\n constructor(path, timeoutMs) {\n super({\n code: \"STORAGE_READ_TIMEOUT\",\n publicMessage: \"The stored file could not be read in time.\",\n logContext: { path, timeoutMs }\n });\n this.path = path;\n this.timeoutMs = timeoutMs;\n }\n};\nfunction isStorageReadTimeout(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TIMEOUT\";\n}\n\nexport {\n StorageReadTooLargeError,\n isStorageReadTooLarge,\n StorageReadTimeoutError,\n isStorageReadTimeout\n};\n","import {\n StorageReadTimeoutError,\n StorageReadTooLargeError\n} from \"./chunk-T3OVIJZM.mjs\";\nimport {\n NextlyError\n} from \"./chunk-IAZJNA2U.mjs\";\n\n// src/utils/validate-external-url.ts\nvar IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$/;\nvar PRIVATE_IPV4_CIDRS = [\n \"0.0.0.0/8\",\n // RFC1122 \"this network\" / wildcard\n \"10.0.0.0/8\",\n // RFC1918\n \"100.64.0.0/10\",\n // RFC6598 CGNAT\n \"127.0.0.0/8\",\n // RFC1122 loopback\n \"169.254.0.0/16\",\n // RFC3927 link-local + AWS/GCP metadata\n \"172.16.0.0/12\",\n // RFC1918\n \"192.0.0.0/24\",\n // RFC6890 IETF protocol assignments\n \"192.0.2.0/24\",\n // RFC5737 documentation\n \"192.168.0.0/16\",\n // RFC1918\n \"198.18.0.0/15\",\n // RFC2544 benchmarking\n \"198.51.100.0/24\",\n // RFC5737 documentation\n \"203.0.113.0/24\",\n // RFC5737 documentation\n \"224.0.0.0/4\",\n // RFC5771 multicast\n \"240.0.0.0/4\"\n // RFC1112 reserved\n];\nvar CLOUD_METADATA_HOSTS = /* @__PURE__ */ new Set([\n \"metadata.google.internal\",\n \"metadata.googleapis.com\",\n \"metadata\"\n // GCP short form\n]);\nvar ExternalUrlError = class extends NextlyError {\n constructor(message, url) {\n super({\n code: \"EXTERNAL_URL_BLOCKED\",\n publicMessage: message,\n logContext: { url }\n });\n this.url = url;\n this.name = \"ExternalUrlError\";\n }\n};\nasync function validateExternalUrl(rawUrl, options = {}) {\n let parsed;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new ExternalUrlError(\"Invalid URL\", rawUrl);\n }\n const hostname = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n const isLocalhostHostname = hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n const baseProtocols = options.allowedProtocols ?? [\"https:\"];\n const protocols = options.allowLocalhost && isLocalhostHostname ? [...baseProtocols, \"http:\"] : baseProtocols;\n if (!protocols.includes(parsed.protocol)) {\n throw new ExternalUrlError(\n `Protocol ${parsed.protocol} not allowed (allowed: ${protocols.join(\", \")})`,\n rawUrl\n );\n }\n if (CLOUD_METADATA_HOSTS.has(hostname)) {\n throw new ExternalUrlError(\n `Cloud-metadata hostname rejected: ${parsed.hostname}`,\n rawUrl\n );\n }\n if (IPV4_RE.test(hostname)) {\n if (!isPublicIpv4(\n hostname,\n options.allowLocalhost === true && hostname === \"127.0.0.1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 4 };\n }\n if (hostname.includes(\":\")) {\n if (!isPublicIpv6(\n hostname,\n options.allowLocalhost === true && hostname === \"::1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 6 };\n }\n const { lookup } = await import(\"dns/promises\");\n let addresses;\n try {\n addresses = await lookup(hostname, { all: true });\n } catch (err) {\n throw new ExternalUrlError(\n `DNS lookup failed: ${err.message}`,\n rawUrl\n );\n }\n if (addresses.length === 0) {\n throw new ExternalUrlError(\"No IPs returned for host\", rawUrl);\n }\n for (const { address, family } of addresses) {\n const allow = options.allowLocalhost === true && isLocalhostHostname && family === 4;\n const ok = family === 4 ? isPublicIpv4(address, allow) : isPublicIpv6(address, allow);\n if (!ok) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${address}`,\n rawUrl\n );\n }\n }\n const first = addresses[0];\n return {\n url: parsed,\n pinnedIp: first.address,\n family: first.family === 6 ? 6 : 4\n };\n}\nvar DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;\nvar DEFAULT_TIMEOUT_MS = 3e4;\nvar SafeFetchError = class extends NextlyError {\n constructor(message, url, reason, status) {\n super({\n code: \"EXTERNAL_REQUEST_FAILED\",\n publicMessage: message,\n logContext: { url, reason }\n });\n this.url = url;\n this.reason = reason;\n this.status = status;\n this.name = \"SafeFetchError\";\n }\n};\nfunction createPinnedLookup(ip, family) {\n return (_hostname, options, callback) => {\n if (options && options.all) {\n callback(null, [{ address: ip, family }]);\n } else {\n callback(null, ip, family);\n }\n };\n}\nasync function safeFetch(rawUrl, options = {}) {\n const {\n allowLocalhost,\n allowedProtocols,\n method,\n headers,\n body,\n signal,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n timeoutMs = DEFAULT_TIMEOUT_MS\n } = options;\n const controller = new AbortController();\n const forwardAbort = () => controller.abort(signal?.reason);\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", forwardAbort, { once: true });\n }\n const timer = setTimeout(\n () => controller.abort(\n new SafeFetchError(`Request exceeded ${timeoutMs}ms`, rawUrl, \"timeout\")\n ),\n timeoutMs\n );\n try {\n const validated = await abortable(\n validateExternalUrl(rawUrl, { allowLocalhost, allowedProtocols }),\n controller.signal\n );\n return await pinnedFetch(validated, {\n method,\n headers,\n body,\n signal: controller.signal,\n maxResponseBytes\n });\n } finally {\n clearTimeout(timer);\n if (signal) signal.removeEventListener(\"abort\", forwardAbort);\n }\n}\nfunction abortError(signal) {\n const reason = signal.reason;\n if (reason instanceof Error) return reason;\n return new DOMException(\"The operation was aborted\", \"AbortError\");\n}\nfunction abortable(promise, signal) {\n if (signal.aborted) return Promise.reject(abortError(signal));\n let onAbort;\n const aborted = new Promise((_resolve, reject) => {\n onAbort = () => reject(abortError(signal));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n return Promise.race([promise, aborted]).finally(\n () => signal.removeEventListener(\"abort\", onAbort)\n );\n}\nasync function pinnedFetch(validated, init) {\n const { url, pinnedIp, family } = validated;\n const httpMod = url.protocol === \"https:\" ? await import(\"https\") : await import(\"http\");\n const zlib = await import(\"zlib\");\n const lookup = createPinnedLookup(pinnedIp, family);\n const outHeaders = toOutgoingHeaders(init.headers);\n if (init.body != null) {\n delete outHeaders[\"transfer-encoding\"];\n outHeaders[\"content-length\"] = String(\n typeof init.body === \"string\" ? Buffer.byteLength(init.body) : init.body.byteLength\n );\n }\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn) => {\n if (settled) return;\n settled = true;\n fn();\n };\n const failure = (err) => init.signal.aborted ? abortError(init.signal) : err;\n const req = httpMod.request(\n url,\n {\n method: init.method ?? \"GET\",\n headers: outHeaders,\n lookup,\n // Fresh socket per request so a pooled connection can't reuse a\n // differently-resolved address, and so the pinned lookup always runs.\n agent: false,\n // The combined deadline/caller signal aborts the request in flight.\n signal: init.signal\n },\n (res) => {\n const chunks = [];\n let received = 0;\n res.on(\"data\", (chunk) => {\n received += chunk.length;\n if (received > init.maxResponseBytes) {\n res.destroy();\n req.destroy();\n settle(\n () => reject(\n new SafeFetchError(\n `Response body exceeded ${init.maxResponseBytes} bytes`,\n url.href,\n \"response-too-large\",\n res.statusCode\n )\n )\n );\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n const decoded = decodeBody(\n Buffer.concat(chunks),\n res.headers[\"content-encoding\"],\n zlib,\n init.maxResponseBytes,\n url.href,\n res.statusCode\n );\n if (decoded instanceof SafeFetchError) {\n settle(() => reject(decoded));\n return;\n }\n settle(\n () => resolve(toWhatwgResponse(res, decoded.body, decoded.decoded))\n );\n });\n res.on(\"error\", (err) => settle(() => reject(failure(err))));\n }\n );\n req.on(\"error\", (err) => settle(() => reject(failure(err))));\n if (!req.destroyed) {\n if (init.body != null) req.write(init.body);\n req.end();\n }\n });\n}\nfunction toOutgoingHeaders(headers) {\n const out = {};\n if (!headers) return out;\n const add = (rawKey, value) => {\n const key = rawKey.toLowerCase();\n if (key === \"host\") return;\n const existing = out[key];\n out[key] = existing === void 0 ? value : [].concat(existing, value);\n };\n if (headers instanceof Headers) {\n headers.forEach((value, key) => add(key, value));\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) add(key, value);\n } else {\n for (const [key, value] of Object.entries(headers)) add(key, value);\n }\n return out;\n}\nfunction toWhatwgResponse(res, body, stripContentHeaders) {\n const headers = new Headers();\n for (const [key, value] of Object.entries(res.headers)) {\n if (value == null) continue;\n const lower = key.toLowerCase();\n if (stripContentHeaders && (lower === \"content-encoding\" || lower === \"content-length\")) {\n continue;\n }\n const values = Array.isArray(value) ? value : [value];\n for (const v of values) {\n try {\n headers.append(key, v);\n } catch {\n }\n }\n }\n const raw = res.statusCode ?? 502;\n const status = raw >= 200 && raw <= 599 ? raw : 502;\n const nullBody = status === 204 || status === 205 || status === 304;\n const bytes = new Uint8Array(body.byteLength);\n bytes.set(body);\n return new Response(nullBody ? null : bytes, {\n status,\n statusText: res.statusMessage ?? \"\",\n headers\n });\n}\nfunction decodeBody(buf, encoding, zlib, cap, url, status) {\n if (!encoding) return { body: buf, decoded: false };\n if (buf.length === 0) return { body: buf, decoded: true };\n const layers = encoding.split(\",\").map((e) => e.trim().toLowerCase()).filter(Boolean);\n const opts = { maxOutputLength: cap };\n let current = buf;\n let decoded = false;\n try {\n for (let i = layers.length - 1; i >= 0; i--) {\n const enc = layers[i];\n if (enc === \"identity\") continue;\n if (enc === \"gzip\" || enc === \"x-gzip\") {\n current = zlib.gunzipSync(current, opts);\n } else if (enc === \"br\") {\n current = zlib.brotliDecompressSync(current, opts);\n } else if (enc === \"deflate\") {\n try {\n current = zlib.inflateSync(current, opts);\n } catch (inflateErr) {\n if (errorCode(inflateErr) === \"ERR_BUFFER_TOO_LARGE\")\n throw inflateErr;\n current = zlib.inflateRawSync(current, opts);\n }\n } else {\n if (decoded) {\n return new SafeFetchError(\n `Cannot fully decode content-encoding \"${encoding}\"`,\n url,\n \"decode-failed\",\n status\n );\n }\n return { body: buf, decoded: false };\n }\n decoded = true;\n }\n return { body: current, decoded };\n } catch (err) {\n const tooLarge = errorCode(err) === \"ERR_BUFFER_TOO_LARGE\";\n return new SafeFetchError(\n tooLarge ? `Decoded ${encoding} response body exceeded the size cap` : `Failed to decode ${encoding} response body`,\n url,\n tooLarge ? \"response-too-large\" : \"decode-failed\",\n status\n );\n }\n}\nfunction errorCode(err) {\n if (err != null && typeof err === \"object\" && \"code\" in err) {\n const code = err.code;\n return typeof code === \"string\" ? code : void 0;\n }\n return void 0;\n}\nfunction ipv4ToInt(ip) {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n let acc = 0;\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 0 || n > 255) return null;\n acc = acc << 8 | n;\n }\n return acc >>> 0;\n}\nfunction isIpv4InCidr(intIp, cidr) {\n const [addr, prefixRaw] = cidr.split(\"/\");\n const intCidr = ipv4ToInt(addr);\n if (intCidr === null) return false;\n const prefix = Number(prefixRaw);\n if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;\n const mask = prefix === 0 ? 0 : -1 >>> 32 - prefix << 32 - prefix;\n return (intIp & mask) >>> 0 === (intCidr & mask) >>> 0;\n}\nfunction isPublicIpv4(addr, allowLoopback) {\n const intIp = ipv4ToInt(addr);\n if (intIp === null) return false;\n if (allowLoopback && addr === \"127.0.0.1\") return true;\n for (const cidr of PRIVATE_IPV4_CIDRS) {\n if (isIpv4InCidr(intIp, cidr)) return false;\n }\n return true;\n}\nfunction mappedIpv4(lower) {\n const dotted = lower.match(/^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n if (dotted) return dotted[1];\n const hex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);\n if (hex) {\n const hi = parseInt(hex[1], 16);\n const lo = parseInt(hex[2], 16);\n return [hi >> 8, hi & 255, lo >> 8, lo & 255].join(\".\");\n }\n return null;\n}\nfunction isPublicIpv6(addr, allowLoopback) {\n const lower = addr.toLowerCase();\n if (allowLoopback && lower === \"::1\") return true;\n const mapped = mappedIpv4(lower);\n if (mapped) {\n return isPublicIpv4(mapped, allowLoopback);\n }\n if (lower === \"::\" || lower === \"::1\") return false;\n const firstHextet = lower.split(\":\")[0] || \"\";\n if (firstHextet.startsWith(\"fc\") || firstHextet.startsWith(\"fd\"))\n return false;\n if (firstHextet.startsWith(\"fe8\") || firstHextet.startsWith(\"fe9\"))\n return false;\n if (firstHextet.startsWith(\"fea\") || firstHextet.startsWith(\"feb\"))\n return false;\n if (firstHextet.startsWith(\"ff\")) return false;\n return true;\n}\n\n// src/storage/fetch-stored-bytes.ts\nasync function fetchStoredBytes(url, context, label, options, signal) {\n let response;\n try {\n response = await safeFetch(url, {\n ...options?.maxBytes === void 0 ? {} : { maxResponseBytes: options.maxBytes },\n /*\n * A caller's signal REPLACES the deadline rather than joining it.\n *\n * These adapters look the object's address up before fetching it, and\n * that lookup can stall too. Starting a fresh timer here would give the\n * fetch its own full budget on top of however long the lookup took, so a\n * read could outlive the deadline the caller was told applied — by\n * roughly double. One signal begun before the lookup governs both phases.\n */\n ...signal !== void 0 ? { signal } : options?.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }\n });\n } catch (error) {\n const verdict = classifyFetchFailure(error);\n if (verdict === \"absent\") return null;\n if (verdict === \"oversized\") {\n throw new StorageReadTooLargeError(\n context,\n resolveReadBounds(options).maxBytes\n );\n }\n throw error;\n }\n if (response.status === 404) return null;\n if (!response.ok) {\n throw NextlyError.internal({\n logContext: {\n service: label,\n path: context,\n status: response.status,\n statusText: response.statusText\n }\n });\n }\n return Buffer.from(await response.arrayBuffer());\n}\nfunction classifyFetchFailure(error) {\n if (!(error instanceof SafeFetchError)) return \"unknown\";\n if (error.status === 404) return \"absent\";\n const successful = error.status !== void 0 && error.status >= 200 && error.status < 300;\n return error.reason === \"response-too-large\" && successful ? \"oversized\" : \"unknown\";\n}\nvar DEFAULT_READ_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;\nvar DEFAULT_READ_MAX_BYTES = DEFAULT_MAX_RESPONSE_BYTES;\nfunction resolveReadBounds(options) {\n return {\n maxBytes: options?.maxBytes ?? DEFAULT_READ_MAX_BYTES,\n timeoutMs: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS\n };\n}\nfunction deadlineSignal(timeoutMs, context) {\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new StorageReadTimeoutError(context, timeoutMs));\n }, timeoutMs);\n timer.unref?.();\n return {\n signal: controller.signal,\n /*\n * Called once the read has settled, whichever way it went. `unref` only\n * stops a pending timer holding the process open; it still sits on the\n * timer heap retaining the controller and the path, so a server doing a\n * thousand reads a second keeps thirty seconds of them alive and then runs\n * a thousand aborts that answer nobody.\n */\n cancel: () => {\n clearTimeout(timer);\n }\n };\n}\nasync function withDeadline(work, signal) {\n if (signal === void 0) return await work;\n return await Promise.race([\n work,\n new Promise((_, reject) => {\n if (signal.aborted) {\n reject(signal.reason);\n return;\n }\n signal.addEventListener(\"abort\", () => reject(signal.reason), {\n once: true\n });\n })\n ]);\n}\n\nexport {\n ExternalUrlError,\n validateExternalUrl,\n SafeFetchError,\n safeFetch,\n fetchStoredBytes,\n classifyFetchFailure,\n DEFAULT_READ_TIMEOUT_MS,\n DEFAULT_READ_MAX_BYTES,\n resolveReadBounds,\n deadlineSignal,\n withDeadline\n};\n","/**\n * S3 Storage Adapter\n *\n * Stores files on AWS S3 or S3-compatible services (Cloudflare R2, MinIO, DigitalOcean Spaces).\n * Uses AWS SDK v3 with automatic multipart upload for large files (>5MB).\n *\n * Features:\n * - Automatic multipart upload via @aws-sdk/lib-storage\n * - Pre-signed URL generation for client-side uploads\n * - Signed URL generation for private file access\n * - Cloudflare R2 support (S3-compatible API)\n * - Custom endpoint support (MinIO, DigitalOcean Spaces)\n * - CDN URL override support\n * - File metadata retrieval\n *\n * @example AWS S3\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: { media: true }\n * });\n * ```\n *\n * @example Cloudflare R2\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media',\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n * publicUrl: 'https://pub-abc.r2.dev',\n * collections: { media: true }\n * });\n * ```\n *\n * @example MinIO (self-hosted)\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * });\n * ```\n */\n\nimport {\n S3Client,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n type PutObjectCommandInput,\n type GetObjectCommandOutput,\n} from \"@aws-sdk/client-s3\";\nimport { Upload } from \"@aws-sdk/lib-storage\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type {\n StorageReadOptions,\n IStorageAdapter,\n UploadOptions,\n UploadResult,\n StorageAdapterInfo,\n ClientUploadData,\n FileMetadata,\n BulkDeleteResult,\n} from \"nextly/storage\";\nimport { resolveReadBounds } from \"nextly/storage/fetch-stored-bytes\";\nimport { StorageReadTooLargeError } from \"nextly/storage/read-errors\";\n\nimport type { S3StorageConfig, ResolvedS3Config } from \"./types\";\n\n// ============================================================\n// S3 Storage Adapter\n// ============================================================\n\n/**\n * S3 Storage Adapter\n *\n * Implements the IStorageAdapter interface for AWS S3 and S3-compatible services.\n * Provides full support for uploads, downloads, signed URLs, and client-side uploads.\n */\n/**\n * The SDK request options carrying the caller's deadline, or none.\n *\n * A free function rather than a branch inside `read`, because it is the only\n * part of that method with nothing to do with telling an absent key apart from\n * an unreachable bucket — which is the distinction a reader has to hold in\n * their head there.\n *\n * `AbortSignal.timeout` rather than a controller and a `setTimeout`: it needs no\n * clearing, so it cannot leak a timer when the read resolves first. The signal\n * covers the BODY as well as the request, because the SDK threads it through\n * the response stream — and a backend that answers headers promptly and then\n * stalls mid-transfer is exactly the case a request-only deadline misses.\n */\nfunction abortAfter(options?: StorageReadOptions): {\n abortSignal: AbortSignal;\n} {\n /*\n * ALWAYS a signal, defaulted through the shared resolver.\n * `StorageReadOptions` promises the deadline whether or not a caller names\n * one, and the URL-backed adapters keep that promise by inheriting\n * `safeFetch`'s. Returning nothing here left the no-options read — which is\n * what the media pipeline does — able to hang on a stalled bucket forever.\n */\n return {\n abortSignal: AbortSignal.timeout(resolveReadBounds(options).timeoutMs),\n };\n}\n\nexport class S3StorageAdapter implements IStorageAdapter {\n private client: S3Client;\n private resolvedConfig: ResolvedS3Config;\n private isR2: boolean;\n\n /**\n * Create a new S3 storage adapter.\n *\n * @param config - S3 storage configuration\n * @throws Error if bucket or region is not provided\n */\n constructor(private config: S3StorageConfig) {\n // Validate required config\n if (!config.bucket) {\n throw new Error(\"@nextly/storage-s3: bucket is required\");\n }\n if (!config.region) {\n throw new Error(\"@nextly/storage-s3: region is required\");\n }\n\n // Detect if this is Cloudflare R2\n this.isR2 = config.endpoint?.includes(\"r2.cloudflarestorage.com\") ?? false;\n\n // Resolve config with defaults\n this.resolvedConfig = {\n bucket: config.bucket,\n region: config.region,\n endpoint: config.endpoint,\n forcePathStyle: config.forcePathStyle ?? false,\n acl: config.acl ?? \"private\",\n publicUrl: config.publicUrl,\n cacheControl: config.cacheControl ?? \"public, max-age=31536000\",\n contentDisposition: config.contentDisposition,\n signedUrlExpiresIn: config.signedUrlExpiresIn ?? 3600,\n };\n\n // Build credentials\n const credentials = this.buildCredentials();\n\n // Initialize S3 client\n this.client = new S3Client({\n region: config.region,\n endpoint: config.endpoint,\n credentials,\n forcePathStyle: this.resolvedConfig.forcePathStyle,\n ...config.config,\n });\n }\n\n /**\n * Build AWS credentials from config.\n * Supports explicit credentials or falls back to SDK default chain.\n */\n private buildCredentials():\n | { accessKeyId: string; secretAccessKey: string }\n | undefined {\n if (this.config.accessKeyId && this.config.secretAccessKey) {\n return {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey,\n };\n }\n\n // Check if credentials are in the config object\n if (this.config.config?.credentials) {\n return undefined; // Let SDK use the provided config.credentials\n }\n\n // Fall back to SDK default credential chain (env vars, IAM roles, etc.)\n return undefined;\n }\n\n // ============================================================\n // Core IStorageAdapter Methods\n // ============================================================\n\n /**\n * Upload file to S3.\n *\n * Uses AWS SDK v3's Upload class from @aws-sdk/lib-storage which:\n * - Automatically handles multipart upload for large files (>5MB)\n * - Provides progress tracking capability\n * - Includes retry logic\n * - Optimizes upload performance\n *\n * @param buffer - File content as Buffer\n * @param options - Upload options (filename, mimeType, folder, collection)\n * @returns Upload result with URL and storage path\n */\n async upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult> {\n const key = this.generateKey(options.filename, options.folder);\n\n // Build upload parameters\n const uploadParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n Body: buffer,\n ContentType: options.contentType || options.mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2 (R2 ignores ACL settings)\n if (!this.isR2) {\n uploadParams.ACL = this.resolvedConfig.acl;\n }\n\n // Add Content-Disposition: per-file override takes priority, then global config\n const disposition =\n options.contentDisposition ?? this.resolvedConfig.contentDisposition;\n if (disposition) {\n const filename = this.sanitizeFilename(options.filename);\n uploadParams.ContentDisposition =\n disposition === \"attachment\"\n ? `attachment; filename=\"${filename}\"`\n : \"inline\";\n }\n\n // Store original filename in metadata for later retrieval\n uploadParams.Metadata = {\n \"original-filename\": options.filename,\n };\n\n // Use Upload for automatic multipart handling\n const upload = new Upload({\n client: this.client,\n params: uploadParams,\n });\n\n await upload.done();\n\n return {\n url: this.getPublicUrl(key),\n path: key,\n };\n }\n\n /**\n * Delete file from S3.\n *\n * @param filePath - Storage path/key to delete\n */\n async delete(filePath: string): Promise<void> {\n const command = new DeleteObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n }\n\n /**\n * Bulk delete files from S3 using a single API call per 1000 keys.\n *\n * Uses AWS SDK v3's DeleteObjectsCommand which supports up to 1000 keys per\n * request. Automatically batches larger arrays and collects per-key results.\n *\n * @param filePaths - Storage paths/keys to delete\n * @returns Object with arrays of successful and failed deletions\n */\n async bulkDelete(filePaths: string[]): Promise<BulkDeleteResult> {\n const successful: string[] = [];\n const failed: Array<{ filePath: string; error: string }> = [];\n const maxBatchSize = 1000; // AWS limit\n\n for (let i = 0; i < filePaths.length; i += maxBatchSize) {\n const batch = filePaths.slice(i, i + maxBatchSize);\n const command = new DeleteObjectsCommand({\n Bucket: this.resolvedConfig.bucket,\n Delete: {\n Objects: batch.map(key => ({ Key: key })),\n Quiet: false,\n },\n });\n\n const response = await this.client.send(command);\n\n if (response.Errors && response.Errors.length > 0) {\n for (const err of response.Errors) {\n if (err.Key) {\n failed.push({\n filePath: err.Key,\n error: err.Message ?? \"Unknown S3 delete error\",\n });\n }\n }\n }\n\n if (response.Deleted) {\n for (const del of response.Deleted) {\n if (del.Key) {\n successful.push(del.Key);\n }\n }\n }\n }\n\n return { successful, failed };\n }\n\n /**\n * Check if file exists in S3.\n *\n * Uses HeadObject command which is more efficient than GetObject\n * for existence checks (doesn't download the file).\n *\n * @param filePath - Storage path/key to check\n * @returns true if file exists, false otherwise\n */\n async exists(filePath: string): Promise<boolean> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n return true;\n } catch (error: unknown) {\n // HeadObject returns 404 if object doesn't exist\n if (this.isNotFoundError(error)) {\n return false;\n }\n // Re-throw other errors (permissions, network, etc.)\n throw error;\n }\n }\n\n /**\n * Get public URL for S3 file.\n *\n * Priority order:\n * 1. Custom publicUrl (CDN or custom domain) if configured\n * 2. Standard S3 URL based on region and bucket\n *\n * For R2: Requires publicUrl configuration (R2 has no default public URLs).\n *\n * @param filePath - Storage path/key\n * @returns Public URL to access the file\n * @throws Error if R2 is used without publicUrl configuration\n */\n getPublicUrl(filePath: string): string {\n // Use custom CDN/public URL if configured\n if (this.resolvedConfig.publicUrl) {\n const baseUrl = this.resolvedConfig.publicUrl.replace(/\\/$/, \"\");\n return `${baseUrl}/${filePath}`;\n }\n\n // R2 requires custom domain or public bucket URL\n if (this.isR2) {\n throw new Error(\n \"@nextly/storage-s3: Cloudflare R2 requires publicUrl configuration.\\n\\n\" +\n \"R2 does not have default public URLs like AWS S3. Configure one of:\\n\" +\n \"1. Public bucket URL: publicUrl: 'https://pub-xxx.r2.dev'\\n\" +\n \"2. Custom domain: publicUrl: 'https://cdn.example.com'\\n\\n\" +\n \"Set up public access in the Cloudflare R2 dashboard.\"\n );\n }\n\n // Standard S3 URL format\n // https://bucket.s3.region.amazonaws.com/key\n return `https://${this.resolvedConfig.bucket}.s3.${this.resolvedConfig.region}.amazonaws.com/${filePath}`;\n }\n\n /**\n * Get storage type identifier.\n *\n * Returns \"s3\" for all S3-compatible services (AWS S3, R2, MinIO, etc.)\n * as they all use the S3 API.\n */\n getType(): \"s3\" {\n return \"s3\";\n }\n\n // ============================================================\n // Optional IStorageAdapter Methods\n // ============================================================\n\n /**\n * Get adapter info including capabilities.\n *\n * @returns Adapter info with type, name, and capability flags\n */\n getInfo(): StorageAdapterInfo {\n return {\n type: \"s3\",\n name: \"S3StorageAdapter\",\n supportsSignedUrls: true,\n supportsClientUploads: true,\n };\n }\n\n /**\n * Get file metadata from S3.\n *\n * Retrieves file information including size, content type, and timestamps\n * using the HeadObject command.\n *\n * @param filePath - Storage path/key\n * @returns File metadata or null if file not found\n */\n async getMetadata(filePath: string): Promise<FileMetadata | null> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n const response = await this.client.send(command);\n const filename = filePath.split(\"/\").pop() || filePath;\n\n return {\n id: filePath,\n filename,\n originalFilename: response.Metadata?.[\"original-filename\"] || filename,\n mimeType: response.ContentType || \"application/octet-stream\",\n size: response.ContentLength || 0,\n url: this.getPublicUrl(filePath),\n createdAt:\n response.LastModified?.toISOString() || new Date().toISOString(),\n };\n } catch (error: unknown) {\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Read a stored object back as bytes, or `null` when it is not there.\n *\n * The contract declares this OPTIONAL and, until now, no adapter supplied\n * it — so a caller reaching for `adapter.read` got `undefined` and fell\n * through whatever branch followed. An optional member nothing implements is\n * a contract that reads as a capability and behaves as an absence.\n *\n * BUFFERS rather than streams, because that is the declared return type and\n * widening it here would make this adapter answer a different question from\n * its siblings. That bounds what it should be used for: an asset the server\n * must serve from its own origin — a font file, a small document — rather\n * than arbitrarily large media, which belongs behind {@link getSignedUrl} or\n * a public URL. A streaming variant is a separate contract member, not a\n * quiet change to this one.\n *\n * `null` for a missing key rather than a throw, matching\n * {@link getMetadata}: absence is an ordinary answer about the bucket, while\n * a credential or network failure is not, so only the first is folded into\n * the return value.\n *\n * @param filePath - Storage path/key\n * @returns The object's bytes, or `null` when no such key exists\n */\n async read(\n filePath: string,\n options?: StorageReadOptions\n ): Promise<Buffer | null> {\n try {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n /*\n * The caller's deadline reaches the SDK, or it is advertised and inert.\n * `StorageReadOptions` promises a bound, and the URL-backed adapters keep\n * it by handing `timeoutMs` to `safeFetch`; without this the same option\n * does nothing here, so a stalled bucket holds the read open past a\n * deadline the caller was told applied — the worst kind of option, one\n * that validates and then has no effect.\n *\n * Aborting covers the BODY as well as the request. `transformToByteArray`\n * pulls the stream after `send` resolves, and a backend that answers\n * headers promptly and then stalls mid-transfer is exactly the case a\n * request-only timeout misses.\n */\n const response = await this.client.send(command, abortAfter(options));\n /*\n * An empty body is not a missing object, and the two must not collapse.\n * `GetObject` on a zero-byte key succeeds with no stream to transform,\n * so returning `null` here would report a stored empty file as absent —\n * and a caller deleting \"missing\" keys would then delete a real one.\n */\n if (response.Body === undefined) return Buffer.alloc(0);\n\n /*\n * Refused on the LENGTH the store reports, before the body is pulled\n * down. `transformToByteArray` buffers the whole object, so a caller that\n * knows its own limit has to be able to stop an oversized read rather\n * than discover the size after paying for it — which is what an\n * attachment path does when it checks a size only after buffering.\n */\n const cap = resolveReadBounds(options).maxBytes;\n // Cheap refusal first, where the store told us the size up front.\n this.refuseOverCap(filePath, response.ContentLength, cap);\n return await this.readCapped(filePath, response.Body, cap);\n } catch (error: unknown) {\n /*\n * Only an OBJECT-level absence becomes `null`. `isNotFoundError` treats\n * any 404 as missing, and `NoSuchBucket` is a 404 — so a bucket that was\n * deleted, renamed or mistyped would report every key in it as absent,\n * which is the configuration failure this method's contract exists to\n * tell apart from an ordinary miss. Named explicitly rather than by\n * status, because the status cannot separate them.\n */\n if (error instanceof Error && error.name === \"NoSuchBucket\") throw error;\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Generate signed URL for temporary private file access.\n *\n * Creates a pre-signed GetObject URL that grants temporary read access\n * to private files. Useful for serving files from private buckets.\n *\n * @param filePath - Storage path/key\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Pre-signed URL for downloading the file\n */\n async getSignedUrl(filePath: string, expiresIn?: number): Promise<string> {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: expiresIn ?? this.resolvedConfig.signedUrlExpiresIn,\n });\n }\n\n /**\n * Generate pre-signed URL for client-side uploads.\n *\n * Creates a pre-signed PutObject URL that allows direct uploads from\n * the browser to S3, bypassing server-side upload limits (e.g., Vercel's 4.5MB).\n *\n * @param key - Storage path/key for the upload\n * @param mimeType - MIME type of the file being uploaded\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Client upload data with URL, method, and headers\n */\n async getPresignedUploadUrl(\n key: string,\n mimeType: string,\n expiresIn?: number\n ): Promise<ClientUploadData> {\n const expiration = expiresIn ?? this.resolvedConfig.signedUrlExpiresIn;\n\n // Build PutObject command parameters\n const commandParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n ContentType: mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2\n if (!this.isR2) {\n commandParams.ACL = this.resolvedConfig.acl;\n }\n\n const command = new PutObjectCommand(commandParams);\n\n const uploadUrl = await getSignedUrl(this.client, command, {\n expiresIn: expiration,\n });\n\n return {\n uploadUrl,\n path: key,\n method: \"PUT\",\n headers: {\n \"Content-Type\": mimeType,\n },\n expiresAt: new Date(Date.now() + expiration * 1000),\n };\n }\n\n // ============================================================\n // Helper Methods\n // ============================================================\n\n /**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}\n *\n * @param filename - Original filename (will be sanitized)\n * @param folder - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\n private generateKey(filename: string, folder?: string): string {\n const sanitized = this.sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const prefix = folder ? `${folder}/${year}/${month}` : `${year}/${month}`;\n\n return `${prefix}/${uuid}-${sanitized}`;\n }\n\n /**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\n private sanitizeFilename(filename: string): string {\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n }\n\n /**\n * Check if an error is a \"not found\" error from S3.\n *\n * @param error - Error to check\n * @returns true if error indicates file not found\n */\n /**\n * The object's bytes, refusing once more than `cap` have actually arrived.\n *\n * `ContentLength` is OPTIONAL in the SDK's response type, and this adapter\n * explicitly supports MinIO, R2 and other compatible services — any of which\n * may omit it or report it wrongly. A preflight check against metadata is\n * therefore a courtesy that fails OPEN: trusting it alone leaves the cap\n * unenforced exactly where an unusual backend is involved, which is where a\n * caller most needs it.\n *\n * Counted while consuming, so an oversized body is abandoned partway rather\n * than after it has all been buffered — the cap exists to bound memory, and\n * one applied after `transformToByteArray` has already spent it.\n *\n * Falls back to buffering when the body is not async-iterable, which the SDK\n * types permit. Stated rather than hidden: on that path the cap bounds what a\n * caller RECEIVES rather than what the process allocates.\n */\n private async readCapped(\n filePath: string,\n body: NonNullable<GetObjectCommandOutput[\"Body\"]>,\n cap: number\n ): Promise<Buffer> {\n /*\n * Probed rather than assumed. The SDK types the body as a union whose Node\n * member is async-iterable and whose browser members are not, and this\n * package supports compatible services whose transport may differ — so the\n * capability is read off the value in hand instead of from the type.\n */\n const iterable = body as Partial<AsyncIterable<Uint8Array>>;\n if (typeof iterable[Symbol.asyncIterator] !== \"function\") {\n const whole = Buffer.from(await body.transformToByteArray());\n if (whole.byteLength > cap) {\n throw new StorageReadTooLargeError(filePath, cap, whole.byteLength);\n }\n return whole;\n }\n\n const chunks: Buffer[] = [];\n let received = 0;\n for await (const chunk of body as AsyncIterable<Uint8Array>) {\n received += chunk.byteLength;\n if (received > cap) {\n throw new StorageReadTooLargeError(filePath, cap, received);\n }\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks);\n }\n\n /**\n * Refuse a read whose object is already known to exceed the caller's cap.\n *\n * Its own method rather than three conditions inside `read`, because the\n * decision has nothing to do with the surrounding error handling and reads\n * as noise there — and because `read`'s branching is the part a reviewer has\n * to hold in their head to check the absent-versus-unavailable distinction.\n *\n * Both `undefined` cases mean \"no opinion\" and pass: a caller that named no\n * cap has none, and a store that reported no length gives nothing to compare.\n * The second is deliberately permissive — refusing an unmeasured object would\n * reject valid reads whenever S3 omits the header.\n */\n private refuseOverCap(\n filePath: string,\n declared: number | undefined,\n cap: number | undefined\n ): void {\n if (cap === undefined || declared === undefined || declared <= cap) return;\n /*\n * The SHARED refusal rather than a generic internal error, because callers\n * act on the difference. The email attachment path answers an over-cap read\n * with a size error the author can do something about; anything else is\n * wrapped as an opaque storage failure, so throwing `internal` here would\n * turn a fixable refusal into one that tells them nothing.\n */\n throw new StorageReadTooLargeError(filePath, cap, declared);\n }\n\n private isNotFoundError(error: unknown): boolean {\n if (error && typeof error === \"object\") {\n const e = error as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n return e.name === \"NotFound\" || e.$metadata?.httpStatusCode === 404;\n }\n return false;\n }\n\n // ============================================================\n // Public Accessors\n // ============================================================\n\n /**\n * Get the S3 client instance.\n * Useful for advanced operations not covered by the adapter interface.\n */\n getClient(): S3Client {\n return this.client;\n }\n\n /**\n * Get the bucket name.\n */\n getBucket(): string {\n return this.resolvedConfig.bucket;\n }\n\n /**\n * Get the AWS region.\n */\n getRegion(): string {\n return this.resolvedConfig.region;\n }\n\n /**\n * Check if this adapter is configured for Cloudflare R2.\n */\n isCloudflareR2(): boolean {\n return this.isR2;\n }\n}\n","/**\n * S3 Storage Plugin\n *\n * Factory function that creates a storage plugin for AWS S3 and S3-compatible services.\n * Returns a StoragePlugin that can be registered with MediaStorage.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With collection-specific configuration\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * collections: {\n * // Simple enable with defaults\n * media: true,\n *\n * // Full configuration for private documents\n * 'private-docs': {\n * prefix: 'private/',\n * clientUploads: true,\n * signedDownloads: true,\n * signedUrlExpiresIn: 3600\n * }\n * }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { StoragePlugin, ClientUploadData } from \"nextly/storage\";\n\nimport { S3StorageAdapter } from \"./adapter\";\nimport type { S3StorageConfig } from \"./types\";\n\n// ============================================================\n// Plugin Factory Function\n// ============================================================\n\n/**\n * Create an S3 storage plugin for Nextly.\n *\n * This factory function creates a StoragePlugin that can be added to\n * the `storage` array in `nextly.config.ts`. It supports:\n *\n * - AWS S3 (standard)\n * - Cloudflare R2 (S3-compatible)\n * - MinIO (S3-compatible, self-hosted)\n * - DigitalOcean Spaces (S3-compatible)\n * - Any other S3-compatible service\n *\n * @param config - S3 storage configuration\n * @returns A StoragePlugin that MediaStorage can register\n *\n * @throws Error if bucket is not provided (via adapter)\n * @throws Error if region is not provided (via adapter)\n */\nexport function s3Storage(config: S3StorageConfig): StoragePlugin {\n // Handle disabled plugin\n // When disabled, return a plugin with no collections and null adapter\n // MediaStorage.registerPlugin() checks for null adapter and skips registration\n if (config.enabled === false) {\n return {\n name: \"s3-storage\",\n type: \"s3\",\n collections: {},\n adapter: null as unknown as StoragePlugin[\"adapter\"],\n };\n }\n\n // Create the S3 adapter\n // Adapter constructor validates required config (bucket, region)\n const adapter = new S3StorageAdapter(config);\n\n // Build and return the plugin\n const plugin: StoragePlugin = {\n name: \"s3-storage\",\n type: \"s3\",\n collections: config.collections,\n adapter,\n\n /**\n * Generate a pre-signed URL for client-side uploads.\n *\n * This allows files to be uploaded directly from the browser to S3,\n * bypassing server-side upload limits (e.g., Vercel's 4.5MB limit).\n *\n * @param filename - Original filename from the client\n * @param mimeType - MIME type of the file\n * @param collection - Collection slug this upload belongs to\n * @returns Client upload data with pre-signed URL\n */\n async getClientUploadUrl(\n filename: string,\n mimeType: string,\n collection: string\n ): Promise<ClientUploadData> {\n // Get collection-specific configuration\n const collectionConfig = config.collections[collection];\n const prefix =\n typeof collectionConfig === \"object\"\n ? collectionConfig.prefix\n : undefined;\n\n // Generate storage key matching adapter's format\n // Format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n const key = generateStorageKey(filename, prefix);\n\n // Get pre-signed upload URL from adapter\n return adapter.getPresignedUploadUrl(key, mimeType);\n },\n\n /**\n * Generate a signed URL for private file downloads.\n *\n * Creates a time-limited URL for accessing files in private buckets.\n * Only works when collection has `signedDownloads: true`.\n *\n * @param path - Storage path/key of the file\n * @param expiresIn - URL validity duration in seconds\n * @returns Signed URL for downloading the file\n */\n async getSignedDownloadUrl(\n path: string,\n expiresIn?: number\n ): Promise<string> {\n return adapter.getSignedUrl(\n path,\n expiresIn ?? config.signedUrlExpiresIn ?? 3600\n );\n },\n };\n\n return plugin;\n}\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n * This matches the format used by S3StorageAdapter for consistency.\n *\n * @param filename - Original filename (will be sanitized)\n * @param prefix - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\nfunction generateStorageKey(filename: string, prefix?: string): string {\n const sanitized = sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const keyPrefix = prefix ? `${prefix}${year}/${month}` : `${year}/${month}`;\n\n return `${keyPrefix}/${uuid}-${sanitized}`;\n}\n\n/**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * Removes path separators and replaces unsafe characters with hyphens.\n * Keeps only: a-z, A-Z, 0-9, dot, underscore, hyphen\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\nfunction sanitizeFilename(filename: string): string {\n // Extract basename (remove any path components)\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n // Replace unsafe characters with hyphens\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n}\n","/**\n * @nextly/storage-s3\n *\n * AWS S3 storage adapter for Nextly CMS.\n * Also works with S3-compatible services like Cloudflare R2, MinIO, and DigitalOcean Spaces.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With MinIO\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\n// ============================================================\n// S3 Storage Plugin Export (Primary API)\n// ============================================================\n\nexport { s3Storage } from \"./plugin\";\n\n// ============================================================\n// S3 Storage Adapter Export\n// ============================================================\n\nexport { S3StorageAdapter } from \"./adapter\";\n\n// ============================================================\n// S3 Types Export\n// ============================================================\n\nexport type {\n S3StorageConfig,\n S3ObjectACL,\n S3CollectionConfig,\n S3CollectionStorageMap,\n ResolvedS3Config,\n} from \"./types\";\n\n// ============================================================\n// Package Metadata\n// ============================================================\n\nexport const PACKAGE_NAME = \"@nextly/storage-s3\";\nexport const PACKAGE_VERSION = \"0.1.0\";\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
|
4
4
|
|
|
5
5
|
// src/adapter.ts
|
|
6
6
|
|
|
7
|
-
// ../nextly/dist/chunk-
|
|
7
|
+
// ../nextly/dist/chunk-IAZJNA2U.mjs
|
|
8
8
|
function isDbError(err) {
|
|
9
9
|
if (!err || typeof err !== "object") return false;
|
|
10
10
|
const obj = err;
|
|
@@ -107,6 +107,13 @@ var NEXTLY_ERROR_STATUS = {
|
|
|
107
107
|
// Plugin platform (P4) — contributes.routes collection (D25).
|
|
108
108
|
NEXTLY_ROUTE_COLLISION: 409,
|
|
109
109
|
NEXTLY_ROUTE_INVALID_PATH: 400,
|
|
110
|
+
// A root-mounted route declared where the request pipeline never reaches the
|
|
111
|
+
// root pass. 400 like an invalid path and not 409: nothing else claims the
|
|
112
|
+
// address, the declaration simply cannot be served from there.
|
|
113
|
+
NEXTLY_ROUTE_UNREACHABLE_ROOT: 400,
|
|
114
|
+
// A `mount` outside the declared union, which only an untyped caller can
|
|
115
|
+
// produce. 400 for the same reason: the declaration itself is unserveable.
|
|
116
|
+
NEXTLY_ROUTE_INVALID_MOUNT: 400,
|
|
110
117
|
// An email transport whose library is an optional peer dependency the host
|
|
111
118
|
// has not installed. 503 rather than 500: the request is not malformed and
|
|
112
119
|
// nothing is broken, the install simply cannot carry it out yet, and the
|
|
@@ -444,7 +451,7 @@ var NextlyError = class _NextlyError extends Error {
|
|
|
444
451
|
}
|
|
445
452
|
};
|
|
446
453
|
|
|
447
|
-
// ../nextly/dist/chunk-
|
|
454
|
+
// ../nextly/dist/chunk-T3OVIJZM.mjs
|
|
448
455
|
var StorageReadTooLargeError = class extends NextlyError {
|
|
449
456
|
constructor(path, maxBytes, size) {
|
|
450
457
|
super({
|
|
@@ -462,7 +469,7 @@ var StorageReadTooLargeError = class extends NextlyError {
|
|
|
462
469
|
}
|
|
463
470
|
};
|
|
464
471
|
|
|
465
|
-
// ../nextly/dist/chunk-
|
|
472
|
+
// ../nextly/dist/chunk-YGE6AGZG.mjs
|
|
466
473
|
var DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
467
474
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
468
475
|
var DEFAULT_READ_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../nextly/dist/chunk-HJ5IQPOY.mjs","../../nextly/dist/chunk-W77AQUUQ.mjs","../../nextly/dist/chunk-C2VHLQKK.mjs","../src/adapter.ts","../src/plugin.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// src/database/errors.ts\nvar DbError = class extends Error {\n kind;\n dialect;\n code;\n meta;\n cause;\n constructor(args) {\n super(args.message);\n this.name = \"DbError\";\n this.kind = args.kind;\n this.dialect = args.dialect;\n this.code = args.code;\n this.meta = args.meta;\n this.cause = args.cause;\n }\n};\nfunction isDbError(err) {\n if (!err || typeof err !== \"object\") return false;\n const obj = err;\n return obj.name === \"DbError\" && typeof obj.kind === \"string\";\n}\nfunction toDbError(dialect, error, meta) {\n if (isDbError(error)) return error;\n const code = safeCode(error);\n const rawMsg = error != null && typeof error === \"object\" && \"message\" in error ? error.message : void 0;\n const message = typeof rawMsg === \"string\" ? rawMsg : \"Database error\";\n let kind = \"internal\";\n switch (dialect) {\n case \"postgresql\":\n kind = mapPostgresCodeToKind(code);\n break;\n case \"mysql\":\n kind = mapMySqlCodeToKind(code);\n break;\n case \"sqlite\":\n kind = mapSqliteCodeToKind(code);\n break;\n }\n if ((dialect === \"mysql\" || dialect === \"sqlite\") && (kind === \"constraint\" || kind === \"internal\") && isLikelyUniqueViolationMessage(dialect, message)) {\n kind = \"unique-violation\";\n }\n if (kind === \"internal\" && code) {\n const c = code.toUpperCase();\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n kind = \"connection-lost\";\n else if (c === \"ETIMEDOUT\") kind = \"timeout\";\n }\n return new DbError({ message, kind, dialect, code, meta, cause: error });\n}\nfunction safeCode(error) {\n if (!error || typeof error !== \"object\") return void 0;\n const e = error;\n let c = e.code ?? e.sqlState ?? e.state ?? e.errno ?? void 0;\n if (!c && typeof e.originalError?.code === \"string\") c = e.originalError.code;\n if (!c && typeof e.cause?.code === \"string\") c = e.cause.code;\n if (!c && typeof e.severity === \"string\" && typeof e.code === \"string\")\n c = e.code;\n if (typeof c === \"number\") return String(c);\n if (typeof c === \"string\" && c.length > 0) return c;\n return void 0;\n}\nfunction mapPostgresCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"40P01\") return \"deadlock\";\n if (c === \"40001\") return \"serialization-failure\";\n if (c === \"57014\") return \"timeout\";\n if (c === \"23505\") return \"unique-violation\";\n if (c === \"23503\") return \"fk-violation\";\n if (c === \"23502\") return \"not-null-violation\";\n if (c === \"42601\") return \"syntax\";\n if (c.startsWith(\"08\") || c === \"57P01\") return \"connection-lost\";\n if (c.startsWith(\"23\")) return \"constraint\";\n return \"internal\";\n}\nfunction mapMySqlCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"1213\" || c === \"ER_LOCK_DEADLOCK\") return \"deadlock\";\n if (c === \"1205\" || c === \"ER_LOCK_WAIT_TIMEOUT\") return \"timeout\";\n if (c === \"1062\" || c === \"ER_DUP_ENTRY\") return \"unique-violation\";\n if (c === \"23000\") return \"constraint\";\n if (c === \"1064\" || c === \"ER_PARSE_ERROR\") return \"syntax\";\n if (c === \"1452\" || c === \"ER_NO_REFERENCED_ROW_2\") return \"fk-violation\";\n if (c === \"1451\" || c === \"ER_ROW_IS_REFERENCED_2\") return \"fk-violation\";\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n return \"connection-lost\";\n if (c === \"ETIMEDOUT\") return \"timeout\";\n return \"internal\";\n}\nfunction mapSqliteCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"SQLITE_BUSY\" || c === \"SQLITE_LOCKED\") return \"deadlock\";\n if (c === \"SQLITE_CONSTRAINT_UNIQUE\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_PRIMARYKEY\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_FOREIGNKEY\") return \"fk-violation\";\n if (c === \"SQLITE_CONSTRAINT_NOTNULL\") return \"not-null-violation\";\n if (c === \"SQLITE_INTERRUPT\") return \"timeout\";\n if (c === \"SQLITE_ERROR\") return \"syntax\";\n if (c.startsWith(\"SQLITE_CONSTRAINT\")) return \"constraint\";\n return \"internal\";\n}\nfunction isLikelyUniqueViolationMessage(dialect, message) {\n const m = message.toLowerCase();\n if (dialect === \"mysql\") {\n return m.includes(\"duplicate entry\") || m.includes(\"duplicate key\");\n }\n if (dialect === \"sqlite\") {\n return m.includes(\"unique constraint failed\");\n }\n return false;\n}\n\n// src/errors/error-codes.ts\nvar NEXTLY_ERROR_STATUS = {\n VALIDATION_ERROR: 400,\n INVALID_INPUT: 400,\n AUTH_REQUIRED: 401,\n AUTH_INVALID_CREDENTIALS: 401,\n TOKEN_EXPIRED: 401,\n FORBIDDEN: 403,\n // The schema builder is off in this environment (production by default).\n // Separate from FORBIDDEN: the caller's permissions are not the problem.\n BUILDER_DISABLED: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n DUPLICATE: 409,\n RATE_LIMITED: 429,\n PAYLOAD_TOO_LARGE: 413,\n UNSUPPORTED_MEDIA_TYPE: 415,\n // 422: understood, well-formed, and refused on a rule the caller can act on.\n // Deliberately NOT added to CANONICAL_CODE_FOR_STATUS -- that list drives the\n // status -> code direction, where 422 stays mapped to INVALID_INPUT.\n BUSINESS_RULE_VIOLATION: 422,\n INTERNAL_ERROR: 500,\n DATABASE_ERROR: 500,\n EXTERNAL_SERVICE_ERROR: 502,\n SERVICE_UNAVAILABLE: 503,\n // Outbound-fetch safety (utils/validate-external-url): a URL refused for SSRF\n // safety, and a fetch that timed out / exceeded the size cap / failed to decode.\n EXTERNAL_URL_BLOCKED: 400,\n EXTERNAL_REQUEST_FAILED: 502,\n FILENAME_INVALID: 400,\n EXTENSION_BLOCKED: 400,\n MIME_BLOCKED: 415,\n MIME_NOT_ALLOWED: 415,\n SIZE_EXCEEDED: 413,\n // A stored object exceeded the cap a READ was given, which is a different\n // question from SIZE_EXCEEDED above: that one refuses an upload the caller\n // is sending, this one refuses to buffer an object already stored. Kept\n // apart so a caller discriminating on the code cannot match both.\n STORAGE_READ_TOO_LARGE: 413,\n // A stored object did not answer within the deadline the read was given. 504\n // rather than 500 because the failure is the BACKEND not answering, and a\n // caller can act on that difference: a gateway timeout is worth retrying, an\n // internal error is not.\n STORAGE_READ_TIMEOUT: 504,\n // The store answered, and answered badly. 502 rather than 500 because the\n // fault is UPSTREAM of this process: a caller can retry it, and an operator\n // reading the log needs to look at the bucket rather than at this service.\n // Distinct from the timeout above, which never got an answer at all.\n STORAGE_READ_UNREACHABLE: 502,\n MAGIC_BYTE_MISMATCH: 400,\n SVG_SANITIZATION_FAILED: 400,\n UNSUPPORTED_FOR_BACKEND: 415,\n // Plan B — schema bookkeeping consolidation.\n NEXTLY_LEGACY_BOOKKEEPING_DETECTED: 409,\n NEXTLY_UPGRADE_TABLE_NAME_COLLISION: 409,\n NEXTLY_UPGRADE_IN_PROGRESS: 409,\n // Plan C2 — nextly migrate phases.\n NEXTLY_MIGRATE_LOCK_BUSY: 409,\n NEXTLY_BASELINE_LOCK_NOT_HELD: 409,\n NEXTLY_RESOLVE_LOCK_NOT_HELD: 409,\n // Boot refused to serve: the migrate lock stayed held past the wait deadline,\n // so this process never established whether the schema matches the code. 503\n // rather than 409 — a load balancer should take the instance out of rotation\n // and retry it, which is exactly the recovery this refusal wants.\n NEXTLY_BOOT_MIGRATIONS_NOT_RUN: 503,\n // Still running rather than refused. 503 for the same reason: retry shortly.\n NEXTLY_BOOT_MIGRATIONS_PENDING: 503,\n NEXTLY_CORE_DESTRUCTIVE_REFUSED: 409,\n NEXTLY_MIGRATION_DRIFT: 409,\n NEXTLY_MIGRATION_APPLY_FAILED: 500,\n // Plan C3 — migrate:resolve recovery command.\n NEXTLY_MIGRATION_FILE_MISSING: 404,\n NEXTLY_MIGRATION_SNAPSHOT_MISSING: 404,\n NEXTLY_MIGRATION_RESOLVE_DRIFT: 409,\n NEXTLY_MIGRATION_RESOLVE_PRECONDITION: 409,\n // Plan D — UI schema support.\n NEXTLY_UI_SCHEMA_INVALID: 400,\n NEXTLY_SCHEMA_SLUG_COLLISION: 409,\n NEXTLY_SCHEMA_RELATION_TARGET_MISSING: 400,\n // Plugin platform (P2b) — schema extend (contributes.extend) + relations (D15).\n NEXTLY_SCHEMA_EXTEND_TARGET_UNKNOWN: 400,\n NEXTLY_SCHEMA_EXTEND_FIELD_DUPLICATE: 409,\n NEXTLY_SCHEMA_CROSS_PLUGIN_RELATION: 409,\n // Plugin platform (P2c) — framework remap (.rename()).\n NEXTLY_SCHEMA_RENAME_UNKNOWN_TARGET: 400,\n // Plugin platform — a declared admin.clientConfig that cannot be delivered\n // to the browser, refused at boot rather than serialized mangled.\n NEXTLY_PLUGIN_CLIENT_CONFIG_INVALID: 500,\n // Plugin platform — a contributed admin widget that cannot be delivered to\n // the browser. Refused at boot because it is serialized into the ONE\n // `/api/admin-meta/workspace` payload: a value `JSON.stringify` throws on\n // fails that request for every admin, not just the widget's own card.\n NEXTLY_PLUGIN_ADMIN_WIDGET_INVALID: 500,\n // Plugin platform (P0) — boot-time plugin dependency/version resolution.\n PLUGIN_RESOLUTION_ERROR: 500,\n // Plugin platform (P4) — contributes.routes collection (D25).\n NEXTLY_ROUTE_COLLISION: 409,\n NEXTLY_ROUTE_INVALID_PATH: 400,\n // An email transport whose library is an optional peer dependency the host\n // has not installed. 503 rather than 500: the request is not malformed and\n // nothing is broken, the install simply cannot carry it out yet, and the\n // remedy is one command on the server rather than a change by the caller.\n NEXTLY_EMAIL_TRANSPORT_UNAVAILABLE: 503,\n // The tooling that compiles `nextly.config.ts` is an optional peer the host\n // has not installed. 503 rather than 500 for the same reason as the mail\n // transport above: nothing is broken and the request is not malformed, the\n // install simply cannot carry it out until one command is run.\n NEXTLY_CONFIG_TOOLING_UNAVAILABLE: 503\n};\nvar CANONICAL_CODE_FOR_STATUS = [\n \"VALIDATION_ERROR\",\n \"AUTH_REQUIRED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"PAYLOAD_TOO_LARGE\",\n \"UNSUPPORTED_MEDIA_TYPE\",\n \"RATE_LIMITED\",\n \"EXTERNAL_SERVICE_ERROR\",\n \"SERVICE_UNAVAILABLE\"\n];\nvar LEGACY_UNPROCESSABLE_STATUS = 422;\nvar STATUS_TO_CODE = {\n ...Object.fromEntries(\n CANONICAL_CODE_FOR_STATUS.map((code) => [NEXTLY_ERROR_STATUS[code], code])\n ),\n [LEGACY_UNPROCESSABLE_STATUS]: \"INVALID_INPUT\"\n};\nfunction statusToErrorCode(statusCode) {\n return STATUS_TO_CODE[statusCode] ?? \"INTERNAL_ERROR\";\n}\nvar GENERIC_PUBLIC_MESSAGE = {\n VALIDATION_ERROR: \"Validation failed.\",\n INVALID_INPUT: \"The request could not be processed.\",\n AUTH_REQUIRED: \"Authentication required.\",\n FORBIDDEN: \"You don't have permission to perform this action.\",\n NOT_FOUND: \"Not found.\",\n CONFLICT: \"The resource has changed since you last loaded it. Please refresh and try again.\",\n DUPLICATE: \"Resource already exists.\",\n PAYLOAD_TOO_LARGE: \"The request is too large.\",\n UNSUPPORTED_MEDIA_TYPE: \"That file type is not supported.\",\n RATE_LIMITED: \"Too many requests. Please try again later.\",\n EXTERNAL_SERVICE_ERROR: \"An upstream service failed.\",\n SERVICE_UNAVAILABLE: \"The service is temporarily unavailable.\",\n INTERNAL_ERROR: \"An unexpected error occurred.\"\n};\nfunction genericPublicMessage(code) {\n return GENERIC_PUBLIC_MESSAGE[code] ?? GENERIC_PUBLIC_MESSAGE.INTERNAL_ERROR;\n}\n\n// src/errors/nextly-error.ts\nvar DB_ERROR_MAPPING = {\n \"unique-violation\": {\n code: \"DUPLICATE\",\n statusCode: 409,\n publicMessage: \"Resource already exists.\"\n },\n \"fk-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"Referenced record does not exist.\"\n },\n \"not-null-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"A required field is missing.\"\n },\n constraint: {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"The provided data violates a constraint.\"\n },\n deadlock: {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n \"serialization-failure\": {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n timeout: {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"The operation timed out. Please try again.\"\n },\n \"connection-lost\": {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"A temporary database error occurred. Please try again.\"\n },\n syntax: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n },\n internal: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n }\n};\nvar NEXTLY_ERROR_BRAND = Symbol.for(\"nextly/NextlyError\");\nfunction hasBrand(value) {\n if (value === null || typeof value !== \"object\" && typeof value !== \"function\") {\n return false;\n }\n return value[NEXTLY_ERROR_BRAND] === true;\n}\nvar NextlyError = class _NextlyError extends Error {\n code;\n statusCode;\n publicMessage;\n publicData;\n messageKey;\n logMessage;\n logContext;\n cause;\n timestamp;\n constructor(opts) {\n super(opts.publicMessage);\n this.name = \"NextlyError\";\n this.code = opts.code;\n this.publicMessage = opts.publicMessage;\n this.publicData = opts.publicData;\n this.messageKey = opts.messageKey;\n this.logMessage = opts.logMessage;\n this.logContext = opts.logContext;\n this.cause = opts.cause;\n this.timestamp = /* @__PURE__ */ new Date();\n this.statusCode = _NextlyError.resolveStatusCode(opts);\n Error.captureStackTrace?.(this, _NextlyError);\n }\n static {\n _NextlyError.prototype[NEXTLY_ERROR_BRAND] = true;\n }\n static resolveStatusCode(opts) {\n if (typeof opts.statusCode === \"number\") return opts.statusCode;\n if (opts.code in NEXTLY_ERROR_STATUS) {\n return NEXTLY_ERROR_STATUS[opts.code];\n }\n return 500;\n }\n /** HTTP-safe JSON. Strips logMessage / logContext / cause / stack. */\n toResponseJSON(requestId) {\n const json = {\n code: String(this.code),\n message: this.publicMessage,\n requestId\n };\n if (this.messageKey) json.messageKey = this.messageKey;\n if (this.publicData !== void 0) json.data = this.publicData;\n return json;\n }\n /** Operator-facing JSON for log lines. Includes everything. */\n toLogJSON(requestId) {\n return {\n code: this.code,\n statusCode: this.statusCode,\n publicMessage: this.publicMessage,\n messageKey: this.messageKey,\n logMessage: this.logMessage,\n logContext: this.logContext,\n cause: this.cause ? {\n name: this.cause.name,\n message: this.cause.message,\n stack: this.cause.stack\n } : void 0,\n timestamp: this.timestamp.toISOString(),\n requestId\n };\n }\n // ────────────────────────────────────────────────────────────────────\n // Static factories — the recommended throw site for every common case.\n // Public messages here follow the §13.8 rubric: complete sentence,\n // generic, no identifiers, no value echoing, no policy hints.\n // ────────────────────────────────────────────────────────────────────\n static invalidCredentials(opts) {\n return new _NextlyError({\n code: \"AUTH_INVALID_CREDENTIALS\",\n publicMessage: \"Invalid email or password.\",\n logMessage: \"Login failed\",\n logContext: opts?.logContext\n });\n }\n static authRequired(opts) {\n return new _NextlyError({\n code: \"AUTH_REQUIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n /**\n * Distinct from `authRequired`: the caller was authenticated but the\n * session token expired. Clients key on the TOKEN_EXPIRED *code* to\n * silently refresh and retry rather than redirecting to login; the public\n * message stays the generic spec §13.6 string (\"Authentication required.\")\n * so the wire never reveals the session state — same as `authRequired`.\n */\n static tokenExpired(opts) {\n return new _NextlyError({\n code: \"TOKEN_EXPIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n static notFound(opts) {\n return new _NextlyError({\n code: \"NOT_FOUND\",\n publicMessage: opts?.message ?? \"Not found.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n static forbidden(opts) {\n return new _NextlyError({\n code: \"FORBIDDEN\",\n publicMessage: \"You don't have permission to perform this action.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * A call the caller got wrong, where naming the mistake IS the value of the\n * error.\n *\n * The only factory that takes its public message from the caller. The\n * generic messages elsewhere exist so an HTTP response cannot leak internal\n * detail; this one is for arguments and configuration a developer controls\n * and must be told about — a missing option, an unusable combination — where\n * `internal()` would reduce the one useful sentence to \"An unexpected error\n * occurred.\" Do not pass user-supplied data through it.\n */\n static invalidInput(opts) {\n return new _NextlyError({\n code: \"INVALID_INPUT\",\n publicMessage: opts.message,\n logContext: opts.logContext\n });\n }\n static validation(opts) {\n return new _NextlyError({\n code: \"VALIDATION_ERROR\",\n publicMessage: \"Validation failed.\",\n publicData: { errors: opts.errors },\n cause: opts.cause,\n logContext: opts.logContext\n });\n }\n static conflict(opts) {\n return new _NextlyError({\n code: \"CONFLICT\",\n publicMessage: opts?.message ?? \"The resource has changed since you last loaded it. Please refresh and try again.\",\n cause: opts?.cause,\n logContext: { reason: opts?.reason, ...opts?.logContext }\n });\n }\n static duplicate(opts) {\n return new _NextlyError({\n code: \"DUPLICATE\",\n publicMessage: \"Resource already exists.\",\n logContext: opts?.logContext\n });\n }\n static rateLimited(opts) {\n return new _NextlyError({\n code: \"RATE_LIMITED\",\n publicMessage: \"Too many requests. Please try again later.\",\n publicData: opts?.retryAfterSeconds !== void 0 ? { retryAfterSeconds: opts.retryAfterSeconds } : void 0,\n logContext: opts?.logContext\n });\n }\n static internal(opts) {\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n publicMessage: \"An unexpected error occurred.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n // Accepts an optional `logMessage` override so callers (e.g. the health\n // route) can record a specific operator narrative (\"Health check failed\")\n // while the public message stays canonical per spec §13.8.5.\n static serviceUnavailable(opts) {\n return new _NextlyError({\n code: \"SERVICE_UNAVAILABLE\",\n publicMessage: opts?.publicMessage ?? \"Service unavailable. Please try again later.\",\n logMessage: opts?.logMessage,\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * Convert a DbError (or arbitrary unknown thrown by the DB layer) to a\n * NextlyError with a generic public message and rich logContext. Used by\n * `withDbErrors` for auto-conversion (Pattern A) and by services that\n * catch DB errors at boundaries (Pattern B). Spec §8.2 mapping table.\n *\n * Never leaks DB driver text, constraint names, or table names into\n * `publicMessage`. All DB context goes into `logContext`. The original\n * DbError is preserved as `cause`.\n */\n static fromDatabaseError(error) {\n if (isDbError(error)) {\n const mapping = DB_ERROR_MAPPING[error.kind];\n const logContext = {\n dbKind: error.kind,\n dialect: error.dialect\n };\n if (error.code !== void 0) logContext.dbCode = error.code;\n if (error.meta !== void 0) logContext.meta = error.meta;\n return new _NextlyError({\n code: mapping.code,\n statusCode: mapping.statusCode,\n publicMessage: mapping.publicMessage,\n logMessage: \"Database error\",\n logContext,\n cause: error\n });\n }\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\",\n logMessage: \"Non-DbError passed to fromDatabaseError\",\n cause: error instanceof Error ? error : void 0,\n logContext: error instanceof Error ? void 0 : { value: String(error) }\n });\n }\n // ────────────────────────────────────────────────────────────────────\n // Type guards. Structural rather than `instanceof` so they survive\n // package-boundary mismatches (one consumer's NextlyError is a\n // different module instance from another's).\n // ────────────────────────────────────────────────────────────────────\n static is(err) {\n return hasBrand(err);\n }\n static isCode(err, code) {\n return hasBrand(err) && err.code === code;\n }\n static isNotFound(err) {\n return _NextlyError.isCode(err, \"NOT_FOUND\");\n }\n static isValidation(err) {\n return _NextlyError.isCode(err, \"VALIDATION_ERROR\");\n }\n static isAuthRequired(err) {\n return _NextlyError.isCode(err, \"AUTH_REQUIRED\");\n }\n static isForbidden(err) {\n return _NextlyError.isCode(err, \"FORBIDDEN\");\n }\n static isConflict(err) {\n return _NextlyError.isCode(err, \"CONFLICT\");\n }\n static isRateLimited(err) {\n return _NextlyError.isCode(err, \"RATE_LIMITED\");\n }\n};\n\nexport {\n DbError,\n isDbError,\n toDbError,\n safeCode,\n NEXTLY_ERROR_STATUS,\n statusToErrorCode,\n genericPublicMessage,\n NextlyError\n};\n","import {\n NextlyError\n} from \"./chunk-HJ5IQPOY.mjs\";\n\n// src/storage/read-errors.ts\nvar StorageReadTooLargeError = class extends NextlyError {\n constructor(path, maxBytes, size) {\n super({\n code: \"STORAGE_READ_TOO_LARGE\",\n publicMessage: \"The stored file is larger than the limit for this read.\",\n logContext: {\n path,\n maxBytes,\n ...size === void 0 ? {} : { size }\n }\n });\n this.path = path;\n this.maxBytes = maxBytes;\n this.size = size;\n }\n};\nfunction isStorageReadTooLarge(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TOO_LARGE\";\n}\nvar StorageReadTimeoutError = class extends NextlyError {\n constructor(path, timeoutMs) {\n super({\n code: \"STORAGE_READ_TIMEOUT\",\n publicMessage: \"The stored file could not be read in time.\",\n logContext: { path, timeoutMs }\n });\n this.path = path;\n this.timeoutMs = timeoutMs;\n }\n};\nfunction isStorageReadTimeout(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TIMEOUT\";\n}\n\nexport {\n StorageReadTooLargeError,\n isStorageReadTooLarge,\n StorageReadTimeoutError,\n isStorageReadTimeout\n};\n","import {\n StorageReadTimeoutError,\n StorageReadTooLargeError\n} from \"./chunk-W77AQUUQ.mjs\";\nimport {\n NextlyError\n} from \"./chunk-HJ5IQPOY.mjs\";\n\n// src/utils/validate-external-url.ts\nvar IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$/;\nvar PRIVATE_IPV4_CIDRS = [\n \"0.0.0.0/8\",\n // RFC1122 \"this network\" / wildcard\n \"10.0.0.0/8\",\n // RFC1918\n \"100.64.0.0/10\",\n // RFC6598 CGNAT\n \"127.0.0.0/8\",\n // RFC1122 loopback\n \"169.254.0.0/16\",\n // RFC3927 link-local + AWS/GCP metadata\n \"172.16.0.0/12\",\n // RFC1918\n \"192.0.0.0/24\",\n // RFC6890 IETF protocol assignments\n \"192.0.2.0/24\",\n // RFC5737 documentation\n \"192.168.0.0/16\",\n // RFC1918\n \"198.18.0.0/15\",\n // RFC2544 benchmarking\n \"198.51.100.0/24\",\n // RFC5737 documentation\n \"203.0.113.0/24\",\n // RFC5737 documentation\n \"224.0.0.0/4\",\n // RFC5771 multicast\n \"240.0.0.0/4\"\n // RFC1112 reserved\n];\nvar CLOUD_METADATA_HOSTS = /* @__PURE__ */ new Set([\n \"metadata.google.internal\",\n \"metadata.googleapis.com\",\n \"metadata\"\n // GCP short form\n]);\nvar ExternalUrlError = class extends NextlyError {\n constructor(message, url) {\n super({\n code: \"EXTERNAL_URL_BLOCKED\",\n publicMessage: message,\n logContext: { url }\n });\n this.url = url;\n this.name = \"ExternalUrlError\";\n }\n};\nasync function validateExternalUrl(rawUrl, options = {}) {\n let parsed;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new ExternalUrlError(\"Invalid URL\", rawUrl);\n }\n const hostname = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n const isLocalhostHostname = hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n const baseProtocols = options.allowedProtocols ?? [\"https:\"];\n const protocols = options.allowLocalhost && isLocalhostHostname ? [...baseProtocols, \"http:\"] : baseProtocols;\n if (!protocols.includes(parsed.protocol)) {\n throw new ExternalUrlError(\n `Protocol ${parsed.protocol} not allowed (allowed: ${protocols.join(\", \")})`,\n rawUrl\n );\n }\n if (CLOUD_METADATA_HOSTS.has(hostname)) {\n throw new ExternalUrlError(\n `Cloud-metadata hostname rejected: ${parsed.hostname}`,\n rawUrl\n );\n }\n if (IPV4_RE.test(hostname)) {\n if (!isPublicIpv4(\n hostname,\n options.allowLocalhost === true && hostname === \"127.0.0.1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 4 };\n }\n if (hostname.includes(\":\")) {\n if (!isPublicIpv6(\n hostname,\n options.allowLocalhost === true && hostname === \"::1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 6 };\n }\n const { lookup } = await import(\"dns/promises\");\n let addresses;\n try {\n addresses = await lookup(hostname, { all: true });\n } catch (err) {\n throw new ExternalUrlError(\n `DNS lookup failed: ${err.message}`,\n rawUrl\n );\n }\n if (addresses.length === 0) {\n throw new ExternalUrlError(\"No IPs returned for host\", rawUrl);\n }\n for (const { address, family } of addresses) {\n const allow = options.allowLocalhost === true && isLocalhostHostname && family === 4;\n const ok = family === 4 ? isPublicIpv4(address, allow) : isPublicIpv6(address, allow);\n if (!ok) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${address}`,\n rawUrl\n );\n }\n }\n const first = addresses[0];\n return {\n url: parsed,\n pinnedIp: first.address,\n family: first.family === 6 ? 6 : 4\n };\n}\nvar DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;\nvar DEFAULT_TIMEOUT_MS = 3e4;\nvar SafeFetchError = class extends NextlyError {\n constructor(message, url, reason, status) {\n super({\n code: \"EXTERNAL_REQUEST_FAILED\",\n publicMessage: message,\n logContext: { url, reason }\n });\n this.url = url;\n this.reason = reason;\n this.status = status;\n this.name = \"SafeFetchError\";\n }\n};\nfunction createPinnedLookup(ip, family) {\n return (_hostname, options, callback) => {\n if (options && options.all) {\n callback(null, [{ address: ip, family }]);\n } else {\n callback(null, ip, family);\n }\n };\n}\nasync function safeFetch(rawUrl, options = {}) {\n const {\n allowLocalhost,\n allowedProtocols,\n method,\n headers,\n body,\n signal,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n timeoutMs = DEFAULT_TIMEOUT_MS\n } = options;\n const controller = new AbortController();\n const forwardAbort = () => controller.abort(signal?.reason);\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", forwardAbort, { once: true });\n }\n const timer = setTimeout(\n () => controller.abort(\n new SafeFetchError(`Request exceeded ${timeoutMs}ms`, rawUrl, \"timeout\")\n ),\n timeoutMs\n );\n try {\n const validated = await abortable(\n validateExternalUrl(rawUrl, { allowLocalhost, allowedProtocols }),\n controller.signal\n );\n return await pinnedFetch(validated, {\n method,\n headers,\n body,\n signal: controller.signal,\n maxResponseBytes\n });\n } finally {\n clearTimeout(timer);\n if (signal) signal.removeEventListener(\"abort\", forwardAbort);\n }\n}\nfunction abortError(signal) {\n const reason = signal.reason;\n if (reason instanceof Error) return reason;\n return new DOMException(\"The operation was aborted\", \"AbortError\");\n}\nfunction abortable(promise, signal) {\n if (signal.aborted) return Promise.reject(abortError(signal));\n let onAbort;\n const aborted = new Promise((_resolve, reject) => {\n onAbort = () => reject(abortError(signal));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n return Promise.race([promise, aborted]).finally(\n () => signal.removeEventListener(\"abort\", onAbort)\n );\n}\nasync function pinnedFetch(validated, init) {\n const { url, pinnedIp, family } = validated;\n const httpMod = url.protocol === \"https:\" ? await import(\"https\") : await import(\"http\");\n const zlib = await import(\"zlib\");\n const lookup = createPinnedLookup(pinnedIp, family);\n const outHeaders = toOutgoingHeaders(init.headers);\n if (init.body != null) {\n delete outHeaders[\"transfer-encoding\"];\n outHeaders[\"content-length\"] = String(\n typeof init.body === \"string\" ? Buffer.byteLength(init.body) : init.body.byteLength\n );\n }\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn) => {\n if (settled) return;\n settled = true;\n fn();\n };\n const failure = (err) => init.signal.aborted ? abortError(init.signal) : err;\n const req = httpMod.request(\n url,\n {\n method: init.method ?? \"GET\",\n headers: outHeaders,\n lookup,\n // Fresh socket per request so a pooled connection can't reuse a\n // differently-resolved address, and so the pinned lookup always runs.\n agent: false,\n // The combined deadline/caller signal aborts the request in flight.\n signal: init.signal\n },\n (res) => {\n const chunks = [];\n let received = 0;\n res.on(\"data\", (chunk) => {\n received += chunk.length;\n if (received > init.maxResponseBytes) {\n res.destroy();\n req.destroy();\n settle(\n () => reject(\n new SafeFetchError(\n `Response body exceeded ${init.maxResponseBytes} bytes`,\n url.href,\n \"response-too-large\",\n res.statusCode\n )\n )\n );\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n const decoded = decodeBody(\n Buffer.concat(chunks),\n res.headers[\"content-encoding\"],\n zlib,\n init.maxResponseBytes,\n url.href,\n res.statusCode\n );\n if (decoded instanceof SafeFetchError) {\n settle(() => reject(decoded));\n return;\n }\n settle(\n () => resolve(toWhatwgResponse(res, decoded.body, decoded.decoded))\n );\n });\n res.on(\"error\", (err) => settle(() => reject(failure(err))));\n }\n );\n req.on(\"error\", (err) => settle(() => reject(failure(err))));\n if (!req.destroyed) {\n if (init.body != null) req.write(init.body);\n req.end();\n }\n });\n}\nfunction toOutgoingHeaders(headers) {\n const out = {};\n if (!headers) return out;\n const add = (rawKey, value) => {\n const key = rawKey.toLowerCase();\n if (key === \"host\") return;\n const existing = out[key];\n out[key] = existing === void 0 ? value : [].concat(existing, value);\n };\n if (headers instanceof Headers) {\n headers.forEach((value, key) => add(key, value));\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) add(key, value);\n } else {\n for (const [key, value] of Object.entries(headers)) add(key, value);\n }\n return out;\n}\nfunction toWhatwgResponse(res, body, stripContentHeaders) {\n const headers = new Headers();\n for (const [key, value] of Object.entries(res.headers)) {\n if (value == null) continue;\n const lower = key.toLowerCase();\n if (stripContentHeaders && (lower === \"content-encoding\" || lower === \"content-length\")) {\n continue;\n }\n const values = Array.isArray(value) ? value : [value];\n for (const v of values) {\n try {\n headers.append(key, v);\n } catch {\n }\n }\n }\n const raw = res.statusCode ?? 502;\n const status = raw >= 200 && raw <= 599 ? raw : 502;\n const nullBody = status === 204 || status === 205 || status === 304;\n const bytes = new Uint8Array(body.byteLength);\n bytes.set(body);\n return new Response(nullBody ? null : bytes, {\n status,\n statusText: res.statusMessage ?? \"\",\n headers\n });\n}\nfunction decodeBody(buf, encoding, zlib, cap, url, status) {\n if (!encoding) return { body: buf, decoded: false };\n if (buf.length === 0) return { body: buf, decoded: true };\n const layers = encoding.split(\",\").map((e) => e.trim().toLowerCase()).filter(Boolean);\n const opts = { maxOutputLength: cap };\n let current = buf;\n let decoded = false;\n try {\n for (let i = layers.length - 1; i >= 0; i--) {\n const enc = layers[i];\n if (enc === \"identity\") continue;\n if (enc === \"gzip\" || enc === \"x-gzip\") {\n current = zlib.gunzipSync(current, opts);\n } else if (enc === \"br\") {\n current = zlib.brotliDecompressSync(current, opts);\n } else if (enc === \"deflate\") {\n try {\n current = zlib.inflateSync(current, opts);\n } catch (inflateErr) {\n if (errorCode(inflateErr) === \"ERR_BUFFER_TOO_LARGE\")\n throw inflateErr;\n current = zlib.inflateRawSync(current, opts);\n }\n } else {\n if (decoded) {\n return new SafeFetchError(\n `Cannot fully decode content-encoding \"${encoding}\"`,\n url,\n \"decode-failed\",\n status\n );\n }\n return { body: buf, decoded: false };\n }\n decoded = true;\n }\n return { body: current, decoded };\n } catch (err) {\n const tooLarge = errorCode(err) === \"ERR_BUFFER_TOO_LARGE\";\n return new SafeFetchError(\n tooLarge ? `Decoded ${encoding} response body exceeded the size cap` : `Failed to decode ${encoding} response body`,\n url,\n tooLarge ? \"response-too-large\" : \"decode-failed\",\n status\n );\n }\n}\nfunction errorCode(err) {\n if (err != null && typeof err === \"object\" && \"code\" in err) {\n const code = err.code;\n return typeof code === \"string\" ? code : void 0;\n }\n return void 0;\n}\nfunction ipv4ToInt(ip) {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n let acc = 0;\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 0 || n > 255) return null;\n acc = acc << 8 | n;\n }\n return acc >>> 0;\n}\nfunction isIpv4InCidr(intIp, cidr) {\n const [addr, prefixRaw] = cidr.split(\"/\");\n const intCidr = ipv4ToInt(addr);\n if (intCidr === null) return false;\n const prefix = Number(prefixRaw);\n if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;\n const mask = prefix === 0 ? 0 : -1 >>> 32 - prefix << 32 - prefix;\n return (intIp & mask) >>> 0 === (intCidr & mask) >>> 0;\n}\nfunction isPublicIpv4(addr, allowLoopback) {\n const intIp = ipv4ToInt(addr);\n if (intIp === null) return false;\n if (allowLoopback && addr === \"127.0.0.1\") return true;\n for (const cidr of PRIVATE_IPV4_CIDRS) {\n if (isIpv4InCidr(intIp, cidr)) return false;\n }\n return true;\n}\nfunction mappedIpv4(lower) {\n const dotted = lower.match(/^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n if (dotted) return dotted[1];\n const hex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);\n if (hex) {\n const hi = parseInt(hex[1], 16);\n const lo = parseInt(hex[2], 16);\n return [hi >> 8, hi & 255, lo >> 8, lo & 255].join(\".\");\n }\n return null;\n}\nfunction isPublicIpv6(addr, allowLoopback) {\n const lower = addr.toLowerCase();\n if (allowLoopback && lower === \"::1\") return true;\n const mapped = mappedIpv4(lower);\n if (mapped) {\n return isPublicIpv4(mapped, allowLoopback);\n }\n if (lower === \"::\" || lower === \"::1\") return false;\n const firstHextet = lower.split(\":\")[0] || \"\";\n if (firstHextet.startsWith(\"fc\") || firstHextet.startsWith(\"fd\"))\n return false;\n if (firstHextet.startsWith(\"fe8\") || firstHextet.startsWith(\"fe9\"))\n return false;\n if (firstHextet.startsWith(\"fea\") || firstHextet.startsWith(\"feb\"))\n return false;\n if (firstHextet.startsWith(\"ff\")) return false;\n return true;\n}\n\n// src/storage/fetch-stored-bytes.ts\nasync function fetchStoredBytes(url, context, label, options, signal) {\n let response;\n try {\n response = await safeFetch(url, {\n ...options?.maxBytes === void 0 ? {} : { maxResponseBytes: options.maxBytes },\n /*\n * A caller's signal REPLACES the deadline rather than joining it.\n *\n * These adapters look the object's address up before fetching it, and\n * that lookup can stall too. Starting a fresh timer here would give the\n * fetch its own full budget on top of however long the lookup took, so a\n * read could outlive the deadline the caller was told applied — by\n * roughly double. One signal begun before the lookup governs both phases.\n */\n ...signal !== void 0 ? { signal } : options?.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }\n });\n } catch (error) {\n const verdict = classifyFetchFailure(error);\n if (verdict === \"absent\") return null;\n if (verdict === \"oversized\") {\n throw new StorageReadTooLargeError(\n context,\n resolveReadBounds(options).maxBytes\n );\n }\n throw error;\n }\n if (response.status === 404) return null;\n if (!response.ok) {\n throw NextlyError.internal({\n logContext: {\n service: label,\n path: context,\n status: response.status,\n statusText: response.statusText\n }\n });\n }\n return Buffer.from(await response.arrayBuffer());\n}\nfunction classifyFetchFailure(error) {\n if (!(error instanceof SafeFetchError)) return \"unknown\";\n if (error.status === 404) return \"absent\";\n const successful = error.status !== void 0 && error.status >= 200 && error.status < 300;\n return error.reason === \"response-too-large\" && successful ? \"oversized\" : \"unknown\";\n}\nvar DEFAULT_READ_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;\nvar DEFAULT_READ_MAX_BYTES = DEFAULT_MAX_RESPONSE_BYTES;\nfunction resolveReadBounds(options) {\n return {\n maxBytes: options?.maxBytes ?? DEFAULT_READ_MAX_BYTES,\n timeoutMs: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS\n };\n}\nfunction deadlineSignal(timeoutMs, context) {\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new StorageReadTimeoutError(context, timeoutMs));\n }, timeoutMs);\n timer.unref?.();\n return {\n signal: controller.signal,\n /*\n * Called once the read has settled, whichever way it went. `unref` only\n * stops a pending timer holding the process open; it still sits on the\n * timer heap retaining the controller and the path, so a server doing a\n * thousand reads a second keeps thirty seconds of them alive and then runs\n * a thousand aborts that answer nobody.\n */\n cancel: () => {\n clearTimeout(timer);\n }\n };\n}\nasync function withDeadline(work, signal) {\n if (signal === void 0) return await work;\n return await Promise.race([\n work,\n new Promise((_, reject) => {\n if (signal.aborted) {\n reject(signal.reason);\n return;\n }\n signal.addEventListener(\"abort\", () => reject(signal.reason), {\n once: true\n });\n })\n ]);\n}\n\nexport {\n ExternalUrlError,\n validateExternalUrl,\n SafeFetchError,\n safeFetch,\n fetchStoredBytes,\n classifyFetchFailure,\n DEFAULT_READ_TIMEOUT_MS,\n DEFAULT_READ_MAX_BYTES,\n resolveReadBounds,\n deadlineSignal,\n withDeadline\n};\n","/**\n * S3 Storage Adapter\n *\n * Stores files on AWS S3 or S3-compatible services (Cloudflare R2, MinIO, DigitalOcean Spaces).\n * Uses AWS SDK v3 with automatic multipart upload for large files (>5MB).\n *\n * Features:\n * - Automatic multipart upload via @aws-sdk/lib-storage\n * - Pre-signed URL generation for client-side uploads\n * - Signed URL generation for private file access\n * - Cloudflare R2 support (S3-compatible API)\n * - Custom endpoint support (MinIO, DigitalOcean Spaces)\n * - CDN URL override support\n * - File metadata retrieval\n *\n * @example AWS S3\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: { media: true }\n * });\n * ```\n *\n * @example Cloudflare R2\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media',\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n * publicUrl: 'https://pub-abc.r2.dev',\n * collections: { media: true }\n * });\n * ```\n *\n * @example MinIO (self-hosted)\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * });\n * ```\n */\n\nimport {\n S3Client,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n type PutObjectCommandInput,\n type GetObjectCommandOutput,\n} from \"@aws-sdk/client-s3\";\nimport { Upload } from \"@aws-sdk/lib-storage\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type {\n StorageReadOptions,\n IStorageAdapter,\n UploadOptions,\n UploadResult,\n StorageAdapterInfo,\n ClientUploadData,\n FileMetadata,\n BulkDeleteResult,\n} from \"nextly/storage\";\nimport { resolveReadBounds } from \"nextly/storage/fetch-stored-bytes\";\nimport { StorageReadTooLargeError } from \"nextly/storage/read-errors\";\n\nimport type { S3StorageConfig, ResolvedS3Config } from \"./types\";\n\n// ============================================================\n// S3 Storage Adapter\n// ============================================================\n\n/**\n * S3 Storage Adapter\n *\n * Implements the IStorageAdapter interface for AWS S3 and S3-compatible services.\n * Provides full support for uploads, downloads, signed URLs, and client-side uploads.\n */\n/**\n * The SDK request options carrying the caller's deadline, or none.\n *\n * A free function rather than a branch inside `read`, because it is the only\n * part of that method with nothing to do with telling an absent key apart from\n * an unreachable bucket — which is the distinction a reader has to hold in\n * their head there.\n *\n * `AbortSignal.timeout` rather than a controller and a `setTimeout`: it needs no\n * clearing, so it cannot leak a timer when the read resolves first. The signal\n * covers the BODY as well as the request, because the SDK threads it through\n * the response stream — and a backend that answers headers promptly and then\n * stalls mid-transfer is exactly the case a request-only deadline misses.\n */\nfunction abortAfter(options?: StorageReadOptions): {\n abortSignal: AbortSignal;\n} {\n /*\n * ALWAYS a signal, defaulted through the shared resolver.\n * `StorageReadOptions` promises the deadline whether or not a caller names\n * one, and the URL-backed adapters keep that promise by inheriting\n * `safeFetch`'s. Returning nothing here left the no-options read — which is\n * what the media pipeline does — able to hang on a stalled bucket forever.\n */\n return {\n abortSignal: AbortSignal.timeout(resolveReadBounds(options).timeoutMs),\n };\n}\n\nexport class S3StorageAdapter implements IStorageAdapter {\n private client: S3Client;\n private resolvedConfig: ResolvedS3Config;\n private isR2: boolean;\n\n /**\n * Create a new S3 storage adapter.\n *\n * @param config - S3 storage configuration\n * @throws Error if bucket or region is not provided\n */\n constructor(private config: S3StorageConfig) {\n // Validate required config\n if (!config.bucket) {\n throw new Error(\"@nextly/storage-s3: bucket is required\");\n }\n if (!config.region) {\n throw new Error(\"@nextly/storage-s3: region is required\");\n }\n\n // Detect if this is Cloudflare R2\n this.isR2 = config.endpoint?.includes(\"r2.cloudflarestorage.com\") ?? false;\n\n // Resolve config with defaults\n this.resolvedConfig = {\n bucket: config.bucket,\n region: config.region,\n endpoint: config.endpoint,\n forcePathStyle: config.forcePathStyle ?? false,\n acl: config.acl ?? \"private\",\n publicUrl: config.publicUrl,\n cacheControl: config.cacheControl ?? \"public, max-age=31536000\",\n contentDisposition: config.contentDisposition,\n signedUrlExpiresIn: config.signedUrlExpiresIn ?? 3600,\n };\n\n // Build credentials\n const credentials = this.buildCredentials();\n\n // Initialize S3 client\n this.client = new S3Client({\n region: config.region,\n endpoint: config.endpoint,\n credentials,\n forcePathStyle: this.resolvedConfig.forcePathStyle,\n ...config.config,\n });\n }\n\n /**\n * Build AWS credentials from config.\n * Supports explicit credentials or falls back to SDK default chain.\n */\n private buildCredentials():\n | { accessKeyId: string; secretAccessKey: string }\n | undefined {\n if (this.config.accessKeyId && this.config.secretAccessKey) {\n return {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey,\n };\n }\n\n // Check if credentials are in the config object\n if (this.config.config?.credentials) {\n return undefined; // Let SDK use the provided config.credentials\n }\n\n // Fall back to SDK default credential chain (env vars, IAM roles, etc.)\n return undefined;\n }\n\n // ============================================================\n // Core IStorageAdapter Methods\n // ============================================================\n\n /**\n * Upload file to S3.\n *\n * Uses AWS SDK v3's Upload class from @aws-sdk/lib-storage which:\n * - Automatically handles multipart upload for large files (>5MB)\n * - Provides progress tracking capability\n * - Includes retry logic\n * - Optimizes upload performance\n *\n * @param buffer - File content as Buffer\n * @param options - Upload options (filename, mimeType, folder, collection)\n * @returns Upload result with URL and storage path\n */\n async upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult> {\n const key = this.generateKey(options.filename, options.folder);\n\n // Build upload parameters\n const uploadParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n Body: buffer,\n ContentType: options.contentType || options.mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2 (R2 ignores ACL settings)\n if (!this.isR2) {\n uploadParams.ACL = this.resolvedConfig.acl;\n }\n\n // Add Content-Disposition: per-file override takes priority, then global config\n const disposition =\n options.contentDisposition ?? this.resolvedConfig.contentDisposition;\n if (disposition) {\n const filename = this.sanitizeFilename(options.filename);\n uploadParams.ContentDisposition =\n disposition === \"attachment\"\n ? `attachment; filename=\"${filename}\"`\n : \"inline\";\n }\n\n // Store original filename in metadata for later retrieval\n uploadParams.Metadata = {\n \"original-filename\": options.filename,\n };\n\n // Use Upload for automatic multipart handling\n const upload = new Upload({\n client: this.client,\n params: uploadParams,\n });\n\n await upload.done();\n\n return {\n url: this.getPublicUrl(key),\n path: key,\n };\n }\n\n /**\n * Delete file from S3.\n *\n * @param filePath - Storage path/key to delete\n */\n async delete(filePath: string): Promise<void> {\n const command = new DeleteObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n }\n\n /**\n * Bulk delete files from S3 using a single API call per 1000 keys.\n *\n * Uses AWS SDK v3's DeleteObjectsCommand which supports up to 1000 keys per\n * request. Automatically batches larger arrays and collects per-key results.\n *\n * @param filePaths - Storage paths/keys to delete\n * @returns Object with arrays of successful and failed deletions\n */\n async bulkDelete(filePaths: string[]): Promise<BulkDeleteResult> {\n const successful: string[] = [];\n const failed: Array<{ filePath: string; error: string }> = [];\n const maxBatchSize = 1000; // AWS limit\n\n for (let i = 0; i < filePaths.length; i += maxBatchSize) {\n const batch = filePaths.slice(i, i + maxBatchSize);\n const command = new DeleteObjectsCommand({\n Bucket: this.resolvedConfig.bucket,\n Delete: {\n Objects: batch.map(key => ({ Key: key })),\n Quiet: false,\n },\n });\n\n const response = await this.client.send(command);\n\n if (response.Errors && response.Errors.length > 0) {\n for (const err of response.Errors) {\n if (err.Key) {\n failed.push({\n filePath: err.Key,\n error: err.Message ?? \"Unknown S3 delete error\",\n });\n }\n }\n }\n\n if (response.Deleted) {\n for (const del of response.Deleted) {\n if (del.Key) {\n successful.push(del.Key);\n }\n }\n }\n }\n\n return { successful, failed };\n }\n\n /**\n * Check if file exists in S3.\n *\n * Uses HeadObject command which is more efficient than GetObject\n * for existence checks (doesn't download the file).\n *\n * @param filePath - Storage path/key to check\n * @returns true if file exists, false otherwise\n */\n async exists(filePath: string): Promise<boolean> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n return true;\n } catch (error: unknown) {\n // HeadObject returns 404 if object doesn't exist\n if (this.isNotFoundError(error)) {\n return false;\n }\n // Re-throw other errors (permissions, network, etc.)\n throw error;\n }\n }\n\n /**\n * Get public URL for S3 file.\n *\n * Priority order:\n * 1. Custom publicUrl (CDN or custom domain) if configured\n * 2. Standard S3 URL based on region and bucket\n *\n * For R2: Requires publicUrl configuration (R2 has no default public URLs).\n *\n * @param filePath - Storage path/key\n * @returns Public URL to access the file\n * @throws Error if R2 is used without publicUrl configuration\n */\n getPublicUrl(filePath: string): string {\n // Use custom CDN/public URL if configured\n if (this.resolvedConfig.publicUrl) {\n const baseUrl = this.resolvedConfig.publicUrl.replace(/\\/$/, \"\");\n return `${baseUrl}/${filePath}`;\n }\n\n // R2 requires custom domain or public bucket URL\n if (this.isR2) {\n throw new Error(\n \"@nextly/storage-s3: Cloudflare R2 requires publicUrl configuration.\\n\\n\" +\n \"R2 does not have default public URLs like AWS S3. Configure one of:\\n\" +\n \"1. Public bucket URL: publicUrl: 'https://pub-xxx.r2.dev'\\n\" +\n \"2. Custom domain: publicUrl: 'https://cdn.example.com'\\n\\n\" +\n \"Set up public access in the Cloudflare R2 dashboard.\"\n );\n }\n\n // Standard S3 URL format\n // https://bucket.s3.region.amazonaws.com/key\n return `https://${this.resolvedConfig.bucket}.s3.${this.resolvedConfig.region}.amazonaws.com/${filePath}`;\n }\n\n /**\n * Get storage type identifier.\n *\n * Returns \"s3\" for all S3-compatible services (AWS S3, R2, MinIO, etc.)\n * as they all use the S3 API.\n */\n getType(): \"s3\" {\n return \"s3\";\n }\n\n // ============================================================\n // Optional IStorageAdapter Methods\n // ============================================================\n\n /**\n * Get adapter info including capabilities.\n *\n * @returns Adapter info with type, name, and capability flags\n */\n getInfo(): StorageAdapterInfo {\n return {\n type: \"s3\",\n name: \"S3StorageAdapter\",\n supportsSignedUrls: true,\n supportsClientUploads: true,\n };\n }\n\n /**\n * Get file metadata from S3.\n *\n * Retrieves file information including size, content type, and timestamps\n * using the HeadObject command.\n *\n * @param filePath - Storage path/key\n * @returns File metadata or null if file not found\n */\n async getMetadata(filePath: string): Promise<FileMetadata | null> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n const response = await this.client.send(command);\n const filename = filePath.split(\"/\").pop() || filePath;\n\n return {\n id: filePath,\n filename,\n originalFilename: response.Metadata?.[\"original-filename\"] || filename,\n mimeType: response.ContentType || \"application/octet-stream\",\n size: response.ContentLength || 0,\n url: this.getPublicUrl(filePath),\n createdAt:\n response.LastModified?.toISOString() || new Date().toISOString(),\n };\n } catch (error: unknown) {\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Read a stored object back as bytes, or `null` when it is not there.\n *\n * The contract declares this OPTIONAL and, until now, no adapter supplied\n * it — so a caller reaching for `adapter.read` got `undefined` and fell\n * through whatever branch followed. An optional member nothing implements is\n * a contract that reads as a capability and behaves as an absence.\n *\n * BUFFERS rather than streams, because that is the declared return type and\n * widening it here would make this adapter answer a different question from\n * its siblings. That bounds what it should be used for: an asset the server\n * must serve from its own origin — a font file, a small document — rather\n * than arbitrarily large media, which belongs behind {@link getSignedUrl} or\n * a public URL. A streaming variant is a separate contract member, not a\n * quiet change to this one.\n *\n * `null` for a missing key rather than a throw, matching\n * {@link getMetadata}: absence is an ordinary answer about the bucket, while\n * a credential or network failure is not, so only the first is folded into\n * the return value.\n *\n * @param filePath - Storage path/key\n * @returns The object's bytes, or `null` when no such key exists\n */\n async read(\n filePath: string,\n options?: StorageReadOptions\n ): Promise<Buffer | null> {\n try {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n /*\n * The caller's deadline reaches the SDK, or it is advertised and inert.\n * `StorageReadOptions` promises a bound, and the URL-backed adapters keep\n * it by handing `timeoutMs` to `safeFetch`; without this the same option\n * does nothing here, so a stalled bucket holds the read open past a\n * deadline the caller was told applied — the worst kind of option, one\n * that validates and then has no effect.\n *\n * Aborting covers the BODY as well as the request. `transformToByteArray`\n * pulls the stream after `send` resolves, and a backend that answers\n * headers promptly and then stalls mid-transfer is exactly the case a\n * request-only timeout misses.\n */\n const response = await this.client.send(command, abortAfter(options));\n /*\n * An empty body is not a missing object, and the two must not collapse.\n * `GetObject` on a zero-byte key succeeds with no stream to transform,\n * so returning `null` here would report a stored empty file as absent —\n * and a caller deleting \"missing\" keys would then delete a real one.\n */\n if (response.Body === undefined) return Buffer.alloc(0);\n\n /*\n * Refused on the LENGTH the store reports, before the body is pulled\n * down. `transformToByteArray` buffers the whole object, so a caller that\n * knows its own limit has to be able to stop an oversized read rather\n * than discover the size after paying for it — which is what an\n * attachment path does when it checks a size only after buffering.\n */\n const cap = resolveReadBounds(options).maxBytes;\n // Cheap refusal first, where the store told us the size up front.\n this.refuseOverCap(filePath, response.ContentLength, cap);\n return await this.readCapped(filePath, response.Body, cap);\n } catch (error: unknown) {\n /*\n * Only an OBJECT-level absence becomes `null`. `isNotFoundError` treats\n * any 404 as missing, and `NoSuchBucket` is a 404 — so a bucket that was\n * deleted, renamed or mistyped would report every key in it as absent,\n * which is the configuration failure this method's contract exists to\n * tell apart from an ordinary miss. Named explicitly rather than by\n * status, because the status cannot separate them.\n */\n if (error instanceof Error && error.name === \"NoSuchBucket\") throw error;\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Generate signed URL for temporary private file access.\n *\n * Creates a pre-signed GetObject URL that grants temporary read access\n * to private files. Useful for serving files from private buckets.\n *\n * @param filePath - Storage path/key\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Pre-signed URL for downloading the file\n */\n async getSignedUrl(filePath: string, expiresIn?: number): Promise<string> {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: expiresIn ?? this.resolvedConfig.signedUrlExpiresIn,\n });\n }\n\n /**\n * Generate pre-signed URL for client-side uploads.\n *\n * Creates a pre-signed PutObject URL that allows direct uploads from\n * the browser to S3, bypassing server-side upload limits (e.g., Vercel's 4.5MB).\n *\n * @param key - Storage path/key for the upload\n * @param mimeType - MIME type of the file being uploaded\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Client upload data with URL, method, and headers\n */\n async getPresignedUploadUrl(\n key: string,\n mimeType: string,\n expiresIn?: number\n ): Promise<ClientUploadData> {\n const expiration = expiresIn ?? this.resolvedConfig.signedUrlExpiresIn;\n\n // Build PutObject command parameters\n const commandParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n ContentType: mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2\n if (!this.isR2) {\n commandParams.ACL = this.resolvedConfig.acl;\n }\n\n const command = new PutObjectCommand(commandParams);\n\n const uploadUrl = await getSignedUrl(this.client, command, {\n expiresIn: expiration,\n });\n\n return {\n uploadUrl,\n path: key,\n method: \"PUT\",\n headers: {\n \"Content-Type\": mimeType,\n },\n expiresAt: new Date(Date.now() + expiration * 1000),\n };\n }\n\n // ============================================================\n // Helper Methods\n // ============================================================\n\n /**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}\n *\n * @param filename - Original filename (will be sanitized)\n * @param folder - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\n private generateKey(filename: string, folder?: string): string {\n const sanitized = this.sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const prefix = folder ? `${folder}/${year}/${month}` : `${year}/${month}`;\n\n return `${prefix}/${uuid}-${sanitized}`;\n }\n\n /**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\n private sanitizeFilename(filename: string): string {\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n }\n\n /**\n * Check if an error is a \"not found\" error from S3.\n *\n * @param error - Error to check\n * @returns true if error indicates file not found\n */\n /**\n * The object's bytes, refusing once more than `cap` have actually arrived.\n *\n * `ContentLength` is OPTIONAL in the SDK's response type, and this adapter\n * explicitly supports MinIO, R2 and other compatible services — any of which\n * may omit it or report it wrongly. A preflight check against metadata is\n * therefore a courtesy that fails OPEN: trusting it alone leaves the cap\n * unenforced exactly where an unusual backend is involved, which is where a\n * caller most needs it.\n *\n * Counted while consuming, so an oversized body is abandoned partway rather\n * than after it has all been buffered — the cap exists to bound memory, and\n * one applied after `transformToByteArray` has already spent it.\n *\n * Falls back to buffering when the body is not async-iterable, which the SDK\n * types permit. Stated rather than hidden: on that path the cap bounds what a\n * caller RECEIVES rather than what the process allocates.\n */\n private async readCapped(\n filePath: string,\n body: NonNullable<GetObjectCommandOutput[\"Body\"]>,\n cap: number\n ): Promise<Buffer> {\n /*\n * Probed rather than assumed. The SDK types the body as a union whose Node\n * member is async-iterable and whose browser members are not, and this\n * package supports compatible services whose transport may differ — so the\n * capability is read off the value in hand instead of from the type.\n */\n const iterable = body as Partial<AsyncIterable<Uint8Array>>;\n if (typeof iterable[Symbol.asyncIterator] !== \"function\") {\n const whole = Buffer.from(await body.transformToByteArray());\n if (whole.byteLength > cap) {\n throw new StorageReadTooLargeError(filePath, cap, whole.byteLength);\n }\n return whole;\n }\n\n const chunks: Buffer[] = [];\n let received = 0;\n for await (const chunk of body as AsyncIterable<Uint8Array>) {\n received += chunk.byteLength;\n if (received > cap) {\n throw new StorageReadTooLargeError(filePath, cap, received);\n }\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks);\n }\n\n /**\n * Refuse a read whose object is already known to exceed the caller's cap.\n *\n * Its own method rather than three conditions inside `read`, because the\n * decision has nothing to do with the surrounding error handling and reads\n * as noise there — and because `read`'s branching is the part a reviewer has\n * to hold in their head to check the absent-versus-unavailable distinction.\n *\n * Both `undefined` cases mean \"no opinion\" and pass: a caller that named no\n * cap has none, and a store that reported no length gives nothing to compare.\n * The second is deliberately permissive — refusing an unmeasured object would\n * reject valid reads whenever S3 omits the header.\n */\n private refuseOverCap(\n filePath: string,\n declared: number | undefined,\n cap: number | undefined\n ): void {\n if (cap === undefined || declared === undefined || declared <= cap) return;\n /*\n * The SHARED refusal rather than a generic internal error, because callers\n * act on the difference. The email attachment path answers an over-cap read\n * with a size error the author can do something about; anything else is\n * wrapped as an opaque storage failure, so throwing `internal` here would\n * turn a fixable refusal into one that tells them nothing.\n */\n throw new StorageReadTooLargeError(filePath, cap, declared);\n }\n\n private isNotFoundError(error: unknown): boolean {\n if (error && typeof error === \"object\") {\n const e = error as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n return e.name === \"NotFound\" || e.$metadata?.httpStatusCode === 404;\n }\n return false;\n }\n\n // ============================================================\n // Public Accessors\n // ============================================================\n\n /**\n * Get the S3 client instance.\n * Useful for advanced operations not covered by the adapter interface.\n */\n getClient(): S3Client {\n return this.client;\n }\n\n /**\n * Get the bucket name.\n */\n getBucket(): string {\n return this.resolvedConfig.bucket;\n }\n\n /**\n * Get the AWS region.\n */\n getRegion(): string {\n return this.resolvedConfig.region;\n }\n\n /**\n * Check if this adapter is configured for Cloudflare R2.\n */\n isCloudflareR2(): boolean {\n return this.isR2;\n }\n}\n","/**\n * S3 Storage Plugin\n *\n * Factory function that creates a storage plugin for AWS S3 and S3-compatible services.\n * Returns a StoragePlugin that can be registered with MediaStorage.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With collection-specific configuration\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * collections: {\n * // Simple enable with defaults\n * media: true,\n *\n * // Full configuration for private documents\n * 'private-docs': {\n * prefix: 'private/',\n * clientUploads: true,\n * signedDownloads: true,\n * signedUrlExpiresIn: 3600\n * }\n * }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { StoragePlugin, ClientUploadData } from \"nextly/storage\";\n\nimport { S3StorageAdapter } from \"./adapter\";\nimport type { S3StorageConfig } from \"./types\";\n\n// ============================================================\n// Plugin Factory Function\n// ============================================================\n\n/**\n * Create an S3 storage plugin for Nextly.\n *\n * This factory function creates a StoragePlugin that can be added to\n * the `storage` array in `nextly.config.ts`. It supports:\n *\n * - AWS S3 (standard)\n * - Cloudflare R2 (S3-compatible)\n * - MinIO (S3-compatible, self-hosted)\n * - DigitalOcean Spaces (S3-compatible)\n * - Any other S3-compatible service\n *\n * @param config - S3 storage configuration\n * @returns A StoragePlugin that MediaStorage can register\n *\n * @throws Error if bucket is not provided (via adapter)\n * @throws Error if region is not provided (via adapter)\n */\nexport function s3Storage(config: S3StorageConfig): StoragePlugin {\n // Handle disabled plugin\n // When disabled, return a plugin with no collections and null adapter\n // MediaStorage.registerPlugin() checks for null adapter and skips registration\n if (config.enabled === false) {\n return {\n name: \"s3-storage\",\n type: \"s3\",\n collections: {},\n adapter: null as unknown as StoragePlugin[\"adapter\"],\n };\n }\n\n // Create the S3 adapter\n // Adapter constructor validates required config (bucket, region)\n const adapter = new S3StorageAdapter(config);\n\n // Build and return the plugin\n const plugin: StoragePlugin = {\n name: \"s3-storage\",\n type: \"s3\",\n collections: config.collections,\n adapter,\n\n /**\n * Generate a pre-signed URL for client-side uploads.\n *\n * This allows files to be uploaded directly from the browser to S3,\n * bypassing server-side upload limits (e.g., Vercel's 4.5MB limit).\n *\n * @param filename - Original filename from the client\n * @param mimeType - MIME type of the file\n * @param collection - Collection slug this upload belongs to\n * @returns Client upload data with pre-signed URL\n */\n async getClientUploadUrl(\n filename: string,\n mimeType: string,\n collection: string\n ): Promise<ClientUploadData> {\n // Get collection-specific configuration\n const collectionConfig = config.collections[collection];\n const prefix =\n typeof collectionConfig === \"object\"\n ? collectionConfig.prefix\n : undefined;\n\n // Generate storage key matching adapter's format\n // Format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n const key = generateStorageKey(filename, prefix);\n\n // Get pre-signed upload URL from adapter\n return adapter.getPresignedUploadUrl(key, mimeType);\n },\n\n /**\n * Generate a signed URL for private file downloads.\n *\n * Creates a time-limited URL for accessing files in private buckets.\n * Only works when collection has `signedDownloads: true`.\n *\n * @param path - Storage path/key of the file\n * @param expiresIn - URL validity duration in seconds\n * @returns Signed URL for downloading the file\n */\n async getSignedDownloadUrl(\n path: string,\n expiresIn?: number\n ): Promise<string> {\n return adapter.getSignedUrl(\n path,\n expiresIn ?? config.signedUrlExpiresIn ?? 3600\n );\n },\n };\n\n return plugin;\n}\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n * This matches the format used by S3StorageAdapter for consistency.\n *\n * @param filename - Original filename (will be sanitized)\n * @param prefix - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\nfunction generateStorageKey(filename: string, prefix?: string): string {\n const sanitized = sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const keyPrefix = prefix ? `${prefix}${year}/${month}` : `${year}/${month}`;\n\n return `${keyPrefix}/${uuid}-${sanitized}`;\n}\n\n/**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * Removes path separators and replaces unsafe characters with hyphens.\n * Keeps only: a-z, A-Z, 0-9, dot, underscore, hyphen\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\nfunction sanitizeFilename(filename: string): string {\n // Extract basename (remove any path components)\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n // Replace unsafe characters with hyphens\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n}\n","/**\n * @nextly/storage-s3\n *\n * AWS S3 storage adapter for Nextly CMS.\n * Also works with S3-compatible services like Cloudflare R2, MinIO, and DigitalOcean Spaces.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With MinIO\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\n// ============================================================\n// S3 Storage Plugin Export (Primary API)\n// ============================================================\n\nexport { s3Storage } from \"./plugin\";\n\n// ============================================================\n// S3 Storage Adapter Export\n// ============================================================\n\nexport { S3StorageAdapter } from \"./adapter\";\n\n// ============================================================\n// S3 Types Export\n// ============================================================\n\nexport type {\n S3StorageConfig,\n S3ObjectACL,\n S3CollectionConfig,\n S3CollectionStorageMap,\n ResolvedS3Config,\n} from \"./types\";\n\n// ============================================================\n// Package Metadata\n// ============================================================\n\nexport const PACKAGE_NAME = \"@nextly/storage-s3\";\nexport const PACKAGE_VERSION = \"0.1.0\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../../nextly/dist/chunk-IAZJNA2U.mjs","../../nextly/dist/chunk-T3OVIJZM.mjs","../../nextly/dist/chunk-YGE6AGZG.mjs","../src/adapter.ts","../src/plugin.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// src/database/errors.ts\nvar DbError = class extends Error {\n kind;\n dialect;\n code;\n meta;\n cause;\n constructor(args) {\n super(args.message);\n this.name = \"DbError\";\n this.kind = args.kind;\n this.dialect = args.dialect;\n this.code = args.code;\n this.meta = args.meta;\n this.cause = args.cause;\n }\n};\nfunction isDbError(err) {\n if (!err || typeof err !== \"object\") return false;\n const obj = err;\n return obj.name === \"DbError\" && typeof obj.kind === \"string\";\n}\nfunction toDbError(dialect, error, meta) {\n if (isDbError(error)) return error;\n const code = safeCode(error);\n const rawMsg = error != null && typeof error === \"object\" && \"message\" in error ? error.message : void 0;\n const message = typeof rawMsg === \"string\" ? rawMsg : \"Database error\";\n let kind = \"internal\";\n switch (dialect) {\n case \"postgresql\":\n kind = mapPostgresCodeToKind(code);\n break;\n case \"mysql\":\n kind = mapMySqlCodeToKind(code);\n break;\n case \"sqlite\":\n kind = mapSqliteCodeToKind(code);\n break;\n }\n if ((dialect === \"mysql\" || dialect === \"sqlite\") && (kind === \"constraint\" || kind === \"internal\") && isLikelyUniqueViolationMessage(dialect, message)) {\n kind = \"unique-violation\";\n }\n if (kind === \"internal\" && code) {\n const c = code.toUpperCase();\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n kind = \"connection-lost\";\n else if (c === \"ETIMEDOUT\") kind = \"timeout\";\n }\n return new DbError({ message, kind, dialect, code, meta, cause: error });\n}\nfunction safeCode(error) {\n if (!error || typeof error !== \"object\") return void 0;\n const e = error;\n let c = e.code ?? e.sqlState ?? e.state ?? e.errno ?? void 0;\n if (!c && typeof e.originalError?.code === \"string\") c = e.originalError.code;\n if (!c && typeof e.cause?.code === \"string\") c = e.cause.code;\n if (!c && typeof e.severity === \"string\" && typeof e.code === \"string\")\n c = e.code;\n if (typeof c === \"number\") return String(c);\n if (typeof c === \"string\" && c.length > 0) return c;\n return void 0;\n}\nfunction mapPostgresCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"40P01\") return \"deadlock\";\n if (c === \"40001\") return \"serialization-failure\";\n if (c === \"57014\") return \"timeout\";\n if (c === \"23505\") return \"unique-violation\";\n if (c === \"23503\") return \"fk-violation\";\n if (c === \"23502\") return \"not-null-violation\";\n if (c === \"42601\") return \"syntax\";\n if (c.startsWith(\"08\") || c === \"57P01\") return \"connection-lost\";\n if (c.startsWith(\"23\")) return \"constraint\";\n return \"internal\";\n}\nfunction mapMySqlCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"1213\" || c === \"ER_LOCK_DEADLOCK\") return \"deadlock\";\n if (c === \"1205\" || c === \"ER_LOCK_WAIT_TIMEOUT\") return \"timeout\";\n if (c === \"1062\" || c === \"ER_DUP_ENTRY\") return \"unique-violation\";\n if (c === \"23000\") return \"constraint\";\n if (c === \"1064\" || c === \"ER_PARSE_ERROR\") return \"syntax\";\n if (c === \"1452\" || c === \"ER_NO_REFERENCED_ROW_2\") return \"fk-violation\";\n if (c === \"1451\" || c === \"ER_ROW_IS_REFERENCED_2\") return \"fk-violation\";\n if (c === \"ECONNRESET\" || c.includes(\"PROTOCOL_CONNECTION_LOST\"))\n return \"connection-lost\";\n if (c === \"ETIMEDOUT\") return \"timeout\";\n return \"internal\";\n}\nfunction mapSqliteCodeToKind(code) {\n if (!code) return \"internal\";\n const c = code.toUpperCase();\n if (c === \"SQLITE_BUSY\" || c === \"SQLITE_LOCKED\") return \"deadlock\";\n if (c === \"SQLITE_CONSTRAINT_UNIQUE\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_PRIMARYKEY\") return \"unique-violation\";\n if (c === \"SQLITE_CONSTRAINT_FOREIGNKEY\") return \"fk-violation\";\n if (c === \"SQLITE_CONSTRAINT_NOTNULL\") return \"not-null-violation\";\n if (c === \"SQLITE_INTERRUPT\") return \"timeout\";\n if (c === \"SQLITE_ERROR\") return \"syntax\";\n if (c.startsWith(\"SQLITE_CONSTRAINT\")) return \"constraint\";\n return \"internal\";\n}\nfunction isLikelyUniqueViolationMessage(dialect, message) {\n const m = message.toLowerCase();\n if (dialect === \"mysql\") {\n return m.includes(\"duplicate entry\") || m.includes(\"duplicate key\");\n }\n if (dialect === \"sqlite\") {\n return m.includes(\"unique constraint failed\");\n }\n return false;\n}\n\n// src/errors/error-codes.ts\nvar NEXTLY_ERROR_STATUS = {\n VALIDATION_ERROR: 400,\n INVALID_INPUT: 400,\n AUTH_REQUIRED: 401,\n AUTH_INVALID_CREDENTIALS: 401,\n TOKEN_EXPIRED: 401,\n FORBIDDEN: 403,\n // The schema builder is off in this environment (production by default).\n // Separate from FORBIDDEN: the caller's permissions are not the problem.\n BUILDER_DISABLED: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n DUPLICATE: 409,\n RATE_LIMITED: 429,\n PAYLOAD_TOO_LARGE: 413,\n UNSUPPORTED_MEDIA_TYPE: 415,\n // 422: understood, well-formed, and refused on a rule the caller can act on.\n // Deliberately NOT added to CANONICAL_CODE_FOR_STATUS -- that list drives the\n // status -> code direction, where 422 stays mapped to INVALID_INPUT.\n BUSINESS_RULE_VIOLATION: 422,\n INTERNAL_ERROR: 500,\n DATABASE_ERROR: 500,\n EXTERNAL_SERVICE_ERROR: 502,\n SERVICE_UNAVAILABLE: 503,\n // Outbound-fetch safety (utils/validate-external-url): a URL refused for SSRF\n // safety, and a fetch that timed out / exceeded the size cap / failed to decode.\n EXTERNAL_URL_BLOCKED: 400,\n EXTERNAL_REQUEST_FAILED: 502,\n FILENAME_INVALID: 400,\n EXTENSION_BLOCKED: 400,\n MIME_BLOCKED: 415,\n MIME_NOT_ALLOWED: 415,\n SIZE_EXCEEDED: 413,\n // A stored object exceeded the cap a READ was given, which is a different\n // question from SIZE_EXCEEDED above: that one refuses an upload the caller\n // is sending, this one refuses to buffer an object already stored. Kept\n // apart so a caller discriminating on the code cannot match both.\n STORAGE_READ_TOO_LARGE: 413,\n // A stored object did not answer within the deadline the read was given. 504\n // rather than 500 because the failure is the BACKEND not answering, and a\n // caller can act on that difference: a gateway timeout is worth retrying, an\n // internal error is not.\n STORAGE_READ_TIMEOUT: 504,\n // The store answered, and answered badly. 502 rather than 500 because the\n // fault is UPSTREAM of this process: a caller can retry it, and an operator\n // reading the log needs to look at the bucket rather than at this service.\n // Distinct from the timeout above, which never got an answer at all.\n STORAGE_READ_UNREACHABLE: 502,\n MAGIC_BYTE_MISMATCH: 400,\n SVG_SANITIZATION_FAILED: 400,\n UNSUPPORTED_FOR_BACKEND: 415,\n // Plan B — schema bookkeeping consolidation.\n NEXTLY_LEGACY_BOOKKEEPING_DETECTED: 409,\n NEXTLY_UPGRADE_TABLE_NAME_COLLISION: 409,\n NEXTLY_UPGRADE_IN_PROGRESS: 409,\n // Plan C2 — nextly migrate phases.\n NEXTLY_MIGRATE_LOCK_BUSY: 409,\n NEXTLY_BASELINE_LOCK_NOT_HELD: 409,\n NEXTLY_RESOLVE_LOCK_NOT_HELD: 409,\n // Boot refused to serve: the migrate lock stayed held past the wait deadline,\n // so this process never established whether the schema matches the code. 503\n // rather than 409 — a load balancer should take the instance out of rotation\n // and retry it, which is exactly the recovery this refusal wants.\n NEXTLY_BOOT_MIGRATIONS_NOT_RUN: 503,\n // Still running rather than refused. 503 for the same reason: retry shortly.\n NEXTLY_BOOT_MIGRATIONS_PENDING: 503,\n NEXTLY_CORE_DESTRUCTIVE_REFUSED: 409,\n NEXTLY_MIGRATION_DRIFT: 409,\n NEXTLY_MIGRATION_APPLY_FAILED: 500,\n // Plan C3 — migrate:resolve recovery command.\n NEXTLY_MIGRATION_FILE_MISSING: 404,\n NEXTLY_MIGRATION_SNAPSHOT_MISSING: 404,\n NEXTLY_MIGRATION_RESOLVE_DRIFT: 409,\n NEXTLY_MIGRATION_RESOLVE_PRECONDITION: 409,\n // Plan D — UI schema support.\n NEXTLY_UI_SCHEMA_INVALID: 400,\n NEXTLY_SCHEMA_SLUG_COLLISION: 409,\n NEXTLY_SCHEMA_RELATION_TARGET_MISSING: 400,\n // Plugin platform (P2b) — schema extend (contributes.extend) + relations (D15).\n NEXTLY_SCHEMA_EXTEND_TARGET_UNKNOWN: 400,\n NEXTLY_SCHEMA_EXTEND_FIELD_DUPLICATE: 409,\n NEXTLY_SCHEMA_CROSS_PLUGIN_RELATION: 409,\n // Plugin platform (P2c) — framework remap (.rename()).\n NEXTLY_SCHEMA_RENAME_UNKNOWN_TARGET: 400,\n // Plugin platform — a declared admin.clientConfig that cannot be delivered\n // to the browser, refused at boot rather than serialized mangled.\n NEXTLY_PLUGIN_CLIENT_CONFIG_INVALID: 500,\n // Plugin platform — a contributed admin widget that cannot be delivered to\n // the browser. Refused at boot because it is serialized into the ONE\n // `/api/admin-meta/workspace` payload: a value `JSON.stringify` throws on\n // fails that request for every admin, not just the widget's own card.\n NEXTLY_PLUGIN_ADMIN_WIDGET_INVALID: 500,\n // Plugin platform (P0) — boot-time plugin dependency/version resolution.\n PLUGIN_RESOLUTION_ERROR: 500,\n // Plugin platform (P4) — contributes.routes collection (D25).\n NEXTLY_ROUTE_COLLISION: 409,\n NEXTLY_ROUTE_INVALID_PATH: 400,\n // A root-mounted route declared where the request pipeline never reaches the\n // root pass. 400 like an invalid path and not 409: nothing else claims the\n // address, the declaration simply cannot be served from there.\n NEXTLY_ROUTE_UNREACHABLE_ROOT: 400,\n // A `mount` outside the declared union, which only an untyped caller can\n // produce. 400 for the same reason: the declaration itself is unserveable.\n NEXTLY_ROUTE_INVALID_MOUNT: 400,\n // An email transport whose library is an optional peer dependency the host\n // has not installed. 503 rather than 500: the request is not malformed and\n // nothing is broken, the install simply cannot carry it out yet, and the\n // remedy is one command on the server rather than a change by the caller.\n NEXTLY_EMAIL_TRANSPORT_UNAVAILABLE: 503,\n // The tooling that compiles `nextly.config.ts` is an optional peer the host\n // has not installed. 503 rather than 500 for the same reason as the mail\n // transport above: nothing is broken and the request is not malformed, the\n // install simply cannot carry it out until one command is run.\n NEXTLY_CONFIG_TOOLING_UNAVAILABLE: 503\n};\nvar CANONICAL_CODE_FOR_STATUS = [\n \"VALIDATION_ERROR\",\n \"AUTH_REQUIRED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"PAYLOAD_TOO_LARGE\",\n \"UNSUPPORTED_MEDIA_TYPE\",\n \"RATE_LIMITED\",\n \"EXTERNAL_SERVICE_ERROR\",\n \"SERVICE_UNAVAILABLE\"\n];\nvar LEGACY_UNPROCESSABLE_STATUS = 422;\nvar STATUS_TO_CODE = {\n ...Object.fromEntries(\n CANONICAL_CODE_FOR_STATUS.map((code) => [NEXTLY_ERROR_STATUS[code], code])\n ),\n [LEGACY_UNPROCESSABLE_STATUS]: \"INVALID_INPUT\"\n};\nfunction statusToErrorCode(statusCode) {\n return STATUS_TO_CODE[statusCode] ?? \"INTERNAL_ERROR\";\n}\nvar GENERIC_PUBLIC_MESSAGE = {\n VALIDATION_ERROR: \"Validation failed.\",\n INVALID_INPUT: \"The request could not be processed.\",\n AUTH_REQUIRED: \"Authentication required.\",\n FORBIDDEN: \"You don't have permission to perform this action.\",\n NOT_FOUND: \"Not found.\",\n CONFLICT: \"The resource has changed since you last loaded it. Please refresh and try again.\",\n DUPLICATE: \"Resource already exists.\",\n PAYLOAD_TOO_LARGE: \"The request is too large.\",\n UNSUPPORTED_MEDIA_TYPE: \"That file type is not supported.\",\n RATE_LIMITED: \"Too many requests. Please try again later.\",\n EXTERNAL_SERVICE_ERROR: \"An upstream service failed.\",\n SERVICE_UNAVAILABLE: \"The service is temporarily unavailable.\",\n INTERNAL_ERROR: \"An unexpected error occurred.\"\n};\nfunction genericPublicMessage(code) {\n return GENERIC_PUBLIC_MESSAGE[code] ?? GENERIC_PUBLIC_MESSAGE.INTERNAL_ERROR;\n}\n\n// src/errors/nextly-error.ts\nvar DB_ERROR_MAPPING = {\n \"unique-violation\": {\n code: \"DUPLICATE\",\n statusCode: 409,\n publicMessage: \"Resource already exists.\"\n },\n \"fk-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"Referenced record does not exist.\"\n },\n \"not-null-violation\": {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"A required field is missing.\"\n },\n constraint: {\n code: \"VALIDATION_ERROR\",\n statusCode: 400,\n publicMessage: \"The provided data violates a constraint.\"\n },\n deadlock: {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n \"serialization-failure\": {\n code: \"CONFLICT\",\n statusCode: 409,\n publicMessage: \"The operation could not be completed. Please retry.\"\n },\n timeout: {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"The operation timed out. Please try again.\"\n },\n \"connection-lost\": {\n code: \"DATABASE_ERROR\",\n statusCode: 500,\n publicMessage: \"A temporary database error occurred. Please try again.\"\n },\n syntax: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n },\n internal: {\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\"\n }\n};\nvar NEXTLY_ERROR_BRAND = Symbol.for(\"nextly/NextlyError\");\nfunction hasBrand(value) {\n if (value === null || typeof value !== \"object\" && typeof value !== \"function\") {\n return false;\n }\n return value[NEXTLY_ERROR_BRAND] === true;\n}\nvar NextlyError = class _NextlyError extends Error {\n code;\n statusCode;\n publicMessage;\n publicData;\n messageKey;\n logMessage;\n logContext;\n cause;\n timestamp;\n constructor(opts) {\n super(opts.publicMessage);\n this.name = \"NextlyError\";\n this.code = opts.code;\n this.publicMessage = opts.publicMessage;\n this.publicData = opts.publicData;\n this.messageKey = opts.messageKey;\n this.logMessage = opts.logMessage;\n this.logContext = opts.logContext;\n this.cause = opts.cause;\n this.timestamp = /* @__PURE__ */ new Date();\n this.statusCode = _NextlyError.resolveStatusCode(opts);\n Error.captureStackTrace?.(this, _NextlyError);\n }\n static {\n _NextlyError.prototype[NEXTLY_ERROR_BRAND] = true;\n }\n static resolveStatusCode(opts) {\n if (typeof opts.statusCode === \"number\") return opts.statusCode;\n if (opts.code in NEXTLY_ERROR_STATUS) {\n return NEXTLY_ERROR_STATUS[opts.code];\n }\n return 500;\n }\n /** HTTP-safe JSON. Strips logMessage / logContext / cause / stack. */\n toResponseJSON(requestId) {\n const json = {\n code: String(this.code),\n message: this.publicMessage,\n requestId\n };\n if (this.messageKey) json.messageKey = this.messageKey;\n if (this.publicData !== void 0) json.data = this.publicData;\n return json;\n }\n /** Operator-facing JSON for log lines. Includes everything. */\n toLogJSON(requestId) {\n return {\n code: this.code,\n statusCode: this.statusCode,\n publicMessage: this.publicMessage,\n messageKey: this.messageKey,\n logMessage: this.logMessage,\n logContext: this.logContext,\n cause: this.cause ? {\n name: this.cause.name,\n message: this.cause.message,\n stack: this.cause.stack\n } : void 0,\n timestamp: this.timestamp.toISOString(),\n requestId\n };\n }\n // ────────────────────────────────────────────────────────────────────\n // Static factories — the recommended throw site for every common case.\n // Public messages here follow the §13.8 rubric: complete sentence,\n // generic, no identifiers, no value echoing, no policy hints.\n // ────────────────────────────────────────────────────────────────────\n static invalidCredentials(opts) {\n return new _NextlyError({\n code: \"AUTH_INVALID_CREDENTIALS\",\n publicMessage: \"Invalid email or password.\",\n logMessage: \"Login failed\",\n logContext: opts?.logContext\n });\n }\n static authRequired(opts) {\n return new _NextlyError({\n code: \"AUTH_REQUIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n /**\n * Distinct from `authRequired`: the caller was authenticated but the\n * session token expired. Clients key on the TOKEN_EXPIRED *code* to\n * silently refresh and retry rather than redirecting to login; the public\n * message stays the generic spec §13.6 string (\"Authentication required.\")\n * so the wire never reveals the session state — same as `authRequired`.\n */\n static tokenExpired(opts) {\n return new _NextlyError({\n code: \"TOKEN_EXPIRED\",\n publicMessage: \"Authentication required.\",\n logContext: opts?.logContext\n });\n }\n static notFound(opts) {\n return new _NextlyError({\n code: \"NOT_FOUND\",\n publicMessage: opts?.message ?? \"Not found.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n static forbidden(opts) {\n return new _NextlyError({\n code: \"FORBIDDEN\",\n publicMessage: \"You don't have permission to perform this action.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * A call the caller got wrong, where naming the mistake IS the value of the\n * error.\n *\n * The only factory that takes its public message from the caller. The\n * generic messages elsewhere exist so an HTTP response cannot leak internal\n * detail; this one is for arguments and configuration a developer controls\n * and must be told about — a missing option, an unusable combination — where\n * `internal()` would reduce the one useful sentence to \"An unexpected error\n * occurred.\" Do not pass user-supplied data through it.\n */\n static invalidInput(opts) {\n return new _NextlyError({\n code: \"INVALID_INPUT\",\n publicMessage: opts.message,\n logContext: opts.logContext\n });\n }\n static validation(opts) {\n return new _NextlyError({\n code: \"VALIDATION_ERROR\",\n publicMessage: \"Validation failed.\",\n publicData: { errors: opts.errors },\n cause: opts.cause,\n logContext: opts.logContext\n });\n }\n static conflict(opts) {\n return new _NextlyError({\n code: \"CONFLICT\",\n publicMessage: opts?.message ?? \"The resource has changed since you last loaded it. Please refresh and try again.\",\n cause: opts?.cause,\n logContext: { reason: opts?.reason, ...opts?.logContext }\n });\n }\n static duplicate(opts) {\n return new _NextlyError({\n code: \"DUPLICATE\",\n publicMessage: \"Resource already exists.\",\n logContext: opts?.logContext\n });\n }\n static rateLimited(opts) {\n return new _NextlyError({\n code: \"RATE_LIMITED\",\n publicMessage: \"Too many requests. Please try again later.\",\n publicData: opts?.retryAfterSeconds !== void 0 ? { retryAfterSeconds: opts.retryAfterSeconds } : void 0,\n logContext: opts?.logContext\n });\n }\n static internal(opts) {\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n publicMessage: \"An unexpected error occurred.\",\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n // Accepts an optional `logMessage` override so callers (e.g. the health\n // route) can record a specific operator narrative (\"Health check failed\")\n // while the public message stays canonical per spec §13.8.5.\n static serviceUnavailable(opts) {\n return new _NextlyError({\n code: \"SERVICE_UNAVAILABLE\",\n publicMessage: opts?.publicMessage ?? \"Service unavailable. Please try again later.\",\n logMessage: opts?.logMessage,\n cause: opts?.cause,\n logContext: opts?.logContext\n });\n }\n /**\n * Convert a DbError (or arbitrary unknown thrown by the DB layer) to a\n * NextlyError with a generic public message and rich logContext. Used by\n * `withDbErrors` for auto-conversion (Pattern A) and by services that\n * catch DB errors at boundaries (Pattern B). Spec §8.2 mapping table.\n *\n * Never leaks DB driver text, constraint names, or table names into\n * `publicMessage`. All DB context goes into `logContext`. The original\n * DbError is preserved as `cause`.\n */\n static fromDatabaseError(error) {\n if (isDbError(error)) {\n const mapping = DB_ERROR_MAPPING[error.kind];\n const logContext = {\n dbKind: error.kind,\n dialect: error.dialect\n };\n if (error.code !== void 0) logContext.dbCode = error.code;\n if (error.meta !== void 0) logContext.meta = error.meta;\n return new _NextlyError({\n code: mapping.code,\n statusCode: mapping.statusCode,\n publicMessage: mapping.publicMessage,\n logMessage: \"Database error\",\n logContext,\n cause: error\n });\n }\n return new _NextlyError({\n code: \"INTERNAL_ERROR\",\n statusCode: 500,\n publicMessage: \"An unexpected error occurred.\",\n logMessage: \"Non-DbError passed to fromDatabaseError\",\n cause: error instanceof Error ? error : void 0,\n logContext: error instanceof Error ? void 0 : { value: String(error) }\n });\n }\n // ────────────────────────────────────────────────────────────────────\n // Type guards. Structural rather than `instanceof` so they survive\n // package-boundary mismatches (one consumer's NextlyError is a\n // different module instance from another's).\n // ────────────────────────────────────────────────────────────────────\n static is(err) {\n return hasBrand(err);\n }\n static isCode(err, code) {\n return hasBrand(err) && err.code === code;\n }\n static isNotFound(err) {\n return _NextlyError.isCode(err, \"NOT_FOUND\");\n }\n static isValidation(err) {\n return _NextlyError.isCode(err, \"VALIDATION_ERROR\");\n }\n static isAuthRequired(err) {\n return _NextlyError.isCode(err, \"AUTH_REQUIRED\");\n }\n static isForbidden(err) {\n return _NextlyError.isCode(err, \"FORBIDDEN\");\n }\n static isConflict(err) {\n return _NextlyError.isCode(err, \"CONFLICT\");\n }\n static isRateLimited(err) {\n return _NextlyError.isCode(err, \"RATE_LIMITED\");\n }\n};\n\nexport {\n DbError,\n isDbError,\n toDbError,\n safeCode,\n NEXTLY_ERROR_STATUS,\n statusToErrorCode,\n genericPublicMessage,\n NextlyError\n};\n","import {\n NextlyError\n} from \"./chunk-IAZJNA2U.mjs\";\n\n// src/storage/read-errors.ts\nvar StorageReadTooLargeError = class extends NextlyError {\n constructor(path, maxBytes, size) {\n super({\n code: \"STORAGE_READ_TOO_LARGE\",\n publicMessage: \"The stored file is larger than the limit for this read.\",\n logContext: {\n path,\n maxBytes,\n ...size === void 0 ? {} : { size }\n }\n });\n this.path = path;\n this.maxBytes = maxBytes;\n this.size = size;\n }\n};\nfunction isStorageReadTooLarge(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TOO_LARGE\";\n}\nvar StorageReadTimeoutError = class extends NextlyError {\n constructor(path, timeoutMs) {\n super({\n code: \"STORAGE_READ_TIMEOUT\",\n publicMessage: \"The stored file could not be read in time.\",\n logContext: { path, timeoutMs }\n });\n this.path = path;\n this.timeoutMs = timeoutMs;\n }\n};\nfunction isStorageReadTimeout(error) {\n return NextlyError.is(error) && error.code === \"STORAGE_READ_TIMEOUT\";\n}\n\nexport {\n StorageReadTooLargeError,\n isStorageReadTooLarge,\n StorageReadTimeoutError,\n isStorageReadTimeout\n};\n","import {\n StorageReadTimeoutError,\n StorageReadTooLargeError\n} from \"./chunk-T3OVIJZM.mjs\";\nimport {\n NextlyError\n} from \"./chunk-IAZJNA2U.mjs\";\n\n// src/utils/validate-external-url.ts\nvar IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$/;\nvar PRIVATE_IPV4_CIDRS = [\n \"0.0.0.0/8\",\n // RFC1122 \"this network\" / wildcard\n \"10.0.0.0/8\",\n // RFC1918\n \"100.64.0.0/10\",\n // RFC6598 CGNAT\n \"127.0.0.0/8\",\n // RFC1122 loopback\n \"169.254.0.0/16\",\n // RFC3927 link-local + AWS/GCP metadata\n \"172.16.0.0/12\",\n // RFC1918\n \"192.0.0.0/24\",\n // RFC6890 IETF protocol assignments\n \"192.0.2.0/24\",\n // RFC5737 documentation\n \"192.168.0.0/16\",\n // RFC1918\n \"198.18.0.0/15\",\n // RFC2544 benchmarking\n \"198.51.100.0/24\",\n // RFC5737 documentation\n \"203.0.113.0/24\",\n // RFC5737 documentation\n \"224.0.0.0/4\",\n // RFC5771 multicast\n \"240.0.0.0/4\"\n // RFC1112 reserved\n];\nvar CLOUD_METADATA_HOSTS = /* @__PURE__ */ new Set([\n \"metadata.google.internal\",\n \"metadata.googleapis.com\",\n \"metadata\"\n // GCP short form\n]);\nvar ExternalUrlError = class extends NextlyError {\n constructor(message, url) {\n super({\n code: \"EXTERNAL_URL_BLOCKED\",\n publicMessage: message,\n logContext: { url }\n });\n this.url = url;\n this.name = \"ExternalUrlError\";\n }\n};\nasync function validateExternalUrl(rawUrl, options = {}) {\n let parsed;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new ExternalUrlError(\"Invalid URL\", rawUrl);\n }\n const hostname = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n const isLocalhostHostname = hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n const baseProtocols = options.allowedProtocols ?? [\"https:\"];\n const protocols = options.allowLocalhost && isLocalhostHostname ? [...baseProtocols, \"http:\"] : baseProtocols;\n if (!protocols.includes(parsed.protocol)) {\n throw new ExternalUrlError(\n `Protocol ${parsed.protocol} not allowed (allowed: ${protocols.join(\", \")})`,\n rawUrl\n );\n }\n if (CLOUD_METADATA_HOSTS.has(hostname)) {\n throw new ExternalUrlError(\n `Cloud-metadata hostname rejected: ${parsed.hostname}`,\n rawUrl\n );\n }\n if (IPV4_RE.test(hostname)) {\n if (!isPublicIpv4(\n hostname,\n options.allowLocalhost === true && hostname === \"127.0.0.1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 4 };\n }\n if (hostname.includes(\":\")) {\n if (!isPublicIpv6(\n hostname,\n options.allowLocalhost === true && hostname === \"::1\"\n )) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${hostname}`,\n rawUrl\n );\n }\n return { url: parsed, pinnedIp: hostname, family: 6 };\n }\n const { lookup } = await import(\"dns/promises\");\n let addresses;\n try {\n addresses = await lookup(hostname, { all: true });\n } catch (err) {\n throw new ExternalUrlError(\n `DNS lookup failed: ${err.message}`,\n rawUrl\n );\n }\n if (addresses.length === 0) {\n throw new ExternalUrlError(\"No IPs returned for host\", rawUrl);\n }\n for (const { address, family } of addresses) {\n const allow = options.allowLocalhost === true && isLocalhostHostname && family === 4;\n const ok = family === 4 ? isPublicIpv4(address, allow) : isPublicIpv6(address, allow);\n if (!ok) {\n throw new ExternalUrlError(\n `Resolved to non-public IP: ${address}`,\n rawUrl\n );\n }\n }\n const first = addresses[0];\n return {\n url: parsed,\n pinnedIp: first.address,\n family: first.family === 6 ? 6 : 4\n };\n}\nvar DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;\nvar DEFAULT_TIMEOUT_MS = 3e4;\nvar SafeFetchError = class extends NextlyError {\n constructor(message, url, reason, status) {\n super({\n code: \"EXTERNAL_REQUEST_FAILED\",\n publicMessage: message,\n logContext: { url, reason }\n });\n this.url = url;\n this.reason = reason;\n this.status = status;\n this.name = \"SafeFetchError\";\n }\n};\nfunction createPinnedLookup(ip, family) {\n return (_hostname, options, callback) => {\n if (options && options.all) {\n callback(null, [{ address: ip, family }]);\n } else {\n callback(null, ip, family);\n }\n };\n}\nasync function safeFetch(rawUrl, options = {}) {\n const {\n allowLocalhost,\n allowedProtocols,\n method,\n headers,\n body,\n signal,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n timeoutMs = DEFAULT_TIMEOUT_MS\n } = options;\n const controller = new AbortController();\n const forwardAbort = () => controller.abort(signal?.reason);\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", forwardAbort, { once: true });\n }\n const timer = setTimeout(\n () => controller.abort(\n new SafeFetchError(`Request exceeded ${timeoutMs}ms`, rawUrl, \"timeout\")\n ),\n timeoutMs\n );\n try {\n const validated = await abortable(\n validateExternalUrl(rawUrl, { allowLocalhost, allowedProtocols }),\n controller.signal\n );\n return await pinnedFetch(validated, {\n method,\n headers,\n body,\n signal: controller.signal,\n maxResponseBytes\n });\n } finally {\n clearTimeout(timer);\n if (signal) signal.removeEventListener(\"abort\", forwardAbort);\n }\n}\nfunction abortError(signal) {\n const reason = signal.reason;\n if (reason instanceof Error) return reason;\n return new DOMException(\"The operation was aborted\", \"AbortError\");\n}\nfunction abortable(promise, signal) {\n if (signal.aborted) return Promise.reject(abortError(signal));\n let onAbort;\n const aborted = new Promise((_resolve, reject) => {\n onAbort = () => reject(abortError(signal));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n return Promise.race([promise, aborted]).finally(\n () => signal.removeEventListener(\"abort\", onAbort)\n );\n}\nasync function pinnedFetch(validated, init) {\n const { url, pinnedIp, family } = validated;\n const httpMod = url.protocol === \"https:\" ? await import(\"https\") : await import(\"http\");\n const zlib = await import(\"zlib\");\n const lookup = createPinnedLookup(pinnedIp, family);\n const outHeaders = toOutgoingHeaders(init.headers);\n if (init.body != null) {\n delete outHeaders[\"transfer-encoding\"];\n outHeaders[\"content-length\"] = String(\n typeof init.body === \"string\" ? Buffer.byteLength(init.body) : init.body.byteLength\n );\n }\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn) => {\n if (settled) return;\n settled = true;\n fn();\n };\n const failure = (err) => init.signal.aborted ? abortError(init.signal) : err;\n const req = httpMod.request(\n url,\n {\n method: init.method ?? \"GET\",\n headers: outHeaders,\n lookup,\n // Fresh socket per request so a pooled connection can't reuse a\n // differently-resolved address, and so the pinned lookup always runs.\n agent: false,\n // The combined deadline/caller signal aborts the request in flight.\n signal: init.signal\n },\n (res) => {\n const chunks = [];\n let received = 0;\n res.on(\"data\", (chunk) => {\n received += chunk.length;\n if (received > init.maxResponseBytes) {\n res.destroy();\n req.destroy();\n settle(\n () => reject(\n new SafeFetchError(\n `Response body exceeded ${init.maxResponseBytes} bytes`,\n url.href,\n \"response-too-large\",\n res.statusCode\n )\n )\n );\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n const decoded = decodeBody(\n Buffer.concat(chunks),\n res.headers[\"content-encoding\"],\n zlib,\n init.maxResponseBytes,\n url.href,\n res.statusCode\n );\n if (decoded instanceof SafeFetchError) {\n settle(() => reject(decoded));\n return;\n }\n settle(\n () => resolve(toWhatwgResponse(res, decoded.body, decoded.decoded))\n );\n });\n res.on(\"error\", (err) => settle(() => reject(failure(err))));\n }\n );\n req.on(\"error\", (err) => settle(() => reject(failure(err))));\n if (!req.destroyed) {\n if (init.body != null) req.write(init.body);\n req.end();\n }\n });\n}\nfunction toOutgoingHeaders(headers) {\n const out = {};\n if (!headers) return out;\n const add = (rawKey, value) => {\n const key = rawKey.toLowerCase();\n if (key === \"host\") return;\n const existing = out[key];\n out[key] = existing === void 0 ? value : [].concat(existing, value);\n };\n if (headers instanceof Headers) {\n headers.forEach((value, key) => add(key, value));\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) add(key, value);\n } else {\n for (const [key, value] of Object.entries(headers)) add(key, value);\n }\n return out;\n}\nfunction toWhatwgResponse(res, body, stripContentHeaders) {\n const headers = new Headers();\n for (const [key, value] of Object.entries(res.headers)) {\n if (value == null) continue;\n const lower = key.toLowerCase();\n if (stripContentHeaders && (lower === \"content-encoding\" || lower === \"content-length\")) {\n continue;\n }\n const values = Array.isArray(value) ? value : [value];\n for (const v of values) {\n try {\n headers.append(key, v);\n } catch {\n }\n }\n }\n const raw = res.statusCode ?? 502;\n const status = raw >= 200 && raw <= 599 ? raw : 502;\n const nullBody = status === 204 || status === 205 || status === 304;\n const bytes = new Uint8Array(body.byteLength);\n bytes.set(body);\n return new Response(nullBody ? null : bytes, {\n status,\n statusText: res.statusMessage ?? \"\",\n headers\n });\n}\nfunction decodeBody(buf, encoding, zlib, cap, url, status) {\n if (!encoding) return { body: buf, decoded: false };\n if (buf.length === 0) return { body: buf, decoded: true };\n const layers = encoding.split(\",\").map((e) => e.trim().toLowerCase()).filter(Boolean);\n const opts = { maxOutputLength: cap };\n let current = buf;\n let decoded = false;\n try {\n for (let i = layers.length - 1; i >= 0; i--) {\n const enc = layers[i];\n if (enc === \"identity\") continue;\n if (enc === \"gzip\" || enc === \"x-gzip\") {\n current = zlib.gunzipSync(current, opts);\n } else if (enc === \"br\") {\n current = zlib.brotliDecompressSync(current, opts);\n } else if (enc === \"deflate\") {\n try {\n current = zlib.inflateSync(current, opts);\n } catch (inflateErr) {\n if (errorCode(inflateErr) === \"ERR_BUFFER_TOO_LARGE\")\n throw inflateErr;\n current = zlib.inflateRawSync(current, opts);\n }\n } else {\n if (decoded) {\n return new SafeFetchError(\n `Cannot fully decode content-encoding \"${encoding}\"`,\n url,\n \"decode-failed\",\n status\n );\n }\n return { body: buf, decoded: false };\n }\n decoded = true;\n }\n return { body: current, decoded };\n } catch (err) {\n const tooLarge = errorCode(err) === \"ERR_BUFFER_TOO_LARGE\";\n return new SafeFetchError(\n tooLarge ? `Decoded ${encoding} response body exceeded the size cap` : `Failed to decode ${encoding} response body`,\n url,\n tooLarge ? \"response-too-large\" : \"decode-failed\",\n status\n );\n }\n}\nfunction errorCode(err) {\n if (err != null && typeof err === \"object\" && \"code\" in err) {\n const code = err.code;\n return typeof code === \"string\" ? code : void 0;\n }\n return void 0;\n}\nfunction ipv4ToInt(ip) {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n let acc = 0;\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 0 || n > 255) return null;\n acc = acc << 8 | n;\n }\n return acc >>> 0;\n}\nfunction isIpv4InCidr(intIp, cidr) {\n const [addr, prefixRaw] = cidr.split(\"/\");\n const intCidr = ipv4ToInt(addr);\n if (intCidr === null) return false;\n const prefix = Number(prefixRaw);\n if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;\n const mask = prefix === 0 ? 0 : -1 >>> 32 - prefix << 32 - prefix;\n return (intIp & mask) >>> 0 === (intCidr & mask) >>> 0;\n}\nfunction isPublicIpv4(addr, allowLoopback) {\n const intIp = ipv4ToInt(addr);\n if (intIp === null) return false;\n if (allowLoopback && addr === \"127.0.0.1\") return true;\n for (const cidr of PRIVATE_IPV4_CIDRS) {\n if (isIpv4InCidr(intIp, cidr)) return false;\n }\n return true;\n}\nfunction mappedIpv4(lower) {\n const dotted = lower.match(/^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n if (dotted) return dotted[1];\n const hex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);\n if (hex) {\n const hi = parseInt(hex[1], 16);\n const lo = parseInt(hex[2], 16);\n return [hi >> 8, hi & 255, lo >> 8, lo & 255].join(\".\");\n }\n return null;\n}\nfunction isPublicIpv6(addr, allowLoopback) {\n const lower = addr.toLowerCase();\n if (allowLoopback && lower === \"::1\") return true;\n const mapped = mappedIpv4(lower);\n if (mapped) {\n return isPublicIpv4(mapped, allowLoopback);\n }\n if (lower === \"::\" || lower === \"::1\") return false;\n const firstHextet = lower.split(\":\")[0] || \"\";\n if (firstHextet.startsWith(\"fc\") || firstHextet.startsWith(\"fd\"))\n return false;\n if (firstHextet.startsWith(\"fe8\") || firstHextet.startsWith(\"fe9\"))\n return false;\n if (firstHextet.startsWith(\"fea\") || firstHextet.startsWith(\"feb\"))\n return false;\n if (firstHextet.startsWith(\"ff\")) return false;\n return true;\n}\n\n// src/storage/fetch-stored-bytes.ts\nasync function fetchStoredBytes(url, context, label, options, signal) {\n let response;\n try {\n response = await safeFetch(url, {\n ...options?.maxBytes === void 0 ? {} : { maxResponseBytes: options.maxBytes },\n /*\n * A caller's signal REPLACES the deadline rather than joining it.\n *\n * These adapters look the object's address up before fetching it, and\n * that lookup can stall too. Starting a fresh timer here would give the\n * fetch its own full budget on top of however long the lookup took, so a\n * read could outlive the deadline the caller was told applied — by\n * roughly double. One signal begun before the lookup governs both phases.\n */\n ...signal !== void 0 ? { signal } : options?.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }\n });\n } catch (error) {\n const verdict = classifyFetchFailure(error);\n if (verdict === \"absent\") return null;\n if (verdict === \"oversized\") {\n throw new StorageReadTooLargeError(\n context,\n resolveReadBounds(options).maxBytes\n );\n }\n throw error;\n }\n if (response.status === 404) return null;\n if (!response.ok) {\n throw NextlyError.internal({\n logContext: {\n service: label,\n path: context,\n status: response.status,\n statusText: response.statusText\n }\n });\n }\n return Buffer.from(await response.arrayBuffer());\n}\nfunction classifyFetchFailure(error) {\n if (!(error instanceof SafeFetchError)) return \"unknown\";\n if (error.status === 404) return \"absent\";\n const successful = error.status !== void 0 && error.status >= 200 && error.status < 300;\n return error.reason === \"response-too-large\" && successful ? \"oversized\" : \"unknown\";\n}\nvar DEFAULT_READ_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;\nvar DEFAULT_READ_MAX_BYTES = DEFAULT_MAX_RESPONSE_BYTES;\nfunction resolveReadBounds(options) {\n return {\n maxBytes: options?.maxBytes ?? DEFAULT_READ_MAX_BYTES,\n timeoutMs: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS\n };\n}\nfunction deadlineSignal(timeoutMs, context) {\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new StorageReadTimeoutError(context, timeoutMs));\n }, timeoutMs);\n timer.unref?.();\n return {\n signal: controller.signal,\n /*\n * Called once the read has settled, whichever way it went. `unref` only\n * stops a pending timer holding the process open; it still sits on the\n * timer heap retaining the controller and the path, so a server doing a\n * thousand reads a second keeps thirty seconds of them alive and then runs\n * a thousand aborts that answer nobody.\n */\n cancel: () => {\n clearTimeout(timer);\n }\n };\n}\nasync function withDeadline(work, signal) {\n if (signal === void 0) return await work;\n return await Promise.race([\n work,\n new Promise((_, reject) => {\n if (signal.aborted) {\n reject(signal.reason);\n return;\n }\n signal.addEventListener(\"abort\", () => reject(signal.reason), {\n once: true\n });\n })\n ]);\n}\n\nexport {\n ExternalUrlError,\n validateExternalUrl,\n SafeFetchError,\n safeFetch,\n fetchStoredBytes,\n classifyFetchFailure,\n DEFAULT_READ_TIMEOUT_MS,\n DEFAULT_READ_MAX_BYTES,\n resolveReadBounds,\n deadlineSignal,\n withDeadline\n};\n","/**\n * S3 Storage Adapter\n *\n * Stores files on AWS S3 or S3-compatible services (Cloudflare R2, MinIO, DigitalOcean Spaces).\n * Uses AWS SDK v3 with automatic multipart upload for large files (>5MB).\n *\n * Features:\n * - Automatic multipart upload via @aws-sdk/lib-storage\n * - Pre-signed URL generation for client-side uploads\n * - Signed URL generation for private file access\n * - Cloudflare R2 support (S3-compatible API)\n * - Custom endpoint support (MinIO, DigitalOcean Spaces)\n * - CDN URL override support\n * - File metadata retrieval\n *\n * @example AWS S3\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: { media: true }\n * });\n * ```\n *\n * @example Cloudflare R2\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-media',\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n * publicUrl: 'https://pub-abc.r2.dev',\n * collections: { media: true }\n * });\n * ```\n *\n * @example MinIO (self-hosted)\n * ```typescript\n * const adapter = new S3StorageAdapter({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * });\n * ```\n */\n\nimport {\n S3Client,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n HeadObjectCommand,\n GetObjectCommand,\n PutObjectCommand,\n type PutObjectCommandInput,\n type GetObjectCommandOutput,\n} from \"@aws-sdk/client-s3\";\nimport { Upload } from \"@aws-sdk/lib-storage\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport type {\n StorageReadOptions,\n IStorageAdapter,\n UploadOptions,\n UploadResult,\n StorageAdapterInfo,\n ClientUploadData,\n FileMetadata,\n BulkDeleteResult,\n} from \"nextly/storage\";\nimport { resolveReadBounds } from \"nextly/storage/fetch-stored-bytes\";\nimport { StorageReadTooLargeError } from \"nextly/storage/read-errors\";\n\nimport type { S3StorageConfig, ResolvedS3Config } from \"./types\";\n\n// ============================================================\n// S3 Storage Adapter\n// ============================================================\n\n/**\n * S3 Storage Adapter\n *\n * Implements the IStorageAdapter interface for AWS S3 and S3-compatible services.\n * Provides full support for uploads, downloads, signed URLs, and client-side uploads.\n */\n/**\n * The SDK request options carrying the caller's deadline, or none.\n *\n * A free function rather than a branch inside `read`, because it is the only\n * part of that method with nothing to do with telling an absent key apart from\n * an unreachable bucket — which is the distinction a reader has to hold in\n * their head there.\n *\n * `AbortSignal.timeout` rather than a controller and a `setTimeout`: it needs no\n * clearing, so it cannot leak a timer when the read resolves first. The signal\n * covers the BODY as well as the request, because the SDK threads it through\n * the response stream — and a backend that answers headers promptly and then\n * stalls mid-transfer is exactly the case a request-only deadline misses.\n */\nfunction abortAfter(options?: StorageReadOptions): {\n abortSignal: AbortSignal;\n} {\n /*\n * ALWAYS a signal, defaulted through the shared resolver.\n * `StorageReadOptions` promises the deadline whether or not a caller names\n * one, and the URL-backed adapters keep that promise by inheriting\n * `safeFetch`'s. Returning nothing here left the no-options read — which is\n * what the media pipeline does — able to hang on a stalled bucket forever.\n */\n return {\n abortSignal: AbortSignal.timeout(resolveReadBounds(options).timeoutMs),\n };\n}\n\nexport class S3StorageAdapter implements IStorageAdapter {\n private client: S3Client;\n private resolvedConfig: ResolvedS3Config;\n private isR2: boolean;\n\n /**\n * Create a new S3 storage adapter.\n *\n * @param config - S3 storage configuration\n * @throws Error if bucket or region is not provided\n */\n constructor(private config: S3StorageConfig) {\n // Validate required config\n if (!config.bucket) {\n throw new Error(\"@nextly/storage-s3: bucket is required\");\n }\n if (!config.region) {\n throw new Error(\"@nextly/storage-s3: region is required\");\n }\n\n // Detect if this is Cloudflare R2\n this.isR2 = config.endpoint?.includes(\"r2.cloudflarestorage.com\") ?? false;\n\n // Resolve config with defaults\n this.resolvedConfig = {\n bucket: config.bucket,\n region: config.region,\n endpoint: config.endpoint,\n forcePathStyle: config.forcePathStyle ?? false,\n acl: config.acl ?? \"private\",\n publicUrl: config.publicUrl,\n cacheControl: config.cacheControl ?? \"public, max-age=31536000\",\n contentDisposition: config.contentDisposition,\n signedUrlExpiresIn: config.signedUrlExpiresIn ?? 3600,\n };\n\n // Build credentials\n const credentials = this.buildCredentials();\n\n // Initialize S3 client\n this.client = new S3Client({\n region: config.region,\n endpoint: config.endpoint,\n credentials,\n forcePathStyle: this.resolvedConfig.forcePathStyle,\n ...config.config,\n });\n }\n\n /**\n * Build AWS credentials from config.\n * Supports explicit credentials or falls back to SDK default chain.\n */\n private buildCredentials():\n | { accessKeyId: string; secretAccessKey: string }\n | undefined {\n if (this.config.accessKeyId && this.config.secretAccessKey) {\n return {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey,\n };\n }\n\n // Check if credentials are in the config object\n if (this.config.config?.credentials) {\n return undefined; // Let SDK use the provided config.credentials\n }\n\n // Fall back to SDK default credential chain (env vars, IAM roles, etc.)\n return undefined;\n }\n\n // ============================================================\n // Core IStorageAdapter Methods\n // ============================================================\n\n /**\n * Upload file to S3.\n *\n * Uses AWS SDK v3's Upload class from @aws-sdk/lib-storage which:\n * - Automatically handles multipart upload for large files (>5MB)\n * - Provides progress tracking capability\n * - Includes retry logic\n * - Optimizes upload performance\n *\n * @param buffer - File content as Buffer\n * @param options - Upload options (filename, mimeType, folder, collection)\n * @returns Upload result with URL and storage path\n */\n async upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult> {\n const key = this.generateKey(options.filename, options.folder);\n\n // Build upload parameters\n const uploadParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n Body: buffer,\n ContentType: options.contentType || options.mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2 (R2 ignores ACL settings)\n if (!this.isR2) {\n uploadParams.ACL = this.resolvedConfig.acl;\n }\n\n // Add Content-Disposition: per-file override takes priority, then global config\n const disposition =\n options.contentDisposition ?? this.resolvedConfig.contentDisposition;\n if (disposition) {\n const filename = this.sanitizeFilename(options.filename);\n uploadParams.ContentDisposition =\n disposition === \"attachment\"\n ? `attachment; filename=\"${filename}\"`\n : \"inline\";\n }\n\n // Store original filename in metadata for later retrieval\n uploadParams.Metadata = {\n \"original-filename\": options.filename,\n };\n\n // Use Upload for automatic multipart handling\n const upload = new Upload({\n client: this.client,\n params: uploadParams,\n });\n\n await upload.done();\n\n return {\n url: this.getPublicUrl(key),\n path: key,\n };\n }\n\n /**\n * Delete file from S3.\n *\n * @param filePath - Storage path/key to delete\n */\n async delete(filePath: string): Promise<void> {\n const command = new DeleteObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n }\n\n /**\n * Bulk delete files from S3 using a single API call per 1000 keys.\n *\n * Uses AWS SDK v3's DeleteObjectsCommand which supports up to 1000 keys per\n * request. Automatically batches larger arrays and collects per-key results.\n *\n * @param filePaths - Storage paths/keys to delete\n * @returns Object with arrays of successful and failed deletions\n */\n async bulkDelete(filePaths: string[]): Promise<BulkDeleteResult> {\n const successful: string[] = [];\n const failed: Array<{ filePath: string; error: string }> = [];\n const maxBatchSize = 1000; // AWS limit\n\n for (let i = 0; i < filePaths.length; i += maxBatchSize) {\n const batch = filePaths.slice(i, i + maxBatchSize);\n const command = new DeleteObjectsCommand({\n Bucket: this.resolvedConfig.bucket,\n Delete: {\n Objects: batch.map(key => ({ Key: key })),\n Quiet: false,\n },\n });\n\n const response = await this.client.send(command);\n\n if (response.Errors && response.Errors.length > 0) {\n for (const err of response.Errors) {\n if (err.Key) {\n failed.push({\n filePath: err.Key,\n error: err.Message ?? \"Unknown S3 delete error\",\n });\n }\n }\n }\n\n if (response.Deleted) {\n for (const del of response.Deleted) {\n if (del.Key) {\n successful.push(del.Key);\n }\n }\n }\n }\n\n return { successful, failed };\n }\n\n /**\n * Check if file exists in S3.\n *\n * Uses HeadObject command which is more efficient than GetObject\n * for existence checks (doesn't download the file).\n *\n * @param filePath - Storage path/key to check\n * @returns true if file exists, false otherwise\n */\n async exists(filePath: string): Promise<boolean> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n await this.client.send(command);\n return true;\n } catch (error: unknown) {\n // HeadObject returns 404 if object doesn't exist\n if (this.isNotFoundError(error)) {\n return false;\n }\n // Re-throw other errors (permissions, network, etc.)\n throw error;\n }\n }\n\n /**\n * Get public URL for S3 file.\n *\n * Priority order:\n * 1. Custom publicUrl (CDN or custom domain) if configured\n * 2. Standard S3 URL based on region and bucket\n *\n * For R2: Requires publicUrl configuration (R2 has no default public URLs).\n *\n * @param filePath - Storage path/key\n * @returns Public URL to access the file\n * @throws Error if R2 is used without publicUrl configuration\n */\n getPublicUrl(filePath: string): string {\n // Use custom CDN/public URL if configured\n if (this.resolvedConfig.publicUrl) {\n const baseUrl = this.resolvedConfig.publicUrl.replace(/\\/$/, \"\");\n return `${baseUrl}/${filePath}`;\n }\n\n // R2 requires custom domain or public bucket URL\n if (this.isR2) {\n throw new Error(\n \"@nextly/storage-s3: Cloudflare R2 requires publicUrl configuration.\\n\\n\" +\n \"R2 does not have default public URLs like AWS S3. Configure one of:\\n\" +\n \"1. Public bucket URL: publicUrl: 'https://pub-xxx.r2.dev'\\n\" +\n \"2. Custom domain: publicUrl: 'https://cdn.example.com'\\n\\n\" +\n \"Set up public access in the Cloudflare R2 dashboard.\"\n );\n }\n\n // Standard S3 URL format\n // https://bucket.s3.region.amazonaws.com/key\n return `https://${this.resolvedConfig.bucket}.s3.${this.resolvedConfig.region}.amazonaws.com/${filePath}`;\n }\n\n /**\n * Get storage type identifier.\n *\n * Returns \"s3\" for all S3-compatible services (AWS S3, R2, MinIO, etc.)\n * as they all use the S3 API.\n */\n getType(): \"s3\" {\n return \"s3\";\n }\n\n // ============================================================\n // Optional IStorageAdapter Methods\n // ============================================================\n\n /**\n * Get adapter info including capabilities.\n *\n * @returns Adapter info with type, name, and capability flags\n */\n getInfo(): StorageAdapterInfo {\n return {\n type: \"s3\",\n name: \"S3StorageAdapter\",\n supportsSignedUrls: true,\n supportsClientUploads: true,\n };\n }\n\n /**\n * Get file metadata from S3.\n *\n * Retrieves file information including size, content type, and timestamps\n * using the HeadObject command.\n *\n * @param filePath - Storage path/key\n * @returns File metadata or null if file not found\n */\n async getMetadata(filePath: string): Promise<FileMetadata | null> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n const response = await this.client.send(command);\n const filename = filePath.split(\"/\").pop() || filePath;\n\n return {\n id: filePath,\n filename,\n originalFilename: response.Metadata?.[\"original-filename\"] || filename,\n mimeType: response.ContentType || \"application/octet-stream\",\n size: response.ContentLength || 0,\n url: this.getPublicUrl(filePath),\n createdAt:\n response.LastModified?.toISOString() || new Date().toISOString(),\n };\n } catch (error: unknown) {\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Read a stored object back as bytes, or `null` when it is not there.\n *\n * The contract declares this OPTIONAL and, until now, no adapter supplied\n * it — so a caller reaching for `adapter.read` got `undefined` and fell\n * through whatever branch followed. An optional member nothing implements is\n * a contract that reads as a capability and behaves as an absence.\n *\n * BUFFERS rather than streams, because that is the declared return type and\n * widening it here would make this adapter answer a different question from\n * its siblings. That bounds what it should be used for: an asset the server\n * must serve from its own origin — a font file, a small document — rather\n * than arbitrarily large media, which belongs behind {@link getSignedUrl} or\n * a public URL. A streaming variant is a separate contract member, not a\n * quiet change to this one.\n *\n * `null` for a missing key rather than a throw, matching\n * {@link getMetadata}: absence is an ordinary answer about the bucket, while\n * a credential or network failure is not, so only the first is folded into\n * the return value.\n *\n * @param filePath - Storage path/key\n * @returns The object's bytes, or `null` when no such key exists\n */\n async read(\n filePath: string,\n options?: StorageReadOptions\n ): Promise<Buffer | null> {\n try {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n /*\n * The caller's deadline reaches the SDK, or it is advertised and inert.\n * `StorageReadOptions` promises a bound, and the URL-backed adapters keep\n * it by handing `timeoutMs` to `safeFetch`; without this the same option\n * does nothing here, so a stalled bucket holds the read open past a\n * deadline the caller was told applied — the worst kind of option, one\n * that validates and then has no effect.\n *\n * Aborting covers the BODY as well as the request. `transformToByteArray`\n * pulls the stream after `send` resolves, and a backend that answers\n * headers promptly and then stalls mid-transfer is exactly the case a\n * request-only timeout misses.\n */\n const response = await this.client.send(command, abortAfter(options));\n /*\n * An empty body is not a missing object, and the two must not collapse.\n * `GetObject` on a zero-byte key succeeds with no stream to transform,\n * so returning `null` here would report a stored empty file as absent —\n * and a caller deleting \"missing\" keys would then delete a real one.\n */\n if (response.Body === undefined) return Buffer.alloc(0);\n\n /*\n * Refused on the LENGTH the store reports, before the body is pulled\n * down. `transformToByteArray` buffers the whole object, so a caller that\n * knows its own limit has to be able to stop an oversized read rather\n * than discover the size after paying for it — which is what an\n * attachment path does when it checks a size only after buffering.\n */\n const cap = resolveReadBounds(options).maxBytes;\n // Cheap refusal first, where the store told us the size up front.\n this.refuseOverCap(filePath, response.ContentLength, cap);\n return await this.readCapped(filePath, response.Body, cap);\n } catch (error: unknown) {\n /*\n * Only an OBJECT-level absence becomes `null`. `isNotFoundError` treats\n * any 404 as missing, and `NoSuchBucket` is a 404 — so a bucket that was\n * deleted, renamed or mistyped would report every key in it as absent,\n * which is the configuration failure this method's contract exists to\n * tell apart from an ordinary miss. Named explicitly rather than by\n * status, because the status cannot separate them.\n */\n if (error instanceof Error && error.name === \"NoSuchBucket\") throw error;\n if (this.isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Generate signed URL for temporary private file access.\n *\n * Creates a pre-signed GetObject URL that grants temporary read access\n * to private files. Useful for serving files from private buckets.\n *\n * @param filePath - Storage path/key\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Pre-signed URL for downloading the file\n */\n async getSignedUrl(filePath: string, expiresIn?: number): Promise<string> {\n const command = new GetObjectCommand({\n Bucket: this.resolvedConfig.bucket,\n Key: filePath,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: expiresIn ?? this.resolvedConfig.signedUrlExpiresIn,\n });\n }\n\n /**\n * Generate pre-signed URL for client-side uploads.\n *\n * Creates a pre-signed PutObject URL that allows direct uploads from\n * the browser to S3, bypassing server-side upload limits (e.g., Vercel's 4.5MB).\n *\n * @param key - Storage path/key for the upload\n * @param mimeType - MIME type of the file being uploaded\n * @param expiresIn - URL validity duration in seconds (default: 3600)\n * @returns Client upload data with URL, method, and headers\n */\n async getPresignedUploadUrl(\n key: string,\n mimeType: string,\n expiresIn?: number\n ): Promise<ClientUploadData> {\n const expiration = expiresIn ?? this.resolvedConfig.signedUrlExpiresIn;\n\n // Build PutObject command parameters\n const commandParams: PutObjectCommandInput = {\n Bucket: this.resolvedConfig.bucket,\n Key: key,\n ContentType: mimeType,\n CacheControl: this.resolvedConfig.cacheControl,\n };\n\n // Add ACL if not R2\n if (!this.isR2) {\n commandParams.ACL = this.resolvedConfig.acl;\n }\n\n const command = new PutObjectCommand(commandParams);\n\n const uploadUrl = await getSignedUrl(this.client, command, {\n expiresIn: expiration,\n });\n\n return {\n uploadUrl,\n path: key,\n method: \"PUT\",\n headers: {\n \"Content-Type\": mimeType,\n },\n expiresAt: new Date(Date.now() + expiration * 1000),\n };\n }\n\n // ============================================================\n // Helper Methods\n // ============================================================\n\n /**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}\n *\n * @param filename - Original filename (will be sanitized)\n * @param folder - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\n private generateKey(filename: string, folder?: string): string {\n const sanitized = this.sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const prefix = folder ? `${folder}/${year}/${month}` : `${year}/${month}`;\n\n return `${prefix}/${uuid}-${sanitized}`;\n }\n\n /**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\n private sanitizeFilename(filename: string): string {\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n }\n\n /**\n * Check if an error is a \"not found\" error from S3.\n *\n * @param error - Error to check\n * @returns true if error indicates file not found\n */\n /**\n * The object's bytes, refusing once more than `cap` have actually arrived.\n *\n * `ContentLength` is OPTIONAL in the SDK's response type, and this adapter\n * explicitly supports MinIO, R2 and other compatible services — any of which\n * may omit it or report it wrongly. A preflight check against metadata is\n * therefore a courtesy that fails OPEN: trusting it alone leaves the cap\n * unenforced exactly where an unusual backend is involved, which is where a\n * caller most needs it.\n *\n * Counted while consuming, so an oversized body is abandoned partway rather\n * than after it has all been buffered — the cap exists to bound memory, and\n * one applied after `transformToByteArray` has already spent it.\n *\n * Falls back to buffering when the body is not async-iterable, which the SDK\n * types permit. Stated rather than hidden: on that path the cap bounds what a\n * caller RECEIVES rather than what the process allocates.\n */\n private async readCapped(\n filePath: string,\n body: NonNullable<GetObjectCommandOutput[\"Body\"]>,\n cap: number\n ): Promise<Buffer> {\n /*\n * Probed rather than assumed. The SDK types the body as a union whose Node\n * member is async-iterable and whose browser members are not, and this\n * package supports compatible services whose transport may differ — so the\n * capability is read off the value in hand instead of from the type.\n */\n const iterable = body as Partial<AsyncIterable<Uint8Array>>;\n if (typeof iterable[Symbol.asyncIterator] !== \"function\") {\n const whole = Buffer.from(await body.transformToByteArray());\n if (whole.byteLength > cap) {\n throw new StorageReadTooLargeError(filePath, cap, whole.byteLength);\n }\n return whole;\n }\n\n const chunks: Buffer[] = [];\n let received = 0;\n for await (const chunk of body as AsyncIterable<Uint8Array>) {\n received += chunk.byteLength;\n if (received > cap) {\n throw new StorageReadTooLargeError(filePath, cap, received);\n }\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks);\n }\n\n /**\n * Refuse a read whose object is already known to exceed the caller's cap.\n *\n * Its own method rather than three conditions inside `read`, because the\n * decision has nothing to do with the surrounding error handling and reads\n * as noise there — and because `read`'s branching is the part a reviewer has\n * to hold in their head to check the absent-versus-unavailable distinction.\n *\n * Both `undefined` cases mean \"no opinion\" and pass: a caller that named no\n * cap has none, and a store that reported no length gives nothing to compare.\n * The second is deliberately permissive — refusing an unmeasured object would\n * reject valid reads whenever S3 omits the header.\n */\n private refuseOverCap(\n filePath: string,\n declared: number | undefined,\n cap: number | undefined\n ): void {\n if (cap === undefined || declared === undefined || declared <= cap) return;\n /*\n * The SHARED refusal rather than a generic internal error, because callers\n * act on the difference. The email attachment path answers an over-cap read\n * with a size error the author can do something about; anything else is\n * wrapped as an opaque storage failure, so throwing `internal` here would\n * turn a fixable refusal into one that tells them nothing.\n */\n throw new StorageReadTooLargeError(filePath, cap, declared);\n }\n\n private isNotFoundError(error: unknown): boolean {\n if (error && typeof error === \"object\") {\n const e = error as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n return e.name === \"NotFound\" || e.$metadata?.httpStatusCode === 404;\n }\n return false;\n }\n\n // ============================================================\n // Public Accessors\n // ============================================================\n\n /**\n * Get the S3 client instance.\n * Useful for advanced operations not covered by the adapter interface.\n */\n getClient(): S3Client {\n return this.client;\n }\n\n /**\n * Get the bucket name.\n */\n getBucket(): string {\n return this.resolvedConfig.bucket;\n }\n\n /**\n * Get the AWS region.\n */\n getRegion(): string {\n return this.resolvedConfig.region;\n }\n\n /**\n * Check if this adapter is configured for Cloudflare R2.\n */\n isCloudflareR2(): boolean {\n return this.isR2;\n }\n}\n","/**\n * S3 Storage Plugin\n *\n * Factory function that creates a storage plugin for AWS S3 and S3-compatible services.\n * Returns a StoragePlugin that can be registered with MediaStorage.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With collection-specific configuration\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * collections: {\n * // Simple enable with defaults\n * media: true,\n *\n * // Full configuration for private documents\n * 'private-docs': {\n * prefix: 'private/',\n * clientUploads: true,\n * signedDownloads: true,\n * signedUrlExpiresIn: 3600\n * }\n * }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { StoragePlugin, ClientUploadData } from \"nextly/storage\";\n\nimport { S3StorageAdapter } from \"./adapter\";\nimport type { S3StorageConfig } from \"./types\";\n\n// ============================================================\n// Plugin Factory Function\n// ============================================================\n\n/**\n * Create an S3 storage plugin for Nextly.\n *\n * This factory function creates a StoragePlugin that can be added to\n * the `storage` array in `nextly.config.ts`. It supports:\n *\n * - AWS S3 (standard)\n * - Cloudflare R2 (S3-compatible)\n * - MinIO (S3-compatible, self-hosted)\n * - DigitalOcean Spaces (S3-compatible)\n * - Any other S3-compatible service\n *\n * @param config - S3 storage configuration\n * @returns A StoragePlugin that MediaStorage can register\n *\n * @throws Error if bucket is not provided (via adapter)\n * @throws Error if region is not provided (via adapter)\n */\nexport function s3Storage(config: S3StorageConfig): StoragePlugin {\n // Handle disabled plugin\n // When disabled, return a plugin with no collections and null adapter\n // MediaStorage.registerPlugin() checks for null adapter and skips registration\n if (config.enabled === false) {\n return {\n name: \"s3-storage\",\n type: \"s3\",\n collections: {},\n adapter: null as unknown as StoragePlugin[\"adapter\"],\n };\n }\n\n // Create the S3 adapter\n // Adapter constructor validates required config (bucket, region)\n const adapter = new S3StorageAdapter(config);\n\n // Build and return the plugin\n const plugin: StoragePlugin = {\n name: \"s3-storage\",\n type: \"s3\",\n collections: config.collections,\n adapter,\n\n /**\n * Generate a pre-signed URL for client-side uploads.\n *\n * This allows files to be uploaded directly from the browser to S3,\n * bypassing server-side upload limits (e.g., Vercel's 4.5MB limit).\n *\n * @param filename - Original filename from the client\n * @param mimeType - MIME type of the file\n * @param collection - Collection slug this upload belongs to\n * @returns Client upload data with pre-signed URL\n */\n async getClientUploadUrl(\n filename: string,\n mimeType: string,\n collection: string\n ): Promise<ClientUploadData> {\n // Get collection-specific configuration\n const collectionConfig = config.collections[collection];\n const prefix =\n typeof collectionConfig === \"object\"\n ? collectionConfig.prefix\n : undefined;\n\n // Generate storage key matching adapter's format\n // Format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n const key = generateStorageKey(filename, prefix);\n\n // Get pre-signed upload URL from adapter\n return adapter.getPresignedUploadUrl(key, mimeType);\n },\n\n /**\n * Generate a signed URL for private file downloads.\n *\n * Creates a time-limited URL for accessing files in private buckets.\n * Only works when collection has `signedDownloads: true`.\n *\n * @param path - Storage path/key of the file\n * @param expiresIn - URL validity duration in seconds\n * @returns Signed URL for downloading the file\n */\n async getSignedDownloadUrl(\n path: string,\n expiresIn?: number\n ): Promise<string> {\n return adapter.getSignedUrl(\n path,\n expiresIn ?? config.signedUrlExpiresIn ?? 3600\n );\n },\n };\n\n return plugin;\n}\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Generate a unique storage key with date-based prefix.\n *\n * Creates keys in format: {prefix}{year}/{month}/{uuid}-{sanitized-filename}\n * This matches the format used by S3StorageAdapter for consistency.\n *\n * @param filename - Original filename (will be sanitized)\n * @param prefix - Optional folder/prefix for organizing uploads\n * @returns Generated storage key\n */\nfunction generateStorageKey(filename: string, prefix?: string): string {\n const sanitized = sanitizeFilename(filename);\n const uuid = crypto.randomUUID();\n const date = new Date();\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n\n const keyPrefix = prefix ? `${prefix}${year}/${month}` : `${year}/${month}`;\n\n return `${keyPrefix}/${uuid}-${sanitized}`;\n}\n\n/**\n * Sanitize filename to prevent directory traversal and S3 key issues.\n *\n * Removes path separators and replaces unsafe characters with hyphens.\n * Keeps only: a-z, A-Z, 0-9, dot, underscore, hyphen\n *\n * @param filename - Original filename\n * @returns Sanitized filename safe for S3 keys\n */\nfunction sanitizeFilename(filename: string): string {\n // Extract basename (remove any path components)\n const basename = filename.split(/[/\\\\]/).pop() || filename;\n // Replace unsafe characters with hyphens\n return basename.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n}\n","/**\n * @nextly/storage-s3\n *\n * AWS S3 storage adapter for Nextly CMS.\n * Also works with S3-compatible services like Cloudflare R2, MinIO, and DigitalOcean Spaces.\n *\n * @example Basic usage with AWS S3\n * ```typescript\n * import { s3Storage } from '@nextly/storage-s3'\n * import { defineConfig } from 'nextly/config'\n *\n * export default defineConfig({\n * storage: [\n * s3Storage({\n * bucket: process.env.S3_BUCKET!,\n * region: process.env.AWS_REGION!,\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * collections: {\n * media: true\n * }\n * })\n * ]\n * })\n * ```\n *\n * @example With Cloudflare R2\n * ```typescript\n * s3Storage({\n * bucket: process.env.R2_BUCKET!,\n * region: 'auto',\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n * publicUrl: process.env.R2_PUBLIC_URL,\n * collections: { media: true }\n * })\n * ```\n *\n * @example With MinIO\n * ```typescript\n * s3Storage({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.MINIO_ACCESS_KEY!,\n * secretAccessKey: process.env.MINIO_SECRET_KEY!,\n * endpoint: 'http://localhost:9000',\n * forcePathStyle: true,\n * collections: { media: true }\n * })\n * ```\n *\n * @packageDocumentation\n */\n\n// ============================================================\n// S3 Storage Plugin Export (Primary API)\n// ============================================================\n\nexport { s3Storage } from \"./plugin\";\n\n// ============================================================\n// S3 Storage Adapter Export\n// ============================================================\n\nexport { S3StorageAdapter } from \"./adapter\";\n\n// ============================================================\n// S3 Types Export\n// ============================================================\n\nexport type {\n S3StorageConfig,\n S3ObjectACL,\n S3CollectionConfig,\n S3CollectionStorageMap,\n ResolvedS3Config,\n} from \"./types\";\n\n// ============================================================\n// Package Metadata\n// ============================================================\n\nexport const PACKAGE_NAME = \"@nextly/storage-s3\";\nexport const PACKAGE_VERSION = \"0.1.0\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextlyhq/storage-s3",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.66",
|
|
4
4
|
"description": "AWS S3 storage adapter for Nextly - supports S3, Cloudflare R2, MinIO",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"typescript": "^5.9.3",
|
|
35
35
|
"vite-tsconfig-paths": "^5.1.4",
|
|
36
36
|
"vitest": "^4.1.0",
|
|
37
|
-
"@nextlyhq/eslint-config": "0.0.2-alpha.
|
|
38
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.
|
|
39
|
-
"nextly": "0.0.2-alpha.
|
|
37
|
+
"@nextlyhq/eslint-config": "0.0.2-alpha.66",
|
|
38
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.66",
|
|
39
|
+
"nextly": "0.0.2-alpha.66"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"nextly",
|