@danceroutine/tango-core 1.11.15 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"errors-DpI5Dxmr.js","names":[],"sources":["../src/errors/factories/HttpErrorFactory.ts","../src/errors/factories/index.ts","../src/errors/ConflictError.ts","../src/errors/ValidationError.ts","../src/errors/MultipleObjectsReturned.ts","../src/errors/NotFoundError.ts","../src/errors/PermissionDenied.ts","../src/errors/AuthenticationError.ts","../src/errors/index.ts"],"sourcesContent":["import { TangoError } from '../TangoError';\nimport type { HttpError } from '../HttpError';\nimport { isError, isObject } from '../../runtime/index';\n\nexport interface HttpErrorFactoryConfig {\n /**\n * When true, raw error messages are included in HTTP responses.\n * When false, generic messages are returned for non-TangoError exceptions.\n * Defaults to true (dev-friendly). Set to false in production.\n */\n exposeErrors?: boolean;\n}\n\ntype ZodLikeIssue = {\n path?: unknown[];\n message?: unknown;\n};\n\ntype ZodLikeError = Error & {\n issues: ZodLikeIssue[];\n};\n\n/**\n * Converts errors into structured HTTP error responses.\n * Supports TangoError subclasses out of the box, and custom error handlers\n * can be registered for third-party or application-specific error types.\n *\n * @example\n * ```typescript\n * // Development (default) — exposes real error messages\n * const devFactory = new HttpErrorFactory();\n *\n * // Production — hides internal error details\n * const prodFactory = new HttpErrorFactory({ exposeErrors: false });\n *\n * // Register a custom handler for a third-party error\n * prodFactory.registerHandler(ZodError, (err) => ({\n * status: 400,\n * body: { error: 'Validation failed', details: err.flatten().fieldErrors },\n * }));\n *\n * // Quick one-shot conversion with dev defaults\n * const httpError = HttpErrorFactory.toHttpError(new NotFoundError('missing'));\n * ```\n */\nexport class HttpErrorFactory {\n static readonly BRAND = 'tango.error_factory.http' as const;\n readonly __tangoBrand: typeof HttpErrorFactory.BRAND = HttpErrorFactory.BRAND;\n\n // oxlint-disable-next-line typescript/no-explicit-any\n private handlers = new Map<new (...args: any[]) => Error, (error: Error) => HttpError>();\n private exposeErrors: boolean;\n\n constructor(config: HttpErrorFactoryConfig = {}) {\n this.exposeErrors = config.exposeErrors ?? true;\n }\n\n /**\n * Narrow an unknown value to `HttpErrorFactory`.\n */\n static isHttpErrorFactory(value: unknown): value is HttpErrorFactory {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === HttpErrorFactory.BRAND\n );\n }\n\n /**\n * Convert an unknown error into an `HttpError` using dev-friendly defaults.\n * Shorthand for `new HttpErrorFactory().create(error)`.\n */\n static toHttpError(error: unknown): HttpError {\n return new HttpErrorFactory().create(error);\n }\n\n private static isZodLikeValidationError(error: unknown): error is ZodLikeError {\n return (\n isError(error) &&\n isObject(error) &&\n Array.isArray((error as { issues?: unknown }).issues) &&\n (error as { name?: unknown }).name === 'ZodError'\n );\n }\n\n private static zodLikeErrorDetails(error: ZodLikeError): Record<string, string[]> {\n const details: Record<string, string[]> = {};\n\n for (const issue of error.issues) {\n const key = Array.isArray(issue.path) && issue.path.length > 0 ? String(issue.path.join('.')) : '_schema';\n const message = typeof issue.message === 'string' ? issue.message : 'Invalid value';\n\n const existing = details[key];\n if (existing) {\n existing.push(message);\n } else {\n details[key] = [message];\n }\n }\n\n return details;\n }\n\n /**\n * Register a custom mapper for an application or third-party error type.\n */\n\n // oxlint-disable-next-line typescript/no-explicit-any\n registerHandler<T extends Error>(errorClass: new (...args: any[]) => T, handler: (error: T) => HttpError): this {\n this.handlers.set(errorClass, handler as (error: Error) => HttpError);\n return this;\n }\n\n /**\n * Convert an unknown error into the normalized HTTP error shape Tango uses.\n */\n create(error: unknown): HttpError {\n if (TangoError.isTangoError(error)) {\n return error.toHttpError();\n }\n\n if (HttpErrorFactory.isZodLikeValidationError(error)) {\n return {\n status: 400,\n body: {\n error: error.message || 'Validation failed',\n details: HttpErrorFactory.zodLikeErrorDetails(error),\n },\n };\n }\n\n for (const [ErrorClass, handler] of this.handlers) {\n if (this.isErrorClassInstance(error, ErrorClass)) {\n return handler(error);\n }\n }\n\n if (isError(error)) {\n return {\n status: 500,\n body: {\n error: this.exposeErrors ? error.message : 'Internal Server Error',\n details: null,\n },\n };\n }\n\n return {\n status: 500,\n body: {\n error: 'Unknown error occurred',\n details: null,\n },\n };\n }\n\n // oxlint-disable-next-line typescript/no-explicit-any\n private isErrorClassInstance(error: unknown, ErrorClass: new (...args: any[]) => Error): error is Error {\n if (!isError(error)) {\n return false;\n }\n\n const expectedBrand = (ErrorClass as { BRAND?: unknown }).BRAND;\n if (\n typeof expectedBrand === 'string' &&\n isObject(error) &&\n (error as { __tangoBrand?: unknown }).__tangoBrand === expectedBrand\n ) {\n return true;\n }\n\n const constructorName = (error as { constructor?: { name?: unknown } }).constructor?.name;\n if (typeof constructorName === 'string' && constructorName === ErrorClass.name) {\n return true;\n }\n\n const errorName = (error as { name?: unknown }).name;\n if (typeof errorName === 'string' && errorName === ErrorClass.name) {\n return true;\n }\n\n return false;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { HttpErrorFactory, type HttpErrorFactoryConfig } from './HttpErrorFactory';\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for conflicting resource state (HTTP 409). */\nexport class ConflictError extends TangoError {\n static readonly BRAND = 'tango.error.conflict' as const;\n readonly __tangoBrand: typeof ConflictError.BRAND = ConflictError.BRAND;\n status = 409;\n\n /** Create a conflict error with optional custom message. */\n constructor(message: string = 'Resource conflict') {\n super(message);\n this.name = 'ConflictError';\n Object.setPrototypeOf(this, ConflictError.prototype);\n }\n\n /**\n * Narrow an unknown value to `ConflictError`.\n */\n static isConflictError(value: unknown): value is ConflictError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === ConflictError.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'conflict';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for request validation failures (HTTP 400). */\nexport class ValidationError extends TangoError {\n readonly __tangoValidationErrorBrand = 'tango.error.validation' as const;\n status = 400;\n\n constructor(\n message: string,\n public details?: ErrorDetails\n ) {\n super(message);\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n\n /**\n * Narrow an unknown value to `ValidationError`, including common legacy shapes.\n */\n static isValidationError(err: unknown): err is ValidationError {\n return (\n (!!err &&\n typeof err === 'object' &&\n (err as { __tangoValidationErrorBrand?: string }).__tangoValidationErrorBrand ===\n 'tango.error.validation') ||\n (typeof err === 'object' &&\n err !== null &&\n 'fields' in err &&\n typeof (err as { fields: unknown }).fields === 'object')\n );\n }\n\n protected override getErrorName(): string {\n return 'ValidationError';\n }\n\n protected override getDetails(): ErrorDetails {\n return this.details;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error when a queryset lookup returns more than one row (HTTP 409). */\nexport class MultipleObjectsReturned extends TangoError {\n static readonly BRAND = 'tango.error.multiple_objects_returned' as const;\n readonly __tangoBrand: typeof MultipleObjectsReturned.BRAND = MultipleObjectsReturned.BRAND;\n status = 409;\n\n constructor(message: string = 'Multiple objects returned') {\n super(message);\n this.name = 'MultipleObjectsReturned';\n Object.setPrototypeOf(this, MultipleObjectsReturned.prototype);\n }\n\n static isMultipleObjectsReturned(value: unknown): value is MultipleObjectsReturned {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === MultipleObjectsReturned.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'multiple_objects_returned';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for missing resources (HTTP 404). */\nexport class NotFoundError extends TangoError {\n static readonly BRAND = 'tango.error.not_found' as const;\n readonly __tangoBrand: typeof NotFoundError.BRAND = NotFoundError.BRAND;\n status = 404;\n\n /** Create a not-found error with optional custom message. */\n constructor(message: string = 'Resource not found') {\n super(message);\n this.name = 'NotFoundError';\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n\n /**\n * Narrow an unknown value to `NotFoundError`.\n */\n static isNotFoundError(value: unknown): value is NotFoundError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === NotFoundError.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'not_found';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for authorization failures (HTTP 403). */\nexport class PermissionDenied extends TangoError {\n static readonly BRAND = 'tango.error.permission_denied' as const;\n readonly __tangoBrand: typeof PermissionDenied.BRAND = PermissionDenied.BRAND;\n status = 403;\n\n /** Create a permission-denied error with optional custom message. */\n constructor(message: string = 'Permission denied') {\n super(message);\n this.name = 'PermissionDenied';\n Object.setPrototypeOf(this, PermissionDenied.prototype);\n }\n\n /**\n * Narrow an unknown value to `PermissionDenied`.\n */\n static isPermissionDenied(value: unknown): value is PermissionDenied {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === PermissionDenied.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'permission_denied';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for missing/invalid authentication (HTTP 401). */\nexport class AuthenticationError extends TangoError {\n static readonly BRAND = 'tango.error.authentication' as const;\n readonly __tangoBrand: typeof AuthenticationError.BRAND = AuthenticationError.BRAND;\n status = 401;\n\n /** Create an authentication error with optional custom message. */\n constructor(message: string = 'Authentication required') {\n super(message);\n this.name = 'AuthenticationError';\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n\n /**\n * Narrow an unknown value to `AuthenticationError`.\n */\n static isAuthenticationError(value: unknown): value is AuthenticationError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === AuthenticationError.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'authentication_error';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nimport type { HttpError } from './HttpError';\nimport * as factories from './factories/index';\nimport { TangoError, type ErrorDetails, type ErrorEnvelope, type ProblemDetails } from './TangoError';\nimport { ConflictError } from './ConflictError';\nimport { ValidationError } from './ValidationError';\nimport { MultipleObjectsReturned } from './MultipleObjectsReturned';\nimport { NotFoundError } from './NotFoundError';\nimport { PermissionDenied } from './PermissionDenied';\nimport { AuthenticationError } from './AuthenticationError';\nimport { HttpErrorFactory, type HttpErrorFactoryConfig } from './factories/HttpErrorFactory';\n\nexport {\n AuthenticationError,\n ConflictError,\n HttpErrorFactory,\n MultipleObjectsReturned,\n NotFoundError,\n PermissionDenied,\n TangoError,\n ValidationError,\n factories,\n};\n\nexport type { ErrorDetails, ErrorEnvelope, HttpError, HttpErrorFactoryConfig, ProblemDetails };\n"],"mappings":"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"}
1
+ {"version":3,"file":"errors-DpI5Dxmr.js","names":[],"sources":["../src/errors/factories/HttpErrorFactory.ts","../src/errors/factories/index.ts","../src/errors/ConflictError.ts","../src/errors/ValidationError.ts","../src/errors/MultipleObjectsReturned.ts","../src/errors/NotFoundError.ts","../src/errors/PermissionDenied.ts","../src/errors/AuthenticationError.ts","../src/errors/index.ts"],"sourcesContent":["import { TangoError } from '../TangoError';\nimport type { HttpError } from '../HttpError';\nimport { isError, isObject } from '../../runtime/index';\n\nexport interface HttpErrorFactoryConfig {\n /**\n * When true, raw error messages are included in HTTP responses.\n * When false, generic messages are returned for non-TangoError exceptions.\n * Defaults to true (dev-friendly). Set to false in production.\n */\n exposeErrors?: boolean;\n}\n\ntype ZodLikeIssue = {\n path?: unknown[];\n message?: unknown;\n};\n\ntype ZodLikeError = Error & {\n issues: ZodLikeIssue[];\n};\n\n/**\n * Converts errors into structured HTTP error responses.\n * Supports TangoError subclasses out of the box, and custom error handlers\n * can be registered for third-party or application-specific error types.\n *\n * @example\n * ```typescript\n * // Development (default) — exposes real error messages\n * const devFactory = new HttpErrorFactory();\n *\n * // Production — hides internal error details\n * const prodFactory = new HttpErrorFactory({ exposeErrors: false });\n *\n * // Register a custom handler for a third-party error\n * prodFactory.registerHandler(ZodError, (err) => ({\n * status: 400,\n * body: { error: 'Validation failed', details: err.flatten().fieldErrors },\n * }));\n *\n * // Quick one-shot conversion with dev defaults\n * const httpError = HttpErrorFactory.toHttpError(new NotFoundError('missing'));\n * ```\n */\nexport class HttpErrorFactory {\n static readonly BRAND = 'tango.error_factory.http' as const;\n readonly __tangoBrand: typeof HttpErrorFactory.BRAND = HttpErrorFactory.BRAND;\n\n // oxlint-disable-next-line typescript/no-explicit-any\n private handlers = new Map<new (...args: any[]) => Error, (error: Error) => HttpError>();\n private exposeErrors: boolean;\n\n constructor(config: HttpErrorFactoryConfig = {}) {\n this.exposeErrors = config.exposeErrors ?? true;\n }\n\n /**\n * Narrow an unknown value to `HttpErrorFactory`.\n */\n static isHttpErrorFactory(value: unknown): value is HttpErrorFactory {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === HttpErrorFactory.BRAND\n );\n }\n\n /**\n * Convert an unknown error into an `HttpError` using dev-friendly defaults.\n * Shorthand for `new HttpErrorFactory().create(error)`.\n */\n static toHttpError(error: unknown): HttpError {\n return new HttpErrorFactory().create(error);\n }\n\n private static isZodLikeValidationError(error: unknown): error is ZodLikeError {\n return (\n isError(error) &&\n isObject(error) &&\n Array.isArray((error as { issues?: unknown }).issues) &&\n (error as { name?: unknown }).name === 'ZodError'\n );\n }\n\n private static zodLikeErrorDetails(error: ZodLikeError): Record<string, string[]> {\n const details: Record<string, string[]> = {};\n\n for (const issue of error.issues) {\n const key = Array.isArray(issue.path) && issue.path.length > 0 ? String(issue.path.join('.')) : '_schema';\n const message = typeof issue.message === 'string' ? issue.message : 'Invalid value';\n\n const existing = details[key];\n if (existing) {\n existing.push(message);\n } else {\n details[key] = [message];\n }\n }\n\n return details;\n }\n\n /**\n * Register a custom mapper for an application or third-party error type.\n */\n\n // oxlint-disable-next-line typescript/no-explicit-any\n registerHandler<T extends Error>(errorClass: new (...args: any[]) => T, handler: (error: T) => HttpError): this {\n this.handlers.set(errorClass, handler as (error: Error) => HttpError);\n return this;\n }\n\n /**\n * Convert an unknown error into the normalized HTTP error shape Tango uses.\n */\n create(error: unknown): HttpError {\n if (TangoError.isTangoError(error)) {\n return error.toHttpError();\n }\n\n if (HttpErrorFactory.isZodLikeValidationError(error)) {\n return {\n status: 400,\n body: {\n error: error.message || 'Validation failed',\n details: HttpErrorFactory.zodLikeErrorDetails(error),\n },\n };\n }\n\n for (const [ErrorClass, handler] of this.handlers) {\n if (this.isErrorClassInstance(error, ErrorClass)) {\n return handler(error);\n }\n }\n\n if (isError(error)) {\n return {\n status: 500,\n body: {\n error: this.exposeErrors ? error.message : 'Internal Server Error',\n details: null,\n },\n };\n }\n\n return {\n status: 500,\n body: {\n error: 'Unknown error occurred',\n details: null,\n },\n };\n }\n\n // oxlint-disable-next-line typescript/no-explicit-any\n private isErrorClassInstance(error: unknown, ErrorClass: new (...args: any[]) => Error): error is Error {\n if (!isError(error)) {\n return false;\n }\n\n const expectedBrand = (ErrorClass as { BRAND?: unknown }).BRAND;\n if (\n typeof expectedBrand === 'string' &&\n isObject(error) &&\n (error as { __tangoBrand?: unknown }).__tangoBrand === expectedBrand\n ) {\n return true;\n }\n\n const constructorName = (error as { constructor?: { name?: unknown } }).constructor?.name;\n if (typeof constructorName === 'string' && constructorName === ErrorClass.name) {\n return true;\n }\n\n const errorName = (error as { name?: unknown }).name;\n if (typeof errorName === 'string' && errorName === ErrorClass.name) {\n return true;\n }\n\n return false;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { HttpErrorFactory, type HttpErrorFactoryConfig } from './HttpErrorFactory';\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for conflicting resource state (HTTP 409). */\nexport class ConflictError extends TangoError {\n static readonly BRAND = 'tango.error.conflict' as const;\n readonly __tangoBrand: typeof ConflictError.BRAND = ConflictError.BRAND;\n status = 409;\n\n /** Create a conflict error with optional custom message. */\n constructor(message: string = 'Resource conflict') {\n super(message);\n this.name = 'ConflictError';\n Object.setPrototypeOf(this, ConflictError.prototype);\n }\n\n /**\n * Narrow an unknown value to `ConflictError`.\n */\n static isConflictError(value: unknown): value is ConflictError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === ConflictError.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'conflict';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for request validation failures (HTTP 400). */\nexport class ValidationError extends TangoError {\n readonly __tangoValidationErrorBrand = 'tango.error.validation' as const;\n status = 400;\n\n constructor(\n message: string,\n public details?: ErrorDetails\n ) {\n super(message);\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n\n /**\n * Narrow an unknown value to `ValidationError`, including common legacy shapes.\n */\n static isValidationError(err: unknown): err is ValidationError {\n return (\n (!!err &&\n typeof err === 'object' &&\n (err as { __tangoValidationErrorBrand?: string }).__tangoValidationErrorBrand ===\n 'tango.error.validation') ||\n (typeof err === 'object' &&\n err !== null &&\n 'fields' in err &&\n typeof (err as { fields: unknown }).fields === 'object')\n );\n }\n\n protected override getErrorName(): string {\n return 'ValidationError';\n }\n\n protected override getDetails(): ErrorDetails {\n return this.details;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error when a queryset lookup returns more than one row (HTTP 409). */\nexport class MultipleObjectsReturned extends TangoError {\n static readonly BRAND = 'tango.error.multiple_objects_returned' as const;\n readonly __tangoBrand: typeof MultipleObjectsReturned.BRAND = MultipleObjectsReturned.BRAND;\n status = 409;\n\n constructor(message: string = 'Multiple objects returned') {\n super(message);\n this.name = 'MultipleObjectsReturned';\n Object.setPrototypeOf(this, MultipleObjectsReturned.prototype);\n }\n\n static isMultipleObjectsReturned(value: unknown): value is MultipleObjectsReturned {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === MultipleObjectsReturned.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'multiple_objects_returned';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for missing resources (HTTP 404). */\nexport class NotFoundError extends TangoError {\n static readonly BRAND = 'tango.error.not_found' as const;\n readonly __tangoBrand: typeof NotFoundError.BRAND = NotFoundError.BRAND;\n status = 404;\n\n /** Create a not-found error with optional custom message. */\n constructor(message: string = 'Resource not found') {\n super(message);\n this.name = 'NotFoundError';\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n\n /**\n * Narrow an unknown value to `NotFoundError`.\n */\n static isNotFoundError(value: unknown): value is NotFoundError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === NotFoundError.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'not_found';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for authorization failures (HTTP 403). */\nexport class PermissionDenied extends TangoError {\n static readonly BRAND = 'tango.error.permission_denied' as const;\n readonly __tangoBrand: typeof PermissionDenied.BRAND = PermissionDenied.BRAND;\n status = 403;\n\n /** Create a permission-denied error with optional custom message. */\n constructor(message: string = 'Permission denied') {\n super(message);\n this.name = 'PermissionDenied';\n Object.setPrototypeOf(this, PermissionDenied.prototype);\n }\n\n /**\n * Narrow an unknown value to `PermissionDenied`.\n */\n static isPermissionDenied(value: unknown): value is PermissionDenied {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === PermissionDenied.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'permission_denied';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\n/** Error for missing/invalid authentication (HTTP 401). */\nexport class AuthenticationError extends TangoError {\n static readonly BRAND = 'tango.error.authentication' as const;\n readonly __tangoBrand: typeof AuthenticationError.BRAND = AuthenticationError.BRAND;\n status = 401;\n\n /** Create an authentication error with optional custom message. */\n constructor(message: string = 'Authentication required') {\n super(message);\n this.name = 'AuthenticationError';\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n\n /**\n * Narrow an unknown value to `AuthenticationError`.\n */\n static isAuthenticationError(value: unknown): value is AuthenticationError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === AuthenticationError.BRAND\n );\n }\n\n protected override getErrorName(): string {\n return 'authentication_error';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nimport type { HttpError } from './HttpError';\nimport * as factories from './factories/index';\nimport { TangoError, type ErrorDetails, type ErrorEnvelope, type ProblemDetails } from './TangoError';\nimport { ConflictError } from './ConflictError';\nimport { ValidationError } from './ValidationError';\nimport { MultipleObjectsReturned } from './MultipleObjectsReturned';\nimport { NotFoundError } from './NotFoundError';\nimport { PermissionDenied } from './PermissionDenied';\nimport { AuthenticationError } from './AuthenticationError';\nimport { HttpErrorFactory, type HttpErrorFactoryConfig } from './factories/HttpErrorFactory';\n\nexport {\n AuthenticationError,\n ConflictError,\n HttpErrorFactory,\n MultipleObjectsReturned,\n NotFoundError,\n PermissionDenied,\n TangoError,\n ValidationError,\n factories,\n};\n\nexport type { ErrorDetails, ErrorEnvelope, HttpError, HttpErrorFactoryConfig, ProblemDetails };\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as TangoHeaders, i as TangoRequest, n as TangoResponse, o as JsonValue, r as TangoQueryParams, s as TangoBody } from "../index-Ds43ITu5.js";
1
+ import { a as TangoHeaders, i as TangoRequest, n as TangoResponse, o as JsonValue, r as TangoQueryParams, s as TangoBody } from "../index-Be9hXvS0.js";
2
2
  export { type JsonValue, TangoBody, TangoHeaders, TangoQueryParams, TangoRequest, TangoResponse };
@@ -1,2 +1,2 @@
1
- import { a as TangoHeaders, i as TangoQueryParams, n as TangoResponse, o as TangoBody, r as TangoRequest } from "../http-BJPRtBGV.js";
1
+ import { a as TangoHeaders, i as TangoQueryParams, n as TangoResponse, o as TangoBody, r as TangoRequest } from "../http-Bf_uQBDm.js";
2
2
  export { TangoBody, TangoHeaders, TangoQueryParams, TangoRequest, TangoResponse };
@@ -320,6 +320,11 @@ var TangoBody = class TangoBody {
320
320
  var TangoHeaders = class TangoHeaders extends Headers {
321
321
  static BRAND = "tango.http.headers";
322
322
  __tangoBrand = TangoHeaders.BRAND;
323
+ cookieEntries = [];
324
+ constructor(init) {
325
+ super();
326
+ this.appendHeadersInit(init);
327
+ }
323
328
  /**
324
329
  * Narrow an unknown value to `TangoHeaders`.
325
330
  */
@@ -343,6 +348,22 @@ var TangoHeaders = class TangoHeaders extends Headers {
343
348
  if (options.partitioned) cookie += "; Partitioned";
344
349
  return cookie;
345
350
  }
351
+ static getCookieIdentity(name, options = {}) {
352
+ return {
353
+ name,
354
+ domain: options.domain?.toLowerCase() ?? "",
355
+ path: options.path ?? "/"
356
+ };
357
+ }
358
+ static hasCookieIdentity(entry, identity) {
359
+ return entry.identity !== null && entry.identity.name === identity.name && entry.identity.domain === identity.domain && entry.identity.path === identity.path;
360
+ }
361
+ static isSetCookieName(name) {
362
+ return name.toLowerCase() === "set-cookie";
363
+ }
364
+ static hasHeaderEntries(value) {
365
+ return typeof value.entries === "function";
366
+ }
346
367
  static hasNumberSize(value) {
347
368
  return typeof value === "object" && value !== null && typeof value.size === "number";
348
369
  }
@@ -353,6 +374,36 @@ var TangoHeaders = class TangoHeaders extends Headers {
353
374
  const maybeBuffer = Buffer;
354
375
  return typeof Buffer !== "undefined" && typeof maybeBuffer.isBuffer === "function" && maybeBuffer.isBuffer(value);
355
376
  }
377
+ append(name, value) {
378
+ if (!TangoHeaders.isSetCookieName(name)) {
379
+ super.append(name, value);
380
+ return;
381
+ }
382
+ this.cookieEntries.push({
383
+ line: value,
384
+ identity: null
385
+ });
386
+ this.syncSetCookieHeader();
387
+ }
388
+ set(name, value) {
389
+ if (!TangoHeaders.isSetCookieName(name)) {
390
+ super.set(name, value);
391
+ return;
392
+ }
393
+ this.cookieEntries = [{
394
+ line: value,
395
+ identity: null
396
+ }];
397
+ this.syncSetCookieHeader();
398
+ }
399
+ delete(name) {
400
+ if (!TangoHeaders.isSetCookieName(name)) {
401
+ super.delete(name);
402
+ return;
403
+ }
404
+ this.cookieEntries = [];
405
+ super.delete("Set-Cookie");
406
+ }
356
407
  /**
357
408
  * Sets the Content-Disposition header with type "inline" and the specified filename.
358
409
  * This is useful to indicate that the content should be displayed inline in the browser,
@@ -379,7 +430,12 @@ var TangoHeaders = class TangoHeaders extends Headers {
379
430
  */
380
431
  clone() {
381
432
  const copy = new TangoHeaders();
382
- for (const [name, value] of this.entries()) copy.append(name, value);
433
+ for (const [name, value] of this.entries()) if (!TangoHeaders.isSetCookieName(name)) copy.append(name, value);
434
+ copy.cookieEntries = this.cookieEntries.map((entry) => ({
435
+ line: entry.line,
436
+ identity: entry.identity === null ? null : { ...entry.identity }
437
+ }));
438
+ copy.syncSetCookieHeader();
383
439
  return copy;
384
440
  }
385
441
  /**
@@ -434,22 +490,30 @@ var TangoHeaders = class TangoHeaders extends Headers {
434
490
  this.set(key, Array.from(nextSet).join(", "));
435
491
  }
436
492
  /**
437
- * Set a cookie header (for Set-Cookie).
438
- * @param name
439
- * @param value
440
- * @param options
493
+ * Set a `Set-Cookie` line, replacing prior helper-managed intent for the same name, domain, and path.
441
494
  */
442
495
  setCookie(name, value, options) {
443
- this.append("Set-Cookie", TangoHeaders.serializeCookie(name, value, options));
496
+ const identity = TangoHeaders.getCookieIdentity(name, options);
497
+ this.replaceCookieIdentity(identity, TangoHeaders.serializeCookie(name, value, options));
444
498
  }
445
499
  /**
446
- * Append (additionally) a new cookie.
500
+ * Append another `Set-Cookie` line without replacing earlier cookie intent.
447
501
  */
448
502
  appendCookie(name, value, options) {
449
- this.append("Set-Cookie", TangoHeaders.serializeCookie(name, value, options));
503
+ this.cookieEntries.push({
504
+ line: TangoHeaders.serializeCookie(name, value, options),
505
+ identity: TangoHeaders.getCookieIdentity(name, options)
506
+ });
507
+ this.syncSetCookieHeader();
508
+ }
509
+ /**
510
+ * Return each `Set-Cookie` header line separately.
511
+ */
512
+ getSetCookie() {
513
+ return this.cookieEntries.map((entry) => entry.line);
450
514
  }
451
515
  /**
452
- * Delete a cookie ("unset" it via expired date).
516
+ * Expire a cookie, replacing prior helper-managed intent for the same name, domain, and path.
453
517
  */
454
518
  deleteCookie(name, options) {
455
519
  this.setCookie(name, "", {
@@ -485,14 +549,14 @@ var TangoHeaders = class TangoHeaders extends Headers {
485
549
  this.set("Content-Type", mime);
486
550
  }
487
551
  /**
488
- * Attempt to guess and set the Content-Type header from a file (string or Blob) and optional filename.
552
+ * Attempt to guess and set the Content-Type header from response body bytes and an optional filename.
489
553
  *
490
- * @param file File-like input (string or Blob)
491
- * @param filename Optional filename to help guess mime type by extension
554
+ * @param body Response body bytes (for example a Blob or Uint8Array)
555
+ * @param filename Optional filename used to guess mime type by extension
492
556
  */
493
- setContentTypeByFile(file, filename) {
557
+ setContentTypeForBody(body, filename) {
494
558
  if (this.has("Content-Type")) return;
495
- if (typeof file === "string" && filename) {
559
+ if (filename) {
496
560
  const dotIndex = filename.lastIndexOf(".");
497
561
  const mime = {
498
562
  txt: "text/plain",
@@ -511,18 +575,25 @@ var TangoHeaders = class TangoHeaders extends Headers {
511
575
  ico: "image/x-icon",
512
576
  md: "text/markdown"
513
577
  }[dotIndex >= 0 ? filename.slice(dotIndex + 1).toLowerCase() : ""];
514
- if (mime) this.set("Content-Type", mime);
515
- else this.set("Content-Type", "application/octet-stream");
516
- return;
578
+ if (mime) {
579
+ this.set("Content-Type", mime);
580
+ return;
581
+ }
517
582
  }
518
- if (isBlob(file)) {
519
- if (file.type && file.type !== "") this.set("Content-Type", file.type);
583
+ if (isBlob(body)) {
584
+ if (body.type && body.type !== "") this.set("Content-Type", body.type);
520
585
  else this.set("Content-Type", "application/octet-stream");
521
586
  return;
522
587
  }
523
588
  this.set("Content-Type", "application/octet-stream");
524
589
  }
525
590
  /**
591
+ * @deprecated Use {@link TangoHeaders.setContentTypeForBody} instead.
592
+ */
593
+ setContentTypeByFile(body, filename) {
594
+ this.setContentTypeForBody(body, filename);
595
+ }
596
+ /**
526
597
  * Sets the Content-Length header, inferring it from the body if not explicitly provided.
527
598
  * If the body is a string, ArrayBuffer, Uint8Array, Blob/Buffer (or has a .size or .length property),
528
599
  * the length is computed. For unsupported types, does nothing.
@@ -617,6 +688,39 @@ var TangoHeaders = class TangoHeaders extends Headers {
617
688
  getResponseTime() {
618
689
  return this.get("X-Response-Time");
619
690
  }
691
+ appendHeadersInit(init) {
692
+ if (!init) return;
693
+ if (TangoHeaders.isTangoHeaders(init)) {
694
+ for (const [name, value] of init.entries()) if (!TangoHeaders.isSetCookieName(name)) super.append(name, value);
695
+ this.cookieEntries = init.cookieEntries.map((entry) => ({
696
+ line: entry.line,
697
+ identity: entry.identity === null ? null : { ...entry.identity }
698
+ }));
699
+ this.syncSetCookieHeader();
700
+ return;
701
+ }
702
+ if (Array.isArray(init)) {
703
+ for (const [name, value] of init) this.append(name, value);
704
+ return;
705
+ }
706
+ if (TangoHeaders.hasHeaderEntries(init)) {
707
+ for (const [name, value] of init.entries()) this.append(name, value);
708
+ return;
709
+ }
710
+ for (const [name, value] of Object.entries(init)) this.append(name, value);
711
+ }
712
+ replaceCookieIdentity(identity, line) {
713
+ this.cookieEntries = this.cookieEntries.filter((entry) => !TangoHeaders.hasCookieIdentity(entry, identity));
714
+ this.cookieEntries.push({
715
+ line,
716
+ identity
717
+ });
718
+ this.syncSetCookieHeader();
719
+ }
720
+ syncSetCookieHeader() {
721
+ super.delete("Set-Cookie");
722
+ for (const entry of this.cookieEntries) super.append("Set-Cookie", entry.line);
723
+ }
620
724
  };
621
725
  //#endregion
622
726
  //#region src/http/TangoQueryParams.ts
@@ -1367,33 +1471,55 @@ var TangoResponse = class TangoResponse {
1367
1471
  });
1368
1472
  }
1369
1473
  /**
1370
- * Returns a response for serving a file.
1474
+ * Create a response with inline Content-Disposition for file-like body content.
1475
+ *
1476
+ * The first argument is response body bytes, not a filesystem path. To serve bytes
1477
+ * from disk, read the file first and pass the result here.
1478
+ *
1479
+ * @example
1480
+ * ```ts
1481
+ * import { readFile } from 'node:fs/promises';
1482
+ *
1483
+ * const bytes = await readFile('/data/report.pdf');
1484
+ * return TangoResponse.file(bytes, { filename: 'report.pdf' });
1485
+ * ```
1371
1486
  */
1372
- static file(file, opts) {
1487
+ static file(body, opts) {
1373
1488
  const headers = new TangoHeaders(opts?.init?.headers ?? {});
1374
1489
  if (opts?.filename) headers.setContentDispositionInline(opts.filename);
1375
1490
  if (opts?.contentType && !headers.has("Content-Type")) headers.set("Content-Type", opts.contentType);
1376
- else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.filename);
1377
- if (!headers.has("Content-Length")) headers.setContentLengthFromBody(file);
1491
+ else if (!headers.has("Content-Type")) headers.setContentTypeForBody(body, opts?.filename);
1492
+ if (!headers.has("Content-Length")) headers.setContentLengthFromBody(body);
1378
1493
  return new TangoResponse({
1379
1494
  ...opts?.init,
1380
- body: file,
1495
+ body,
1381
1496
  headers
1382
1497
  });
1383
1498
  }
1384
1499
  /**
1385
- * Returns a response that prompts the user to download the file.
1500
+ * Create a response with attachment Content-Disposition for file-like body content.
1501
+ *
1502
+ * The first argument must be the response body bytes, not a filesystem path. To serve bytes
1503
+ * from disk, read the file first and pass the result here.
1504
+ *
1505
+ * @example
1506
+ * ```ts
1507
+ * import { readFile } from 'node:fs/promises';
1508
+ *
1509
+ * const bytes = await readFile('/data/report.pdf');
1510
+ * return TangoResponse.download(bytes, { filename: 'report.pdf' });
1511
+ * ```
1386
1512
  */
1387
- static download(file, opts) {
1513
+ static download(body, opts) {
1388
1514
  const headers = new TangoHeaders(opts?.init?.headers ?? {});
1389
1515
  if (opts?.filename) headers.setContentDispositionAttachment(opts.filename);
1390
1516
  else headers.set("Content-Disposition", "attachment");
1391
1517
  if (opts?.contentType && !headers.has("Content-Type")) headers.set("Content-Type", opts.contentType);
1392
- else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.filename);
1393
- if (!headers.has("Content-Length")) headers.setContentLengthFromBody(file);
1518
+ else if (!headers.has("Content-Type")) headers.setContentTypeForBody(body, opts?.filename);
1519
+ if (!headers.has("Content-Length")) headers.setContentLengthFromBody(body);
1394
1520
  return new TangoResponse({
1395
1521
  ...opts?.init,
1396
- body: file,
1522
+ body,
1397
1523
  headers
1398
1524
  });
1399
1525
  }
@@ -1457,19 +1583,19 @@ var TangoResponse = class TangoResponse {
1457
1583
  this.headers.vary(...fields);
1458
1584
  }
1459
1585
  /**
1460
- * Add a `Set-Cookie` header that replaces prior application intent.
1586
+ * Add a `Set-Cookie` header that replaces prior helper-managed intent for the same name, domain, and path.
1461
1587
  */
1462
1588
  setCookie(name, value, options) {
1463
1589
  this.headers.setCookie(name, value, options);
1464
1590
  }
1465
1591
  /**
1466
- * Append another `Set-Cookie` header.
1592
+ * Append another `Set-Cookie` header without replacing earlier cookie intent.
1467
1593
  */
1468
1594
  appendCookie(name, value, options) {
1469
1595
  this.headers.appendCookie(name, value, options);
1470
1596
  }
1471
1597
  /**
1472
- * Expire a cookie by issuing a matching deletion cookie header.
1598
+ * Expire a cookie by replacing prior helper-managed intent for the same name, domain, and path.
1473
1599
  */
1474
1600
  deleteCookie(name, options) {
1475
1601
  this.headers.deleteCookie(name, options);
@@ -1571,8 +1697,13 @@ var TangoResponse = class TangoResponse {
1571
1697
  toWebResponse() {
1572
1698
  const responseForTransfer = !this.bodyUsed && isReadableStream(this.bodySource) ? this.clone() : this;
1573
1699
  const body = TangoResponse.normalizeWebBody(responseForTransfer.bodySource);
1700
+ const headers = new Headers();
1701
+ responseForTransfer.headers.forEach((value, key) => {
1702
+ if (key.toLowerCase() !== "set-cookie") headers.append(key, value);
1703
+ });
1704
+ for (const cookie of responseForTransfer.headers.getSetCookie()) headers.append("Set-Cookie", cookie);
1574
1705
  return new Response(body, {
1575
- headers: new Headers(responseForTransfer.headers),
1706
+ headers,
1576
1707
  status: responseForTransfer.status,
1577
1708
  statusText: responseForTransfer.statusText
1578
1709
  });
@@ -1657,4 +1788,4 @@ var http_exports = /* @__PURE__ */ __exportAll({
1657
1788
  //#endregion
1658
1789
  export { TangoHeaders as a, TangoQueryParams as i, TangoResponse as n, TangoBody as o, TangoRequest as r, http_exports as t };
1659
1790
 
1660
- //# sourceMappingURL=http-BJPRtBGV.js.map
1791
+ //# sourceMappingURL=http-Bf_uQBDm.js.map