@danceroutine/tango-adapters-express 1.11.14 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-kC_MF6Wt.js","names":[],"sources":["../src/adapter/ExpressAdapter.ts","../src/adapter/index.ts"],"sourcesContent":["import type { Request as ExpressRequest, Response as ExpressResponse, NextFunction, RequestHandler } from 'express';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream as NodeReadableStream } from 'node:stream/web';\nimport { Router } from 'express';\nimport { RequestContext } from '@danceroutine/tango-resources';\nimport { TangoQueryParams, TangoResponse } from '@danceroutine/tango-core';\nimport {\n FRAMEWORK_ADAPTER_BRAND,\n FrameworkAdapterRequestExecutor,\n type FrameworkAdapter,\n type FrameworkAdapterOptions,\n} from '@danceroutine/tango-adapters-core/adapter';\n\ntype ViewSetActionMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\ntype ResolvedViewSetActionDescriptor = {\n name: string;\n scope: 'detail' | 'collection';\n methods: readonly ViewSetActionMethod[];\n path: string;\n};\n\n/**\n * Adapter options for Express integration.\n */\nexport type AdaptExpressOptions = FrameworkAdapterOptions<ExpressRequest>;\n\n/**\n * Minimal CRUD viewset contract used by adapter route registration helpers.\n */\nexport interface ExpressCrudViewSet {\n list(ctx: RequestContext): Promise<TangoResponse>;\n create(ctx: RequestContext): Promise<TangoResponse>;\n retrieve(ctx: RequestContext, id: string): Promise<TangoResponse>;\n update(ctx: RequestContext, id: string): Promise<TangoResponse>;\n destroy(ctx: RequestContext, id: string): Promise<TangoResponse>;\n}\n\nexport interface ExpressAPIView {\n dispatch(ctx: RequestContext): Promise<TangoResponse>;\n}\n\n/**\n * Minimal route registrar interface implemented by Express apps and routers.\n */\nexport interface ExpressRouteRegistrar {\n get(path: string, handler: RequestHandler): unknown;\n post(path: string, handler: RequestHandler): unknown;\n patch(path: string, handler: RequestHandler): unknown;\n put(path: string, handler: RequestHandler): unknown;\n delete(path: string, handler: RequestHandler): unknown;\n}\n\n/**\n * Express adapter that translates Express handlers to Tango `RequestContext`.\n */\nexport class ExpressAdapter implements FrameworkAdapter<Response, RequestHandler, ExpressRequest> {\n readonly __tangoBrand: typeof FRAMEWORK_ADAPTER_BRAND = FRAMEWORK_ADAPTER_BRAND;\n private readonly requestExecutor = new FrameworkAdapterRequestExecutor();\n\n /**\n * Normalize an Express request into Tango query params.\n */\n toQueryParams(req: ExpressRequest): TangoQueryParams {\n const request = this.toRequestFromExpress(req);\n return TangoQueryParams.fromRequest(request);\n }\n\n /**\n * Adapt a Tango-style handler into an Express request handler.\n */\n adapt(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptExpressOptions = {}\n ): RequestHandler {\n return this.createHandler(handler, options);\n }\n\n /**\n * Build an Express router that wires all CRUD routes for a viewset.\n */\n createViewSetRouter(viewset: ExpressCrudViewSet, options: AdaptExpressOptions = {}): Router {\n const router = Router();\n this.registerViewSet(router, '', viewset, options);\n return router;\n }\n\n /**\n * Build an Express router for a single-path APIView.\n */\n createAPIViewRouter(apiView: ExpressAPIView, options: AdaptExpressOptions = {}): Router {\n const router = Router();\n this.registerAPIView(router, '', apiView, options);\n return router;\n }\n\n /**\n * Register all CRUD routes for a viewset under a base path.\n */\n registerViewSet(\n registrar: ExpressRouteRegistrar,\n basePath: string,\n viewset: ExpressCrudViewSet,\n options: AdaptExpressOptions = {}\n ): void {\n const collectionPath = this.normalizeBasePath(basePath);\n const detailPath = collectionPath === '/' ? '/:id' : `${collectionPath}/:id`;\n\n registrar.get(\n collectionPath,\n this.adapt((ctx) => viewset.list(ctx), options)\n );\n registrar.post(\n collectionPath,\n this.adapt((ctx) => viewset.create(ctx), options)\n );\n registrar.get(\n detailPath,\n this.adapt((ctx, id) => viewset.retrieve(ctx, String(id)), options)\n );\n registrar.patch(\n detailPath,\n this.adapt((ctx, id) => viewset.update(ctx, String(id)), options)\n );\n registrar.put(\n detailPath,\n this.adapt((ctx, id) => viewset.update(ctx, String(id)), options)\n );\n registrar.delete(\n detailPath,\n this.adapt((ctx, id) => viewset.destroy(ctx, String(id)), options)\n );\n\n const actions = this.getViewSetActions(viewset);\n actions.forEach((action) => {\n const actionPath =\n action.scope === 'detail'\n ? this.joinPath(detailPath, action.path)\n : this.joinPath(collectionPath, action.path);\n const handler =\n action.scope === 'detail'\n ? this.adapt((ctx, id) => this.invokeDetailAction(viewset, action, ctx, String(id)), options)\n : this.adapt((ctx) => this.invokeCollectionAction(viewset, action, ctx), options);\n\n this.registerActionMethods(registrar, action.methods, actionPath, handler);\n });\n }\n\n /**\n * Register one APIView on a single path and dispatch by HTTP method.\n */\n registerAPIView(\n registrar: ExpressRouteRegistrar,\n path: string,\n apiView: ExpressAPIView,\n options: AdaptExpressOptions = {}\n ): void {\n const normalizedPath = this.normalizeBasePath(path);\n this.registerAllMethods(\n registrar,\n normalizedPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n }\n\n /**\n * Register one GenericAPIView with collection and detail routes.\n */\n registerGenericAPIView(\n registrar: ExpressRouteRegistrar,\n collectionPath: string,\n detailPath: string | undefined,\n apiView: ExpressAPIView,\n options: AdaptExpressOptions = {}\n ): void {\n const normalizedCollectionPath = this.normalizeBasePath(collectionPath);\n const normalizedDetailPath = detailPath?.trim().length\n ? this.normalizeBasePath(detailPath)\n : this.joinPath(normalizedCollectionPath, ':id');\n\n registrar.get(\n normalizedCollectionPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.post(\n normalizedCollectionPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.get(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.put(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.patch(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.delete(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n }\n\n private invokeDetailAction(\n viewset: ExpressCrudViewSet,\n action: ResolvedViewSetActionDescriptor,\n ctx: RequestContext,\n id: string\n ): Promise<TangoResponse> {\n const candidate = (viewset as unknown as Record<string, unknown>)[action.name];\n if (typeof candidate !== 'function') {\n throw new TypeError(`Missing detail action method '${action.name}' on viewset.`);\n }\n return (\n candidate as (this: ExpressCrudViewSet, ctx: RequestContext, id: string) => Promise<TangoResponse>\n ).call(viewset, ctx, id);\n }\n\n private invokeCollectionAction(\n viewset: ExpressCrudViewSet,\n action: ResolvedViewSetActionDescriptor,\n ctx: RequestContext\n ): Promise<TangoResponse> {\n const candidate = (viewset as unknown as Record<string, unknown>)[action.name];\n if (typeof candidate !== 'function') {\n throw new TypeError(`Missing collection action method '${action.name}' on viewset.`);\n }\n return (candidate as (this: ExpressCrudViewSet, ctx: RequestContext) => Promise<TangoResponse>).call(\n viewset,\n ctx\n );\n }\n\n private createHandler(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptExpressOptions\n ): RequestHandler {\n return async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {\n try {\n const user = options.getUser ? await options.getUser(req) : null;\n\n const request = this.toRequestFromExpress(req);\n const ctx = RequestContext.create(request, user);\n ctx.params = this.normalizeParams(req.params);\n\n const rawId = req.params.id;\n const id = Array.isArray(rawId) ? rawId[0] : rawId;\n const tangoResponse = await this.requestExecutor\n .forHandler({ handler, ctx, id })\n .runResponse(req.method, options.transaction);\n const response = tangoResponse.toWebResponse();\n\n res.status(response.status);\n\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n if (response.body === null) {\n res.end();\n return;\n }\n\n if (tangoResponse.body !== null) {\n await this.pipeReadableStream(response.body, res);\n return;\n }\n\n const body = new Uint8Array<ArrayBuffer>(await response.arrayBuffer());\n res.send(this.normalizeResponseBody(body, response.headers));\n } catch (error) {\n next(error);\n }\n };\n }\n\n private async pipeReadableStream(\n body: ReadableStream<Uint8Array<ArrayBuffer>>,\n res: ExpressResponse\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const stream = Readable.fromWeb(body as unknown as NodeReadableStream);\n stream.on('error', reject);\n res.on('error', reject);\n res.on('finish', () => resolve());\n stream.pipe(res);\n });\n }\n\n private normalizeResponseBody(body: Uint8Array<ArrayBuffer>, headers: Headers): string | Buffer {\n const contentType = headers.get('content-type')?.toLowerCase() ?? '';\n if (contentType.startsWith('text/') || contentType.includes('json')) {\n return new TextDecoder().decode(body);\n }\n\n return Buffer.from(body);\n }\n\n private normalizeParams(params: Record<string, string | string[]>): Record<string, string> {\n return Object.fromEntries(\n Object.entries(params).map(([key, value]) => [key, Array.isArray(value) ? (value[0] ?? '') : value])\n );\n }\n\n private toRequestFromExpress(req: ExpressRequest): Request {\n const protocol = req.protocol || 'http';\n const host = req.get('host') || 'localhost';\n const url = `${protocol}://${host}${req.originalUrl || req.url}`;\n const headers = new Headers(req.headers as HeadersInit);\n const body = this.normalizeBody(req);\n\n if (body !== undefined && !headers.has('content-type') && this.isJsonLike(req.body)) {\n headers.set('content-type', 'application/json; charset=utf-8');\n }\n\n return new Request(url, {\n method: req.method,\n headers,\n body,\n });\n }\n\n private normalizeBody(req: ExpressRequest): BodyInit | null | undefined {\n if (['GET', 'HEAD'].includes(req.method)) {\n return undefined;\n }\n\n if (req.body === null || req.body === undefined) {\n return undefined;\n }\n\n if (\n typeof req.body === 'string' ||\n this.hasTag(req.body, 'Uint8Array') ||\n this.hasTag(req.body, 'ArrayBuffer')\n ) {\n return req.body;\n }\n\n if (this.isJsonLike(req.body)) {\n return JSON.stringify(req.body);\n }\n\n return undefined;\n }\n\n private isJsonLike(value: unknown): boolean {\n if (value === null) return true;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;\n if (Array.isArray(value)) return value.every((item) => this.isJsonLike(item));\n if (typeof value === 'object') {\n return Object.values(value as Record<string, unknown>).every((item) => this.isJsonLike(item));\n }\n return false;\n }\n\n private hasTag(value: unknown, tag: string): boolean {\n return value !== null && value !== undefined && Object.prototype.toString.call(value) === `[object ${tag}]`;\n }\n\n private registerMethod(\n registrar: ExpressRouteRegistrar,\n method: ViewSetActionMethod,\n path: string,\n handler: RequestHandler\n ): void {\n switch (method) {\n case 'GET':\n registrar.get(path, handler);\n return;\n case 'POST':\n registrar.post(path, handler);\n return;\n case 'PATCH':\n registrar.patch(path, handler);\n return;\n case 'PUT':\n registrar.put(path, handler);\n return;\n default:\n registrar.delete(path, handler);\n }\n }\n\n private normalizeBasePath(basePath: string): string {\n const trimmed = basePath.trim();\n if (!trimmed || trimmed === '/') {\n return '/';\n }\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n }\n\n private joinPath(base: string, subPath: string): string {\n const normalizedSubPath = subPath.replace(/^\\/+|\\/+$/g, '');\n return base === '/' ? `/${normalizedSubPath}` : `${base}/${normalizedSubPath}`;\n }\n\n private registerActionMethods(\n registrar: ExpressRouteRegistrar,\n methods: readonly ViewSetActionMethod[],\n path: string,\n handler: RequestHandler\n ): void {\n for (const method of methods) {\n this.registerMethod(registrar, method, path, handler);\n }\n }\n\n private registerAllMethods(registrar: ExpressRouteRegistrar, path: string, handler: RequestHandler): void {\n this.registerMethod(registrar, 'GET', path, handler);\n this.registerMethod(registrar, 'POST', path, handler);\n this.registerMethod(registrar, 'PUT', path, handler);\n this.registerMethod(registrar, 'PATCH', path, handler);\n this.registerMethod(registrar, 'DELETE', path, handler);\n }\n\n private getViewSetActions(viewset: ExpressCrudViewSet): readonly ResolvedViewSetActionDescriptor[] {\n const constructorValue = viewset.constructor as {\n getActions?: (input: ExpressCrudViewSet) => readonly ResolvedViewSetActionDescriptor[];\n };\n\n if (typeof constructorValue.getActions !== 'function') {\n return [];\n }\n\n return constructorValue.getActions(viewset);\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n ExpressAdapter,\n type AdaptExpressOptions,\n type ExpressAPIView,\n type ExpressCrudViewSet,\n type ExpressRouteRegistrar,\n} from './ExpressAdapter';\n"],"mappings":"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"}
1
+ {"version":3,"file":"adapter-kC_MF6Wt.js","names":[],"sources":["../src/adapter/ExpressAdapter.ts","../src/adapter/index.ts"],"sourcesContent":["import type { Request as ExpressRequest, Response as ExpressResponse, NextFunction, RequestHandler } from 'express';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream as NodeReadableStream } from 'node:stream/web';\nimport { Router } from 'express';\nimport { RequestContext } from '@danceroutine/tango-resources';\nimport { TangoQueryParams, TangoResponse } from '@danceroutine/tango-core';\nimport {\n FRAMEWORK_ADAPTER_BRAND,\n FrameworkAdapterRequestExecutor,\n type FrameworkAdapter,\n type FrameworkAdapterOptions,\n} from '@danceroutine/tango-adapters-core/adapter';\n\ntype ViewSetActionMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\ntype ResolvedViewSetActionDescriptor = {\n name: string;\n scope: 'detail' | 'collection';\n methods: readonly ViewSetActionMethod[];\n path: string;\n};\n\n/**\n * Adapter options for Express integration.\n */\nexport type AdaptExpressOptions = FrameworkAdapterOptions<ExpressRequest>;\n\n/**\n * Minimal CRUD viewset contract used by adapter route registration helpers.\n */\nexport interface ExpressCrudViewSet {\n list(ctx: RequestContext): Promise<TangoResponse>;\n create(ctx: RequestContext): Promise<TangoResponse>;\n retrieve(ctx: RequestContext, id: string): Promise<TangoResponse>;\n update(ctx: RequestContext, id: string): Promise<TangoResponse>;\n destroy(ctx: RequestContext, id: string): Promise<TangoResponse>;\n}\n\nexport interface ExpressAPIView {\n dispatch(ctx: RequestContext): Promise<TangoResponse>;\n}\n\n/**\n * Minimal route registrar interface implemented by Express apps and routers.\n */\nexport interface ExpressRouteRegistrar {\n get(path: string, handler: RequestHandler): unknown;\n post(path: string, handler: RequestHandler): unknown;\n patch(path: string, handler: RequestHandler): unknown;\n put(path: string, handler: RequestHandler): unknown;\n delete(path: string, handler: RequestHandler): unknown;\n}\n\n/**\n * Express adapter that translates Express handlers to Tango `RequestContext`.\n */\nexport class ExpressAdapter implements FrameworkAdapter<Response, RequestHandler, ExpressRequest> {\n readonly __tangoBrand: typeof FRAMEWORK_ADAPTER_BRAND = FRAMEWORK_ADAPTER_BRAND;\n private readonly requestExecutor = new FrameworkAdapterRequestExecutor();\n\n /**\n * Normalize an Express request into Tango query params.\n */\n toQueryParams(req: ExpressRequest): TangoQueryParams {\n const request = this.toRequestFromExpress(req);\n return TangoQueryParams.fromRequest(request);\n }\n\n /**\n * Adapt a Tango-style handler into an Express request handler.\n */\n adapt(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptExpressOptions = {}\n ): RequestHandler {\n return this.createHandler(handler, options);\n }\n\n /**\n * Build an Express router that wires all CRUD routes for a viewset.\n */\n createViewSetRouter(viewset: ExpressCrudViewSet, options: AdaptExpressOptions = {}): Router {\n const router = Router();\n this.registerViewSet(router, '', viewset, options);\n return router;\n }\n\n /**\n * Build an Express router for a single-path APIView.\n */\n createAPIViewRouter(apiView: ExpressAPIView, options: AdaptExpressOptions = {}): Router {\n const router = Router();\n this.registerAPIView(router, '', apiView, options);\n return router;\n }\n\n /**\n * Register all CRUD routes for a viewset under a base path.\n */\n registerViewSet(\n registrar: ExpressRouteRegistrar,\n basePath: string,\n viewset: ExpressCrudViewSet,\n options: AdaptExpressOptions = {}\n ): void {\n const collectionPath = this.normalizeBasePath(basePath);\n const detailPath = collectionPath === '/' ? '/:id' : `${collectionPath}/:id`;\n\n registrar.get(\n collectionPath,\n this.adapt((ctx) => viewset.list(ctx), options)\n );\n registrar.post(\n collectionPath,\n this.adapt((ctx) => viewset.create(ctx), options)\n );\n registrar.get(\n detailPath,\n this.adapt((ctx, id) => viewset.retrieve(ctx, String(id)), options)\n );\n registrar.patch(\n detailPath,\n this.adapt((ctx, id) => viewset.update(ctx, String(id)), options)\n );\n registrar.put(\n detailPath,\n this.adapt((ctx, id) => viewset.update(ctx, String(id)), options)\n );\n registrar.delete(\n detailPath,\n this.adapt((ctx, id) => viewset.destroy(ctx, String(id)), options)\n );\n\n const actions = this.getViewSetActions(viewset);\n actions.forEach((action) => {\n const actionPath =\n action.scope === 'detail'\n ? this.joinPath(detailPath, action.path)\n : this.joinPath(collectionPath, action.path);\n const handler =\n action.scope === 'detail'\n ? this.adapt((ctx, id) => this.invokeDetailAction(viewset, action, ctx, String(id)), options)\n : this.adapt((ctx) => this.invokeCollectionAction(viewset, action, ctx), options);\n\n this.registerActionMethods(registrar, action.methods, actionPath, handler);\n });\n }\n\n /**\n * Register one APIView on a single path and dispatch by HTTP method.\n */\n registerAPIView(\n registrar: ExpressRouteRegistrar,\n path: string,\n apiView: ExpressAPIView,\n options: AdaptExpressOptions = {}\n ): void {\n const normalizedPath = this.normalizeBasePath(path);\n this.registerAllMethods(\n registrar,\n normalizedPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n }\n\n /**\n * Register one GenericAPIView with collection and detail routes.\n */\n registerGenericAPIView(\n registrar: ExpressRouteRegistrar,\n collectionPath: string,\n detailPath: string | undefined,\n apiView: ExpressAPIView,\n options: AdaptExpressOptions = {}\n ): void {\n const normalizedCollectionPath = this.normalizeBasePath(collectionPath);\n const normalizedDetailPath = detailPath?.trim().length\n ? this.normalizeBasePath(detailPath)\n : this.joinPath(normalizedCollectionPath, ':id');\n\n registrar.get(\n normalizedCollectionPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.post(\n normalizedCollectionPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.get(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.put(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.patch(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n registrar.delete(\n normalizedDetailPath,\n this.adapt((ctx) => apiView.dispatch(ctx), options)\n );\n }\n\n private invokeDetailAction(\n viewset: ExpressCrudViewSet,\n action: ResolvedViewSetActionDescriptor,\n ctx: RequestContext,\n id: string\n ): Promise<TangoResponse> {\n const candidate = (viewset as unknown as Record<string, unknown>)[action.name];\n if (typeof candidate !== 'function') {\n throw new TypeError(`Missing detail action method '${action.name}' on viewset.`);\n }\n return (\n candidate as (this: ExpressCrudViewSet, ctx: RequestContext, id: string) => Promise<TangoResponse>\n ).call(viewset, ctx, id);\n }\n\n private invokeCollectionAction(\n viewset: ExpressCrudViewSet,\n action: ResolvedViewSetActionDescriptor,\n ctx: RequestContext\n ): Promise<TangoResponse> {\n const candidate = (viewset as unknown as Record<string, unknown>)[action.name];\n if (typeof candidate !== 'function') {\n throw new TypeError(`Missing collection action method '${action.name}' on viewset.`);\n }\n return (candidate as (this: ExpressCrudViewSet, ctx: RequestContext) => Promise<TangoResponse>).call(\n viewset,\n ctx\n );\n }\n\n private createHandler(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptExpressOptions\n ): RequestHandler {\n return async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {\n try {\n const user = options.getUser ? await options.getUser(req) : null;\n\n const request = this.toRequestFromExpress(req);\n const ctx = RequestContext.create(request, user);\n ctx.params = this.normalizeParams(req.params);\n\n const rawId = req.params.id;\n const id = Array.isArray(rawId) ? rawId[0] : rawId;\n const tangoResponse = await this.requestExecutor\n .forHandler({ handler, ctx, id })\n .runResponse(req.method, options.transaction);\n const response = tangoResponse.toWebResponse();\n\n res.status(response.status);\n\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n if (response.body === null) {\n res.end();\n return;\n }\n\n if (tangoResponse.body !== null) {\n await this.pipeReadableStream(response.body, res);\n return;\n }\n\n const body = new Uint8Array<ArrayBuffer>(await response.arrayBuffer());\n res.send(this.normalizeResponseBody(body, response.headers));\n } catch (error) {\n next(error);\n }\n };\n }\n\n private async pipeReadableStream(\n body: ReadableStream<Uint8Array<ArrayBuffer>>,\n res: ExpressResponse\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const stream = Readable.fromWeb(body as unknown as NodeReadableStream);\n stream.on('error', reject);\n res.on('error', reject);\n res.on('finish', () => resolve());\n stream.pipe(res);\n });\n }\n\n private normalizeResponseBody(body: Uint8Array<ArrayBuffer>, headers: Headers): string | Buffer {\n const contentType = headers.get('content-type')?.toLowerCase() ?? '';\n if (contentType.startsWith('text/') || contentType.includes('json')) {\n return new TextDecoder().decode(body);\n }\n\n return Buffer.from(body);\n }\n\n private normalizeParams(params: Record<string, string | string[]>): Record<string, string> {\n return Object.fromEntries(\n Object.entries(params).map(([key, value]) => [key, Array.isArray(value) ? (value[0] ?? '') : value])\n );\n }\n\n private toRequestFromExpress(req: ExpressRequest): Request {\n const protocol = req.protocol || 'http';\n const host = req.get('host') || 'localhost';\n const url = `${protocol}://${host}${req.originalUrl || req.url}`;\n const headers = new Headers(req.headers as HeadersInit);\n const body = this.normalizeBody(req);\n\n if (body !== undefined && !headers.has('content-type') && this.isJsonLike(req.body)) {\n headers.set('content-type', 'application/json; charset=utf-8');\n }\n\n return new Request(url, {\n method: req.method,\n headers,\n body,\n });\n }\n\n private normalizeBody(req: ExpressRequest): BodyInit | null | undefined {\n if (['GET', 'HEAD'].includes(req.method)) {\n return undefined;\n }\n\n if (req.body === null || req.body === undefined) {\n return undefined;\n }\n\n if (\n typeof req.body === 'string' ||\n this.hasTag(req.body, 'Uint8Array') ||\n this.hasTag(req.body, 'ArrayBuffer')\n ) {\n return req.body;\n }\n\n if (this.isJsonLike(req.body)) {\n return JSON.stringify(req.body);\n }\n\n return undefined;\n }\n\n private isJsonLike(value: unknown): boolean {\n if (value === null) return true;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;\n if (Array.isArray(value)) return value.every((item) => this.isJsonLike(item));\n if (typeof value === 'object') {\n return Object.values(value as Record<string, unknown>).every((item) => this.isJsonLike(item));\n }\n return false;\n }\n\n private hasTag(value: unknown, tag: string): boolean {\n return value !== null && value !== undefined && Object.prototype.toString.call(value) === `[object ${tag}]`;\n }\n\n private registerMethod(\n registrar: ExpressRouteRegistrar,\n method: ViewSetActionMethod,\n path: string,\n handler: RequestHandler\n ): void {\n switch (method) {\n case 'GET':\n registrar.get(path, handler);\n return;\n case 'POST':\n registrar.post(path, handler);\n return;\n case 'PATCH':\n registrar.patch(path, handler);\n return;\n case 'PUT':\n registrar.put(path, handler);\n return;\n default:\n registrar.delete(path, handler);\n }\n }\n\n private normalizeBasePath(basePath: string): string {\n const trimmed = basePath.trim();\n if (!trimmed || trimmed === '/') {\n return '/';\n }\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n }\n\n private joinPath(base: string, subPath: string): string {\n const normalizedSubPath = subPath.replace(/^\\/+|\\/+$/g, '');\n return base === '/' ? `/${normalizedSubPath}` : `${base}/${normalizedSubPath}`;\n }\n\n private registerActionMethods(\n registrar: ExpressRouteRegistrar,\n methods: readonly ViewSetActionMethod[],\n path: string,\n handler: RequestHandler\n ): void {\n for (const method of methods) {\n this.registerMethod(registrar, method, path, handler);\n }\n }\n\n private registerAllMethods(registrar: ExpressRouteRegistrar, path: string, handler: RequestHandler): void {\n this.registerMethod(registrar, 'GET', path, handler);\n this.registerMethod(registrar, 'POST', path, handler);\n this.registerMethod(registrar, 'PUT', path, handler);\n this.registerMethod(registrar, 'PATCH', path, handler);\n this.registerMethod(registrar, 'DELETE', path, handler);\n }\n\n private getViewSetActions(viewset: ExpressCrudViewSet): readonly ResolvedViewSetActionDescriptor[] {\n const constructorValue = viewset.constructor as {\n getActions?: (input: ExpressCrudViewSet) => readonly ResolvedViewSetActionDescriptor[];\n };\n\n if (typeof constructorValue.getActions !== 'function') {\n return [];\n }\n\n return constructorValue.getActions(viewset);\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n ExpressAdapter,\n type AdaptExpressOptions,\n type ExpressAPIView,\n type ExpressCrudViewSet,\n type ExpressRouteRegistrar,\n} from './ExpressAdapter';\n"],"mappings":";;;;;;;;;;AAwDA,IAAa,iBAAb,MAAkG;CAC9F,eAAwD;CACxD,kBAAmC,IAAI,gCAAgC;;;;CAKvE,cAAc,KAAuC;EACjD,MAAM,UAAU,KAAK,qBAAqB,GAAG;EAC7C,OAAO,iBAAiB,YAAY,OAAO;CAC/C;;;;CAKA,MACI,SACA,UAA+B,CAAC,GAClB;EACd,OAAO,KAAK,cAAc,SAAS,OAAO;CAC9C;;;;CAKA,oBAAoB,SAA6B,UAA+B,CAAC,GAAW;EACxF,MAAM,SAAS,OAAO;EACtB,KAAK,gBAAgB,QAAQ,IAAI,SAAS,OAAO;EACjD,OAAO;CACX;;;;CAKA,oBAAoB,SAAyB,UAA+B,CAAC,GAAW;EACpF,MAAM,SAAS,OAAO;EACtB,KAAK,gBAAgB,QAAQ,IAAI,SAAS,OAAO;EACjD,OAAO;CACX;;;;CAKA,gBACI,WACA,UACA,SACA,UAA+B,CAAC,GAC5B;EACJ,MAAM,iBAAiB,KAAK,kBAAkB,QAAQ;EACtD,MAAM,aAAa,mBAAmB,MAAM,SAAS,GAAG,eAAe;EAEvE,UAAU,IACN,gBACA,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG,GAAG,OAAO,CAClD;EACA,UAAU,KACN,gBACA,KAAK,OAAO,QAAQ,QAAQ,OAAO,GAAG,GAAG,OAAO,CACpD;EACA,UAAU,IACN,YACA,KAAK,OAAO,KAAK,OAAO,QAAQ,SAAS,KAAK,OAAO,EAAE,CAAC,GAAG,OAAO,CACtE;EACA,UAAU,MACN,YACA,KAAK,OAAO,KAAK,OAAO,QAAQ,OAAO,KAAK,OAAO,EAAE,CAAC,GAAG,OAAO,CACpE;EACA,UAAU,IACN,YACA,KAAK,OAAO,KAAK,OAAO,QAAQ,OAAO,KAAK,OAAO,EAAE,CAAC,GAAG,OAAO,CACpE;EACA,UAAU,OACN,YACA,KAAK,OAAO,KAAK,OAAO,QAAQ,QAAQ,KAAK,OAAO,EAAE,CAAC,GAAG,OAAO,CACrE;EAGA,KADqB,kBAAkB,OACjC,CAAC,CAAC,SAAS,WAAW;GACxB,MAAM,aACF,OAAO,UAAU,WACX,KAAK,SAAS,YAAY,OAAO,IAAI,IACrC,KAAK,SAAS,gBAAgB,OAAO,IAAI;GACnD,MAAM,UACF,OAAO,UAAU,WACX,KAAK,OAAO,KAAK,OAAO,KAAK,mBAAmB,SAAS,QAAQ,KAAK,OAAO,EAAE,CAAC,GAAG,OAAO,IAC1F,KAAK,OAAO,QAAQ,KAAK,uBAAuB,SAAS,QAAQ,GAAG,GAAG,OAAO;GAExF,KAAK,sBAAsB,WAAW,OAAO,SAAS,YAAY,OAAO;EAC7E,CAAC;CACL;;;;CAKA,gBACI,WACA,MACA,SACA,UAA+B,CAAC,GAC5B;EACJ,MAAM,iBAAiB,KAAK,kBAAkB,IAAI;EAClD,KAAK,mBACD,WACA,gBACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;CACJ;;;;CAKA,uBACI,WACA,gBACA,YACA,SACA,UAA+B,CAAC,GAC5B;EACJ,MAAM,2BAA2B,KAAK,kBAAkB,cAAc;EACtE,MAAM,uBAAuB,YAAY,KAAK,CAAC,CAAC,SAC1C,KAAK,kBAAkB,UAAU,IACjC,KAAK,SAAS,0BAA0B,KAAK;EAEnD,UAAU,IACN,0BACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;EACA,UAAU,KACN,0BACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;EACA,UAAU,IACN,sBACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;EACA,UAAU,IACN,sBACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;EACA,UAAU,MACN,sBACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;EACA,UAAU,OACN,sBACA,KAAK,OAAO,QAAQ,QAAQ,SAAS,GAAG,GAAG,OAAO,CACtD;CACJ;CAEA,mBACI,SACA,QACA,KACA,IACsB;EACtB,MAAM,YAAa,QAA+C,OAAO;EACzE,IAAI,OAAO,cAAc,YACrB,MAAM,IAAI,UAAU,iCAAiC,OAAO,KAAK,cAAc;EAEnF,OACI,UACF,KAAK,SAAS,KAAK,EAAE;CAC3B;CAEA,uBACI,SACA,QACA,KACsB;EACtB,MAAM,YAAa,QAA+C,OAAO;EACzE,IAAI,OAAO,cAAc,YACrB,MAAM,IAAI,UAAU,qCAAqC,OAAO,KAAK,cAAc;EAEvF,OAAQ,UAAwF,KAC5F,SACA,GACJ;CACJ;CAEA,cACI,SACA,SACc;EACd,OAAO,OAAO,KAAqB,KAAsB,SAAuB;GAC5E,IAAI;IACA,MAAM,OAAO,QAAQ,UAAU,MAAM,QAAQ,QAAQ,GAAG,IAAI;IAE5D,MAAM,UAAU,KAAK,qBAAqB,GAAG;IAC7C,MAAM,MAAM,eAAe,OAAO,SAAS,IAAI;IAC/C,IAAI,SAAS,KAAK,gBAAgB,IAAI,MAAM;IAE5C,MAAM,QAAQ,IAAI,OAAO;IACzB,MAAM,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;IAC7C,MAAM,gBAAgB,MAAM,KAAK,gBAC5B,WAAW;KAAE;KAAS;KAAK;IAAG,CAAC,CAAC,CAChC,YAAY,IAAI,QAAQ,QAAQ,WAAW;IAChD,MAAM,WAAW,cAAc,cAAc;IAE7C,IAAI,OAAO,SAAS,MAAM;IAE1B,SAAS,QAAQ,SAAS,OAAO,QAAQ;KACrC,IAAI,UAAU,KAAK,KAAK;IAC5B,CAAC;IAED,IAAI,SAAS,SAAS,MAAM;KACxB,IAAI,IAAI;KACR;IACJ;IAEA,IAAI,cAAc,SAAS,MAAM;KAC7B,MAAM,KAAK,mBAAmB,SAAS,MAAM,GAAG;KAChD;IACJ;IAEA,MAAM,OAAO,IAAI,WAAwB,MAAM,SAAS,YAAY,CAAC;IACrE,IAAI,KAAK,KAAK,sBAAsB,MAAM,SAAS,OAAO,CAAC;GAC/D,SAAS,OAAO;IACZ,KAAK,KAAK;GACd;EACJ;CACJ;CAEA,MAAc,mBACV,MACA,KACa;EACb,MAAM,IAAI,SAAe,SAAS,WAAW;GACzC,MAAM,SAAS,SAAS,QAAQ,IAAqC;GACrE,OAAO,GAAG,SAAS,MAAM;GACzB,IAAI,GAAG,SAAS,MAAM;GACtB,IAAI,GAAG,gBAAgB,QAAQ,CAAC;GAChC,OAAO,KAAK,GAAG;EACnB,CAAC;CACL;CAEA,sBAA8B,MAA+B,SAAmC;EAC5F,MAAM,cAAc,QAAQ,IAAI,cAAc,CAAC,EAAE,YAAY,KAAK;EAClE,IAAI,YAAY,WAAW,OAAO,KAAK,YAAY,SAAS,MAAM,GAC9D,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;EAGxC,OAAO,OAAO,KAAK,IAAI;CAC3B;CAEA,gBAAwB,QAAmE;EACvF,OAAO,OAAO,YACV,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,MAAM,QAAQ,KAAK,IAAK,MAAM,MAAM,KAAM,KAAK,CAAC,CACvG;CACJ;CAEA,qBAA6B,KAA8B;EAGvD,MAAM,MAAM,GAFK,IAAI,YAAY,OAET,KADX,IAAI,IAAI,MAAM,KAAK,cACI,IAAI,eAAe,IAAI;EAC3D,MAAM,UAAU,IAAI,QAAQ,IAAI,OAAsB;EACtD,MAAM,OAAO,KAAK,cAAc,GAAG;EAEnC,IAAI,SAAS,KAAA,KAAa,CAAC,QAAQ,IAAI,cAAc,KAAK,KAAK,WAAW,IAAI,IAAI,GAC9E,QAAQ,IAAI,gBAAgB,iCAAiC;EAGjE,OAAO,IAAI,QAAQ,KAAK;GACpB,QAAQ,IAAI;GACZ;GACA;EACJ,CAAC;CACL;CAEA,cAAsB,KAAkD;EACpE,IAAI,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,IAAI,MAAM,GACnC;EAGJ,IAAI,IAAI,SAAS,QAAQ,IAAI,SAAS,KAAA,GAClC;EAGJ,IACI,OAAO,IAAI,SAAS,YACpB,KAAK,OAAO,IAAI,MAAM,YAAY,KAClC,KAAK,OAAO,IAAI,MAAM,aAAa,GAEnC,OAAO,IAAI;EAGf,IAAI,KAAK,WAAW,IAAI,IAAI,GACxB,OAAO,KAAK,UAAU,IAAI,IAAI;CAItC;CAEA,WAAmB,OAAyB;EACxC,IAAI,UAAU,MAAM,OAAO;EAC3B,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO;EACjG,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC;EAC5E,IAAI,OAAO,UAAU,UACjB,OAAO,OAAO,OAAO,KAAgC,CAAC,CAAC,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC;EAEhG,OAAO;CACX;CAEA,OAAe,OAAgB,KAAsB;EACjD,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,OAAO,UAAU,SAAS,KAAK,KAAK,MAAM,WAAW,IAAI;CAC7G;CAEA,eACI,WACA,QACA,MACA,SACI;EACJ,QAAQ,QAAR;GACI,KAAK;IACD,UAAU,IAAI,MAAM,OAAO;IAC3B;GACJ,KAAK;IACD,UAAU,KAAK,MAAM,OAAO;IAC5B;GACJ,KAAK;IACD,UAAU,MAAM,MAAM,OAAO;IAC7B;GACJ,KAAK;IACD,UAAU,IAAI,MAAM,OAAO;IAC3B;GACJ,SACI,UAAU,OAAO,MAAM,OAAO;EACtC;CACJ;CAEA,kBAA0B,UAA0B;EAChD,MAAM,UAAU,SAAS,KAAK;EAC9B,IAAI,CAAC,WAAW,YAAY,KACxB,OAAO;EAEX,OAAO,QAAQ,WAAW,GAAG,IAAI,UAAU,IAAI;CACnD;CAEA,SAAiB,MAAc,SAAyB;EACpD,MAAM,oBAAoB,QAAQ,QAAQ,cAAc,EAAE;EAC1D,OAAO,SAAS,MAAM,IAAI,sBAAsB,GAAG,KAAK,GAAG;CAC/D;CAEA,sBACI,WACA,SACA,MACA,SACI;EACJ,KAAK,MAAM,UAAU,SACjB,KAAK,eAAe,WAAW,QAAQ,MAAM,OAAO;CAE5D;CAEA,mBAA2B,WAAkC,MAAc,SAA+B;EACtG,KAAK,eAAe,WAAW,OAAO,MAAM,OAAO;EACnD,KAAK,eAAe,WAAW,QAAQ,MAAM,OAAO;EACpD,KAAK,eAAe,WAAW,OAAO,MAAM,OAAO;EACnD,KAAK,eAAe,WAAW,SAAS,MAAM,OAAO;EACrD,KAAK,eAAe,WAAW,UAAU,MAAM,OAAO;CAC1D;CAEA,kBAA0B,SAAyE;EAC/F,MAAM,mBAAmB,QAAQ;EAIjC,IAAI,OAAO,iBAAiB,eAAe,YACvC,OAAO,CAAC;EAGZ,OAAO,iBAAiB,WAAW,OAAO;CAC9C;AACJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceroutine/tango-adapters-express",
3
- "version": "1.11.14",
3
+ "version": "1.12.0",
4
4
  "description": "Express adapter for Tango",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,9 +31,9 @@
31
31
  "directory": "packages/adapters/express"
32
32
  },
33
33
  "dependencies": {
34
- "@danceroutine/tango-core": "1.11.14",
35
- "@danceroutine/tango-resources": "1.11.14",
36
- "@danceroutine/tango-adapters-core": "1.11.14"
34
+ "@danceroutine/tango-core": "1.12.0",
35
+ "@danceroutine/tango-resources": "1.12.0",
36
+ "@danceroutine/tango-adapters-core": "1.12.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "express": "^4.21.1 || ^5.0.0"
@@ -42,11 +42,11 @@
42
42
  "@types/express": "^5.0.0",
43
43
  "@types/node": "^25.9.1",
44
44
  "express": "^5.2.1",
45
- "tsdown": "^0.22.1",
45
+ "tsdown": "^0.22.2",
46
46
  "typescript": "^6.0.3",
47
- "vitest": "^4.1.7",
48
- "@danceroutine/tango-orm": "1.11.14",
49
- "@danceroutine/tango-testing": "1.11.14"
47
+ "vitest": "^4.1.8",
48
+ "@danceroutine/tango-orm": "1.12.0",
49
+ "@danceroutine/tango-testing": "1.12.0"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsdown",