@danceroutine/tango-adapters-next 1.10.3 → 1.11.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/README.md CHANGED
@@ -43,6 +43,16 @@ For detail requests, the adapter resolves the identifier, passes it as the secon
43
43
 
44
44
  `NextAdapter` also exposes `toQueryParams(searchParams)` for route modules and server components that want the same normalized query contract resources use internally. That helper returns `TangoQueryParams` from `@danceroutine/tango-core`.
45
45
 
46
+ If one resource should treat each write request as a single database unit of work, pass the adapter's writes-only transaction mode when you adapt the viewset:
47
+
48
+ ```ts
49
+ export const { GET, POST, PATCH, PUT, DELETE } = adapter.adaptViewSet(viewset, {
50
+ transaction: 'writes',
51
+ });
52
+ ```
53
+
54
+ That option wraps `POST`, `PUT`, `PATCH`, and `DELETE` requests in one `transaction.atomic(...)` boundary. `GET`, `HEAD`, and `OPTIONS` stay outside the wrapper. The current adapter transaction mode uses the Tango runtime your application installs as its default runtime.
55
+
46
56
  ## Public API
47
57
 
48
58
  The root export includes:
@@ -53,6 +53,7 @@ export interface NextViewSetRouteHandlers {
53
53
  export declare class NextAdapter implements FrameworkAdapter<Response, NextRouteHandler, NextRequest> {
54
54
  readonly __tangoBrand: typeof FRAMEWORK_ADAPTER_BRAND;
55
55
  private readonly logger;
56
+ private readonly requestExecutor;
56
57
  /**
57
58
  * Normalize Next.js-style route search params into Tango query params.
58
59
  */
@@ -1,3 +1,3 @@
1
- import { NextAdapter } from "../adapter-_WsZ0kE-.js";
1
+ import { NextAdapter } from "../adapter-Em1-1ezY.js";
2
2
 
3
3
  export { NextAdapter };
@@ -1,6 +1,6 @@
1
1
  import { RequestContext } from "@danceroutine/tango-resources";
2
2
  import { HttpErrorFactory, TangoQueryParams, TangoRequest, TangoResponse, getLogger } from "@danceroutine/tango-core";
3
- import { FRAMEWORK_ADAPTER_BRAND } from "@danceroutine/tango-adapters-core/adapter";
3
+ import { FRAMEWORK_ADAPTER_BRAND, FrameworkAdapterRequestExecutor } from "@danceroutine/tango-adapters-core/adapter";
4
4
  import { InternalActionMatchKind, InternalActionScope, InternalHttpMethod } from "@danceroutine/tango-adapters-core";
5
5
 
6
6
  //#region rolldown:runtime
@@ -17,6 +17,7 @@ var __export = (target, all) => {
17
17
  var NextAdapter = class {
18
18
  __tangoBrand = FRAMEWORK_ADAPTER_BRAND;
19
19
  logger = getLogger("tango.adapter.next");
20
+ requestExecutor = new FrameworkAdapterRequestExecutor();
20
21
  /**
21
22
  * Normalize Next.js-style route search params into Tango query params.
22
23
  */
@@ -160,8 +161,11 @@ var NextAdapter = class {
160
161
  const ctx = RequestContext.create(this.toTangoRequest(request), user);
161
162
  if (Object.keys(params).length > 0) ctx.params = params;
162
163
  const id = params?.id;
163
- if (id && handler.length > 1) return (await handler(ctx, id)).toWebResponse();
164
- return (await handler(ctx)).toWebResponse();
164
+ return await this.requestExecutor.forHandler({
165
+ handler,
166
+ ctx,
167
+ id
168
+ }).runWebResponse(request.method, options.transaction);
165
169
  } catch (error) {
166
170
  return this.internalServerError(error);
167
171
  }
@@ -265,4 +269,4 @@ __export(adapter_exports, { NextAdapter: () => NextAdapter });
265
269
 
266
270
  //#endregion
267
271
  export { NextAdapter, adapter_exports };
268
- //# sourceMappingURL=adapter-_WsZ0kE-.js.map
272
+ //# sourceMappingURL=adapter-Em1-1ezY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-Em1-1ezY.js","names":["searchParams: Record<string, string | string[] | undefined>","handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>","options: AdaptNextOptions","viewset: NextCrudViewSet","options: AdaptNextViewSetOptions","factory: NextViewSetFactory","factory: NextAPIViewFactory","apiView: NextAPIView","request: NextRequest","method: HttpMethod","ctx: RequestContext","paramKey: string","fallback: (segments: string[], directId: string | null) => TangoResponse | Promise<TangoResponse>","action: ResolvedViewSetActionDescriptor","id: string","error: unknown","factory: () => Promise<NextViewSetRouteHandlers>","handlersPromise: Promise<NextViewSetRouteHandlers> | null","method: keyof NextViewSetRouteHandlers","params: Record<string, string>","raw: Record<string, string | string[]>","segments: string[]"],"sources":["../src/adapter/NextAdapter.ts","../src/adapter/index.ts"],"sourcesContent":["import type { NextRequest } from 'next/server';\nimport { RequestContext } from '@danceroutine/tango-resources';\nimport {\n HttpErrorFactory,\n TangoQueryParams,\n TangoRequest,\n TangoResponse,\n getLogger,\n type JsonValue,\n} from '@danceroutine/tango-core';\nimport {\n FRAMEWORK_ADAPTER_BRAND,\n FrameworkAdapterRequestExecutor,\n type FrameworkAdapter,\n type FrameworkAdapterOptions,\n} from '@danceroutine/tango-adapters-core/adapter';\nimport {\n InternalHttpMethod,\n InternalActionScope,\n InternalActionMatchKind,\n type ActionScope,\n type HttpMethod,\n} from '@danceroutine/tango-adapters-core';\n\ntype ResolvedViewSetActionDescriptor = {\n name: string;\n scope: ActionScope;\n methods: readonly HttpMethod[];\n path: string;\n};\n\n/**\n * Next.js route handler signature produced by the adapter.\n */\nexport type NextRouteHandler = (\n request: NextRequest,\n context?: { params?: Promise<Record<string, string | string[]>> }\n) => Promise<Response>;\n\nexport type NextDynamicRouteContext = {\n params: Promise<Record<string, string | string[]>>;\n};\n\nexport type NextDynamicRouteHandler = (request: NextRequest, context: NextDynamicRouteContext) => Promise<Response>;\n\n/**\n * Adapter options for Next.js integration.\n */\nexport type AdaptNextOptions = FrameworkAdapterOptions<NextRequest>;\n\n/**\n * Minimal CRUD viewset contract used by the Next adapter route helpers.\n */\nexport interface NextCrudViewSet {\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 NextAPIView {\n dispatch(ctx: RequestContext): Promise<TangoResponse>;\n}\n\nexport type NextViewSetFactory = () => NextCrudViewSet | Promise<NextCrudViewSet>;\nexport type NextAPIViewFactory = () => NextAPIView | Promise<NextAPIView>;\n\n/**\n * Options for auto-generated viewset route handlers.\n */\nexport type AdaptNextViewSetOptions = AdaptNextOptions & {\n paramKey?: string;\n};\n\n/**\n * HTTP method handlers generated from a CRUD viewset.\n */\nexport interface NextViewSetRouteHandlers {\n GET: NextDynamicRouteHandler;\n POST: NextDynamicRouteHandler;\n PATCH: NextDynamicRouteHandler;\n PUT: NextDynamicRouteHandler;\n DELETE: NextDynamicRouteHandler;\n}\n\ntype ActionMatch =\n | { kind: typeof InternalActionMatchKind.DETAIL; action: ResolvedViewSetActionDescriptor; id: string }\n | { kind: typeof InternalActionMatchKind.COLLECTION; action: ResolvedViewSetActionDescriptor }\n | { kind: typeof InternalActionMatchKind.METHOD_NOT_ALLOWED };\n\n/**\n * Next.js adapter that translates route handlers to Tango `RequestContext`.\n */\nexport class NextAdapter implements FrameworkAdapter<Response, NextRouteHandler, NextRequest> {\n readonly __tangoBrand: typeof FRAMEWORK_ADAPTER_BRAND = FRAMEWORK_ADAPTER_BRAND;\n private readonly logger = getLogger('tango.adapter.next');\n private readonly requestExecutor = new FrameworkAdapterRequestExecutor();\n /**\n * Normalize Next.js-style route search params into Tango query params.\n */\n toQueryParams(searchParams: Record<string, string | string[] | undefined>): TangoQueryParams {\n return TangoQueryParams.fromRecord(searchParams);\n }\n\n /**\n * Adapt a Tango-style handler into a Next.js route handler.\n */\n adapt(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptNextOptions = {}\n ): NextRouteHandler {\n return this.createHandler(handler, options);\n }\n\n /**\n * Build Next route handlers that map HTTP verbs to standard CRUD viewset actions.\n */\n adaptViewSet(viewset: NextCrudViewSet, options: AdaptNextViewSetOptions = {}): NextViewSetRouteHandlers {\n const paramKey = options.paramKey ?? 'tango';\n return {\n GET: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(viewset, InternalHttpMethod.GET, ctx, paramKey, (segments, directId) => {\n if (directId) return viewset.retrieve(ctx, directId);\n if (segments.length === 0) return viewset.list(ctx);\n if (segments.length === 1) return viewset.retrieve(ctx, segments[0] as string);\n return this.notFoundResponse();\n }),\n options\n ),\n POST: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(\n viewset,\n InternalHttpMethod.POST,\n ctx,\n paramKey,\n (segments, directId) => {\n if (segments.length === 0 && !directId) return viewset.create(ctx);\n return this.methodNotAllowedResponse();\n }\n ),\n options\n ),\n PATCH: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(\n viewset,\n InternalHttpMethod.PATCH,\n ctx,\n paramKey,\n (segments, directId) => {\n const id = directId ?? segments[0];\n return id ? viewset.update(ctx, id) : this.methodNotAllowedResponse();\n }\n ),\n options\n ),\n PUT: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(viewset, InternalHttpMethod.PUT, ctx, paramKey, (segments, directId) => {\n const id = directId ?? segments[0];\n return id ? viewset.update(ctx, id) : this.methodNotAllowedResponse();\n }),\n options\n ),\n DELETE: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(\n viewset,\n InternalHttpMethod.DELETE,\n ctx,\n paramKey,\n (segments, directId) => {\n const id = directId ?? segments[0];\n return id ? viewset.destroy(ctx, id) : this.methodNotAllowedResponse();\n }\n ),\n options\n ),\n };\n }\n\n /**\n * Build Next route handlers from a lazy viewset factory and memoize initialization.\n * Initialization failures clear the memoized promise so subsequent requests can retry.\n */\n adaptViewSetFactory(factory: NextViewSetFactory, options: AdaptNextViewSetOptions = {}): NextViewSetRouteHandlers {\n return this.adaptRouteHandlersFactory(async () => {\n const viewset = await factory();\n return this.adaptViewSet(viewset, options);\n });\n }\n\n /**\n * Build Next route handlers from a lazy GenericAPIView factory and memoize initialization.\n */\n adaptGenericAPIViewFactory(\n factory: NextAPIViewFactory,\n options: AdaptNextViewSetOptions = {}\n ): NextViewSetRouteHandlers {\n return this.adaptRouteHandlersFactory(async () => {\n const apiView = await factory();\n return this.adaptGenericAPIView(apiView, options);\n });\n }\n\n /**\n * Build Next route handlers that dispatch an APIView by HTTP method.\n */\n adaptAPIView(apiView: NextAPIView, options: AdaptNextOptions = {}): NextViewSetRouteHandlers {\n return {\n GET: this.adapt((ctx) => apiView.dispatch(ctx), options),\n POST: this.adapt((ctx) => apiView.dispatch(ctx), options),\n PATCH: this.adapt((ctx) => apiView.dispatch(ctx), options),\n PUT: this.adapt((ctx) => apiView.dispatch(ctx), options),\n DELETE: this.adapt((ctx) => apiView.dispatch(ctx), options),\n };\n }\n\n /**\n * Build handlers for GenericAPIView-style collection/detail splits in catch-all routes.\n */\n adaptGenericAPIView(apiView: NextAPIView, options: AdaptNextViewSetOptions = {}): NextViewSetRouteHandlers {\n // Default catch-all param matches the Next.js [[...tango]] route convention.\n const paramKey = options.paramKey ?? 'tango';\n return {\n GET: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return apiView.dispatch(ctx);\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n POST: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (detailId) {\n return this.methodNotAllowedResponse();\n }\n return apiView.dispatch(ctx);\n }, options),\n PATCH: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return this.methodNotAllowedResponse();\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n PUT: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return this.methodNotAllowedResponse();\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n DELETE: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return this.methodNotAllowedResponse();\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n };\n }\n\n private toTangoRequest(request: NextRequest): TangoRequest {\n if (TangoRequest.isTangoRequest(request)) {\n return request;\n }\n\n // oxlint-disable-next-line eslint-js/no-restricted-syntax\n if (request instanceof Request) {\n return new TangoRequest(request);\n }\n\n return new TangoRequest(String((request as { url?: unknown }).url ?? 'http://localhost'));\n }\n\n private dispatchViewSetAction(\n viewset: NextCrudViewSet,\n method: HttpMethod,\n ctx: RequestContext,\n paramKey: string,\n fallback: (segments: string[], directId: string | null) => TangoResponse | Promise<TangoResponse>\n ): Promise<TangoResponse> {\n const segments = this.extractCatchAllSegments(ctx.params, paramKey);\n const directId = this.extractDirectId(ctx.params);\n const actionMatch = this.resolveActionMatch(viewset, method, segments);\n\n if (actionMatch?.kind === InternalActionMatchKind.METHOD_NOT_ALLOWED) {\n return Promise.resolve(this.methodNotAllowedResponse());\n }\n if (actionMatch?.kind === InternalActionMatchKind.DETAIL) {\n return this.invokeDetailAction(viewset, actionMatch.action, ctx, actionMatch.id);\n }\n if (actionMatch?.kind === InternalActionMatchKind.COLLECTION) {\n return this.invokeCollectionAction(viewset, actionMatch.action, ctx);\n }\n\n return Promise.resolve(fallback(segments, directId));\n }\n\n private invokeDetailAction(\n viewset: NextCrudViewSet,\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 return Promise.resolve(this.notFoundResponse());\n }\n return (candidate as (this: NextCrudViewSet, ctx: RequestContext, id: string) => Promise<TangoResponse>).call(\n viewset,\n ctx,\n id\n );\n }\n\n private invokeCollectionAction(\n viewset: NextCrudViewSet,\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 return Promise.resolve(this.notFoundResponse());\n }\n return (candidate as (this: NextCrudViewSet, ctx: RequestContext) => Promise<TangoResponse>).call(viewset, ctx);\n }\n\n private createHandler(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptNextOptions\n ): NextRouteHandler {\n return async (request: NextRequest, routeContext) => {\n try {\n const user = options.getUser ? await options.getUser(request) : null;\n const rawParams = routeContext?.params ? await routeContext.params : {};\n const params = this.normalizeRouteParams(rawParams);\n\n const ctx = RequestContext.create(this.toTangoRequest(request), user);\n if (Object.keys(params).length > 0) {\n ctx.params = params;\n }\n\n const id = params?.id;\n return await this.requestExecutor\n .forHandler({ handler, ctx, id })\n .runWebResponse(request.method, options.transaction);\n } catch (error) {\n return this.internalServerError(error);\n }\n };\n }\n\n private internalServerError(error: unknown): Response {\n this.logger.error('Adapter error:', error);\n const httpError = HttpErrorFactory.toHttpError(error);\n return TangoResponse.json(httpError.body as JsonValue, { status: httpError.status }).toWebResponse();\n }\n\n private adaptRouteHandlersFactory(factory: () => Promise<NextViewSetRouteHandlers>): NextViewSetRouteHandlers {\n let handlersPromise: Promise<NextViewSetRouteHandlers> | null = null;\n\n const getHandlers = async (): Promise<NextViewSetRouteHandlers> => {\n if (!handlersPromise) {\n const initializing = factory();\n handlersPromise = initializing.catch((error) => {\n handlersPromise = null;\n throw error;\n });\n }\n return handlersPromise;\n };\n\n const createLazyHandler = (method: keyof NextViewSetRouteHandlers): NextDynamicRouteHandler => {\n return async (request, context) => {\n try {\n const handlers = await getHandlers();\n return handlers[method](request, context);\n } catch (error) {\n return this.internalServerError(error);\n }\n };\n };\n\n return {\n GET: createLazyHandler('GET'),\n POST: createLazyHandler('POST'),\n PATCH: createLazyHandler('PATCH'),\n PUT: createLazyHandler('PUT'),\n DELETE: createLazyHandler('DELETE'),\n };\n }\n\n private resolveDetailId(params: Record<string, string>, paramKey: string): string | null {\n const directId = this.extractDirectId(params);\n if (directId) {\n return directId;\n }\n const segments = this.extractCatchAllSegments(params, paramKey);\n if (segments.length !== 1) {\n return null;\n }\n return segments[0] as string;\n }\n\n private normalizeRouteParams(raw: Record<string, string | string[]>): Record<string, string> {\n const entries = Object.entries(raw).map(([key, value]) => {\n return [key, Array.isArray(value) ? value.join('/') : value];\n });\n\n return Object.fromEntries(entries);\n }\n\n private extractDirectId(params: Record<string, string>): string | null {\n const directId = params.id?.trim();\n return directId || null;\n }\n\n private extractCatchAllSegments(params: Record<string, string>, paramKey: string): string[] {\n const catchAll = params[paramKey]?.trim() ?? '';\n if (!catchAll) {\n return [];\n }\n return catchAll.split('/').filter(Boolean);\n }\n\n private methodNotAllowedResponse(): TangoResponse {\n return TangoResponse.json(\n {\n error: 'Method not allowed for this route.',\n },\n { status: 405 }\n );\n }\n\n private notFoundResponse(): TangoResponse {\n return TangoResponse.json(\n {\n error: 'Not found.',\n },\n { status: 404 }\n );\n }\n\n private resolveActionMatch(viewset: NextCrudViewSet, method: HttpMethod, segments: string[]): ActionMatch | null {\n if (segments.length === 0) {\n return null;\n }\n\n const actions = this.getViewSetActions(viewset);\n\n if (segments.length >= 2) {\n const detailPath = segments.slice(1).join('/');\n const detailMatch = actions.find(\n (action) => action.scope === InternalActionScope.DETAIL && action.path === detailPath\n );\n if (detailMatch) {\n return detailMatch.methods.includes(method)\n ? { kind: InternalActionMatchKind.DETAIL, action: detailMatch, id: segments[0] as string }\n : { kind: InternalActionMatchKind.METHOD_NOT_ALLOWED };\n }\n }\n\n if (method === InternalHttpMethod.GET && segments.length === 1) {\n return null;\n }\n\n const collectionPath = segments.join('/');\n const collectionMatch = actions.find(\n (action) => action.scope === InternalActionScope.COLLECTION && action.path === collectionPath\n );\n if (!collectionMatch) {\n return null;\n }\n return collectionMatch.methods.includes(method)\n ? { kind: InternalActionMatchKind.COLLECTION, action: collectionMatch }\n : { kind: InternalActionMatchKind.METHOD_NOT_ALLOWED };\n }\n\n private getViewSetActions(viewset: NextCrudViewSet): readonly ResolvedViewSetActionDescriptor[] {\n const constructorValue = viewset.constructor as {\n getActions?: (input: NextCrudViewSet) => 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 NextAdapter,\n type AdaptNextOptions,\n type AdaptNextViewSetOptions,\n type NextAPIView,\n type NextAPIViewFactory,\n type NextCrudViewSet,\n type NextDynamicRouteContext,\n type NextDynamicRouteHandler,\n type NextRouteHandler,\n type NextViewSetFactory,\n type NextViewSetRouteHandlers,\n} from './NextAdapter';\n"],"mappings":";;;;;;;;;;;;;;;;IA8Fa,cAAN,MAAuF;CAC1F,eAAwD;CACxD,SAA0B,UAAU,qBAAqB;CACzD,kBAAmC,IAAI;;;;CAIvC,cAAcA,cAA+E;AACzF,SAAO,iBAAiB,WAAW,aAAa;CACnD;;;;CAKD,MACIC,SACAC,UAA4B,CAAE,GACd;AAChB,SAAO,KAAK,cAAc,SAAS,QAAQ;CAC9C;;;;CAKD,aAAaC,SAA0BC,UAAmC,CAAE,GAA4B;EACpG,MAAM,WAAW,QAAQ,YAAY;AACrC,SAAO;GACH,KAAK,KAAK,MACN,CAAC,QACG,KAAK,sBAAsB,SAAS,mBAAmB,KAAK,KAAK,UAAU,CAAC,UAAU,aAAa;AAC/F,QAAI,SAAU,QAAO,QAAQ,SAAS,KAAK,SAAS;AACpD,QAAI,SAAS,WAAW,EAAG,QAAO,QAAQ,KAAK,IAAI;AACnD,QAAI,SAAS,WAAW,EAAG,QAAO,QAAQ,SAAS,KAAK,SAAS,GAAa;AAC9E,WAAO,KAAK,kBAAkB;GACjC,EAAC,EACN,QACH;GACD,MAAM,KAAK,MACP,CAAC,QACG,KAAK,sBACD,SACA,mBAAmB,MACnB,KACA,UACA,CAAC,UAAU,aAAa;AACpB,QAAI,SAAS,WAAW,MAAM,SAAU,QAAO,QAAQ,OAAO,IAAI;AAClE,WAAO,KAAK,0BAA0B;GACzC,EACJ,EACL,QACH;GACD,OAAO,KAAK,MACR,CAAC,QACG,KAAK,sBACD,SACA,mBAAmB,OACnB,KACA,UACA,CAAC,UAAU,aAAa;IACpB,MAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,QAAQ,OAAO,KAAK,GAAG,GAAG,KAAK,0BAA0B;GACxE,EACJ,EACL,QACH;GACD,KAAK,KAAK,MACN,CAAC,QACG,KAAK,sBAAsB,SAAS,mBAAmB,KAAK,KAAK,UAAU,CAAC,UAAU,aAAa;IAC/F,MAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,QAAQ,OAAO,KAAK,GAAG,GAAG,KAAK,0BAA0B;GACxE,EAAC,EACN,QACH;GACD,QAAQ,KAAK,MACT,CAAC,QACG,KAAK,sBACD,SACA,mBAAmB,QACnB,KACA,UACA,CAAC,UAAU,aAAa;IACpB,MAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,QAAQ,QAAQ,KAAK,GAAG,GAAG,KAAK,0BAA0B;GACzE,EACJ,EACL,QACH;EACJ;CACJ;;;;;CAMD,oBAAoBC,SAA6BD,UAAmC,CAAE,GAA4B;AAC9G,SAAO,KAAK,0BAA0B,YAAY;GAC9C,MAAM,UAAU,MAAM,SAAS;AAC/B,UAAO,KAAK,aAAa,SAAS,QAAQ;EAC7C,EAAC;CACL;;;;CAKD,2BACIE,SACAF,UAAmC,CAAE,GACb;AACxB,SAAO,KAAK,0BAA0B,YAAY;GAC9C,MAAM,UAAU,MAAM,SAAS;AAC/B,UAAO,KAAK,oBAAoB,SAAS,QAAQ;EACpD,EAAC;CACL;;;;CAKD,aAAaG,SAAsBL,UAA4B,CAAE,GAA4B;AACzF,SAAO;GACH,KAAK,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GACxD,MAAM,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GACzD,OAAO,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GAC1D,KAAK,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GACxD,QAAQ,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;EAC9D;CACJ;;;;CAKD,oBAAoBK,SAAsBH,UAAmC,CAAE,GAA4B;EAEvG,MAAM,WAAW,QAAQ,YAAY;AACrC,SAAO;GACH,KAAK,KAAK,MAAM,OAAO,QAAQ;IAC3B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,QAAQ,SAAS,IAAI;AAEhC,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,MAAM,KAAK,MAAM,OAAO,QAAQ;IAC5B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,QAAI,SACA,QAAO,KAAK,0BAA0B;AAE1C,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,OAAO,KAAK,MAAM,OAAO,QAAQ;IAC7B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,KAAK,0BAA0B;AAE1C,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,KAAK,KAAK,MAAM,OAAO,QAAQ;IAC3B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,KAAK,0BAA0B;AAE1C,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,QAAQ,KAAK,MAAM,OAAO,QAAQ;IAC9B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,KAAK,0BAA0B;AAE1C,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;EACd;CACJ;CAED,eAAuBI,SAAoC;AACvD,MAAI,aAAa,eAAe,QAAQ,CACpC,QAAO;AAIX,MAAI,mBAAmB,QACnB,QAAO,IAAI,aAAa;AAG5B,SAAO,IAAI,aAAa,OAAQ,QAA8B,OAAO,mBAAmB;CAC3F;CAED,sBACIL,SACAM,QACAC,KACAC,UACAC,UACsB;EACtB,MAAM,WAAW,KAAK,wBAAwB,IAAI,QAAQ,SAAS;EACnE,MAAM,WAAW,KAAK,gBAAgB,IAAI,OAAO;EACjD,MAAM,cAAc,KAAK,mBAAmB,SAAS,QAAQ,SAAS;AAEtE,MAAI,aAAa,SAAS,wBAAwB,mBAC9C,QAAO,QAAQ,QAAQ,KAAK,0BAA0B,CAAC;AAE3D,MAAI,aAAa,SAAS,wBAAwB,OAC9C,QAAO,KAAK,mBAAmB,SAAS,YAAY,QAAQ,KAAK,YAAY,GAAG;AAEpF,MAAI,aAAa,SAAS,wBAAwB,WAC9C,QAAO,KAAK,uBAAuB,SAAS,YAAY,QAAQ,IAAI;AAGxE,SAAO,QAAQ,QAAQ,SAAS,UAAU,SAAS,CAAC;CACvD;CAED,mBACIT,SACAU,QACAH,KACAI,IACsB;EACtB,MAAM,YAAa,QAA+C,OAAO;AACzE,aAAW,cAAc,WACrB,QAAO,QAAQ,QAAQ,KAAK,kBAAkB,CAAC;AAEnD,SAAO,UAAkG,KACrG,SACA,KACA,GACH;CACJ;CAED,uBACIX,SACAU,QACAH,KACsB;EACtB,MAAM,YAAa,QAA+C,OAAO;AACzE,aAAW,cAAc,WACrB,QAAO,QAAQ,QAAQ,KAAK,kBAAkB,CAAC;AAEnD,SAAO,UAAsF,KAAK,SAAS,IAAI;CAClH;CAED,cACIT,SACAC,SACgB;AAChB,SAAO,OAAOM,SAAsB,iBAAiB;AACjD,OAAI;IACA,MAAM,OAAO,QAAQ,UAAU,MAAM,QAAQ,QAAQ,QAAQ,GAAG;IAChE,MAAM,YAAY,cAAc,SAAS,MAAM,aAAa,SAAS,CAAE;IACvE,MAAM,SAAS,KAAK,qBAAqB,UAAU;IAEnD,MAAM,MAAM,eAAe,OAAO,KAAK,eAAe,QAAQ,EAAE,KAAK;AACrE,QAAI,OAAO,KAAK,OAAO,CAAC,SAAS,EAC7B,KAAI,SAAS;IAGjB,MAAM,KAAK,QAAQ;AACnB,WAAO,MAAM,KAAK,gBACb,WAAW;KAAE;KAAS;KAAK;IAAI,EAAC,CAChC,eAAe,QAAQ,QAAQ,QAAQ,YAAY;GAC3D,SAAQ,OAAO;AACZ,WAAO,KAAK,oBAAoB,MAAM;GACzC;EACJ;CACJ;CAED,oBAA4BO,OAA0B;AAClD,OAAK,OAAO,MAAM,kBAAkB,MAAM;EAC1C,MAAM,YAAY,iBAAiB,YAAY,MAAM;AACrD,SAAO,cAAc,KAAK,UAAU,MAAmB,EAAE,QAAQ,UAAU,OAAQ,EAAC,CAAC,eAAe;CACvG;CAED,0BAAkCC,SAA4E;EAC1G,IAAIC,kBAA4D;EAEhE,MAAM,cAAc,YAA+C;AAC/D,QAAK,iBAAiB;IAClB,MAAM,eAAe,SAAS;AAC9B,sBAAkB,aAAa,MAAM,CAAC,UAAU;AAC5C,uBAAkB;AAClB,WAAM;IACT,EAAC;GACL;AACD,UAAO;EACV;EAED,MAAM,oBAAoB,CAACC,WAAoE;AAC3F,UAAO,OAAO,SAAS,YAAY;AAC/B,QAAI;KACA,MAAM,WAAW,MAAM,aAAa;AACpC,YAAO,SAAS,QAAQ,SAAS,QAAQ;IAC5C,SAAQ,OAAO;AACZ,YAAO,KAAK,oBAAoB,MAAM;IACzC;GACJ;EACJ;AAED,SAAO;GACH,KAAK,kBAAkB,MAAM;GAC7B,MAAM,kBAAkB,OAAO;GAC/B,OAAO,kBAAkB,QAAQ;GACjC,KAAK,kBAAkB,MAAM;GAC7B,QAAQ,kBAAkB,SAAS;EACtC;CACJ;CAED,gBAAwBC,QAAgCR,UAAiC;EACrF,MAAM,WAAW,KAAK,gBAAgB,OAAO;AAC7C,MAAI,SACA,QAAO;EAEX,MAAM,WAAW,KAAK,wBAAwB,QAAQ,SAAS;AAC/D,MAAI,SAAS,WAAW,EACpB,QAAO;AAEX,SAAO,SAAS;CACnB;CAED,qBAA6BS,KAAgE;EACzF,MAAM,UAAU,OAAO,QAAQ,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,MAAM,KAAK;AACtD,UAAO,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,KAAM;EAC/D,EAAC;AAEF,SAAO,OAAO,YAAY,QAAQ;CACrC;CAED,gBAAwBD,QAA+C;EACnE,MAAM,WAAW,OAAO,IAAI,MAAM;AAClC,SAAO,YAAY;CACtB;CAED,wBAAgCA,QAAgCR,UAA4B;EACxF,MAAM,WAAW,OAAO,WAAW,MAAM,IAAI;AAC7C,OAAK,SACD,QAAO,CAAE;AAEb,SAAO,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;CAC7C;CAED,2BAAkD;AAC9C,SAAO,cAAc,KACjB,EACI,OAAO,qCACV,GACD,EAAE,QAAQ,IAAK,EAClB;CACJ;CAED,mBAA0C;AACtC,SAAO,cAAc,KACjB,EACI,OAAO,aACV,GACD,EAAE,QAAQ,IAAK,EAClB;CACJ;CAED,mBAA2BR,SAA0BM,QAAoBY,UAAwC;AAC7G,MAAI,SAAS,WAAW,EACpB,QAAO;EAGX,MAAM,UAAU,KAAK,kBAAkB,QAAQ;AAE/C,MAAI,SAAS,UAAU,GAAG;GACtB,MAAM,aAAa,SAAS,MAAM,EAAE,CAAC,KAAK,IAAI;GAC9C,MAAM,cAAc,QAAQ,KACxB,CAAC,WAAW,OAAO,UAAU,oBAAoB,UAAU,OAAO,SAAS,WAC9E;AACD,OAAI,YACA,QAAO,YAAY,QAAQ,SAAS,OAAO,GACrC;IAAE,MAAM,wBAAwB;IAAQ,QAAQ;IAAa,IAAI,SAAS;GAAc,IACxF,EAAE,MAAM,wBAAwB,mBAAoB;EAEjE;AAED,MAAI,WAAW,mBAAmB,OAAO,SAAS,WAAW,EACzD,QAAO;EAGX,MAAM,iBAAiB,SAAS,KAAK,IAAI;EACzC,MAAM,kBAAkB,QAAQ,KAC5B,CAAC,WAAW,OAAO,UAAU,oBAAoB,cAAc,OAAO,SAAS,eAClF;AACD,OAAK,gBACD,QAAO;AAEX,SAAO,gBAAgB,QAAQ,SAAS,OAAO,GACzC;GAAE,MAAM,wBAAwB;GAAY,QAAQ;EAAiB,IACrE,EAAE,MAAM,wBAAwB,mBAAoB;CAC7D;CAED,kBAA0BlB,SAAsE;EAC5F,MAAM,mBAAmB,QAAQ;AAIjC,aAAW,iBAAiB,eAAe,WACvC,QAAO,CAAE;AAGb,SAAO,iBAAiB,WAAW,QAAQ;CAC9C;AACJ"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { NextAdapter, adapter_exports } from "./adapter-_WsZ0kE-.js";
1
+ import { NextAdapter, adapter_exports } from "./adapter-Em1-1ezY.js";
2
2
 
3
3
  export { NextAdapter, adapter_exports as adapter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceroutine/tango-adapters-next",
3
- "version": "1.10.3",
3
+ "version": "1.11.1",
4
4
  "description": "Next.js App Router adapter for Tango viewsets",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,9 +32,9 @@
32
32
  "directory": "packages/adapters/next"
33
33
  },
34
34
  "dependencies": {
35
- "@danceroutine/tango-core": "1.10.3",
36
- "@danceroutine/tango-resources": "1.10.3",
37
- "@danceroutine/tango-adapters-core": "1.10.3"
35
+ "@danceroutine/tango-core": "1.11.1",
36
+ "@danceroutine/tango-resources": "1.11.1",
37
+ "@danceroutine/tango-adapters-core": "1.11.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "next": "^15.0.0"
@@ -44,7 +44,7 @@
44
44
  "next": "^15.0.3",
45
45
  "tsdown": "^0.4.0",
46
46
  "typescript": "^5.6.3",
47
- "vitest": "^4.0.6"
47
+ "vitest": "^4.1.7"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsdown",
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter-_WsZ0kE-.js","names":["searchParams: Record<string, string | string[] | undefined>","handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>","options: AdaptNextOptions","viewset: NextCrudViewSet","options: AdaptNextViewSetOptions","factory: NextViewSetFactory","factory: NextAPIViewFactory","apiView: NextAPIView","request: NextRequest","method: HttpMethod","ctx: RequestContext","paramKey: string","fallback: (segments: string[], directId: string | null) => TangoResponse | Promise<TangoResponse>","action: ResolvedViewSetActionDescriptor","id: string","error: unknown","factory: () => Promise<NextViewSetRouteHandlers>","handlersPromise: Promise<NextViewSetRouteHandlers> | null","method: keyof NextViewSetRouteHandlers","params: Record<string, string>","raw: Record<string, string | string[]>","segments: string[]"],"sources":["../src/adapter/NextAdapter.ts","../src/adapter/index.ts"],"sourcesContent":["import type { NextRequest } from 'next/server';\nimport { RequestContext } from '@danceroutine/tango-resources';\nimport {\n HttpErrorFactory,\n TangoQueryParams,\n TangoRequest,\n TangoResponse,\n getLogger,\n type JsonValue,\n} from '@danceroutine/tango-core';\nimport {\n FRAMEWORK_ADAPTER_BRAND,\n type FrameworkAdapter,\n type FrameworkAdapterOptions,\n} from '@danceroutine/tango-adapters-core/adapter';\nimport {\n InternalHttpMethod,\n InternalActionScope,\n InternalActionMatchKind,\n type ActionScope,\n type HttpMethod,\n} from '@danceroutine/tango-adapters-core';\n\ntype ResolvedViewSetActionDescriptor = {\n name: string;\n scope: ActionScope;\n methods: readonly HttpMethod[];\n path: string;\n};\n\n/**\n * Next.js route handler signature produced by the adapter.\n */\nexport type NextRouteHandler = (\n request: NextRequest,\n context?: { params?: Promise<Record<string, string | string[]>> }\n) => Promise<Response>;\n\nexport type NextDynamicRouteContext = {\n params: Promise<Record<string, string | string[]>>;\n};\n\nexport type NextDynamicRouteHandler = (request: NextRequest, context: NextDynamicRouteContext) => Promise<Response>;\n\n/**\n * Adapter options for Next.js integration.\n */\nexport type AdaptNextOptions = FrameworkAdapterOptions<NextRequest>;\n\n/**\n * Minimal CRUD viewset contract used by the Next adapter route helpers.\n */\nexport interface NextCrudViewSet {\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 NextAPIView {\n dispatch(ctx: RequestContext): Promise<TangoResponse>;\n}\n\nexport type NextViewSetFactory = () => NextCrudViewSet | Promise<NextCrudViewSet>;\nexport type NextAPIViewFactory = () => NextAPIView | Promise<NextAPIView>;\n\n/**\n * Options for auto-generated viewset route handlers.\n */\nexport type AdaptNextViewSetOptions = AdaptNextOptions & {\n paramKey?: string;\n};\n\n/**\n * HTTP method handlers generated from a CRUD viewset.\n */\nexport interface NextViewSetRouteHandlers {\n GET: NextDynamicRouteHandler;\n POST: NextDynamicRouteHandler;\n PATCH: NextDynamicRouteHandler;\n PUT: NextDynamicRouteHandler;\n DELETE: NextDynamicRouteHandler;\n}\n\ntype ActionMatch =\n | { kind: typeof InternalActionMatchKind.DETAIL; action: ResolvedViewSetActionDescriptor; id: string }\n | { kind: typeof InternalActionMatchKind.COLLECTION; action: ResolvedViewSetActionDescriptor }\n | { kind: typeof InternalActionMatchKind.METHOD_NOT_ALLOWED };\n\n/**\n * Next.js adapter that translates route handlers to Tango `RequestContext`.\n */\nexport class NextAdapter implements FrameworkAdapter<Response, NextRouteHandler, NextRequest> {\n readonly __tangoBrand: typeof FRAMEWORK_ADAPTER_BRAND = FRAMEWORK_ADAPTER_BRAND;\n private readonly logger = getLogger('tango.adapter.next');\n /**\n * Normalize Next.js-style route search params into Tango query params.\n */\n toQueryParams(searchParams: Record<string, string | string[] | undefined>): TangoQueryParams {\n return TangoQueryParams.fromRecord(searchParams);\n }\n\n /**\n * Adapt a Tango-style handler into a Next.js route handler.\n */\n adapt(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptNextOptions = {}\n ): NextRouteHandler {\n return this.createHandler(handler, options);\n }\n\n /**\n * Build Next route handlers that map HTTP verbs to standard CRUD viewset actions.\n */\n adaptViewSet(viewset: NextCrudViewSet, options: AdaptNextViewSetOptions = {}): NextViewSetRouteHandlers {\n const paramKey = options.paramKey ?? 'tango';\n return {\n GET: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(viewset, InternalHttpMethod.GET, ctx, paramKey, (segments, directId) => {\n if (directId) return viewset.retrieve(ctx, directId);\n if (segments.length === 0) return viewset.list(ctx);\n if (segments.length === 1) return viewset.retrieve(ctx, segments[0] as string);\n return this.notFoundResponse();\n }),\n options\n ),\n POST: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(\n viewset,\n InternalHttpMethod.POST,\n ctx,\n paramKey,\n (segments, directId) => {\n if (segments.length === 0 && !directId) return viewset.create(ctx);\n return this.methodNotAllowedResponse();\n }\n ),\n options\n ),\n PATCH: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(\n viewset,\n InternalHttpMethod.PATCH,\n ctx,\n paramKey,\n (segments, directId) => {\n const id = directId ?? segments[0];\n return id ? viewset.update(ctx, id) : this.methodNotAllowedResponse();\n }\n ),\n options\n ),\n PUT: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(viewset, InternalHttpMethod.PUT, ctx, paramKey, (segments, directId) => {\n const id = directId ?? segments[0];\n return id ? viewset.update(ctx, id) : this.methodNotAllowedResponse();\n }),\n options\n ),\n DELETE: this.adapt(\n (ctx) =>\n this.dispatchViewSetAction(\n viewset,\n InternalHttpMethod.DELETE,\n ctx,\n paramKey,\n (segments, directId) => {\n const id = directId ?? segments[0];\n return id ? viewset.destroy(ctx, id) : this.methodNotAllowedResponse();\n }\n ),\n options\n ),\n };\n }\n\n /**\n * Build Next route handlers from a lazy viewset factory and memoize initialization.\n * Initialization failures clear the memoized promise so subsequent requests can retry.\n */\n adaptViewSetFactory(factory: NextViewSetFactory, options: AdaptNextViewSetOptions = {}): NextViewSetRouteHandlers {\n return this.adaptRouteHandlersFactory(async () => {\n const viewset = await factory();\n return this.adaptViewSet(viewset, options);\n });\n }\n\n /**\n * Build Next route handlers from a lazy GenericAPIView factory and memoize initialization.\n */\n adaptGenericAPIViewFactory(\n factory: NextAPIViewFactory,\n options: AdaptNextViewSetOptions = {}\n ): NextViewSetRouteHandlers {\n return this.adaptRouteHandlersFactory(async () => {\n const apiView = await factory();\n return this.adaptGenericAPIView(apiView, options);\n });\n }\n\n /**\n * Build Next route handlers that dispatch an APIView by HTTP method.\n */\n adaptAPIView(apiView: NextAPIView, options: AdaptNextOptions = {}): NextViewSetRouteHandlers {\n return {\n GET: this.adapt((ctx) => apiView.dispatch(ctx), options),\n POST: this.adapt((ctx) => apiView.dispatch(ctx), options),\n PATCH: this.adapt((ctx) => apiView.dispatch(ctx), options),\n PUT: this.adapt((ctx) => apiView.dispatch(ctx), options),\n DELETE: this.adapt((ctx) => apiView.dispatch(ctx), options),\n };\n }\n\n /**\n * Build handlers for GenericAPIView-style collection/detail splits in catch-all routes.\n */\n adaptGenericAPIView(apiView: NextAPIView, options: AdaptNextViewSetOptions = {}): NextViewSetRouteHandlers {\n // Default catch-all param matches the Next.js [[...tango]] route convention.\n const paramKey = options.paramKey ?? 'tango';\n return {\n GET: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return apiView.dispatch(ctx);\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n POST: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (detailId) {\n return this.methodNotAllowedResponse();\n }\n return apiView.dispatch(ctx);\n }, options),\n PATCH: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return this.methodNotAllowedResponse();\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n PUT: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return this.methodNotAllowedResponse();\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n DELETE: this.adapt(async (ctx) => {\n const detailId = this.resolveDetailId(ctx.params, paramKey);\n if (!detailId) {\n return this.methodNotAllowedResponse();\n }\n ctx.params.id = detailId;\n return apiView.dispatch(ctx);\n }, options),\n };\n }\n\n private toTangoRequest(request: NextRequest): TangoRequest {\n if (TangoRequest.isTangoRequest(request)) {\n return request;\n }\n\n // oxlint-disable-next-line eslint-js/no-restricted-syntax\n if (request instanceof Request) {\n return new TangoRequest(request);\n }\n\n return new TangoRequest(String((request as { url?: unknown }).url ?? 'http://localhost'));\n }\n\n private dispatchViewSetAction(\n viewset: NextCrudViewSet,\n method: HttpMethod,\n ctx: RequestContext,\n paramKey: string,\n fallback: (segments: string[], directId: string | null) => TangoResponse | Promise<TangoResponse>\n ): Promise<TangoResponse> {\n const segments = this.extractCatchAllSegments(ctx.params, paramKey);\n const directId = this.extractDirectId(ctx.params);\n const actionMatch = this.resolveActionMatch(viewset, method, segments);\n\n if (actionMatch?.kind === InternalActionMatchKind.METHOD_NOT_ALLOWED) {\n return Promise.resolve(this.methodNotAllowedResponse());\n }\n if (actionMatch?.kind === InternalActionMatchKind.DETAIL) {\n return this.invokeDetailAction(viewset, actionMatch.action, ctx, actionMatch.id);\n }\n if (actionMatch?.kind === InternalActionMatchKind.COLLECTION) {\n return this.invokeCollectionAction(viewset, actionMatch.action, ctx);\n }\n\n return Promise.resolve(fallback(segments, directId));\n }\n\n private invokeDetailAction(\n viewset: NextCrudViewSet,\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 return Promise.resolve(this.notFoundResponse());\n }\n return (candidate as (this: NextCrudViewSet, ctx: RequestContext, id: string) => Promise<TangoResponse>).call(\n viewset,\n ctx,\n id\n );\n }\n\n private invokeCollectionAction(\n viewset: NextCrudViewSet,\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 return Promise.resolve(this.notFoundResponse());\n }\n return (candidate as (this: NextCrudViewSet, ctx: RequestContext) => Promise<TangoResponse>).call(viewset, ctx);\n }\n\n private createHandler(\n handler: (ctx: RequestContext, ...args: unknown[]) => Promise<TangoResponse>,\n options: AdaptNextOptions\n ): NextRouteHandler {\n return async (request: NextRequest, routeContext) => {\n try {\n const user = options.getUser ? await options.getUser(request) : null;\n const rawParams = routeContext?.params ? await routeContext.params : {};\n const params = this.normalizeRouteParams(rawParams);\n\n const ctx = RequestContext.create(this.toTangoRequest(request), user);\n if (Object.keys(params).length > 0) {\n ctx.params = params;\n }\n\n const id = params?.id;\n if (id && handler.length > 1) {\n return (await handler(ctx, id)).toWebResponse();\n }\n\n return (await handler(ctx)).toWebResponse();\n } catch (error) {\n return this.internalServerError(error);\n }\n };\n }\n\n private internalServerError(error: unknown): Response {\n this.logger.error('Adapter error:', error);\n const httpError = HttpErrorFactory.toHttpError(error);\n return TangoResponse.json(httpError.body as JsonValue, { status: httpError.status }).toWebResponse();\n }\n\n private adaptRouteHandlersFactory(factory: () => Promise<NextViewSetRouteHandlers>): NextViewSetRouteHandlers {\n let handlersPromise: Promise<NextViewSetRouteHandlers> | null = null;\n\n const getHandlers = async (): Promise<NextViewSetRouteHandlers> => {\n if (!handlersPromise) {\n const initializing = factory();\n handlersPromise = initializing.catch((error) => {\n handlersPromise = null;\n throw error;\n });\n }\n return handlersPromise;\n };\n\n const createLazyHandler = (method: keyof NextViewSetRouteHandlers): NextDynamicRouteHandler => {\n return async (request, context) => {\n try {\n const handlers = await getHandlers();\n return handlers[method](request, context);\n } catch (error) {\n return this.internalServerError(error);\n }\n };\n };\n\n return {\n GET: createLazyHandler('GET'),\n POST: createLazyHandler('POST'),\n PATCH: createLazyHandler('PATCH'),\n PUT: createLazyHandler('PUT'),\n DELETE: createLazyHandler('DELETE'),\n };\n }\n\n private resolveDetailId(params: Record<string, string>, paramKey: string): string | null {\n const directId = this.extractDirectId(params);\n if (directId) {\n return directId;\n }\n const segments = this.extractCatchAllSegments(params, paramKey);\n if (segments.length !== 1) {\n return null;\n }\n return segments[0] as string;\n }\n\n private normalizeRouteParams(raw: Record<string, string | string[]>): Record<string, string> {\n const entries = Object.entries(raw).map(([key, value]) => {\n return [key, Array.isArray(value) ? value.join('/') : value];\n });\n\n return Object.fromEntries(entries);\n }\n\n private extractDirectId(params: Record<string, string>): string | null {\n const directId = params.id?.trim();\n return directId || null;\n }\n\n private extractCatchAllSegments(params: Record<string, string>, paramKey: string): string[] {\n const catchAll = params[paramKey]?.trim() ?? '';\n if (!catchAll) {\n return [];\n }\n return catchAll.split('/').filter(Boolean);\n }\n\n private methodNotAllowedResponse(): TangoResponse {\n return TangoResponse.json(\n {\n error: 'Method not allowed for this route.',\n },\n { status: 405 }\n );\n }\n\n private notFoundResponse(): TangoResponse {\n return TangoResponse.json(\n {\n error: 'Not found.',\n },\n { status: 404 }\n );\n }\n\n private resolveActionMatch(viewset: NextCrudViewSet, method: HttpMethod, segments: string[]): ActionMatch | null {\n if (segments.length === 0) {\n return null;\n }\n\n const actions = this.getViewSetActions(viewset);\n\n if (segments.length >= 2) {\n const detailPath = segments.slice(1).join('/');\n const detailMatch = actions.find(\n (action) => action.scope === InternalActionScope.DETAIL && action.path === detailPath\n );\n if (detailMatch) {\n return detailMatch.methods.includes(method)\n ? { kind: InternalActionMatchKind.DETAIL, action: detailMatch, id: segments[0] as string }\n : { kind: InternalActionMatchKind.METHOD_NOT_ALLOWED };\n }\n }\n\n if (method === InternalHttpMethod.GET && segments.length === 1) {\n return null;\n }\n\n const collectionPath = segments.join('/');\n const collectionMatch = actions.find(\n (action) => action.scope === InternalActionScope.COLLECTION && action.path === collectionPath\n );\n if (!collectionMatch) {\n return null;\n }\n return collectionMatch.methods.includes(method)\n ? { kind: InternalActionMatchKind.COLLECTION, action: collectionMatch }\n : { kind: InternalActionMatchKind.METHOD_NOT_ALLOWED };\n }\n\n private getViewSetActions(viewset: NextCrudViewSet): readonly ResolvedViewSetActionDescriptor[] {\n const constructorValue = viewset.constructor as {\n getActions?: (input: NextCrudViewSet) => 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 NextAdapter,\n type AdaptNextOptions,\n type AdaptNextViewSetOptions,\n type NextAPIView,\n type NextAPIViewFactory,\n type NextCrudViewSet,\n type NextDynamicRouteContext,\n type NextDynamicRouteHandler,\n type NextRouteHandler,\n type NextViewSetFactory,\n type NextViewSetRouteHandlers,\n} from './NextAdapter';\n"],"mappings":";;;;;;;;;;;;;;;;IA6Fa,cAAN,MAAuF;CAC1F,eAAwD;CACxD,SAA0B,UAAU,qBAAqB;;;;CAIzD,cAAcA,cAA+E;AACzF,SAAO,iBAAiB,WAAW,aAAa;CACnD;;;;CAKD,MACIC,SACAC,UAA4B,CAAE,GACd;AAChB,SAAO,KAAK,cAAc,SAAS,QAAQ;CAC9C;;;;CAKD,aAAaC,SAA0BC,UAAmC,CAAE,GAA4B;EACpG,MAAM,WAAW,QAAQ,YAAY;AACrC,SAAO;GACH,KAAK,KAAK,MACN,CAAC,QACG,KAAK,sBAAsB,SAAS,mBAAmB,KAAK,KAAK,UAAU,CAAC,UAAU,aAAa;AAC/F,QAAI,SAAU,QAAO,QAAQ,SAAS,KAAK,SAAS;AACpD,QAAI,SAAS,WAAW,EAAG,QAAO,QAAQ,KAAK,IAAI;AACnD,QAAI,SAAS,WAAW,EAAG,QAAO,QAAQ,SAAS,KAAK,SAAS,GAAa;AAC9E,WAAO,KAAK,kBAAkB;GACjC,EAAC,EACN,QACH;GACD,MAAM,KAAK,MACP,CAAC,QACG,KAAK,sBACD,SACA,mBAAmB,MACnB,KACA,UACA,CAAC,UAAU,aAAa;AACpB,QAAI,SAAS,WAAW,MAAM,SAAU,QAAO,QAAQ,OAAO,IAAI;AAClE,WAAO,KAAK,0BAA0B;GACzC,EACJ,EACL,QACH;GACD,OAAO,KAAK,MACR,CAAC,QACG,KAAK,sBACD,SACA,mBAAmB,OACnB,KACA,UACA,CAAC,UAAU,aAAa;IACpB,MAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,QAAQ,OAAO,KAAK,GAAG,GAAG,KAAK,0BAA0B;GACxE,EACJ,EACL,QACH;GACD,KAAK,KAAK,MACN,CAAC,QACG,KAAK,sBAAsB,SAAS,mBAAmB,KAAK,KAAK,UAAU,CAAC,UAAU,aAAa;IAC/F,MAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,QAAQ,OAAO,KAAK,GAAG,GAAG,KAAK,0BAA0B;GACxE,EAAC,EACN,QACH;GACD,QAAQ,KAAK,MACT,CAAC,QACG,KAAK,sBACD,SACA,mBAAmB,QACnB,KACA,UACA,CAAC,UAAU,aAAa;IACpB,MAAM,KAAK,YAAY,SAAS;AAChC,WAAO,KAAK,QAAQ,QAAQ,KAAK,GAAG,GAAG,KAAK,0BAA0B;GACzE,EACJ,EACL,QACH;EACJ;CACJ;;;;;CAMD,oBAAoBC,SAA6BD,UAAmC,CAAE,GAA4B;AAC9G,SAAO,KAAK,0BAA0B,YAAY;GAC9C,MAAM,UAAU,MAAM,SAAS;AAC/B,UAAO,KAAK,aAAa,SAAS,QAAQ;EAC7C,EAAC;CACL;;;;CAKD,2BACIE,SACAF,UAAmC,CAAE,GACb;AACxB,SAAO,KAAK,0BAA0B,YAAY;GAC9C,MAAM,UAAU,MAAM,SAAS;AAC/B,UAAO,KAAK,oBAAoB,SAAS,QAAQ;EACpD,EAAC;CACL;;;;CAKD,aAAaG,SAAsBL,UAA4B,CAAE,GAA4B;AACzF,SAAO;GACH,KAAK,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GACxD,MAAM,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GACzD,OAAO,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GAC1D,KAAK,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;GACxD,QAAQ,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;EAC9D;CACJ;;;;CAKD,oBAAoBK,SAAsBH,UAAmC,CAAE,GAA4B;EAEvG,MAAM,WAAW,QAAQ,YAAY;AACrC,SAAO;GACH,KAAK,KAAK,MAAM,OAAO,QAAQ;IAC3B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,QAAQ,SAAS,IAAI;AAEhC,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,MAAM,KAAK,MAAM,OAAO,QAAQ;IAC5B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,QAAI,SACA,QAAO,KAAK,0BAA0B;AAE1C,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,OAAO,KAAK,MAAM,OAAO,QAAQ;IAC7B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,KAAK,0BAA0B;AAE1C,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,KAAK,KAAK,MAAM,OAAO,QAAQ;IAC3B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,KAAK,0BAA0B;AAE1C,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;GACX,QAAQ,KAAK,MAAM,OAAO,QAAQ;IAC9B,MAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC3D,SAAK,SACD,QAAO,KAAK,0BAA0B;AAE1C,QAAI,OAAO,KAAK;AAChB,WAAO,QAAQ,SAAS,IAAI;GAC/B,GAAE,QAAQ;EACd;CACJ;CAED,eAAuBI,SAAoC;AACvD,MAAI,aAAa,eAAe,QAAQ,CACpC,QAAO;AAIX,MAAI,mBAAmB,QACnB,QAAO,IAAI,aAAa;AAG5B,SAAO,IAAI,aAAa,OAAQ,QAA8B,OAAO,mBAAmB;CAC3F;CAED,sBACIL,SACAM,QACAC,KACAC,UACAC,UACsB;EACtB,MAAM,WAAW,KAAK,wBAAwB,IAAI,QAAQ,SAAS;EACnE,MAAM,WAAW,KAAK,gBAAgB,IAAI,OAAO;EACjD,MAAM,cAAc,KAAK,mBAAmB,SAAS,QAAQ,SAAS;AAEtE,MAAI,aAAa,SAAS,wBAAwB,mBAC9C,QAAO,QAAQ,QAAQ,KAAK,0BAA0B,CAAC;AAE3D,MAAI,aAAa,SAAS,wBAAwB,OAC9C,QAAO,KAAK,mBAAmB,SAAS,YAAY,QAAQ,KAAK,YAAY,GAAG;AAEpF,MAAI,aAAa,SAAS,wBAAwB,WAC9C,QAAO,KAAK,uBAAuB,SAAS,YAAY,QAAQ,IAAI;AAGxE,SAAO,QAAQ,QAAQ,SAAS,UAAU,SAAS,CAAC;CACvD;CAED,mBACIT,SACAU,QACAH,KACAI,IACsB;EACtB,MAAM,YAAa,QAA+C,OAAO;AACzE,aAAW,cAAc,WACrB,QAAO,QAAQ,QAAQ,KAAK,kBAAkB,CAAC;AAEnD,SAAO,UAAkG,KACrG,SACA,KACA,GACH;CACJ;CAED,uBACIX,SACAU,QACAH,KACsB;EACtB,MAAM,YAAa,QAA+C,OAAO;AACzE,aAAW,cAAc,WACrB,QAAO,QAAQ,QAAQ,KAAK,kBAAkB,CAAC;AAEnD,SAAO,UAAsF,KAAK,SAAS,IAAI;CAClH;CAED,cACIT,SACAC,SACgB;AAChB,SAAO,OAAOM,SAAsB,iBAAiB;AACjD,OAAI;IACA,MAAM,OAAO,QAAQ,UAAU,MAAM,QAAQ,QAAQ,QAAQ,GAAG;IAChE,MAAM,YAAY,cAAc,SAAS,MAAM,aAAa,SAAS,CAAE;IACvE,MAAM,SAAS,KAAK,qBAAqB,UAAU;IAEnD,MAAM,MAAM,eAAe,OAAO,KAAK,eAAe,QAAQ,EAAE,KAAK;AACrE,QAAI,OAAO,KAAK,OAAO,CAAC,SAAS,EAC7B,KAAI,SAAS;IAGjB,MAAM,KAAK,QAAQ;AACnB,QAAI,MAAM,QAAQ,SAAS,EACvB,QAAO,CAAC,MAAM,QAAQ,KAAK,GAAG,EAAE,eAAe;AAGnD,WAAO,CAAC,MAAM,QAAQ,IAAI,EAAE,eAAe;GAC9C,SAAQ,OAAO;AACZ,WAAO,KAAK,oBAAoB,MAAM;GACzC;EACJ;CACJ;CAED,oBAA4BO,OAA0B;AAClD,OAAK,OAAO,MAAM,kBAAkB,MAAM;EAC1C,MAAM,YAAY,iBAAiB,YAAY,MAAM;AACrD,SAAO,cAAc,KAAK,UAAU,MAAmB,EAAE,QAAQ,UAAU,OAAQ,EAAC,CAAC,eAAe;CACvG;CAED,0BAAkCC,SAA4E;EAC1G,IAAIC,kBAA4D;EAEhE,MAAM,cAAc,YAA+C;AAC/D,QAAK,iBAAiB;IAClB,MAAM,eAAe,SAAS;AAC9B,sBAAkB,aAAa,MAAM,CAAC,UAAU;AAC5C,uBAAkB;AAClB,WAAM;IACT,EAAC;GACL;AACD,UAAO;EACV;EAED,MAAM,oBAAoB,CAACC,WAAoE;AAC3F,UAAO,OAAO,SAAS,YAAY;AAC/B,QAAI;KACA,MAAM,WAAW,MAAM,aAAa;AACpC,YAAO,SAAS,QAAQ,SAAS,QAAQ;IAC5C,SAAQ,OAAO;AACZ,YAAO,KAAK,oBAAoB,MAAM;IACzC;GACJ;EACJ;AAED,SAAO;GACH,KAAK,kBAAkB,MAAM;GAC7B,MAAM,kBAAkB,OAAO;GAC/B,OAAO,kBAAkB,QAAQ;GACjC,KAAK,kBAAkB,MAAM;GAC7B,QAAQ,kBAAkB,SAAS;EACtC;CACJ;CAED,gBAAwBC,QAAgCR,UAAiC;EACrF,MAAM,WAAW,KAAK,gBAAgB,OAAO;AAC7C,MAAI,SACA,QAAO;EAEX,MAAM,WAAW,KAAK,wBAAwB,QAAQ,SAAS;AAC/D,MAAI,SAAS,WAAW,EACpB,QAAO;AAEX,SAAO,SAAS;CACnB;CAED,qBAA6BS,KAAgE;EACzF,MAAM,UAAU,OAAO,QAAQ,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,MAAM,KAAK;AACtD,UAAO,CAAC,KAAK,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,KAAM;EAC/D,EAAC;AAEF,SAAO,OAAO,YAAY,QAAQ;CACrC;CAED,gBAAwBD,QAA+C;EACnE,MAAM,WAAW,OAAO,IAAI,MAAM;AAClC,SAAO,YAAY;CACtB;CAED,wBAAgCA,QAAgCR,UAA4B;EACxF,MAAM,WAAW,OAAO,WAAW,MAAM,IAAI;AAC7C,OAAK,SACD,QAAO,CAAE;AAEb,SAAO,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;CAC7C;CAED,2BAAkD;AAC9C,SAAO,cAAc,KACjB,EACI,OAAO,qCACV,GACD,EAAE,QAAQ,IAAK,EAClB;CACJ;CAED,mBAA0C;AACtC,SAAO,cAAc,KACjB,EACI,OAAO,aACV,GACD,EAAE,QAAQ,IAAK,EAClB;CACJ;CAED,mBAA2BR,SAA0BM,QAAoBY,UAAwC;AAC7G,MAAI,SAAS,WAAW,EACpB,QAAO;EAGX,MAAM,UAAU,KAAK,kBAAkB,QAAQ;AAE/C,MAAI,SAAS,UAAU,GAAG;GACtB,MAAM,aAAa,SAAS,MAAM,EAAE,CAAC,KAAK,IAAI;GAC9C,MAAM,cAAc,QAAQ,KACxB,CAAC,WAAW,OAAO,UAAU,oBAAoB,UAAU,OAAO,SAAS,WAC9E;AACD,OAAI,YACA,QAAO,YAAY,QAAQ,SAAS,OAAO,GACrC;IAAE,MAAM,wBAAwB;IAAQ,QAAQ;IAAa,IAAI,SAAS;GAAc,IACxF,EAAE,MAAM,wBAAwB,mBAAoB;EAEjE;AAED,MAAI,WAAW,mBAAmB,OAAO,SAAS,WAAW,EACzD,QAAO;EAGX,MAAM,iBAAiB,SAAS,KAAK,IAAI;EACzC,MAAM,kBAAkB,QAAQ,KAC5B,CAAC,WAAW,OAAO,UAAU,oBAAoB,cAAc,OAAO,SAAS,eAClF;AACD,OAAK,gBACD,QAAO;AAEX,SAAO,gBAAgB,QAAQ,SAAS,OAAO,GACzC;GAAE,MAAM,wBAAwB;GAAY,QAAQ;EAAiB,IACrE,EAAE,MAAM,wBAAwB,mBAAoB;CAC7D;CAED,kBAA0BlB,SAAsE;EAC5F,MAAM,mBAAmB,QAAQ;AAIjC,aAAW,iBAAiB,eAAe,WACvC,QAAO,CAAE;AAGb,SAAO,iBAAiB,WAAW,QAAQ;CAC9C;AACJ"}