@objectstack/nextjs 3.0.1 → 3.0.3
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 +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +94 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +133 -0
- package/src/nextjs.test.ts +94 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/nextjs@3.0.
|
|
2
|
+
> @objectstack/nextjs@3.0.3 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
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m7.70 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m16.53 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 66ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m8.94 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m16.58 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 81ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 12738ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m2.48 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.48 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @objectstack/nextjs
|
|
2
2
|
|
|
3
|
+
## 3.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c7267f6: Patch release for maintenance updates and improvements.
|
|
8
|
+
- Updated dependencies [c7267f6]
|
|
9
|
+
- @objectstack/runtime@3.0.3
|
|
10
|
+
|
|
11
|
+
## 3.0.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- @objectstack/runtime@3.0.2
|
|
16
|
+
|
|
3
17
|
## 3.0.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -19,5 +19,67 @@ declare function createRouteHandler(options: NextAdapterOptions): (req: NextRequ
|
|
|
19
19
|
* Handles /.well-known/objectstack
|
|
20
20
|
*/
|
|
21
21
|
declare function createDiscoveryHandler(options: NextAdapterOptions): (req: NextRequest) => Promise<NextResponse<unknown>>;
|
|
22
|
+
/**
|
|
23
|
+
* Result type for server actions
|
|
24
|
+
*/
|
|
25
|
+
interface ServerActionResult<T = any> {
|
|
26
|
+
success: boolean;
|
|
27
|
+
data?: T;
|
|
28
|
+
error?: {
|
|
29
|
+
message: string;
|
|
30
|
+
code: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates type-safe React Server Actions for ObjectStack data operations.
|
|
35
|
+
* Each action maps to a dispatcher method and can be called directly from
|
|
36
|
+
* React Server Components or client components via `"use server"`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // app/actions.ts
|
|
41
|
+
* "use server";
|
|
42
|
+
* import { createServerActions } from '@objectstack/nextjs';
|
|
43
|
+
* import { kernel } from '@/lib/kernel';
|
|
44
|
+
*
|
|
45
|
+
* const actions = createServerActions({ kernel });
|
|
46
|
+
*
|
|
47
|
+
* export async function getAccounts() {
|
|
48
|
+
* return actions.query('account');
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* export async function createAccount(formData: FormData) {
|
|
52
|
+
* return actions.create('account', {
|
|
53
|
+
* name: formData.get('name') as string,
|
|
54
|
+
* });
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function createServerActions(options: NextAdapterOptions): {
|
|
59
|
+
/**
|
|
60
|
+
* Query records from an object
|
|
61
|
+
*/
|
|
62
|
+
query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Get a single record by ID
|
|
65
|
+
*/
|
|
66
|
+
getById(objectName: string, id: string): Promise<ServerActionResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Create a new record
|
|
69
|
+
*/
|
|
70
|
+
create(objectName: string, data: Record<string, any>): Promise<ServerActionResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Update a record by ID
|
|
73
|
+
*/
|
|
74
|
+
update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Delete a record by ID
|
|
77
|
+
*/
|
|
78
|
+
remove(objectName: string, id: string): Promise<ServerActionResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Get metadata for objects
|
|
81
|
+
*/
|
|
82
|
+
getMetadata(path?: string): Promise<ServerActionResult>;
|
|
83
|
+
};
|
|
22
84
|
|
|
23
|
-
export { type NextAdapterOptions, createDiscoveryHandler, createRouteHandler };
|
|
85
|
+
export { type NextAdapterOptions, type ServerActionResult, createDiscoveryHandler, createRouteHandler, createServerActions };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,5 +19,67 @@ declare function createRouteHandler(options: NextAdapterOptions): (req: NextRequ
|
|
|
19
19
|
* Handles /.well-known/objectstack
|
|
20
20
|
*/
|
|
21
21
|
declare function createDiscoveryHandler(options: NextAdapterOptions): (req: NextRequest) => Promise<NextResponse<unknown>>;
|
|
22
|
+
/**
|
|
23
|
+
* Result type for server actions
|
|
24
|
+
*/
|
|
25
|
+
interface ServerActionResult<T = any> {
|
|
26
|
+
success: boolean;
|
|
27
|
+
data?: T;
|
|
28
|
+
error?: {
|
|
29
|
+
message: string;
|
|
30
|
+
code: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates type-safe React Server Actions for ObjectStack data operations.
|
|
35
|
+
* Each action maps to a dispatcher method and can be called directly from
|
|
36
|
+
* React Server Components or client components via `"use server"`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // app/actions.ts
|
|
41
|
+
* "use server";
|
|
42
|
+
* import { createServerActions } from '@objectstack/nextjs';
|
|
43
|
+
* import { kernel } from '@/lib/kernel';
|
|
44
|
+
*
|
|
45
|
+
* const actions = createServerActions({ kernel });
|
|
46
|
+
*
|
|
47
|
+
* export async function getAccounts() {
|
|
48
|
+
* return actions.query('account');
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* export async function createAccount(formData: FormData) {
|
|
52
|
+
* return actions.create('account', {
|
|
53
|
+
* name: formData.get('name') as string,
|
|
54
|
+
* });
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function createServerActions(options: NextAdapterOptions): {
|
|
59
|
+
/**
|
|
60
|
+
* Query records from an object
|
|
61
|
+
*/
|
|
62
|
+
query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Get a single record by ID
|
|
65
|
+
*/
|
|
66
|
+
getById(objectName: string, id: string): Promise<ServerActionResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Create a new record
|
|
69
|
+
*/
|
|
70
|
+
create(objectName: string, data: Record<string, any>): Promise<ServerActionResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Update a record by ID
|
|
73
|
+
*/
|
|
74
|
+
update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Delete a record by ID
|
|
77
|
+
*/
|
|
78
|
+
remove(objectName: string, id: string): Promise<ServerActionResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Get metadata for objects
|
|
81
|
+
*/
|
|
82
|
+
getMetadata(path?: string): Promise<ServerActionResult>;
|
|
83
|
+
};
|
|
22
84
|
|
|
23
|
-
export { type NextAdapterOptions, createDiscoveryHandler, createRouteHandler };
|
|
85
|
+
export { type NextAdapterOptions, type ServerActionResult, createDiscoveryHandler, createRouteHandler, createServerActions };
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createDiscoveryHandler: () => createDiscoveryHandler,
|
|
24
|
-
createRouteHandler: () => createRouteHandler
|
|
24
|
+
createRouteHandler: () => createRouteHandler,
|
|
25
|
+
createServerActions: () => createServerActions
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
27
28
|
var import_server = require("next/server");
|
|
@@ -128,9 +129,100 @@ function createDiscoveryHandler(options) {
|
|
|
128
129
|
return import_server.NextResponse.redirect(targetUrl);
|
|
129
130
|
};
|
|
130
131
|
}
|
|
132
|
+
function createServerActions(options) {
|
|
133
|
+
const dispatcher = new import_runtime.HttpDispatcher(options.kernel);
|
|
134
|
+
const emptyContext = { request: void 0 };
|
|
135
|
+
return {
|
|
136
|
+
/**
|
|
137
|
+
* Query records from an object
|
|
138
|
+
*/
|
|
139
|
+
async query(objectName, params) {
|
|
140
|
+
try {
|
|
141
|
+
const result = await dispatcher.handleData(`/${objectName}`, "GET", {}, params || {}, emptyContext);
|
|
142
|
+
if (result.handled && result.response) {
|
|
143
|
+
return { success: true, data: result.response.body };
|
|
144
|
+
}
|
|
145
|
+
return { success: false, error: { message: "Not found", code: 404 } };
|
|
146
|
+
} catch (err) {
|
|
147
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* Get a single record by ID
|
|
152
|
+
*/
|
|
153
|
+
async getById(objectName, id) {
|
|
154
|
+
try {
|
|
155
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, "GET", {}, {}, emptyContext);
|
|
156
|
+
if (result.handled && result.response) {
|
|
157
|
+
return { success: true, data: result.response.body };
|
|
158
|
+
}
|
|
159
|
+
return { success: false, error: { message: "Not found", code: 404 } };
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
/**
|
|
165
|
+
* Create a new record
|
|
166
|
+
*/
|
|
167
|
+
async create(objectName, data) {
|
|
168
|
+
try {
|
|
169
|
+
const result = await dispatcher.handleData(`/${objectName}`, "POST", data, {}, emptyContext);
|
|
170
|
+
if (result.handled && result.response) {
|
|
171
|
+
return { success: true, data: result.response.body };
|
|
172
|
+
}
|
|
173
|
+
return { success: false, error: { message: "Create failed", code: 500 } };
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
/**
|
|
179
|
+
* Update a record by ID
|
|
180
|
+
*/
|
|
181
|
+
async update(objectName, id, data) {
|
|
182
|
+
try {
|
|
183
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, "PATCH", data, {}, emptyContext);
|
|
184
|
+
if (result.handled && result.response) {
|
|
185
|
+
return { success: true, data: result.response.body };
|
|
186
|
+
}
|
|
187
|
+
return { success: false, error: { message: "Update failed", code: 500 } };
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
/**
|
|
193
|
+
* Delete a record by ID
|
|
194
|
+
*/
|
|
195
|
+
async remove(objectName, id) {
|
|
196
|
+
try {
|
|
197
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, "DELETE", {}, {}, emptyContext);
|
|
198
|
+
if (result.handled && result.response) {
|
|
199
|
+
return { success: true, data: result.response.body };
|
|
200
|
+
}
|
|
201
|
+
return { success: false, error: { message: "Delete failed", code: 500 } };
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
/**
|
|
207
|
+
* Get metadata for objects
|
|
208
|
+
*/
|
|
209
|
+
async getMetadata(path) {
|
|
210
|
+
try {
|
|
211
|
+
const result = await dispatcher.handleMetadata(path || "", emptyContext, "GET");
|
|
212
|
+
if (result.handled && result.response) {
|
|
213
|
+
return { success: true, data: result.response.body };
|
|
214
|
+
}
|
|
215
|
+
return { success: false, error: { message: "Not found", code: 404 } };
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
131
222
|
// Annotate the CommonJS export names for ESM import in node:
|
|
132
223
|
0 && (module.exports = {
|
|
133
224
|
createDiscoveryHandler,
|
|
134
|
-
createRouteHandler
|
|
225
|
+
createRouteHandler,
|
|
226
|
+
createServerActions
|
|
135
227
|
});
|
|
136
228
|
//# sourceMappingURL=index.js.map
|
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"],"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 (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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -103,8 +103,99 @@ function createDiscoveryHandler(options) {
|
|
|
103
103
|
return NextResponse.redirect(targetUrl);
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
+
function createServerActions(options) {
|
|
107
|
+
const dispatcher = new HttpDispatcher(options.kernel);
|
|
108
|
+
const emptyContext = { request: void 0 };
|
|
109
|
+
return {
|
|
110
|
+
/**
|
|
111
|
+
* Query records from an object
|
|
112
|
+
*/
|
|
113
|
+
async query(objectName, params) {
|
|
114
|
+
try {
|
|
115
|
+
const result = await dispatcher.handleData(`/${objectName}`, "GET", {}, params || {}, emptyContext);
|
|
116
|
+
if (result.handled && result.response) {
|
|
117
|
+
return { success: true, data: result.response.body };
|
|
118
|
+
}
|
|
119
|
+
return { success: false, error: { message: "Not found", code: 404 } };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* Get a single record by ID
|
|
126
|
+
*/
|
|
127
|
+
async getById(objectName, id) {
|
|
128
|
+
try {
|
|
129
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, "GET", {}, {}, emptyContext);
|
|
130
|
+
if (result.handled && result.response) {
|
|
131
|
+
return { success: true, data: result.response.body };
|
|
132
|
+
}
|
|
133
|
+
return { success: false, error: { message: "Not found", code: 404 } };
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
/**
|
|
139
|
+
* Create a new record
|
|
140
|
+
*/
|
|
141
|
+
async create(objectName, data) {
|
|
142
|
+
try {
|
|
143
|
+
const result = await dispatcher.handleData(`/${objectName}`, "POST", data, {}, emptyContext);
|
|
144
|
+
if (result.handled && result.response) {
|
|
145
|
+
return { success: true, data: result.response.body };
|
|
146
|
+
}
|
|
147
|
+
return { success: false, error: { message: "Create failed", code: 500 } };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
/**
|
|
153
|
+
* Update a record by ID
|
|
154
|
+
*/
|
|
155
|
+
async update(objectName, id, data) {
|
|
156
|
+
try {
|
|
157
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, "PATCH", data, {}, emptyContext);
|
|
158
|
+
if (result.handled && result.response) {
|
|
159
|
+
return { success: true, data: result.response.body };
|
|
160
|
+
}
|
|
161
|
+
return { success: false, error: { message: "Update failed", code: 500 } };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* Delete a record by ID
|
|
168
|
+
*/
|
|
169
|
+
async remove(objectName, id) {
|
|
170
|
+
try {
|
|
171
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, "DELETE", {}, {}, emptyContext);
|
|
172
|
+
if (result.handled && result.response) {
|
|
173
|
+
return { success: true, data: result.response.body };
|
|
174
|
+
}
|
|
175
|
+
return { success: false, error: { message: "Delete failed", code: 500 } };
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* Get metadata for objects
|
|
182
|
+
*/
|
|
183
|
+
async getMetadata(path) {
|
|
184
|
+
try {
|
|
185
|
+
const result = await dispatcher.handleMetadata(path || "", emptyContext, "GET");
|
|
186
|
+
if (result.handled && result.response) {
|
|
187
|
+
return { success: true, data: result.response.body };
|
|
188
|
+
}
|
|
189
|
+
return { success: false, error: { message: "Not found", code: 404 } };
|
|
190
|
+
} catch (err) {
|
|
191
|
+
return { success: false, error: { message: err.message || "Internal Server Error", code: err.statusCode || 500 } };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
106
196
|
export {
|
|
107
197
|
createDiscoveryHandler,
|
|
108
|
-
createRouteHandler
|
|
198
|
+
createRouteHandler,
|
|
199
|
+
createServerActions
|
|
109
200
|
};
|
|
110
201
|
//# sourceMappingURL=index.mjs.map
|
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"],"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 (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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/nextjs",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
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.0.
|
|
11
|
+
"@objectstack/runtime": "3.0.3"
|
|
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.0.
|
|
19
|
+
"@objectstack/runtime": "3.0.3"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsup --config ../../../tsup.config.ts",
|
package/src/index.ts
CHANGED
|
@@ -160,3 +160,136 @@ export function createDiscoveryHandler(options: NextAdapterOptions) {
|
|
|
160
160
|
return NextResponse.redirect(targetUrl);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
// ─── Server Actions ──────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Result type for server actions
|
|
168
|
+
*/
|
|
169
|
+
export interface ServerActionResult<T = any> {
|
|
170
|
+
success: boolean;
|
|
171
|
+
data?: T;
|
|
172
|
+
error?: { message: string; code: number };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Creates type-safe React Server Actions for ObjectStack data operations.
|
|
177
|
+
* Each action maps to a dispatcher method and can be called directly from
|
|
178
|
+
* React Server Components or client components via `"use server"`.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* // app/actions.ts
|
|
183
|
+
* "use server";
|
|
184
|
+
* import { createServerActions } from '@objectstack/nextjs';
|
|
185
|
+
* import { kernel } from '@/lib/kernel';
|
|
186
|
+
*
|
|
187
|
+
* const actions = createServerActions({ kernel });
|
|
188
|
+
*
|
|
189
|
+
* export async function getAccounts() {
|
|
190
|
+
* return actions.query('account');
|
|
191
|
+
* }
|
|
192
|
+
*
|
|
193
|
+
* export async function createAccount(formData: FormData) {
|
|
194
|
+
* return actions.create('account', {
|
|
195
|
+
* name: formData.get('name') as string,
|
|
196
|
+
* });
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export function createServerActions(options: NextAdapterOptions) {
|
|
201
|
+
const dispatcher = new HttpDispatcher(options.kernel);
|
|
202
|
+
const emptyContext = { request: undefined };
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
/**
|
|
206
|
+
* Query records from an object
|
|
207
|
+
*/
|
|
208
|
+
async query(objectName: string, params?: Record<string, any>): Promise<ServerActionResult> {
|
|
209
|
+
try {
|
|
210
|
+
const result = await dispatcher.handleData(`/${objectName}`, 'GET', {}, params || {}, emptyContext);
|
|
211
|
+
if (result.handled && result.response) {
|
|
212
|
+
return { success: true, data: result.response.body };
|
|
213
|
+
}
|
|
214
|
+
return { success: false, error: { message: 'Not found', code: 404 } };
|
|
215
|
+
} catch (err: any) {
|
|
216
|
+
return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get a single record by ID
|
|
222
|
+
*/
|
|
223
|
+
async getById(objectName: string, id: string): Promise<ServerActionResult> {
|
|
224
|
+
try {
|
|
225
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, 'GET', {}, {}, emptyContext);
|
|
226
|
+
if (result.handled && result.response) {
|
|
227
|
+
return { success: true, data: result.response.body };
|
|
228
|
+
}
|
|
229
|
+
return { success: false, error: { message: 'Not found', code: 404 } };
|
|
230
|
+
} catch (err: any) {
|
|
231
|
+
return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create a new record
|
|
237
|
+
*/
|
|
238
|
+
async create(objectName: string, data: Record<string, any>): Promise<ServerActionResult> {
|
|
239
|
+
try {
|
|
240
|
+
const result = await dispatcher.handleData(`/${objectName}`, 'POST', data, {}, emptyContext);
|
|
241
|
+
if (result.handled && result.response) {
|
|
242
|
+
return { success: true, data: result.response.body };
|
|
243
|
+
}
|
|
244
|
+
return { success: false, error: { message: 'Create failed', code: 500 } };
|
|
245
|
+
} catch (err: any) {
|
|
246
|
+
return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Update a record by ID
|
|
252
|
+
*/
|
|
253
|
+
async update(objectName: string, id: string, data: Record<string, any>): Promise<ServerActionResult> {
|
|
254
|
+
try {
|
|
255
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, 'PATCH', data, {}, emptyContext);
|
|
256
|
+
if (result.handled && result.response) {
|
|
257
|
+
return { success: true, data: result.response.body };
|
|
258
|
+
}
|
|
259
|
+
return { success: false, error: { message: 'Update failed', code: 500 } };
|
|
260
|
+
} catch (err: any) {
|
|
261
|
+
return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Delete a record by ID
|
|
267
|
+
*/
|
|
268
|
+
async remove(objectName: string, id: string): Promise<ServerActionResult> {
|
|
269
|
+
try {
|
|
270
|
+
const result = await dispatcher.handleData(`/${objectName}/${id}`, 'DELETE', {}, {}, emptyContext);
|
|
271
|
+
if (result.handled && result.response) {
|
|
272
|
+
return { success: true, data: result.response.body };
|
|
273
|
+
}
|
|
274
|
+
return { success: false, error: { message: 'Delete failed', code: 500 } };
|
|
275
|
+
} catch (err: any) {
|
|
276
|
+
return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get metadata for objects
|
|
282
|
+
*/
|
|
283
|
+
async getMetadata(path?: string): Promise<ServerActionResult> {
|
|
284
|
+
try {
|
|
285
|
+
const result = await dispatcher.handleMetadata(path || '', emptyContext, 'GET');
|
|
286
|
+
if (result.handled && result.response) {
|
|
287
|
+
return { success: true, data: result.response.body };
|
|
288
|
+
}
|
|
289
|
+
return { success: false, error: { message: 'Not found', code: 404 } };
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
return { success: false, error: { message: err.message || 'Internal Server Error', code: err.statusCode || 500 } };
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
package/src/nextjs.test.ts
CHANGED
|
@@ -453,3 +453,97 @@ describe('createDiscoveryHandler', () => {
|
|
|
453
453
|
expect((res as any).redirectUrl).toBe('http://localhost/v2/api');
|
|
454
454
|
});
|
|
455
455
|
});
|
|
456
|
+
|
|
457
|
+
// --- Server Actions Tests ---
|
|
458
|
+
import { createServerActions } from './index';
|
|
459
|
+
|
|
460
|
+
describe('createServerActions', () => {
|
|
461
|
+
beforeEach(() => {
|
|
462
|
+
vi.clearAllMocks();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('query returns data on success', async () => {
|
|
466
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
467
|
+
const result = await actions.query('account');
|
|
468
|
+
expect(result.success).toBe(true);
|
|
469
|
+
expect(result.data).toEqual({ records: [] });
|
|
470
|
+
expect(mockDispatcher.handleData).toHaveBeenCalledWith(
|
|
471
|
+
'/account', 'GET', {}, {},
|
|
472
|
+
expect.anything(),
|
|
473
|
+
);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('query passes params', async () => {
|
|
477
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
478
|
+
await actions.query('account', { limit: 10 });
|
|
479
|
+
expect(mockDispatcher.handleData).toHaveBeenCalledWith(
|
|
480
|
+
'/account', 'GET', {}, { limit: 10 },
|
|
481
|
+
expect.anything(),
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('getById returns single record', async () => {
|
|
486
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
487
|
+
const result = await actions.getById('account', '123');
|
|
488
|
+
expect(result.success).toBe(true);
|
|
489
|
+
expect(mockDispatcher.handleData).toHaveBeenCalledWith(
|
|
490
|
+
'/account/123', 'GET', {}, {},
|
|
491
|
+
expect.anything(),
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('create sends data', async () => {
|
|
496
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
497
|
+
const result = await actions.create('account', { name: 'Acme' });
|
|
498
|
+
expect(result.success).toBe(true);
|
|
499
|
+
expect(mockDispatcher.handleData).toHaveBeenCalledWith(
|
|
500
|
+
'/account', 'POST', { name: 'Acme' }, {},
|
|
501
|
+
expect.anything(),
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('update sends data with id', async () => {
|
|
506
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
507
|
+
const result = await actions.update('account', '123', { name: 'Updated' });
|
|
508
|
+
expect(result.success).toBe(true);
|
|
509
|
+
expect(mockDispatcher.handleData).toHaveBeenCalledWith(
|
|
510
|
+
'/account/123', 'PATCH', { name: 'Updated' }, {},
|
|
511
|
+
expect.anything(),
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('remove deletes record', async () => {
|
|
516
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
517
|
+
const result = await actions.remove('account', '123');
|
|
518
|
+
expect(result.success).toBe(true);
|
|
519
|
+
expect(mockDispatcher.handleData).toHaveBeenCalledWith(
|
|
520
|
+
'/account/123', 'DELETE', {}, {},
|
|
521
|
+
expect.anything(),
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('getMetadata returns metadata', async () => {
|
|
526
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
527
|
+
const result = await actions.getMetadata('/objects');
|
|
528
|
+
expect(result.success).toBe(true);
|
|
529
|
+
expect(mockDispatcher.handleMetadata).toHaveBeenCalledWith(
|
|
530
|
+
'/objects', expect.anything(), 'GET',
|
|
531
|
+
);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('returns error on exception', async () => {
|
|
535
|
+
mockDispatcher.handleData.mockRejectedValueOnce(new Error('Server down'));
|
|
536
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
537
|
+
const result = await actions.query('account');
|
|
538
|
+
expect(result.success).toBe(false);
|
|
539
|
+
expect(result.error?.message).toBe('Server down');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('returns error when not handled', async () => {
|
|
543
|
+
mockDispatcher.handleData.mockResolvedValueOnce({ handled: false });
|
|
544
|
+
const actions = createServerActions({ kernel: mockKernel });
|
|
545
|
+
const result = await actions.query('missing');
|
|
546
|
+
expect(result.success).toBe(false);
|
|
547
|
+
expect(result.error?.code).toBe(404);
|
|
548
|
+
});
|
|
549
|
+
});
|