@objectstack/nextjs 3.2.7 → 3.2.9

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,5 +1,5 @@
1
1
 
2
- > @objectstack/nextjs@3.2.7 build /home/runner/work/spec/spec/packages/adapters/nextjs
2
+ > @objectstack/nextjs@3.2.9 build /home/runner/work/spec/spec/packages/adapters/nextjs
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- CJS dist/index.js 9.21 KB
14
- CJS dist/index.js.map 17.15 KB
15
- CJS ⚡️ Build success in 75ms
16
- ESM dist/index.mjs 7.97 KB
17
- ESM dist/index.mjs.map 17.10 KB
18
- ESM ⚡️ Build success in 81ms
13
+ ESM dist/index.mjs 7.52 KB
14
+ ESM dist/index.mjs.map 16.55 KB
15
+ ESM ⚡️ Build success in 60ms
16
+ CJS dist/index.js 8.76 KB
17
+ CJS dist/index.js.map 16.60 KB
18
+ CJS ⚡️ Build success in 71ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 13930ms
21
- DTS dist/index.d.mts 2.48 KB
22
- DTS dist/index.d.ts 2.48 KB
20
+ DTS ⚡️ Build success in 13961ms
21
+ DTS dist/index.d.mts 2.63 KB
22
+ DTS dist/index.d.ts 2.63 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @objectstack/nextjs
2
2
 
