@tablecraft/adapter-next 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @tablecraft/adapter-next
2
+
3
+ Next.js App Router adapter for [TableCraft](https://github.com/your-org/tablecraft).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @tablecraft/engine @tablecraft/adapter-next
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Dynamic route (multiple tables)
14
+
15
+ ```ts
16
+ // app/api/data/[table]/route.ts
17
+ import { createNextHandler } from '@tablecraft/adapter-next';
18
+ import { db } from '@/db';
19
+ import * as schema from '@/db/schema';
20
+ import { configs } from '@/tablecraft.config';
21
+
22
+ const handler = createNextHandler({
23
+ db,
24
+ schema,
25
+ configs,
26
+ getContext: async (request) => {
27
+ // Extract from session, JWT, etc.
28
+ return { tenantId: 'tenant_123', user: { id: '1', roles: ['admin'] } };
29
+ },
30
+ });
31
+
32
+ export const GET = handler;
33
+ ```
34
+
35
+ ### Single route (one table)
36
+
37
+ ```ts
38
+ // app/api/users/route.ts
39
+ import { createNextRouteHandler } from '@tablecraft/adapter-next';
40
+
41
+ export const GET = createNextRouteHandler({
42
+ db,
43
+ schema,
44
+ config: usersConfig,
45
+ });
46
+ ```
47
+
48
+ ### Query from the client
49
+
50
+ ```
51
+ GET /api/data/users?page=1&pageSize=25&sort=-createdAt&filter[status]=active&search=john
52
+ GET /api/data/orders?export=csv
53
+ ```
@@ -0,0 +1,56 @@
1
+ import { TableConfig, ConfigInput, EngineContext } from '@tablecraft/engine';
2
+ export interface NextHandlerOptions {
3
+ db: unknown;
4
+ schema: Record<string, unknown>;
5
+ configs: ConfigInput[] | Record<string, ConfigInput>;
6
+ /**
7
+ * Extract context (tenantId, user, etc.) from the incoming request.
8
+ * Called on every request before the query runs.
9
+ */
10
+ getContext?: (request: Request) => EngineContext | Promise<EngineContext>;
11
+ /**
12
+ * Override built-in access check with your own logic.
13
+ * Return true to allow, false to deny.
14
+ */
15
+ checkAccess?: (config: TableConfig, context: EngineContext, request: Request) => boolean | Promise<boolean>;
16
+ }
17
+ /**
18
+ * Creates a Next.js App Router GET handler for dynamic `[table]` routes.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // app/api/data/[table]/route.ts
23
+ * import { createNextHandler } from '@tablecraft/adapter-next';
24
+ * import { db } from '@/db';
25
+ * import * as schema from '@/db/schema';
26
+ * import { configs } from '@/tablecraft.config';
27
+ *
28
+ * const handler = createNextHandler({ db, schema, configs });
29
+ * export const GET = handler;
30
+ * ```
31
+ */
32
+ export declare function createNextHandler(options: NextHandlerOptions): (request: Request, routeContext: {
33
+ params: Promise<{
34
+ table: string;
35
+ }> | {
36
+ table: string;
37
+ };
38
+ }) => Promise<Response>;
39
+ /**
40
+ * Creates a handler for a single, known table (no dynamic route needed).
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // app/api/users/route.ts
45
+ * import { createNextRouteHandler } from '@tablecraft/adapter-next';
46
+ * export const GET = createNextRouteHandler({ db, schema, config: usersConfig });
47
+ * ```
48
+ */
49
+ export declare function createNextRouteHandler(options: {
50
+ db: unknown;
51
+ schema: Record<string, unknown>;
52
+ config: ConfigInput;
53
+ getContext?: (request: Request) => EngineContext | Promise<EngineContext>;
54
+ checkAccess?: (config: TableConfig, context: EngineContext, request: Request) => boolean | Promise<boolean>;
55
+ }): (request: Request) => Promise<Response>;
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,WAAW,EACX,WAAW,EACX,aAAa,EACd,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrD;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1E;;;OAGG;IACH,WAAW,CAAC,EAAE,CACZ,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,OAAO,KACb,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,IAQzD,SAAS,OAAO,EAChB,cAAc;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KACvE,OAAO,CAAC,QAAQ,CAAC,CA2FrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAC9C,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1E,WAAW,CAAC,EAAE,CACZ,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,OAAO,KACb,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC,IAY2B,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAQ/D"}
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ import { createEngines, parseRequest, checkAccess as defaultCheckAccess, getExportMeta, } from '@tablecraft/engine';
2
+ /**
3
+ * Creates a Next.js App Router GET handler for dynamic `[table]` routes.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // app/api/data/[table]/route.ts
8
+ * import { createNextHandler } from '@tablecraft/adapter-next';
9
+ * import { db } from '@/db';
10
+ * import * as schema from '@/db/schema';
11
+ * import { configs } from '@/tablecraft.config';
12
+ *
13
+ * const handler = createNextHandler({ db, schema, configs });
14
+ * export const GET = handler;
15
+ * ```
16
+ */
17
+ export function createNextHandler(options) {
18
+ const engines = createEngines({
19
+ db: options.db,
20
+ schema: options.schema,
21
+ configs: options.configs,
22
+ });
23
+ return async function GET(request, routeContext) {
24
+ try {
25
+ // Resolve params (Next.js 15 makes params async)
26
+ const resolved = await Promise.resolve(routeContext.params);
27
+ const tableName = resolved.table;
28
+ // ─── Metadata endpoint: GET /api/data/users/_meta ───
29
+ if (tableName.endsWith('/_meta') || tableName.endsWith('_meta')) {
30
+ const actualName = tableName.replace(/\/?_meta$/, '');
31
+ const engine = engines[actualName];
32
+ if (!engine) {
33
+ return Response.json({ error: `Unknown resource '${actualName}'` }, { status: 404 });
34
+ }
35
+ const context = options.getContext
36
+ ? await options.getContext(request)
37
+ : {};
38
+ const metadata = engine.getMetadata(context);
39
+ return Response.json(metadata);
40
+ }
41
+ const engine = engines[tableName];
42
+ if (!engine) {
43
+ return Response.json({ error: `Unknown resource '${tableName}'` }, { status: 404 });
44
+ }
45
+ const config = engine.getConfig();
46
+ // --- Context ---
47
+ const context = options.getContext
48
+ ? await options.getContext(request)
49
+ : {};
50
+ // --- Access control ---
51
+ const hasAccess = options.checkAccess
52
+ ? await options.checkAccess(config, context, request)
53
+ : defaultCheckAccess(config, context);
54
+ if (!hasAccess) {
55
+ return Response.json({ error: 'Forbidden' }, { status: 403 });
56
+ }
57
+ // --- Parse URL ---
58
+ const url = new URL(request.url);
59
+ const params = parseRequest(url.searchParams);
60
+ // --- Export mode ---
61
+ if (params.export) {
62
+ const allowed = config.export?.formats ?? ['csv', 'json'];
63
+ const enabled = config.export?.enabled ?? true;
64
+ if (!enabled || !allowed.includes(params.export)) {
65
+ return Response.json({ error: `Export format '${params.export}' is not allowed` }, { status: 400 });
66
+ }
67
+ const body = await engine.exportData(params, context);
68
+ const { contentType, filename } = getExportMeta(tableName, params.export);
69
+ return new Response(body, {
70
+ status: 200,
71
+ headers: {
72
+ 'Content-Type': contentType,
73
+ 'Content-Disposition': `attachment; filename="${filename}"`,
74
+ },
75
+ });
76
+ }
77
+ // --- Normal query ---
78
+ const result = await engine.query(params, context);
79
+ return Response.json(result, {
80
+ status: 200,
81
+ headers: {
82
+ 'X-Total-Count': String(result.meta.total),
83
+ },
84
+ });
85
+ }
86
+ catch (err) {
87
+ const message = err instanceof Error ? err.message : 'Internal server error';
88
+ console.error('[tablecraft/next]', err);
89
+ return Response.json({ error: message }, { status: 500 });
90
+ }
91
+ };
92
+ }
93
+ /**
94
+ * Creates a handler for a single, known table (no dynamic route needed).
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * // app/api/users/route.ts
99
+ * import { createNextRouteHandler } from '@tablecraft/adapter-next';
100
+ * export const GET = createNextRouteHandler({ db, schema, config: usersConfig });
101
+ * ```
102
+ */
103
+ export function createNextRouteHandler(options) {
104
+ const { db, schema, config, getContext, checkAccess } = options;
105
+ // Reuse the multi-table handler with a single config
106
+ const handler = createNextHandler({
107
+ db,
108
+ schema,
109
+ configs: [config],
110
+ getContext,
111
+ checkAccess,
112
+ });
113
+ return async function GET(request) {
114
+ // Simulate the dynamic route param
115
+ // We resolve the name from the config input
116
+ const resolvedConfig = 'toConfig' in config
117
+ ? config.toConfig()
118
+ : config;
119
+ return handler(request, { params: { table: resolvedConfig.name } });
120
+ };
121
+ }
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@tablecraft/adapter-next",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "Next.js App Router adapter for TableCraft",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "bun run --bun tsc",
19
+ "dev": "bun run --bun tsc --watch",
20
+ "test": "vitest",
21
+ "typecheck": "bun run --bun tsc --noEmit"
22
+ },
23
+ "peerDependencies": {
24
+ "@tablecraft/engine": ">=0.1.0-beta.1",
25
+ "next": ">=14.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "@tablecraft/engine": "workspace:*",
29
+ "@types/node": "^25.2.2",
30
+ "typescript": "^5.3.3",
31
+ "vitest": "^1.2.0"
32
+ },
33
+ "keywords": [
34
+ "tablecraft",
35
+ "nextjs",
36
+ "adapter",
37
+ "next"
38
+ ],
39
+ "license": "MIT"
40
+ }