@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.
- package/dist/errors-DpI5Dxmr.js.map +1 -1
- package/dist/http/index.d.ts +1 -1
- package/dist/http/index.js +1 -1
- package/dist/{http-BJPRtBGV.js → http-Bf_uQBDm.js} +165 -34
- package/dist/http-Bf_uQBDm.js.map +1 -0
- package/dist/{index-Ds43ITu5.d.ts → index-Be9hXvS0.d.ts} +77 -53
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sql-CIPnuTYO.js.map +1 -1
- package/package.json +2 -2
- package/dist/http-BJPRtBGV.js.map +0 -1
|
@@ -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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"}
|
package/dist/http/index.d.ts
CHANGED
|
@@ -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-
|
|
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 };
|
package/dist/http/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as TangoHeaders, i as TangoQueryParams, n as TangoResponse, o as TangoBody, r as TangoRequest } from "../http-
|
|
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
|
|
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
|
-
|
|
496
|
+
const identity = TangoHeaders.getCookieIdentity(name, options);
|
|
497
|
+
this.replaceCookieIdentity(identity, TangoHeaders.serializeCookie(name, value, options));
|
|
444
498
|
}
|
|
445
499
|
/**
|
|
446
|
-
* Append
|
|
500
|
+
* Append another `Set-Cookie` line without replacing earlier cookie intent.
|
|
447
501
|
*/
|
|
448
502
|
appendCookie(name, value, options) {
|
|
449
|
-
this.
|
|
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
|
-
*
|
|
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
|
|
552
|
+
* Attempt to guess and set the Content-Type header from response body bytes and an optional filename.
|
|
489
553
|
*
|
|
490
|
-
* @param
|
|
491
|
-
* @param filename Optional filename to
|
|
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
|
-
|
|
557
|
+
setContentTypeForBody(body, filename) {
|
|
494
558
|
if (this.has("Content-Type")) return;
|
|
495
|
-
if (
|
|
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)
|
|
515
|
-
|
|
516
|
-
|
|
578
|
+
if (mime) {
|
|
579
|
+
this.set("Content-Type", mime);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
517
582
|
}
|
|
518
|
-
if (isBlob(
|
|
519
|
-
if (
|
|
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
|
-
*
|
|
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(
|
|
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.
|
|
1377
|
-
if (!headers.has("Content-Length")) headers.setContentLengthFromBody(
|
|
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
|
|
1495
|
+
body,
|
|
1381
1496
|
headers
|
|
1382
1497
|
});
|
|
1383
1498
|
}
|
|
1384
1499
|
/**
|
|
1385
|
-
*
|
|
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(
|
|
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.
|
|
1393
|
-
if (!headers.has("Content-Length")) headers.setContentLengthFromBody(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
1791
|
+
//# sourceMappingURL=http-Bf_uQBDm.js.map
|