3
+ ## 3.2.9
4
+
5
+ ### Patch Changes
6
+
7
+ - @objectstack/runtime@3.2.9
8
+
9
+ ## 3.2.8
10
+
11
+ ### Patch Changes
12
+
13
+ - @objectstack/runtime@3.2.8
14
+
15
+ ## 3.2.8
16
+
17
+ ### Patch Changes
18
+
19
+ - fix: unified catch-all dispatch pattern — `createRouteHandler()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
20
+ - Only auth (service check), storage (formData), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
21
+
3
22
  ## 3.2.7
4
23
 
5
24
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -8,6 +8,9 @@ interface NextAdapterOptions {
8
8
  /**
9
9
  * Creates a route handler for Next.js App Router
10
10
  * Handles /api/[...objectstack] pattern
11
+ *
12
+ * Only auth, GraphQL, storage, and discovery need explicit handling.
13
+ * All other routes delegate to `HttpDispatcher.dispatch()` automatically.
11
14
  */
12
15
  declare function createRouteHandler(options: NextAdapterOptions): (req: NextRequest, { params }: {
13
16
  params: {
package/dist/index.d.ts CHANGED
@@ -8,6 +8,9 @@ interface NextAdapterOptions {
8
8
  /**
9
9
  * Creates a route handler for Next.js App Router
10
10
  * Handles /api/[...objectstack] pattern
11
+ *
12
+ * Only auth, GraphQL, storage, and discovery need explicit handling.
13
+ * All other routes delegate to `HttpDispatcher.dispatch()` automatically.
11
14
  */
12
15
  declare function createRouteHandler(options: NextAdapterOptions): (req: NextRequest, { params }: {
13
16
  params: {
package/dist/index.js CHANGED
@@ -76,43 +76,22 @@ function createRouteHandler(options) {
76
76
  }
77
77
  if (authService && typeof authService.handleRequest === "function") {
78
78
  const response = await authService.handleRequest(req);
79
- const body2 = await response.text();
79
+ const body3 = await response.text();
80
80
  const headers = {};
81
81
  response.headers.forEach((v, k) => {
82
82
  headers[k] = v;
83
83
  });
84
- return new import_server.NextResponse(body2, { status: response.status, headers });
84
+ return new import_server.NextResponse(body3, { status: response.status, headers });
85
85
  }
86
86
  const subPath = segments.slice(1).join("/");
87
- const body = method === "POST" ? await req.json().catch(() => ({})) : {};
88
- const result = await dispatcher.handleAuth(subPath, method, body, { request: req });
89
- return toResponse(result);
87
+ const body2 = method === "POST" ? await req.json().catch(() => ({})) : {};
88
+ const result2 = await dispatcher.handleAuth(subPath, method, body2, { request: req });
89
+ return toResponse(result2);
90
90
  }
91
91
  if (segments[0] === "graphql" && method === "POST") {
92
- const body = await req.json();
93
- const result = await dispatcher.handleGraphQL(body, { request: rawRequest });
94
- return import_server.NextResponse.json(result);
95
- }
96
- if (segments[0] === "meta") {
97
- const subPath = segments.slice(1).join("/");
98
- let body = void 0;
99
- if (method === "PUT" || method === "POST") {
100
- body = await req.json().catch(() => ({}));
101
- }
102
- const result = await dispatcher.handleMetadata(subPath, { request: rawRequest }, method, body);
103
- return toResponse(result);
104
- }
105
- if (segments[0] === "data") {
106
- const subPath = segments.slice(1).join("/");
107
- let body = {};
108
- if (method === "POST" || method === "PATCH") {
109
- body = await req.json().catch(() => ({}));
110
- }
111
- const url = new URL(req.url);
112
- const queryParams = {};
113
- url.searchParams.forEach((val, key) => queryParams[key] = val);
114
- const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: rawRequest });
115
- return toResponse(result);
92
+ const body2 = await req.json();
93
+ const result2 = await dispatcher.handleGraphQL(body2, { request: rawRequest });
94
+ return import_server.NextResponse.json(result2);
116
95
  }
117
96
  if (segments[0] === "storage") {
118
97
  const subPath = segments.slice(1).join("/");
@@ -121,10 +100,19 @@ function createRouteHandler(options) {
121
100
  const formData = await req.formData();
122
101
  file = formData.get("file");
123
102
  }
124
- const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });
125
- return toResponse(result);
103
+ const result2 = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });
104
+ return toResponse(result2);
105
+ }
106
+ const path = "/" + segments.join("/");
107
+ let body = void 0;
108
+ if (method === "POST" || method === "PUT" || method === "PATCH") {
109
+ body = await req.json().catch(() => ({}));
126
110
  }
127
- return error("Not Found", 404);
111
+ const url = new URL(req.url);
112
+ const queryParams = {};
113
+ url.searchParams.forEach((val, key) => queryParams[key] = val);
114
+ const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest });
115
+ return toResponse(result);
128
116
  } catch (err) {
129
117
  return error(err.message || "Internal Server Error", err.statusCode || 500);
130
118
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface NextAdapterOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Auth service interface with handleRequest method\n */\ninterface AuthService {\n handleRequest(request: Request): Promise<Response>;\n}\n\n/**\n * Creates a route handler for Next.js App Router\n * Handles /api/[...objectstack] pattern\n */\nexport function createRouteHandler(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const error = (msg: string, code: number = 500) => NextResponse.json({ success: false, error: { message: msg, code } }, { status: code });\n\n // Helper to convert DispatchResult to NextResponse\n const toResponse = (result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n return NextResponse.json(result.response.body, { \n status: result.response.status, \n headers: result.response.headers \n });\n }\n if (result.result) {\n const res = result.result;\n // Redirect\n if (res.type === 'redirect' && res.url) {\n return NextResponse.redirect(res.url);\n }\n // Stream\n if (res.type === 'stream' && res.stream) {\n return new NextResponse(res.stream, {\n status: 200,\n headers: res.headers\n });\n }\n // If it's a standard response object (like from another fetch)\n // Next.js might handle it, or we return it directly\n return res; \n }\n }\n return error('Not Found', 404);\n }\n\n return async function handler(req: NextRequest, { params }: { params: { objectstack: string[] } }) {\n const resolvedParams = await Promise.resolve(params);\n const segments = resolvedParams.objectstack || [];\n const method = req.method;\n \n // --- 0. Discovery Endpoint ---\n if (segments.length === 0 && method === 'GET') {\n return NextResponse.json({ data: await dispatcher.getDiscoveryInfo(options.prefix || '/api') });\n }\n\n try {\n const rawRequest = req;\n\n // --- 1. Auth ---\n if (segments[0] === 'auth') {\n // Try AuthPlugin service first (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\n\n if (authService && typeof authService.handleRequest === 'function') {\n const response = await authService.handleRequest(req);\n // Convert Web Response to NextResponse\n const body = await response.text();\n const headers: Record<string, string> = {};\n response.headers.forEach((v: string, k: string) => { headers[k] = v; });\n return new NextResponse(body, { status: response.status, headers });\n }\n\n // Fallback to legacy dispatcher\n const subPath = segments.slice(1).join('/');\n const body = method === 'POST' ? await req.json().catch(() => ({})) : {};\n const result = await dispatcher.handleAuth(subPath, method, body, { request: req });\n return toResponse(result);\n }\n\n // --- 2. GraphQL ---\n if (segments[0] === 'graphql' && method === 'POST') {\n const body = await req.json();\n const result = await dispatcher.handleGraphQL(body as any, { request: rawRequest } as any);\n return NextResponse.json(result);\n }\n\n // --- 3. Metadata ---\n if (segments[0] === 'meta') {\n const subPath = segments.slice(1).join('/');\n \n let body: any = undefined;\n if (method === 'PUT' || method === 'POST') {\n body = await req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleMetadata(subPath, { request: rawRequest }, method, body);\n return toResponse(result);\n }\n\n // --- 4. Data ---\n if (segments[0] === 'data') {\n const subPath = segments.slice(1).join('/');\n let body: any = {};\n if (method === 'POST' || method === 'PATCH') {\n body = await req.json().catch(() => ({}));\n }\n \n // Extract query params\n const url = new URL(req.url);\n const queryParams: Record<string, any> = {};\n url.searchParams.forEach((val, key) => queryParams[key] = val);\n\n const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: rawRequest } as any);\n return toResponse(result);\n }\n\n // --- 5. Storage ---\n if (segments[0] === 'storage') {\n const subPath = segments.slice(1).join('/');\n \n let file: any = undefined;\n if (method === 'POST' && subPath === 'upload') {\n const formData = await req.formData();\n file = formData.get('file');\n }\n\n const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });\n return toResponse(result);\n }\n \n return error('Not Found', 404);\n\n } catch (err: any) {\n return error(err.message || 'Internal Server Error', err.statusCode || 500);\n }\n }\n}\n\n/**\n * Creates a discovery handler for Next.js App Router\n * Handles /.well-known/objectstack\n */\nexport function createDiscoveryHandler(options: NextAdapterOptions) {\n return async function discoveryHandler(req: NextRequest) {\n const apiPath = options.prefix || '/api';\n const url = new URL(req.url);\n const targetUrl = new URL(apiPath, url.origin);\n return NextResponse.redirect(targetUrl);\n }\n}\n\n// ─── Server Actions ──────────────────────────────────────────────────────────\n\n/**\n * Result type for server actions\n */\nexport interface ServerActionResult<T = any> {\n success: boolean;\n data?: T;\n error?: { message: string; code: number };\n}\n\n/**\n * Creates type-safe React Server Actions for ObjectStack data operations.\n * Each action maps to a dispatcher method and can be called directly from\n * React Server Components or client components via `\"use server\"`.\n *\n * @example\n * ```ts\n * // app/actions.ts\n * \"use server\";\n * import { createServerActions } from '@objectstack/nextjs';\n * import { kernel } from '@/lib/kernel';\n *\n * const actions = createServerActions({ kernel });\n *\n * export async function getAccounts() {\n * return actions.query('account');\n * }\n *\n * export async function createAccount(formData: FormData) {\n * return actions.create('account', {\n * name: formData.get('name') as string,\n * });\n * }\n * ```\n */\nexport function createServerActions(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const emptyContext = { request: undefined };\n\n return {\n /**\n * Query records from an object\n */\n async query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'GET', {}, params || {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get a single record by ID\n */\n async getById(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'GET', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Create a new record\n */\n async create(objectName: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'POST', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Create failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Update a record by ID\n */\n async update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'PATCH', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Update failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Delete a record by ID\n */\n async remove(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'DELETE', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Delete failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get metadata for objects\n */\n async getMetadata(path?: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleMetadata(path || '', emptyContext, 'GET');\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n };\n}\n"],"mappings":"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","names":["body"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface NextAdapterOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Auth service interface with handleRequest method\n */\ninterface AuthService {\n handleRequest(request: Request): Promise<Response>;\n}\n\n/**\n * Creates a route handler for Next.js App Router\n * Handles /api/[...objectstack] pattern\n *\n * Only auth, GraphQL, storage, and discovery need explicit handling.\n * All other routes delegate to `HttpDispatcher.dispatch()` automatically.\n */\nexport function createRouteHandler(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const error = (msg: string, code: number = 500) => NextResponse.json({ success: false, error: { message: msg, code } }, { status: code });\n\n // Helper to convert DispatchResult to NextResponse\n const toResponse = (result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n return NextResponse.json(result.response.body, { \n status: result.response.status, \n headers: result.response.headers \n });\n }\n if (result.result) {\n const res = result.result;\n // Redirect\n if (res.type === 'redirect' && res.url) {\n return NextResponse.redirect(res.url);\n }\n // Stream\n if (res.type === 'stream' && res.stream) {\n return new NextResponse(res.stream, {\n status: 200,\n headers: res.headers\n });\n }\n // If it's a standard response object (like from another fetch)\n // Next.js might handle it, or we return it directly\n return res; \n }\n }\n return error('Not Found', 404);\n }\n\n return async function handler(req: NextRequest, { params }: { params: { objectstack: string[] } }) {\n const resolvedParams = await Promise.resolve(params);\n const segments = resolvedParams.objectstack || [];\n const method = req.method;\n \n // --- 0. Discovery Endpoint ---\n if (segments.length === 0 && method === 'GET') {\n return NextResponse.json({ data: await dispatcher.getDiscoveryInfo(options.prefix || '/api') });\n }\n\n try {\n const rawRequest = req;\n\n // --- 1. Auth (needs auth service integration) ---\n if (segments[0] === 'auth') {\n // Try AuthPlugin service first (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\n\n if (authService && typeof authService.handleRequest === 'function') {\n const response = await authService.handleRequest(req);\n // Convert Web Response to NextResponse\n const body = await response.text();\n const headers: Record<string, string> = {};\n response.headers.forEach((v: string, k: string) => { headers[k] = v; });\n return new NextResponse(body, { status: response.status, headers });\n }\n\n // Fallback to legacy dispatcher\n const subPath = segments.slice(1).join('/');\n const body = method === 'POST' ? await req.json().catch(() => ({})) : {};\n const result = await dispatcher.handleAuth(subPath, method, body, { request: req });\n return toResponse(result);\n }\n\n // --- 2. GraphQL (returns raw result, not HttpDispatcherResult) ---\n if (segments[0] === 'graphql' && method === 'POST') {\n const body = await req.json();\n const result = await dispatcher.handleGraphQL(body as any, { request: rawRequest } as any);\n return NextResponse.json(result);\n }\n\n // --- 3. Storage (needs formData parsing) ---\n if (segments[0] === 'storage') {\n const subPath = segments.slice(1).join('/');\n \n let file: any = undefined;\n if (method === 'POST' && subPath === 'upload') {\n const formData = await req.formData();\n file = formData.get('file');\n }\n\n const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });\n return toResponse(result);\n }\n\n // --- 4. Catch-all: delegate to dispatcher.dispatch() ---\n // Handles meta, data, packages, analytics, automation, i18n, ui,\n // openapi, custom API endpoints, and any future routes.\n const path = '/' + segments.join('/');\n\n let body: any = undefined;\n if (method === 'POST' || method === 'PUT' || method === 'PATCH') {\n body = await req.json().catch(() => ({}));\n }\n\n const url = new URL(req.url);\n const queryParams: Record<string, any> = {};\n url.searchParams.forEach((val, key) => queryParams[key] = val);\n\n const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest });\n return toResponse(result);\n\n } catch (err: any) {\n return error(err.message || 'Internal Server Error', err.statusCode || 500);\n }\n }\n}\n\n/**\n * Creates a discovery handler for Next.js App Router\n * Handles /.well-known/objectstack\n */\nexport function createDiscoveryHandler(options: NextAdapterOptions) {\n return async function discoveryHandler(req: NextRequest) {\n const apiPath = options.prefix || '/api';\n const url = new URL(req.url);\n const targetUrl = new URL(apiPath, url.origin);\n return NextResponse.redirect(targetUrl);\n }\n}\n\n// ─── Server Actions ──────────────────────────────────────────────────────────\n\n/**\n * Result type for server actions\n */\nexport interface ServerActionResult<T = any> {\n success: boolean;\n data?: T;\n error?: { message: string; code: number };\n}\n\n/**\n * Creates type-safe React Server Actions for ObjectStack data operations.\n * Each action maps to a dispatcher method and can be called directly from\n * React Server Components or client components via `\"use server\"`.\n *\n * @example\n * ```ts\n * // app/actions.ts\n * \"use server\";\n * import { createServerActions } from '@objectstack/nextjs';\n * import { kernel } from '@/lib/kernel';\n *\n * const actions = createServerActions({ kernel });\n *\n * export async function getAccounts() {\n * return actions.query('account');\n * }\n *\n * export async function createAccount(formData: FormData) {\n * return actions.create('account', {\n * name: formData.get('name') as string,\n * });\n * }\n * ```\n */\nexport function createServerActions(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const emptyContext = { request: undefined };\n\n return {\n /**\n * Query records from an object\n */\n async query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'GET', {}, params || {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get a single record by ID\n */\n async getById(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'GET', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Create a new record\n */\n async create(objectName: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'POST', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Create failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Update a record by ID\n */\n async update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'PATCH', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Update failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Delete a record by ID\n */\n async remove(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'DELETE', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Delete failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get metadata for objects\n */\n async getMetadata(path?: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleMetadata(path || '', emptyContext, 'GET');\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n };\n}\n"],"mappings":"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","names":["body","result"]}
package/dist/index.mjs CHANGED
@@ -50,43 +50,22 @@ function createRouteHandler(options) {
50
50
  }
51
51
  if (authService && typeof authService.handleRequest === "function") {
52
52
  const response = await authService.handleRequest(req);
53
- const body2 = await response.text();
53
+ const body3 = await response.text();
54
54
  const headers = {};
55
55
  response.headers.forEach((v, k) => {
56
56
  headers[k] = v;
57
57
  });
58
- return new NextResponse(body2, { status: response.status, headers });
58
+ return new NextResponse(body3, { status: response.status, headers });
59
59
  }
60
60
  const subPath = segments.slice(1).join("/");
61
- const body = method === "POST" ? await req.json().catch(() => ({})) : {};
62
- const result = await dispatcher.handleAuth(subPath, method, body, { request: req });
63
- return toResponse(result);
61
+ const body2 = method === "POST" ? await req.json().catch(() => ({})) : {};
62
+ const result2 = await dispatcher.handleAuth(subPath, method, body2, { request: req });
63
+ return toResponse(result2);
64
64
  }
65
65
  if (segments[0] === "graphql" && method === "POST") {
66
- const body = await req.json();
67
- const result = await dispatcher.handleGraphQL(body, { request: rawRequest });
68
- return NextResponse.json(result);
69
- }
70
- if (segments[0] === "meta") {
71
- const subPath = segments.slice(1).join("/");
72
- let body = void 0;
73
- if (method === "PUT" || method === "POST") {
74
- body = await req.json().catch(() => ({}));
75
- }
76
- const result = await dispatcher.handleMetadata(subPath, { request: rawRequest }, method, body);
77
- return toResponse(result);
78
- }
79
- if (segments[0] === "data") {
80
- const subPath = segments.slice(1).join("/");
81
- let body = {};
82
- if (method === "POST" || method === "PATCH") {
83
- body = await req.json().catch(() => ({}));
84
- }
85
- const url = new URL(req.url);
86
- const queryParams = {};
87
- url.searchParams.forEach((val, key) => queryParams[key] = val);
88
- const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: rawRequest });
89
- return toResponse(result);
66
+ const body2 = await req.json();
67
+ const result2 = await dispatcher.handleGraphQL(body2, { request: rawRequest });
68
+ return NextResponse.json(result2);
90
69
  }
