@objectstack/nextjs 3.1.1 → 3.2.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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +12 -0
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +12 -4
- package/src/nextjs.test.ts +31 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/nextjs@3.
|
|
2
|
+
> @objectstack/nextjs@3.2.1 build /home/runner/work/spec/spec/packages/adapters/nextjs
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m9.20 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m17.14 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 67ms
|
|
16
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m7.96 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m17.08 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 76ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 14139ms
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m2.48 KB[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.48 KB[39m
|
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -64,7 +64,16 @@ function createRouteHandler(options) {
|
|
|
64
64
|
try {
|
|
65
65
|
const rawRequest = req;
|
|
66
66
|
if (segments[0] === "auth") {
|
|
67
|
-
|
|
67
|
+
let authService = null;
|
|
68
|
+
try {
|
|
69
|
+
if (typeof options.kernel.getServiceAsync === "function") {
|
|
70
|
+
authService = await options.kernel.getServiceAsync("auth");
|
|
71
|
+
} else if (typeof options.kernel.getService === "function") {
|
|
72
|
+
authService = options.kernel.getService("auth");
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
authService = null;
|
|
76
|
+
}
|
|
68
77
|
if (authService && typeof authService.handleRequest === "function") {
|
|
69
78
|
const response = await authService.handleRequest(req);
|
|
70
79
|
const body2 = await response.text();
|
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: 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 (preferred path)\n const authService = typeof options.kernel.getService === 'function'\n ? options.kernel.getService<AuthService>('auth')\n : null;\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 */\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: 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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -38,7 +38,16 @@ function createRouteHandler(options) {
|
|
|
38
38
|
try {
|
|
39
39
|
const rawRequest = req;
|
|
40
40
|
if (segments[0] === "auth") {
|
|
41
|
-
|
|
41
|
+
let authService = null;
|
|
42
|
+
try {
|
|
43
|
+
if (typeof options.kernel.getServiceAsync === "function") {
|
|
44
|
+
authService = await options.kernel.getServiceAsync("auth");
|
|
45
|
+
} else if (typeof options.kernel.getService === "function") {
|
|
46
|
+
authService = options.kernel.getService("auth");
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
authService = null;
|
|
50
|
+
}
|
|
42
51
|
if (authService && typeof authService.handleRequest === "function") {
|
|
43
52
|
const response = await authService.handleRequest(req);
|
|
44
53
|
const body2 = await response.text();
|
package/dist/index.mjs.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: 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 (preferred path)\n const authService = typeof options.kernel.getService === 'function'\n ? options.kernel.getService<AuthService>('auth')\n : null;\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 */\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: 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":";AAEA,SAAsB,oBAAoB;AAC1C,SAA4B,sBAA4C;AAkBjE,SAAS,mBAAmB,SAA6B;AAC9D,QAAM,aAAa,IAAI,eAAe,QAAQ,MAAM;AACpD,QAAM,QAAQ,CAAC,KAAa,OAAe,QAAQ,aAAa,KAAK,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,EAAE,GAAG,EAAE,QAAQ,KAAK,CAAC;AAGxI,QAAM,aAAa,CAAC,WAAiC;AACjD,QAAI,OAAO,SAAS;AAChB,UAAI,OAAO,UAAU;AAChB,eAAO,aAAa,KAAK,OAAO,SAAS,MAAM;AAAA,UAC3C,QAAQ,OAAO,SAAS;AAAA,UACxB,SAAS,OAAO,SAAS;AAAA,QAC7B,CAAC;AAAA,MACN;AACA,UAAI,OAAO,QAAQ;AACf,cAAM,MAAM,OAAO;AAEnB,YAAI,IAAI,SAAS,cAAc,IAAI,KAAK;AACpC,iBAAO,aAAa,SAAS,IAAI,GAAG;AAAA,QACxC;AAEA,YAAI,IAAI,SAAS,YAAY,IAAI,QAAQ;AACrC,iBAAO,IAAI,aAAa,IAAI,QAAQ;AAAA,YAChC,QAAQ;AAAA,YACR,SAAS,IAAI;AAAA,UACjB,CAAC;AAAA,QACL;AAGA,eAAO;AAAA,MACX;AAAA,IACJ;AACA,WAAO,MAAM,aAAa,GAAG;AAAA,EACjC;AAEA,SAAO,eAAe,QAAQ,KAAkB,EAAE,OAAO,GAA0C;AACjG,UAAM,iBAAiB,MAAM,QAAQ,QAAQ,MAAM;AACnD,UAAM,WAAW,eAAe,eAAe,CAAC;AAChD,UAAM,SAAS,IAAI;AAGnB,QAAI,SAAS,WAAW,KAAK,WAAW,OAAO;AAC7C,aAAO,aAAa,KAAK,EAAE,MAAM,WAAW,iBAAiB,QAAQ,UAAU,MAAM,EAAE,CAAC;AAAA,IAC1F;AAEA,QAAI;AACA,YAAM,aAAa;AAGnB,UAAI,SAAS,CAAC,MAAM,QAAQ;AAEzB,YAAI,cAAkC;AACtC,YAAI;AACF,cAAI,OAAO,QAAQ,OAAO,oBAAoB,YAAY;AACxD,0BAAc,MAAM,QAAQ,OAAO,gBAA6B,MAAM;AAAA,UACxE,WAAW,OAAO,QAAQ,OAAO,eAAe,YAAY;AAC1D,0BAAc,QAAQ,OAAO,WAAwB,MAAM;AAAA,UAC7D;AAAA,QACF,QAAQ;AAEN,wBAAc;AAAA,QAChB;AAEA,YAAI,eAAe,OAAO,YAAY,kBAAkB,YAAY;AAClE,gBAAM,WAAW,MAAM,YAAY,cAAc,GAAG;AAEpD,gBAAMA,QAAO,MAAM,SAAS,KAAK;AACjC,gBAAM,UAAkC,CAAC;AACzC,mBAAS,QAAQ,QAAQ,CAAC,GAAW,MAAc;AAAE,oBAAQ,CAAC,IAAI;AAAA,UAAG,CAAC;AACtE,iBAAO,IAAI,aAAaA,OAAM,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AAAA,QACpE;AAGA,cAAM,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,GAAG;AAC1C,cAAM,OAAO,WAAW,SAAS,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE,IAAI,CAAC;AACvE,cAAM,SAAS,MAAM,WAAW,WAAW,SAAS,QAAQ,MAAM,EAAE,SAAS,IAAI,CAAC;AAClF,eAAO,WAAW,MAAM;AAAA,MAC3B;AAGA,UAAI,SAAS,CAAC,MAAM,aAAa,WAAW,QAAQ;AAChD,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,cAAM,SAAS,MAAM,WAAW,cAAc,MAAa,EAAE,SAAS,WAAW,CAAQ;AACzF,eAAO,aAAa,KAAK,MAAM;AAAA,MACnC;AAGA,UAAI,SAAS,CAAC,MAAM,QAAQ;AACxB,cAAM,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,GAAG;AAE1C,YAAI,OAAY;AAChB,YAAI,WAAW,SAAS,WAAW,QAAQ;AACvC,iBAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAAA,QAC5C;AAEA,cAAM,SAAS,MAAM,WAAW,eAAe,SAAS,EAAE,SAAS,WAAW,GAAG,QAAQ,IAAI;AAC7F,eAAO,WAAW,MAAM;AAAA,MAC5B;AAGA,UAAI,SAAS,CAAC,MAAM,QAAQ;AACxB,cAAM,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,GAAG;AAC1C,YAAI,OAAY,CAAC;AACjB,YAAI,WAAW,UAAU,WAAW,SAAS;AACzC,iBAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAAA,QAC5C;AAGA,cAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,cAAM,cAAmC,CAAC;AAC1C,YAAI,aAAa,QAAQ,CAAC,KAAK,QAAQ,YAAY,GAAG,IAAI,GAAG;AAE7D,cAAM,SAAS,MAAM,WAAW,WAAW,SAAS,QAAQ,MAAM,aAAa,EAAE,SAAS,WAAW,CAAQ;AAC7G,eAAO,WAAW,MAAM;AAAA,MAC5B;AAGA,UAAI,SAAS,CAAC,MAAM,WAAW;AAC3B,cAAM,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,GAAG;AAE1C,YAAI,OAAY;AAChB,YAAI,WAAW,UAAU,YAAY,UAAU;AAC3C,gBAAM,WAAW,MAAM,IAAI,SAAS;AACpC,iBAAO,SAAS,IAAI,MAAM;AAAA,QAC9B;AAEA,cAAM,SAAS,MAAM,WAAW,cAAc,SAAS,QAAQ,MAAM,EAAE,SAAS,WAAW,CAAC;AAC5F,eAAO,WAAW,MAAM;AAAA,MAC5B;AAEA,aAAO,MAAM,aAAa,GAAG;AAAA,IAEjC,SAAS,KAAU;AACf,aAAO,MAAM,IAAI,WAAW,yBAAyB,IAAI,cAAc,GAAG;AAAA,IAC9E;AAAA,EACF;AACF;AAMO,SAAS,uBAAuB,SAA6B;AAClE,SAAO,eAAe,iBAAiB,KAAkB;AACrD,UAAM,UAAU,QAAQ,UAAU;AAClC,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,YAAY,IAAI,IAAI,SAAS,IAAI,MAAM;AAC7C,WAAO,aAAa,SAAS,SAAS;AAAA,EAC1C;AACF;AAsCO,SAAS,oBAAoB,SAA6B;AAC/D,QAAM,aAAa,IAAI,eAAe,QAAQ,MAAM;AACpD,QAAM,eAAe,EAAE,SAAS,OAAU;AAE1C,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,MAAM,MAAM,YAAoB,QAA2D;AACzF,UAAI;AACF,cAAM,SAAS,MAAM,WAAW,WAAW,IAAI,UAAU,IAAI,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,YAAY;AAClG,YAAI,OAAO,WAAW,OAAO,UAAU;AACrC,iBAAO,EAAE,SAAS,MAAM,MAAM,OAAO,SAAS,KAAK;AAAA,QACrD;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,aAAa,MAAM,IAAI,EAAE;AAAA,MACtE,SAAS,KAAU;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,IAAI,WAAW,yBAAyB,MAAM,IAAI,cAAc,IAAI,EAAE;AAAA,MACnH;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,QAAQ,YAAoB,IAAyC;AACzE,UAAI;AACF,cAAM,SAAS,MAAM,WAAW,WAAW,IAAI,UAAU,IAAI,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,YAAY;AAC9F,YAAI,OAAO,WAAW,OAAO,UAAU;AACrC,iBAAO,EAAE,SAAS,MAAM,MAAM,OAAO,SAAS,KAAK;AAAA,QACrD;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,aAAa,MAAM,IAAI,EAAE;AAAA,MACtE,SAAS,KAAU;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,IAAI,WAAW,yBAAyB,MAAM,IAAI,cAAc,IAAI,EAAE;AAAA,MACnH;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,OAAO,YAAoB,MAAwD;AACvF,UAAI;AACF,cAAM,SAAS,MAAM,WAAW,WAAW,IAAI,UAAU,IAAI,QAAQ,MAAM,CAAC,GAAG,YAAY;AAC3F,YAAI,OAAO,WAAW,OAAO,UAAU;AACrC,iBAAO,EAAE,SAAS,MAAM,MAAM,OAAO,SAAS,KAAK;AAAA,QACrD;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,iBAAiB,MAAM,IAAI,EAAE;AAAA,MAC1E,SAAS,KAAU;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,IAAI,WAAW,yBAAyB,MAAM,IAAI,cAAc,IAAI,EAAE;AAAA,MACnH;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,OAAO,YAAoB,IAAY,MAAwD;AACnG,UAAI;AACF,cAAM,SAAS,MAAM,WAAW,WAAW,IAAI,UAAU,IAAI,EAAE,IAAI,SAAS,MAAM,CAAC,GAAG,YAAY;AAClG,YAAI,OAAO,WAAW,OAAO,UAAU;AACrC,iBAAO,EAAE,SAAS,MAAM,MAAM,OAAO,SAAS,KAAK;AAAA,QACrD;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,iBAAiB,MAAM,IAAI,EAAE;AAAA,MAC1E,SAAS,KAAU;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,IAAI,WAAW,yBAAyB,MAAM,IAAI,cAAc,IAAI,EAAE;AAAA,MACnH;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,OAAO,YAAoB,IAAyC;AACxE,UAAI;AACF,cAAM,SAAS,MAAM,WAAW,WAAW,IAAI,UAAU,IAAI,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,YAAY;AACjG,YAAI,OAAO,WAAW,OAAO,UAAU;AACrC,iBAAO,EAAE,SAAS,MAAM,MAAM,OAAO,SAAS,KAAK;AAAA,QACrD;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,iBAAiB,MAAM,IAAI,EAAE;AAAA,MAC1E,SAAS,KAAU;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,IAAI,WAAW,yBAAyB,MAAM,IAAI,cAAc,IAAI,EAAE;AAAA,MACnH;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,YAAY,MAA4C;AAC5D,UAAI;AACF,cAAM,SAAS,MAAM,WAAW,eAAe,QAAQ,IAAI,cAAc,KAAK;AAC9E,YAAI,OAAO,WAAW,OAAO,UAAU;AACrC,iBAAO,EAAE,SAAS,MAAM,MAAM,OAAO,SAAS,KAAK;AAAA,QACrD;AACA,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,aAAa,MAAM,IAAI,EAAE;AAAA,MACtE,SAAS,KAAU;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE,SAAS,IAAI,WAAW,yBAAyB,MAAM,IAAI,cAAc,IAAI,EAAE;AAAA,MACnH;AAAA,IACF;AAAA,EACF;AACF;","names":["body"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/nextjs",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"next": "^16.1.6",
|
|
9
9
|
"react": "^19.2.4",
|
|
10
10
|
"react-dom": "^19.2.4",
|
|
11
|
-
"@objectstack/runtime": "^3.
|
|
11
|
+
"@objectstack/runtime": "^3.2.1"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"next": "^16.1.6",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"react-dom": "^19.2.4",
|
|
17
17
|
"typescript": "^5.0.0",
|
|
18
18
|
"vitest": "^4.0.18",
|
|
19
|
-
"@objectstack/runtime": "3.
|
|
19
|
+
"@objectstack/runtime": "3.2.1"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsup --config ../../../tsup.config.ts",
|
package/src/index.ts
CHANGED
|
@@ -68,10 +68,18 @@ export function createRouteHandler(options: NextAdapterOptions) {
|
|
|
68
68
|
|
|
69
69
|
// --- 1. Auth ---
|
|
70
70
|
if (segments[0] === 'auth') {
|
|
71
|
-
// Try AuthPlugin service first (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
// Try AuthPlugin service first (prefer async to support factory-based services)
|
|
72
|
+
let authService: AuthService | null = null;
|
|
73
|
+
try {
|
|
74
|
+
if (typeof options.kernel.getServiceAsync === 'function') {
|
|
75
|
+
authService = await options.kernel.getServiceAsync<AuthService>('auth');
|
|
76
|
+
} else if (typeof options.kernel.getService === 'function') {
|
|
77
|
+
authService = options.kernel.getService<AuthService>('auth');
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Service not registered — fall through to dispatcher
|
|
81
|
+
authService = null;
|
|
82
|
+
}
|
|
75
83
|
|
|
76
84
|
if (authService && typeof authService.handleRequest === 'function') {
|
|
77
85
|
const response = await authService.handleRequest(req);
|
package/src/nextjs.test.ts
CHANGED
|
@@ -213,6 +213,37 @@ describe('createRouteHandler', () => {
|
|
|
213
213
|
expect(res.status).toBe(500);
|
|
214
214
|
expect(res.body.success).toBe(false);
|
|
215
215
|
});
|
|
216
|
+
|
|
217
|
+
it('uses kernel.getServiceAsync("auth") when available (async factory)', async () => {
|
|
218
|
+
const mockHandleRequest = vi.fn().mockResolvedValue(
|
|
219
|
+
new Response(JSON.stringify({ user: { id: '2' } }), {
|
|
220
|
+
status: 200,
|
|
221
|
+
headers: { 'Content-Type': 'application/json' },
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
const kernelWithAsyncAuth = {
|
|
225
|
+
...mockKernel,
|
|
226
|
+
getServiceAsync: vi.fn().mockResolvedValue({ handleRequest: mockHandleRequest }),
|
|
227
|
+
};
|
|
228
|
+
const handler = createRouteHandler({ kernel: kernelWithAsyncAuth });
|
|
229
|
+
const req = makeReq('http://localhost/api/auth/sign-in/email', 'POST', { email: 'a@b.com', password: 'pass' });
|
|
230
|
+
const res = await handler(req, { params: { objectstack: ['auth', 'sign-in', 'email'] } });
|
|
231
|
+
expect(res.status).toBe(200);
|
|
232
|
+
expect(kernelWithAsyncAuth.getServiceAsync).toHaveBeenCalledWith('auth');
|
|
233
|
+
expect(mockHandleRequest).toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('falls back to dispatcher when getServiceAsync throws', async () => {
|
|
237
|
+
const kernelWithFailingAsync = {
|
|
238
|
+
...mockKernel,
|
|
239
|
+
getServiceAsync: vi.fn().mockRejectedValue(new Error("Service 'auth' not found")),
|
|
240
|
+
};
|
|
241
|
+
const handler = createRouteHandler({ kernel: kernelWithFailingAsync });
|
|
242
|
+
const req = makeReq('http://localhost/api/auth/login', 'POST', { email: 'a@b.com' });
|
|
243
|
+
const res = await handler(req, { params: { objectstack: ['auth', 'login'] } });
|
|
244
|
+
expect(res.status).toBe(200);
|
|
245
|
+
expect(mockDispatcher.handleAuth).toHaveBeenCalled();
|
|
246
|
+
});
|
|
216
247
|
});
|
|
217
248
|
|
|
218
249
|
describe('GraphQL Endpoint', () => {
|