91
70
  if (segments[0] === "storage") {
92
71
  const subPath = segments.slice(1).join("/");
@@ -95,10 +74,19 @@ function createRouteHandler(options) {
95
74
  const formData = await req.formData();
96
75
  file = formData.get("file");
97
76
  }
98
- const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });
99
- return toResponse(result);
77
+ const result2 = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });
78
+ return toResponse(result2);
79
+ }
80
+ const path = "/" + segments.join("/");
81
+ let body = void 0;
82
+ if (method === "POST" || method === "PUT" || method === "PATCH") {
83
+ body = await req.json().catch(() => ({}));
100
84
  }
101
- return error("Not Found", 404);
85
+ const url = new URL(req.url);
86
+ const queryParams = {};
87
+ url.searchParams.forEach((val, key) => queryParams[key] = val);
88
+ const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest });
89
+ return toResponse(result);
102
90
  } catch (err) {
103
91
  return error(err.message || "Internal Server Error", err.statusCode || 500);
104
92
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface NextAdapterOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Auth service interface with handleRequest method\n */\ninterface AuthService {\n handleRequest(request: Request): Promise<Response>;\n}\n\n/**\n * Creates a route handler for Next.js App Router\n * Handles /api/[...objectstack] pattern\n */\nexport function createRouteHandler(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const error = (msg: string, code: number = 500) => NextResponse.json({ success: false, error: { message: msg, code } }, { status: code });\n\n // Helper to convert DispatchResult to NextResponse\n const toResponse = (result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n return NextResponse.json(result.response.body, { \n status: result.response.status, \n headers: result.response.headers \n });\n }\n if (result.result) {\n const res = result.result;\n // Redirect\n if (res.type === 'redirect' && res.url) {\n return NextResponse.redirect(res.url);\n }\n // Stream\n if (res.type === 'stream' && res.stream) {\n return new NextResponse(res.stream, {\n status: 200,\n headers: res.headers\n });\n }\n // If it's a standard response object (like from another fetch)\n // Next.js might handle it, or we return it directly\n return res; \n }\n }\n return error('Not Found', 404);\n }\n\n return async function handler(req: NextRequest, { params }: { params: { objectstack: string[] } }) {\n const resolvedParams = await Promise.resolve(params);\n const segments = resolvedParams.objectstack || [];\n const method = req.method;\n \n // --- 0. Discovery Endpoint ---\n if (segments.length === 0 && method === 'GET') {\n return NextResponse.json({ data: await dispatcher.getDiscoveryInfo(options.prefix || '/api') });\n }\n\n try {\n const rawRequest = req;\n\n // --- 1. Auth ---\n if (segments[0] === 'auth') {\n // Try AuthPlugin service first (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\n\n if (authService && typeof authService.handleRequest === 'function') {\n const response = await authService.handleRequest(req);\n // Convert Web Response to NextResponse\n const body = await response.text();\n const headers: Record<string, string> = {};\n response.headers.forEach((v: string, k: string) => { headers[k] = v; });\n return new NextResponse(body, { status: response.status, headers });\n }\n\n // Fallback to legacy dispatcher\n const subPath = segments.slice(1).join('/');\n const body = method === 'POST' ? await req.json().catch(() => ({})) : {};\n const result = await dispatcher.handleAuth(subPath, method, body, { request: req });\n return toResponse(result);\n }\n\n // --- 2. GraphQL ---\n if (segments[0] === 'graphql' && method === 'POST') {\n const body = await req.json();\n const result = await dispatcher.handleGraphQL(body as any, { request: rawRequest } as any);\n return NextResponse.json(result);\n }\n\n // --- 3. Metadata ---\n if (segments[0] === 'meta') {\n const subPath = segments.slice(1).join('/');\n \n let body: any = undefined;\n if (method === 'PUT' || method === 'POST') {\n body = await req.json().catch(() => ({}));\n }\n\n const result = await dispatcher.handleMetadata(subPath, { request: rawRequest }, method, body);\n return toResponse(result);\n }\n\n // --- 4. Data ---\n if (segments[0] === 'data') {\n const subPath = segments.slice(1).join('/');\n let body: any = {};\n if (method === 'POST' || method === 'PATCH') {\n body = await req.json().catch(() => ({}));\n }\n \n // Extract query params\n const url = new URL(req.url);\n const queryParams: Record<string, any> = {};\n url.searchParams.forEach((val, key) => queryParams[key] = val);\n\n const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: rawRequest } as any);\n return toResponse(result);\n }\n\n // --- 5. Storage ---\n if (segments[0] === 'storage') {\n const subPath = segments.slice(1).join('/');\n \n let file: any = undefined;\n if (method === 'POST' && subPath === 'upload') {\n const formData = await req.formData();\n file = formData.get('file');\n }\n\n const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });\n return toResponse(result);\n }\n \n return error('Not Found', 404);\n\n } catch (err: any) {\n return error(err.message || 'Internal Server Error', err.statusCode || 500);\n }\n }\n}\n\n/**\n * Creates a discovery handler for Next.js App Router\n * Handles /.well-known/objectstack\n */\nexport function createDiscoveryHandler(options: NextAdapterOptions) {\n return async function discoveryHandler(req: NextRequest) {\n const apiPath = options.prefix || '/api';\n const url = new URL(req.url);\n const targetUrl = new URL(apiPath, url.origin);\n return NextResponse.redirect(targetUrl);\n }\n}\n\n// ─── Server Actions ──────────────────────────────────────────────────────────\n\n/**\n * Result type for server actions\n */\nexport interface ServerActionResult<T = any> {\n success: boolean;\n data?: T;\n error?: { message: string; code: number };\n}\n\n/**\n * Creates type-safe React Server Actions for ObjectStack data operations.\n * Each action maps to a dispatcher method and can be called directly from\n * React Server Components or client components via `\"use server\"`.\n *\n * @example\n * ```ts\n * // app/actions.ts\n * \"use server\";\n * import { createServerActions } from '@objectstack/nextjs';\n * import { kernel } from '@/lib/kernel';\n *\n * const actions = createServerActions({ kernel });\n *\n * export async function getAccounts() {\n * return actions.query('account');\n * }\n *\n * export async function createAccount(formData: FormData) {\n * return actions.create('account', {\n * name: formData.get('name') as string,\n * });\n * }\n * ```\n */\nexport function createServerActions(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const emptyContext = { request: undefined };\n\n return {\n /**\n * Query records from an object\n */\n async query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'GET', {}, params || {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get a single record by ID\n */\n async getById(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'GET', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Create a new record\n */\n async create(objectName: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'POST', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Create failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Update a record by ID\n */\n async update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'PATCH', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Update failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Delete a record by ID\n */\n async remove(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'DELETE', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Delete failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get metadata for objects\n */\n async getMetadata(path?: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleMetadata(path || '', emptyContext, 'GET');\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n };\n}\n"],"mappings":"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","names":["body"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';\n\nexport interface NextAdapterOptions {\n kernel: ObjectKernel;\n prefix?: string;\n}\n\n/**\n * Auth service interface with handleRequest method\n */\ninterface AuthService {\n handleRequest(request: Request): Promise<Response>;\n}\n\n/**\n * Creates a route handler for Next.js App Router\n * Handles /api/[...objectstack] pattern\n *\n * Only auth, GraphQL, storage, and discovery need explicit handling.\n * All other routes delegate to `HttpDispatcher.dispatch()` automatically.\n */\nexport function createRouteHandler(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const error = (msg: string, code: number = 500) => NextResponse.json({ success: false, error: { message: msg, code } }, { status: code });\n\n // Helper to convert DispatchResult to NextResponse\n const toResponse = (result: HttpDispatcherResult) => {\n if (result.handled) {\n if (result.response) {\n return NextResponse.json(result.response.body, { \n status: result.response.status, \n headers: result.response.headers \n });\n }\n if (result.result) {\n const res = result.result;\n // Redirect\n if (res.type === 'redirect' && res.url) {\n return NextResponse.redirect(res.url);\n }\n // Stream\n if (res.type === 'stream' && res.stream) {\n return new NextResponse(res.stream, {\n status: 200,\n headers: res.headers\n });\n }\n // If it's a standard response object (like from another fetch)\n // Next.js might handle it, or we return it directly\n return res; \n }\n }\n return error('Not Found', 404);\n }\n\n return async function handler(req: NextRequest, { params }: { params: { objectstack: string[] } }) {\n const resolvedParams = await Promise.resolve(params);\n const segments = resolvedParams.objectstack || [];\n const method = req.method;\n \n // --- 0. Discovery Endpoint ---\n if (segments.length === 0 && method === 'GET') {\n return NextResponse.json({ data: await dispatcher.getDiscoveryInfo(options.prefix || '/api') });\n }\n\n try {\n const rawRequest = req;\n\n // --- 1. Auth (needs auth service integration) ---\n if (segments[0] === 'auth') {\n // Try AuthPlugin service first (prefer async to support factory-based services)\n let authService: AuthService | null = null;\n try {\n if (typeof options.kernel.getServiceAsync === 'function') {\n authService = await options.kernel.getServiceAsync<AuthService>('auth');\n } else if (typeof options.kernel.getService === 'function') {\n authService = options.kernel.getService<AuthService>('auth');\n }\n } catch {\n // Service not registered — fall through to dispatcher\n authService = null;\n }\n\n if (authService && typeof authService.handleRequest === 'function') {\n const response = await authService.handleRequest(req);\n // Convert Web Response to NextResponse\n const body = await response.text();\n const headers: Record<string, string> = {};\n response.headers.forEach((v: string, k: string) => { headers[k] = v; });\n return new NextResponse(body, { status: response.status, headers });\n }\n\n // Fallback to legacy dispatcher\n const subPath = segments.slice(1).join('/');\n const body = method === 'POST' ? await req.json().catch(() => ({})) : {};\n const result = await dispatcher.handleAuth(subPath, method, body, { request: req });\n return toResponse(result);\n }\n\n // --- 2. GraphQL (returns raw result, not HttpDispatcherResult) ---\n if (segments[0] === 'graphql' && method === 'POST') {\n const body = await req.json();\n const result = await dispatcher.handleGraphQL(body as any, { request: rawRequest } as any);\n return NextResponse.json(result);\n }\n\n // --- 3. Storage (needs formData parsing) ---\n if (segments[0] === 'storage') {\n const subPath = segments.slice(1).join('/');\n \n let file: any = undefined;\n if (method === 'POST' && subPath === 'upload') {\n const formData = await req.formData();\n file = formData.get('file');\n }\n\n const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });\n return toResponse(result);\n }\n\n // --- 4. Catch-all: delegate to dispatcher.dispatch() ---\n // Handles meta, data, packages, analytics, automation, i18n, ui,\n // openapi, custom API endpoints, and any future routes.\n const path = '/' + segments.join('/');\n\n let body: any = undefined;\n if (method === 'POST' || method === 'PUT' || method === 'PATCH') {\n body = await req.json().catch(() => ({}));\n }\n\n const url = new URL(req.url);\n const queryParams: Record<string, any> = {};\n url.searchParams.forEach((val, key) => queryParams[key] = val);\n\n const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest });\n return toResponse(result);\n\n } catch (err: any) {\n return error(err.message || 'Internal Server Error', err.statusCode || 500);\n }\n }\n}\n\n/**\n * Creates a discovery handler for Next.js App Router\n * Handles /.well-known/objectstack\n */\nexport function createDiscoveryHandler(options: NextAdapterOptions) {\n return async function discoveryHandler(req: NextRequest) {\n const apiPath = options.prefix || '/api';\n const url = new URL(req.url);\n const targetUrl = new URL(apiPath, url.origin);\n return NextResponse.redirect(targetUrl);\n }\n}\n\n// ─── Server Actions ──────────────────────────────────────────────────────────\n\n/**\n * Result type for server actions\n */\nexport interface ServerActionResult<T = any> {\n success: boolean;\n data?: T;\n error?: { message: string; code: number };\n}\n\n/**\n * Creates type-safe React Server Actions for ObjectStack data operations.\n * Each action maps to a dispatcher method and can be called directly from\n * React Server Components or client components via `\"use server\"`.\n *\n * @example\n * ```ts\n * // app/actions.ts\n * \"use server\";\n * import { createServerActions } from '@objectstack/nextjs';\n * import { kernel } from '@/lib/kernel';\n *\n * const actions = createServerActions({ kernel });\n *\n * export async function getAccounts() {\n * return actions.query('account');\n * }\n *\n * export async function createAccount(formData: FormData) {\n * return actions.create('account', {\n * name: formData.get('name') as string,\n * });\n * }\n * ```\n */\nexport function createServerActions(options: NextAdapterOptions) {\n const dispatcher = new HttpDispatcher(options.kernel);\n const emptyContext = { request: undefined };\n\n return {\n /**\n * Query records from an object\n */\n async query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'GET', {}, params || {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get a single record by ID\n */\n async getById(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'GET', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Create a new record\n */\n async create(objectName: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}`, 'POST', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Create failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Update a record by ID\n */\n async update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'PATCH', data, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Update failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Delete a record by ID\n */\n async remove(objectName: string, id: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleData(`/${objectName}/${id}`, 'DELETE', {}, {}, emptyContext);\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Delete failed', code: 500 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n\n /**\n * Get metadata for objects\n */\n async getMetadata(path?: string): Promise<ServerActionResult> {\n try {\n const result = await dispatcher.handleMetadata(path || '', emptyContext, 'GET');\n if (result.handled && result.response) {\n return { success: true, data: result.response.body };\n }\n return { success: false, error: { message: 'Not found', code: 404 } };\n } catch (err: any) {\n return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };\n }\n },\n };\n}\n"],"mappings":"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","names":["body","result"]}
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@objectstack/nextjs",
3
- "version": "3.2.7",
3
+ "version": "3.2.9",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "peerDependencies": {
8
- "next": "^16.1.6",
8
+ "next": "^16.1.7",
9
9
  "react": "^19.2.4",
10
10
  "react-dom": "^19.2.4",
11
- "@objectstack/runtime": "^3.2.7"
11
+ "@objectstack/runtime": "^3.2.9"
12
12
  },
13
13
  "devDependencies": {
14
- "next": "^16.1.6",
14
+ "next": "^16.1.7",
15
15
  "react": "^19.2.4",
16
16
  "react-dom": "^19.2.4",
17
17
  "typescript": "^5.0.0",
18
- "vitest": "^4.0.18",
19
- "@objectstack/runtime": "3.2.7"
18
+ "vitest": "^4.1.0",
19
+ "@objectstack/runtime": "3.2.9"
20
20
  },
21
21
  "scripts": {
22
22
  "build": "tsup --config ../../../tsup.config.ts",
package/src/index.ts CHANGED
@@ -18,6 +18,9 @@ interface AuthService {
18
18
  /**
19
19
  * Creates a route handler for Next.js App Router
20
20
  * Handles /api/[...objectstack] pattern
21
+ *
22
+ * Only auth, GraphQL, storage, and discovery need explicit handling.
23
+ * All other routes delegate to `HttpDispatcher.dispatch()` automatically.
21
24
  */
22
25
  export function createRouteHandler(options: NextAdapterOptions) {
23
26
  const dispatcher = new HttpDispatcher(options.kernel);
@@ -66,7 +69,7 @@ export function createRouteHandler(options: NextAdapterOptions) {
66
69
  try {
67
70
  const rawRequest = req;
68
71
 
69
- // --- 1. Auth ---
72
+ // --- 1. Auth (needs auth service integration) ---
70
73
  if (segments[0] === 'auth') {
71
74
  // Try AuthPlugin service first (prefer async to support factory-based services)
72
75
  let authService: AuthService | null = null;
@@ -97,44 +100,14 @@ export function createRouteHandler(options: NextAdapterOptions) {
97
100
  return toResponse(result);
98
101
  }
99
102
 
100
- // --- 2. GraphQL ---
103
+ // --- 2. GraphQL (returns raw result, not HttpDispatcherResult) ---
101
104
  if (segments[0] === 'graphql' && method === 'POST') {
102
105
  const body = await req.json();
103
106
  const result = await dispatcher.handleGraphQL(body as any, { request: rawRequest } as any);
104
107
  return NextResponse.json(result);
105
108
  }
106
109
 
107
- // --- 3. Metadata ---
108
- if (segments[0] === 'meta') {
109
- const subPath = segments.slice(1).join('/');
110
-
111
- let body: any = undefined;
112
- if (method === 'PUT' || method === 'POST') {
113
- body = await req.json().catch(() => ({}));
114
- }
115
-
116
- const result = await dispatcher.handleMetadata(subPath, { request: rawRequest }, method, body);
117
- return toResponse(result);
118
- }
119
-
120
- // --- 4. Data ---
121
- if (segments[0] === 'data') {
122
- const subPath = segments.slice(1).join('/');
123
- let body: any = {};
124
- if (method === 'POST' || method === 'PATCH') {
125
- body = await req.json().catch(() => ({}));
126
- }
127
-
128
- // Extract query params
129
- const url = new URL(req.url);
130
- const queryParams: Record<string, any> = {};
131
- url.searchParams.forEach((val, key) => queryParams[key] = val);
132
-
133
- const result = await dispatcher.handleData(subPath, method, body, queryParams, { request: rawRequest } as any);
134
- return toResponse(result);
135
- }
136
-
137
- // --- 5. Storage ---
110
+ // --- 3. Storage (needs formData parsing) ---
138
111
  if (segments[0] === 'storage') {
139
112
  const subPath = segments.slice(1).join('/');
140
113
 
@@ -147,8 +120,23 @@ export function createRouteHandler(options: NextAdapterOptions) {
147
120
  const result = await dispatcher.handleStorage(subPath, method, file, { request: rawRequest });
148
121
  return toResponse(result);
149
122
  }
150
-
151
- return error('Not Found', 404);
123
+
124
+ // --- 4. Catch-all: delegate to dispatcher.dispatch() ---
125
+ // Handles meta, data, packages, analytics, automation, i18n, ui,
126
+ // openapi, custom API endpoints, and any future routes.
127
+ const path = '/' + segments.join('/');
128
+
129
+ let body: any = undefined;
130
+ if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
131
+ body = await req.json().catch(() => ({}));
132
+ }
133
+
134
+ const url = new URL(req.url);
135
+ const queryParams: Record<string, any> = {};
136
+ url.searchParams.forEach((val, key) => queryParams[key] = val);
137
+
138
+ const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest });
139
+ return toResponse(result);
152
140
 
153
141
  } catch (err: any) {
154
142
  return error(err.message || 'Internal Server Error', err.statusCode || 500);
@@ -19,6 +19,7 @@ const mockDispatcher = {
19
19
  handleMetadata: vi.fn().mockResolvedValue({ handled: true, response: { body: { success: true }, status: 200 } }),
20
20
  handleData: vi.fn().mockResolvedValue({ handled: true, response: { body: { records: [] }, status: 200 } }),
21
21
  handleStorage: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
22
+ dispatch: vi.fn().mockResolvedValue({ handled: true, response: { body: { success: true }, status: 200 } }),
22
23
  };
23
24
 
24
25
  vi.mock('@objectstack/runtime', () => {
@@ -106,8 +107,8 @@ describe('Next.js Metadata API Integration Tests', () => {
106
107
 
107
108
  describe('CRUD Operations', () => {
108
109
  describe('GET meta/objects — List all objects', () => {
109
- it('dispatches to handleMetadata with correct path', async () => {
110
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
110
+ it('dispatches to dispatch with correct path', async () => {
111
+ mockDispatcher.dispatch.mockResolvedValueOnce({
111
112
  handled: true,
112
113
  response: {
113
114
  body: {
@@ -125,7 +126,7 @@ describe('Next.js Metadata API Integration Tests', () => {
125
126
  const res = await handler(req, { params: { objectstack: ['meta', 'objects'] } });
126
127
  expect(res.status).toBe(200);
127
128
  expect(res.body.data).toHaveLength(2);
128
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
129
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
129
130
  'objects',
130
131
  expect.objectContaining({ request: expect.anything() }),
131
132
  'GET',
@@ -135,8 +136,8 @@ describe('Next.js Metadata API Integration Tests', () => {
135
136
  });
136
137
 
137
138
  describe('GET meta/objects/account — Get single object', () => {
138
- it('dispatches to handleMetadata with item-level path', async () => {
139
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
139
+ it('dispatches to dispatch with item-level path', async () => {
140
+ mockDispatcher.dispatch.mockResolvedValueOnce({
140
141
  handled: true,
141
142
  response: {
142
143
  body: {
@@ -151,7 +152,7 @@ describe('Next.js Metadata API Integration Tests', () => {
151
152
  const res = await handler(req, { params: { objectstack: ['meta', 'objects', 'account'] } });
152
153
  expect(res.status).toBe(200);
153
154
  expect(res.body.data.name).toBe('account');
154
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
155
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
155
156
  'objects/account',
156
157
  expect.objectContaining({ request: expect.anything() }),
157
158
  'GET',
@@ -168,7 +169,7 @@ describe('Next.js Metadata API Integration Tests', () => {
168
169
  data: { label: 'Project Task', fields: {} },
169
170
  };
170
171
 
171
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
172
+ mockDispatcher.dispatch.mockResolvedValueOnce({
172
173
  handled: true,
173
174
  response: { body: { success: true }, status: 201 },
174
175
  });
@@ -176,7 +177,7 @@ describe('Next.js Metadata API Integration Tests', () => {
176
177
  const req = makeReq('http://localhost/api/meta/objects', 'POST', body);
177
178
  const res = await handler(req, { params: { objectstack: ['meta', 'objects'] } });
178
179
  expect(res.status).toBe(201);
179
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
180
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
180
181
  'objects',
181
182
  expect.objectContaining({ request: expect.anything() }),
182
183
  'POST',
@@ -189,7 +190,7 @@ describe('Next.js Metadata API Integration Tests', () => {
189
190
  it('dispatches PUT with JSON body', async () => {
190
191
  const body = { label: 'Updated Account' };
191
192
 
192
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
193
+ mockDispatcher.dispatch.mockResolvedValueOnce({
193
194
  handled: true,
194
195
  response: { body: { success: true }, status: 200 },
195
196
  });
@@ -197,7 +198,7 @@ describe('Next.js Metadata API Integration Tests', () => {
197
198
  const req = makeReq('http://localhost/api/meta/objects/account', 'PUT', body);
198
199
  const res = await handler(req, { params: { objectstack: ['meta', 'objects', 'account'] } });
199
200
  expect(res.status).toBe(200);
200
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
201
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
201
202
  'objects/account',
202
203
  expect.objectContaining({ request: expect.anything() }),
203
204
  'PUT',
@@ -207,8 +208,8 @@ describe('Next.js Metadata API Integration Tests', () => {
207
208
  });
208
209
 
209
210
  describe('DELETE meta/objects/old_entity — Delete metadata', () => {
210
- it('dispatches DELETE to handleMetadata', async () => {
211
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
211
+ it('dispatches DELETE to dispatch', async () => {
212
+ mockDispatcher.dispatch.mockResolvedValueOnce({
212
213
  handled: true,
213
214
  response: {
214
215
  body: { success: true, data: { type: 'object', name: 'old_entity' } },
@@ -227,7 +228,7 @@ describe('Next.js Metadata API Integration Tests', () => {
227
228
  it('dispatches for views', async () => {
228
229
  const req = makeReq('http://localhost/api/meta/views');
229
230
  await handler(req, { params: { objectstack: ['meta', 'views'] } });
230
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
231
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
231
232
  'views',
232
233
  expect.objectContaining({ request: expect.anything() }),
233
234
  'GET',
@@ -238,7 +239,7 @@ describe('Next.js Metadata API Integration Tests', () => {
238
239
  it('dispatches for flows', async () => {
239
240
  const req = makeReq('http://localhost/api/meta/flows');
240
241
  await handler(req, { params: { objectstack: ['meta', 'flows'] } });
241
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
242
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
242
243
  'flows',
243
244
  expect.objectContaining({ request: expect.anything() }),
244
245
  'GET',
@@ -249,7 +250,7 @@ describe('Next.js Metadata API Integration Tests', () => {
249
250
  it('dispatches for agents', async () => {
250
251
  const req = makeReq('http://localhost/api/meta/agents');
251
252
  await handler(req, { params: { objectstack: ['meta', 'agents'] } });
252
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
253
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
253
254
  'agents',
254
255
  expect.objectContaining({ request: expect.anything() }),
255
256
  'GET',
@@ -273,7 +274,7 @@ describe('Next.js Metadata API Integration Tests', () => {
273
274
  pageSize: 25,
274
275
  };
275
276
 
276
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
277
+ mockDispatcher.dispatch.mockResolvedValueOnce({
277
278
  handled: true,
278
279
  response: {
279
280
  body: {
@@ -305,7 +306,7 @@ describe('Next.js Metadata API Integration Tests', () => {
305
306
  describe('Bulk Operations', () => {
306
307
  describe('POST meta/bulk/register — Bulk register', () => {
307
308
  it('dispatches bulk register', async () => {
308
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
309
+ mockDispatcher.dispatch.mockResolvedValueOnce({
309
310
  handled: true,
310
311
  response: {
311
312
  body: { success: true, data: { total: 2, succeeded: 2, failed: 0 } },
@@ -328,7 +329,7 @@ describe('Next.js Metadata API Integration Tests', () => {
328
329
 
329
330
  describe('POST meta/bulk/unregister — Bulk unregister', () => {
330
331
  it('dispatches bulk unregister', async () => {
331
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
332
+ mockDispatcher.dispatch.mockResolvedValueOnce({
332
333
  handled: true,
333
334
  response: {
334
335
  body: { success: true, data: { total: 2, succeeded: 2, failed: 0 } },
@@ -348,7 +349,7 @@ describe('Next.js Metadata API Integration Tests', () => {
348
349
 
349
350
  describe('Bulk operation with partial failures', () => {
350
351
  it('returns error details', async () => {
351
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
352
+ mockDispatcher.dispatch.mockResolvedValueOnce({
352
353
  handled: true,
353
354
  response: {
354
355
  body: {
@@ -387,7 +388,7 @@ describe('Next.js Metadata API Integration Tests', () => {
387
388
  describe('Overlay / Customization', () => {
388
389
  describe('GET meta/objects/account/overlay — Get overlay', () => {
389
390
  it('dispatches overlay retrieval', async () => {
390
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
391
+ mockDispatcher.dispatch.mockResolvedValueOnce({
391
392
  handled: true,
392
393
  response: {
393
394
  body: {
@@ -413,7 +414,7 @@ describe('Next.js Metadata API Integration Tests', () => {
413
414
 
414
415
  describe('PUT meta/objects/account/overlay — Save overlay', () => {
415
416
  it('dispatches overlay save', async () => {
416
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
417
+ mockDispatcher.dispatch.mockResolvedValueOnce({
417
418
  handled: true,
418
419
  response: { body: { success: true }, status: 200 },
419
420
  });
@@ -431,7 +432,7 @@ describe('Next.js Metadata API Integration Tests', () => {
431
432
 
432
433
  describe('GET meta/objects/account/effective — Get effective metadata', () => {
433
434
  it('dispatches effective metadata retrieval', async () => {
434
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
435
+ mockDispatcher.dispatch.mockResolvedValueOnce({
435
436
  handled: true,
436
437
  response: {
437
438
  body: {
@@ -457,7 +458,7 @@ describe('Next.js Metadata API Integration Tests', () => {
457
458
  describe('Import / Export', () => {
458
459
  describe('POST meta/export — Export metadata', () => {
459
460
  it('dispatches export request', async () => {
460
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
461
+ mockDispatcher.dispatch.mockResolvedValueOnce({
461
462
  handled: true,
462
463
  response: {
463
464
  body: { success: true, data: { version: '1.0', objects: {} } },
@@ -474,7 +475,7 @@ describe('Next.js Metadata API Integration Tests', () => {
474
475
 
475
476
  describe('POST meta/import — Import metadata', () => {
476
477
  it('dispatches import request', async () => {
477
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
478
+ mockDispatcher.dispatch.mockResolvedValueOnce({
478
479
  handled: true,
479
480
  response: {
480
481
  body: { success: true, data: { total: 3, imported: 3, skipped: 0, failed: 0 } },
@@ -500,7 +501,7 @@ describe('Next.js Metadata API Integration Tests', () => {
500
501
  describe('Validation', () => {
501
502
  describe('POST meta/validate — Validate metadata', () => {
502
503
  it('dispatches validation', async () => {
503
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
504
+ mockDispatcher.dispatch.mockResolvedValueOnce({
504
505
  handled: true,
505
506
  response: {
506
507
  body: { success: true, data: { valid: true } },
@@ -515,7 +516,7 @@ describe('Next.js Metadata API Integration Tests', () => {
515
516
  });
516
517
 
517
518
  it('returns errors for invalid metadata', async () => {
518
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
519
+ mockDispatcher.dispatch.mockResolvedValueOnce({
519
520
  handled: true,
520
521
  response: {
521
522
  body: {
@@ -544,7 +545,7 @@ describe('Next.js Metadata API Integration Tests', () => {
544
545
  describe('Type Registry', () => {
545
546
  describe('GET meta/types — List types', () => {
546
547
  it('returns all registered types', async () => {
547
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
548
+ mockDispatcher.dispatch.mockResolvedValueOnce({
548
549
  handled: true,
549
550
  response: {
550
551
  body: { success: true, data: ['object', 'view', 'flow', 'agent'] },
@@ -561,7 +562,7 @@ describe('Next.js Metadata API Integration Tests', () => {
561
562
 
562
563
  describe('GET meta/types/object — Get type info', () => {
563
564
  it('returns type metadata', async () => {
564
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
565
+ mockDispatcher.dispatch.mockResolvedValueOnce({
565
566
  handled: true,
566
567
  response: {
567
568
  body: {
@@ -593,7 +594,7 @@ describe('Next.js Metadata API Integration Tests', () => {
593
594
  describe('Dependency Tracking', () => {
594
595
  describe('GET meta/objects/account/dependencies — Get dependencies', () => {
595
596
  it('returns dependencies', async () => {
596
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
597
+ mockDispatcher.dispatch.mockResolvedValueOnce({
597
598
  handled: true,
598
599
  response: {
599
600
  body: {
@@ -619,7 +620,7 @@ describe('Next.js Metadata API Integration Tests', () => {
619
620
 
620
621
  describe('GET meta/objects/account/dependents — Get dependents', () => {
621
622
  it('returns dependents', async () => {
622
- mockDispatcher.handleMetadata.mockResolvedValueOnce({
623
+ mockDispatcher.dispatch.mockResolvedValueOnce({
623
624
  handled: true,
624
625
  response: {
625
626
  body: {
@@ -647,7 +648,7 @@ describe('Next.js Metadata API Integration Tests', () => {
647
648
 
648
649
  describe('Error Handling', () => {
649
650
  it('returns 404 when metadata not found', async () => {
650
- mockDispatcher.handleMetadata.mockResolvedValueOnce({ handled: false });
651
+ mockDispatcher.dispatch.mockResolvedValueOnce({ handled: false });
651
652
 
652
653
  const req = makeReq('http://localhost/api/meta/objects/nonexistent');
653
654
  const res = await handler(req, { params: { objectstack: ['meta', 'objects', 'nonexistent'] } });
@@ -655,7 +656,7 @@ describe('Next.js Metadata API Integration Tests', () => {
655
656
  });
656
657
 
657
658
  it('returns 500 on dispatcher exception', async () => {
658
- mockDispatcher.handleMetadata.mockRejectedValueOnce(new Error('Internal error'));
659
+ mockDispatcher.dispatch.mockRejectedValueOnce(new Error('Internal error'));
659
660
 
660
661
  const req = makeReq('http://localhost/api/meta/objects');
661
662
  const res = await handler(req, { params: { objectstack: ['meta', 'objects'] } });
@@ -664,7 +665,7 @@ describe('Next.js Metadata API Integration Tests', () => {
664
665
  });
665
666
 
666
667
  it('returns custom status code from error', async () => {
667
- mockDispatcher.handleMetadata.mockRejectedValueOnce(
668
+ mockDispatcher.dispatch.mockRejectedValueOnce(
668
669
  Object.assign(new Error('Forbidden'), { statusCode: 403 }),
669
670
  );
670
671
 
@@ -682,7 +683,7 @@ describe('Next.js Metadata API Integration Tests', () => {
682
683
  it('correctly joins nested segments', async () => {
683
684
  const req = makeReq('http://localhost/api/meta/objects/account/fields/name');
684
685
  await handler(req, { params: { objectstack: ['meta', 'objects', 'account', 'fields', 'name'] } });
685
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
686
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
686
687
  'objects/account/fields/name',
687
688
  expect.any(Object),
688
689
  'GET',
@@ -694,7 +695,7 @@ describe('Next.js Metadata API Integration Tests', () => {
694
695
  const req = makeReq('http://localhost/api/meta');
695
696
  // With just ['meta'], subPath becomes empty after slice(1)
696
697
  await handler(req, { params: { objectstack: ['meta'] } });
697
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
698
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
698
699
  '',
699
700
  expect.any(Object),
700
701
  'GET',
@@ -10,6 +10,7 @@ const mockDispatcher = {
10
10
  handleMetadata: vi.fn().mockResolvedValue({ handled: true, response: { body: { objects: [] }, status: 200 } }),
11
11
  handleData: vi.fn().mockResolvedValue({ handled: true, response: { body: { records: [] }, status: 200 } }),
12
12
  handleStorage: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
13
+ dispatch: vi.fn().mockResolvedValue({ handled: true, response: { body: { success: true }, status: 200 } }),
13
14
  };
14
15
 
15
16
  vi.mock('@objectstack/runtime', () => {
@@ -272,17 +273,17 @@ describe('createRouteHandler', () => {
272
273
  });
273
274
 
274
275
  describe('Metadata Endpoint', () => {
275
- it('GET meta/objects calls handleMetadata', async () => {
276
+ it('GET meta/objects delegates to dispatch()', async () => {
276
277
  const handler = createRouteHandler({ kernel: mockKernel });
277
278
  const req = makeReq('http://localhost/api/meta/objects', 'GET');
278
279
  const res = await handler(req, { params: { objectstack: ['meta', 'objects'] } });
279
280
  expect(res.status).toBe(200);
280
- expect(res.body).toEqual({ objects: [] });
281
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
282
- 'objects',
283
- expect.objectContaining({ request: expect.anything() }),
281
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
284
282
  'GET',
283
+ '/meta/objects',
285
284
  undefined,
285
+ expect.any(Object),
286
+ expect.objectContaining({ request: expect.anything() }),
286
287
  );
287
288
  });
288
289
 
@@ -292,11 +293,12 @@ describe('createRouteHandler', () => {
292
293
  const req = makeReq('http://localhost/api/meta/objects', 'PUT', body);
293
294
  const res = await handler(req, { params: { objectstack: ['meta', 'objects'] } });
294
295
  expect(res.status).toBe(200);
295
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
296
- 'objects',
297
- expect.objectContaining({ request: expect.anything() }),
296
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
298
297
  'PUT',
298
+ '/meta/objects',
299
299
  body,
300
+ expect.any(Object),
301
+ expect.objectContaining({ request: expect.anything() }),
300
302
  );
301
303
  });
302
304
 
@@ -306,26 +308,26 @@ describe('createRouteHandler', () => {
306
308
  const req = makeReq('http://localhost/api/meta/objects', 'POST', body);
307
309
  const res = await handler(req, { params: { objectstack: ['meta', 'objects'] } });
308
310
  expect(res.status).toBe(200);
309
- expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
310
- 'objects',
311
- expect.objectContaining({ request: expect.anything() }),
311
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
312
312
  'POST',
313
+ '/meta/objects',
313
314
  body,
315
+ expect.any(Object),
316
+ expect.objectContaining({ request: expect.anything() }),
314
317
  );
315
318
  });
316
319
  });
317
320
 
318
321
  describe('Data Endpoint', () => {
319
- it('GET data/account calls handleData', async () => {
322
+ it('GET data/account delegates to dispatch()', async () => {
320
323
  const handler = createRouteHandler({ kernel: mockKernel });
321
324
  const req = makeReq('http://localhost/api/data/account', 'GET');
322
325
  const res = await handler(req, { params: { objectstack: ['data', 'account'] } });
323
326
  expect(res.status).toBe(200);
324
- expect(res.body).toEqual({ records: [] });
325
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
326
- 'account',
327
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
327
328
  'GET',
328
- {},
329
+ '/data/account',
330
+ undefined,
329
331
  expect.any(Object),
330
332
  expect.objectContaining({ request: expect.anything() }),
331
333
  );
@@ -337,9 +339,9 @@ describe('createRouteHandler', () => {
337
339
  const req = makeReq('http://localhost/api/data/account', 'POST', body);
338
340
  const res = await handler(req, { params: { objectstack: ['data', 'account'] } });
339
341
  expect(res.status).toBe(200);
340
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
341
- 'account',
342
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
342
343
  'POST',
344
+ '/data/account',
343
345
  body,
344
346
  expect.any(Object),
345
347
  expect.objectContaining({ request: expect.anything() }),
@@ -352,9 +354,9 @@ describe('createRouteHandler', () => {
352
354
  const req = makeReq('http://localhost/api/data/account', 'PATCH', body);
353
355
  const res = await handler(req, { params: { objectstack: ['data', 'account'] } });
354
356
  expect(res.status).toBe(200);
355
- expect(mockDispatcher.handleData).toHaveBeenCalledWith(
356
- 'account',
357
+ expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
357
358
  'PATCH',
359
+ '/data/account',
358
360
  body,
359
361
  expect.any(Object),
360
362
  expect.objectContaining({ request: expect.anything() }),
@@ -362,7 +364,7 @@ describe('createRouteHandler', () => {
362
364
  });
363
365
 
364
366
  it('returns 404 when result is not handled', async () => {
365
- mockDispatcher.handleData.mockResolvedValueOnce({ handled: false });
367
+ mockDispatcher.dispatch.mockResolvedValueOnce({ handled: false });
366
368
  const handler = createRouteHandler({ kernel: mockKernel });
367
369
  const req = makeReq('http://localhost/api/data/missing', 'GET');
368
370
  const res = await handler(req, { params: { objectstack: ['data', 'missing'] } });
@@ -401,6 +403,7 @@ describe('createRouteHandler', () => {
401
403
 
402
404
  describe('Error Handling', () => {
403
405
  it('returns 404 for unknown route segments', async () => {
406
+ mockDispatcher.dispatch.mockResolvedValueOnce({ handled: false });
404
407
  const handler = createRouteHandler({ kernel: mockKernel });
405
408
  const req = makeReq('http://localhost/api/unknown/path', 'GET');
406
409
  const res = await handler(req, { params: { objectstack: ['unknown', 'path'] } });
@@ -410,7 +413,7 @@ describe('createRouteHandler', () => {
410
413
  });
411
414
 
412
415
  it('returns 500 with default message on generic error', async () => {
413
- mockDispatcher.handleData.mockRejectedValueOnce(new Error());
416
+ mockDispatcher.dispatch.mockRejectedValueOnce(new Error());
414
417
  const handler = createRouteHandler({ kernel: mockKernel });
415
418
  const req = makeReq('http://localhost/api/data/account', 'GET');
416
419
  const res = await handler(req, { params: { objectstack: ['data', 'account'] } });
@@ -419,7 +422,7 @@ describe('createRouteHandler', () => {
419
422
  });
420
423
 
421
424
  it('uses custom statusCode from error', async () => {
422
- mockDispatcher.handleData.mockRejectedValueOnce(
425
+ mockDispatcher.dispatch.mockRejectedValueOnce(
423
426
  Object.assign(new Error('Forbidden'), { statusCode: 403 }),
424
427
  );
425
428
  const handler = createRouteHandler({ kernel: mockKernel });
@@ -432,7 +435,7 @@ describe('createRouteHandler', () => {
432
435
 
433
436
  describe('toResponse', () => {
434
437
  it('handles redirect result', async () => {
435
- mockDispatcher.handleData.mockResolvedValueOnce({
438
+ mockDispatcher.dispatch.mockResolvedValueOnce({
436
439
  handled: true,
437
440
  result: { type: 'redirect', url: 'https://example.com' },
438
441
  });
@@ -444,7 +447,7 @@ describe('createRouteHandler', () => {
444
447
 
445
448
  it('handles stream result', async () => {
446
449
  const stream = 'mock-stream';
447
- mockDispatcher.handleData.mockResolvedValueOnce({
450
+ mockDispatcher.dispatch.mockResolvedValueOnce({
448
451
  handled: true,
449
452
  result: { type: 'stream', stream, headers: { 'Content-Type': 'text/plain' } },
450
453
  });
@@ -457,7 +460,7 @@ describe('createRouteHandler', () => {
457
460
 
458
461
  it('returns raw result when handled but no response/redirect/stream', async () => {
459
462
  const rawResult = { type: 'custom', data: 'test' };
460
- mockDispatcher.handleData.mockResolvedValueOnce({
463
+ mockDispatcher.dispatch.mockResolvedValueOnce({
461
464
  handled: true,
462
465
  result: rawResult,
463
466
  });