@objectstack/sveltekit 4.0.3 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,19 +1,26 @@
1
1
  # @objectstack/sveltekit
2
2
 
3
- The official SvelteKit adapter for ObjectStack.
3
+ > SvelteKit adapter for ObjectStack — server endpoints and `handle` hook for the auto-generated REST API.
4
4
 
5
- ## Features
6
- - SvelteKit API route handler
7
- - Full Auth/GraphQL/Metadata/Data/Storage routes
8
- - AuthPlugin service support
9
- - Handle hook for attaching kernel to event.locals
5
+ [![npm](https://img.shields.io/npm/v/@objectstack/sveltekit.svg)](https://www.npmjs.com/package/@objectstack/sveltekit)
6
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
10
7
 
11
- ## Usage
8
+ ## Overview
9
+
10
+ Bridges SvelteKit request events to `HttpDispatcher`. Ship ObjectStack either as a catch-all `+server.ts` route or as a `handle` hook for global interception.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @objectstack/sveltekit
16
+ ```
17
+
18
+ ## Quick Start — catch-all endpoint
12
19
 
13
20
  ```typescript
14
21
  // src/routes/api/[...path]/+server.ts
15
22
  import { createRequestHandler } from '@objectstack/sveltekit';
16
- import { kernel } from '$lib/kernel';
23
+ import { kernel } from '$lib/server/kernel';
17
24
 
18
25
  const handler = createRequestHandler({ kernel });
19
26
 
@@ -23,3 +30,44 @@ export const PUT = handler;
23
30
  export const PATCH = handler;
24
31
  export const DELETE = handler;
25
32
  ```
33
+
34
+ ### Global `handle` hook
35
+
36
+ ```typescript
37
+ // src/hooks.server.ts
38
+ import { sequence } from '@sveltejs/kit/hooks';
39
+ import { createHandle } from '@objectstack/sveltekit';
40
+ import { kernel } from '$lib/server/kernel';
41
+
42
+ export const handle = sequence(createHandle({ kernel, prefix: '/api' }));
43
+ ```
44
+
45
+ ## Key Exports
46
+
47
+ | Export | Kind | Description |
48
+ |:---|:---|:---|
49
+ | `createRequestHandler(options)` | function | Returns a `RequestHandler` for catch-all routes. |
50
+ | `createHandle(options)` | function | Returns a SvelteKit `Handle` hook. |
51
+ | `SvelteKitAdapterOptions` | interface | `{ kernel, prefix? }`. |
52
+
53
+ ## When to use
54
+
55
+ - ✅ SvelteKit 2.x applications.
56
+ - ✅ Projects preferring the `handle` hook over route files.
57
+
58
+ ## When not to use
59
+
60
+ - ❌ Sapper and SvelteKit 1.x are not supported.
61
+
62
+ ## Related Packages
63
+
64
+ - [`@objectstack/runtime`](../../runtime), [`@objectstack/rest`](../../rest).
65
+
66
+ ## Links
67
+
68
+ - 📖 Docs: <https://objectstack.ai/docs>
69
+ - 📚 API Reference: <https://objectstack.ai/docs/references>
70
+
71
+ ## License
72
+
73
+ Apache-2.0 © ObjectStack
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/sveltekit",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,14 +12,39 @@
12
12
  }
13
13
  },
14
14
  "peerDependencies": {
15
- "@sveltejs/kit": "^2.57.1",
16
- "@objectstack/runtime": "^4.0.3"
15
+ "@sveltejs/kit": "^2.58.0",
16
+ "@objectstack/runtime": "^4.0.5"
17
17
  },
18
18
  "devDependencies": {
19
- "@sveltejs/kit": "^2.57.1",
20
- "typescript": "^6.0.2",
21
- "vitest": "^4.1.4",
22
- "@objectstack/runtime": "4.0.3"
19
+ "@sveltejs/kit": "^2.59.1",
20
+ "typescript": "^6.0.3",
21
+ "vitest": "^4.1.5",
22
+ "@objectstack/runtime": "4.0.5"
23
+ },
24
+ "description": "SvelteKit adapter for ObjectStack — server endpoints for the ObjectStack REST API.",
25
+ "keywords": [
26
+ "objectstack",
27
+ "sveltekit",
28
+ "adapter",
29
+ "rest"
30
+ ],
31
+ "author": "ObjectStack",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/objectstack-ai/framework.git",
35
+ "directory": "packages/adapters/sveltekit"
36
+ },
37
+ "homepage": "https://objectstack.ai/docs",
38
+ "bugs": "https://github.com/objectstack-ai/framework/issues",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md"
45
+ ],
46
+ "engines": {
47
+ "node": ">=18.0.0"
23
48
  },
24
49
  "scripts": {
25
50
  "build": "tsup --config ../../../tsup.config.ts",
@@ -1,22 +0,0 @@
1
-
2
- > @objectstack/sveltekit@4.0.3 build /home/runner/work/framework/framework/packages/adapters/sveltekit
3
- > tsup --config ../../../tsup.config.ts
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
9
- CLI Target: es2020
10
- CLI Cleaning output folder
11
- ESM Build start
12
- CJS Build start
13
- ESM dist/index.mjs 4.78 KB
14
- ESM dist/index.mjs.map 10.84 KB
15
- ESM ⚡️ Build success in 27ms
16
- CJS dist/index.js 5.83 KB
17
- CJS dist/index.js.map 10.88 KB
18
- CJS ⚡️ Build success in 27ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 10309ms
21
- DTS dist/index.d.mts 1.65 KB
22
- DTS dist/index.d.ts 1.65 KB
package/CHANGELOG.md DELETED
@@ -1,188 +0,0 @@
1
- # @objectstack/sveltekit
2
-
3
- ## 4.0.3
4
-
5
- ### Patch Changes
6
-
7
- - @objectstack/runtime@4.0.3
8
-
9
- ## 4.0.2
10
-
11
- ### Patch Changes
12
-
13
- - @objectstack/runtime@4.0.2
14
-
15
- ## 4.0.0
16
-
17
- ### Patch Changes
18
-
19
- - f08ffc3: Fix discovery API endpoint routing and protocol consistency.
20
-
21
- **Discovery route standardization:**
22
-
23
- - All adapters (Express, Fastify, Hono, NestJS, Next.js, Nuxt, SvelteKit) now mount the discovery endpoint at `{prefix}/discovery` instead of `{prefix}` root.
24
- - `.well-known/objectstack` redirects now point to `{prefix}/discovery`.
25
- - Client `connect()` fallback URL changed from `/api/v1` to `/api/v1/discovery`.
26
- - Runtime dispatcher handles both `/discovery` (standard) and `/` (legacy) for backward compatibility.
27
-
28
- **Schema & route alignment:**
29
-
30
- - Added `storage` (service: `file-storage`) and `feed` (service: `data`) routes to `DEFAULT_DISPATCHER_ROUTES`.
31
- - Added `feed` and `discovery` fields to `ApiRoutesSchema`.
32
- - Unified `GetDiscoveryResponseSchema` with `DiscoverySchema` as single source of truth.
33
- - Client `getRoute('feed')` fallback updated from `/api/v1/data` to `/api/v1/feed`.
34
-
35
- **Type safety:**
36
-
37
- - Extracted `ApiRouteType` from `ApiRoutes` keys for type-safe client route resolution.
38
- - Removed `as any` type casting in client route access.
39
-
40
- - Updated dependencies [f08ffc3]
41
- - Updated dependencies [e0b0a78]
42
- - @objectstack/runtime@4.0.0
43
-
44
- ## 3.3.1
45
-
46
- ### Patch Changes
47
-
48
- - @objectstack/runtime@3.3.1
49
-
50
- ## 3.3.0
51
-
52
- ### Patch Changes
53
-
54
- - @objectstack/runtime@3.3.0
55
-
56
- ## 3.2.9
57
-
58
- ### Patch Changes
59
-
60
- - @objectstack/runtime@3.2.9
61
-
62
- ## 3.2.8
63
-
64
- ### Patch Changes
65
-
66
- - @objectstack/runtime@3.2.8
67
-
68
- ## 3.2.8
69
-
70
- ### Patch Changes
71
-
72
- - fix: unified catch-all dispatch pattern — `createRequestHandler()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
73
- - Only auth (service check), storage (formData), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
74
-
75
- ## 3.2.7
76
-
77
- ### Patch Changes
78
-
79
- - @objectstack/runtime@3.2.7
80
-
81
- ## 3.2.6
82
-
83
- ### Patch Changes
84
-
85
- - @objectstack/runtime@3.2.6
86
-
87
- ## 3.2.5
88
-
89
- ### Patch Changes
90
-
91
- - @objectstack/runtime@3.2.5
92
-
93
- ## 3.2.4
94
-
95
- ### Patch Changes
96
-
97
- - @objectstack/runtime@3.2.4
98
-
99
- ## 3.2.3
100
-
101
- ### Patch Changes
102
-
103
- - @objectstack/runtime@3.2.3
104
-
105
- ## 3.2.2
106
-
107
- ### Patch Changes
108
-
109
- - @objectstack/runtime@3.2.2
110
-
111
- ## 3.2.1
112
-
113
- ### Patch Changes
114
-
115
- - @objectstack/runtime@3.2.1
116
-
117
- ## 3.2.0
118
-
119
- ### Patch Changes
120
-
121
- - @objectstack/runtime@3.2.0
122
-
123
- ## 3.1.1
124
-
125
- ### Patch Changes
126
-
127
- - @objectstack/runtime@3.1.1
128
-
129
- ## 3.1.0
130
-
131
- ### Patch Changes
132
-
133
- - @objectstack/runtime@3.1.0
134
-
135
- ## 3.0.11
136
-
137
- ### Patch Changes
138
-
139
- - @objectstack/runtime@3.0.11
140
-
141
- ## 3.0.10
142
-
143
- ### Patch Changes
144
-
145
- - @objectstack/runtime@3.0.10
146
-
147
- ## 3.0.9
148
-
149
- ### Patch Changes
150
-
151
- - @objectstack/runtime@3.0.9
152
-
153
- ## 3.0.8
154
-
155
- ### Patch Changes
156
-
157
- - @objectstack/runtime@3.0.8
158
-
159
- ## 3.0.7
160
-
161
- ### Patch Changes
162
-
163
- - @objectstack/runtime@3.0.7
164
-
165
- ## 3.0.6
166
-
167
- ### Patch Changes
168
-
169
- - @objectstack/runtime@3.0.6
170
-
171
- ## 3.0.5
172
-
173
- ### Patch Changes
174
-
175
- - @objectstack/runtime@3.0.5
176
-
177
- ## 3.0.4
178
-
179
- ### Patch Changes
180
-
181
- - @objectstack/runtime@3.0.4
182
-
183
- ## 3.0.3
184
-
185
- ### Patch Changes
186
-
187
- - Updated dependencies [c7267f6]
188
- - @objectstack/runtime@3.0.3
@@ -1,4 +0,0 @@
1
- // Stub for @objectstack/runtime - replaced by vi.mock in tests
2
- export const HttpDispatcher = class {};
3
- export type ObjectKernel = any;
4
- export type HttpDispatcherResult = any;
package/src/index.ts DELETED
@@ -1,209 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';
4
-
5
- export interface SvelteKitAdapterOptions {
6
- kernel: ObjectKernel;
7
- prefix?: string;
8
- }
9
-
10
- /**
11
- * Auth service interface with handleRequest method
12
- */
13
- interface AuthService {
14
- handleRequest(request: Request): Promise<Response>;
15
- }
16
-
17
- /**
18
- * SvelteKit request event type (minimal interface to avoid hard dependency on @sveltejs/kit types at runtime)
19
- */
20
- interface RequestEvent {
21
- request: Request;
22
- url: URL;
23
- params: Record<string, string>;
24
- }
25
-
26
- /**
27
- * Creates a SvelteKit request handler for ObjectStack API routes.
28
- * Use in a catch-all `+server.ts` route like `src/routes/api/[...path]/+server.ts`.
29
- *
30
- * Only auth, GraphQL, storage, and discovery need explicit handling.
31
- * All other routes delegate to `HttpDispatcher.dispatch()` automatically.
32
- *
33
- * @example
34
- * ```ts
35
- * // src/routes/api/[...path]/+server.ts
36
- * import { createRequestHandler } from '@objectstack/sveltekit';
37
- * import { kernel } from '$lib/kernel';
38
- *
39
- * const handler = createRequestHandler({ kernel });
40
- *
41
- * export const GET = handler;
42
- * export const POST = handler;
43
- * export const PUT = handler;
44
- * export const PATCH = handler;
45
- * export const DELETE = handler;
46
- * ```
47
- */
48
- export function createRequestHandler(options: SvelteKitAdapterOptions) {
49
- const dispatcher = new HttpDispatcher(options.kernel);
50
- const prefix = options.prefix || '/api';
51
-
52
- const errorJson = (message: string, code: number = 500) => {
53
- return new Response(JSON.stringify({ success: false, error: { message, code } }), {
54
- status: code,
55
- headers: { 'Content-Type': 'application/json' },
56
- });
57
- };
58
-
59
- const toResponse = (result: HttpDispatcherResult): Response => {
60
- if (result.handled) {
61
- if (result.response) {
62
- const headers = new Headers({ 'Content-Type': 'application/json' });
63
- if (result.response.headers) {
64
- Object.entries(result.response.headers).forEach(([k, v]) => headers.set(k, v as string));
65
- }
66
- return new Response(JSON.stringify(result.response.body), {
67
- status: result.response.status,
68
- headers,
69
- });
70
- }
71
- if (result.result) {
72
- const res = result.result;
73
- if (res.type === 'redirect' && res.url) {
74
- return new Response(null, {
75
- status: 302,
76
- headers: { Location: res.url },
77
- });
78
- }
79
- if (res.type === 'stream' && res.stream) {
80
- const headers = new Headers();
81
- if (res.headers) {
82
- Object.entries(res.headers).forEach(([k, v]) => headers.set(k, v as string));
83
- }
84
- return new Response(res.stream, { status: 200, headers });
85
- }
86
- return new Response(JSON.stringify(res), {
87
- status: 200,
88
- headers: { 'Content-Type': 'application/json' },
89
- });
90
- }
91
- }
92
- return errorJson('Not Found', 404);
93
- };
94
-
95
- return async function handler(event: RequestEvent): Promise<Response> {
96
- const { request, url } = event;
97
- const method = request.method;
98
- const path = url.pathname.substring(prefix.length);
99
- const segments = path.split('/').filter(Boolean);
100
-
101
- // --- Discovery ---
102
- if (segments.length === 0 && method === 'GET') {
103
- return new Response(JSON.stringify({ data: await dispatcher.getDiscoveryInfo(prefix) }), {
104
- status: 200,
105
- headers: { 'Content-Type': 'application/json' },
106
- });
107
- }
108
-
109
- if (segments.length === 1 && segments[0] === 'discovery' && method === 'GET') {
110
- return new Response(JSON.stringify({ data: await dispatcher.getDiscoveryInfo(prefix) }), {
111
- status: 200,
112
- headers: { 'Content-Type': 'application/json' },
113
- });
114
- }
115
-
116
- try {
117
- // --- Auth (needs auth service integration) ---
118
- if (segments[0] === 'auth') {
119
- const subPath = segments.slice(1).join('/');
120
-
121
- // Try AuthPlugin service first (prefer async to support factory-based services)
122
- let authService: AuthService | null = null;
123
- try {
124
- if (typeof options.kernel.getServiceAsync === 'function') {
125
- authService = await options.kernel.getServiceAsync<AuthService>('auth');
126
- } else if (typeof options.kernel.getService === 'function') {
127
- authService = options.kernel.getService<AuthService>('auth');
128
- }
129
- } catch {
130
- // Service not registered — fall through to dispatcher
131
- authService = null;
132
- }
133
-
134
- if (authService && typeof authService.handleRequest === 'function') {
135
- return await authService.handleRequest(request);
136
- }
137
-
138
- // Fallback to dispatcher
139
- const body = method === 'GET' || method === 'HEAD'
140
- ? {}
141
- : await request.json().catch(() => ({}));
142
- const result = await dispatcher.handleAuth(subPath, method, body, { request });
143
- return toResponse(result);
144
- }
145
-
146
- // --- GraphQL (returns raw result, not HttpDispatcherResult) ---
147
- if (segments[0] === 'graphql' && method === 'POST') {
148
- const body = await request.json() as { query: string; variables?: any };
149
- const result = await dispatcher.handleGraphQL(body, { request });
150
- return new Response(JSON.stringify(result), {
151
- status: 200,
152
- headers: { 'Content-Type': 'application/json' },
153
- });
154
- }
155
-
156
- // --- Storage (needs formData parsing) ---
157
- if (segments[0] === 'storage') {
158
- const subPath = segments.slice(1).join('/');
159
- let file: any = undefined;
160
- if (method === 'POST' && subPath === 'upload') {
161
- const formData = await request.formData();
162
- file = formData.get('file');
163
- }
164
- const result = await dispatcher.handleStorage(
165
- subPath ? `/${subPath}` : '',
166
- method,
167
- file,
168
- { request },
169
- );
170
- return toResponse(result);
171
- }
172
-
173
- // --- Catch-all: delegate to dispatcher.dispatch() ---
174
- // Handles meta, data, packages, analytics, automation, i18n, ui,
175
- // openapi, custom API endpoints, and any future routes.
176
- const subPath = path || '';
177
-
178
- let body: any = undefined;
179
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
180
- body = await request.json().catch(() => ({}));
181
- }
182
-
183
- const queryParams: Record<string, any> = {};
184
- url.searchParams.forEach((val, key) => { queryParams[key] = val; });
185
-
186
- const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request }, prefix);
187
- return toResponse(result);
188
- } catch (err: any) {
189
- return errorJson(err.message || 'Internal Server Error', err.statusCode || 500);
190
- }
191
- };
192
- }
193
-
194
- /**
195
- * Creates a SvelteKit handle hook that attaches the kernel to event.locals.
196
- *
197
- * @example
198
- * ```ts
199
- * // src/hooks.server.ts
200
- * import { createHandle } from '@objectstack/sveltekit';
201
- * export const handle = createHandle({ kernel });
202
- * ```
203
- */
204
- export function createHandle(options: SvelteKitAdapterOptions) {
205
- return async function handle({ event, resolve }: { event: any; resolve: (event: any) => Promise<Response> }) {
206
- event.locals.objectStack = options.kernel;
207
- return resolve(event);
208
- };
209
- }
@@ -1,287 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { describe, it, expect, vi, beforeEach } from 'vitest';
4
-
5
- // Mock dispatcher instance
6
- const mockDispatcher = {
7
- getDiscoveryInfo: vi.fn().mockReturnValue({ version: '1.0', endpoints: [] }),
8
- handleAuth: vi.fn().mockResolvedValue({ handled: true, response: { body: { ok: true }, status: 200 } }),
9
- handleGraphQL: vi.fn().mockResolvedValue({ data: {} }),
10
- handleStorage: vi.fn().mockResolvedValue({ handled: true, response: { body: {}, status: 200 } }),
11
- dispatch: vi.fn().mockResolvedValue({ handled: true, response: { body: { success: true }, status: 200 } }),
12
- };
13
-
14
- vi.mock('@objectstack/runtime', () => {
15
- return {
16
- HttpDispatcher: function HttpDispatcher() {
17
- return mockDispatcher;
18
- },
19
- };
20
- });
21
-
22
- import { createRequestHandler, createHandle } from './index';
23
-
24
- const mockKernel = { name: 'test-kernel' } as any;
25
-
26
- function makeEvent(url: string, method = 'GET', body?: any): any {
27
- const parsedUrl = new URL(url, 'http://localhost');
28
- const init: RequestInit = { method };
29
- if (body) {
30
- init.body = JSON.stringify(body);
31
- init.headers = { 'Content-Type': 'application/json' };
32
- }
33
- return {
34
- request: new Request(parsedUrl, init),
35
- url: parsedUrl,
36
- params: {},
37
- };
38
- }
39
-
40
- describe('createRequestHandler', () => {
41
- let handler: ReturnType<typeof createRequestHandler>;
42
-
43
- beforeEach(() => {
44
- vi.clearAllMocks();
45
- handler = createRequestHandler({ kernel: mockKernel });
46
- });
47
-
48
- describe('Discovery', () => {
49
- it('GET /api returns discovery info', async () => {
50
- const event = makeEvent('http://localhost/api');
51
- const res = await handler(event);
52
- expect(res.status).toBe(200);
53
- const json = await res.json();
54
- expect(json.data.version).toBe('1.0');
55
- expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api');
56
- });
57
-
58
- it('uses custom prefix', async () => {
59
- const customHandler = createRequestHandler({ kernel: mockKernel, prefix: '/v2' });
60
- const event = makeEvent('http://localhost/v2');
61
- const res = await customHandler(event);
62
- expect(res.status).toBe(200);
63
- expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2');
64
- });
65
- });
66
-
67
- describe('Auth', () => {
68
- it('POST /api/auth/login calls handleAuth', async () => {
69
- const event = makeEvent('http://localhost/api/auth/login', 'POST', { email: 'a@b.com' });
70
- const res = await handler(event);
71
- expect(res.status).toBe(200);
72
- const json = await res.json();
73
- expect(json.ok).toBe(true);
74
- expect(mockDispatcher.handleAuth).toHaveBeenCalledWith(
75
- 'login', 'POST', { email: 'a@b.com' },
76
- expect.objectContaining({ request: expect.anything() }),
77
- );
78
- });
79
-
80
- it('GET /api/auth/callback calls handleAuth with empty body', async () => {
81
- const event = makeEvent('http://localhost/api/auth/callback');
82
- const res = await handler(event);
83
- expect(res.status).toBe(200);
84
- expect(mockDispatcher.handleAuth).toHaveBeenCalledWith(
85
- 'callback', 'GET', {},
86
- expect.objectContaining({ request: expect.anything() }),
87
- );
88
- });
89
-
90
- it('returns error on exception', async () => {
91
- mockDispatcher.handleAuth.mockRejectedValueOnce(
92
- Object.assign(new Error('Unauthorized'), { statusCode: 401 }),
93
- );
94
- const event = makeEvent('http://localhost/api/auth/login', 'POST', {});
95
- const res = await handler(event);
96
- expect(res.status).toBe(401);
97
- const json = await res.json();
98
- expect(json.success).toBe(false);
99
- });
100
- });
101
-
102
- describe('Auth via AuthPlugin service', () => {
103
- it('uses kernel.getService("auth") when available', async () => {
104
- const mockHandleRequest = vi.fn().mockResolvedValue(
105
- new Response(JSON.stringify({ user: { id: '1' } }), {
106
- status: 200,
107
- headers: { 'Content-Type': 'application/json' },
108
- }),
109
- );
110
- const kernelWithAuth = {
111
- ...mockKernel,
112
- getService: vi.fn().mockReturnValue({ handleRequest: mockHandleRequest }),
113
- };
114
- const authHandler = createRequestHandler({ kernel: kernelWithAuth });
115
- const event = makeEvent('http://localhost/api/auth/sign-in/email', 'POST', { email: 'a@b.com' });
116
- const res = await authHandler(event);
117
- expect(res.status).toBe(200);
118
- expect(kernelWithAuth.getService).toHaveBeenCalledWith('auth');
119
- expect(mockHandleRequest).toHaveBeenCalled();
120
- });
121
-
122
- it('uses kernel.getServiceAsync("auth") when available (async factory)', async () => {
123
- const mockHandleRequest = vi.fn().mockResolvedValue(
124
- new Response(JSON.stringify({ user: { id: '2' } }), {
125
- status: 200,
126
- headers: { 'Content-Type': 'application/json' },
127
- }),
128
- );
129
- const kernelWithAsyncAuth = {
130
- ...mockKernel,
131
- getServiceAsync: vi.fn().mockResolvedValue({ handleRequest: mockHandleRequest }),
132
- };
133
- const authHandler = createRequestHandler({ kernel: kernelWithAsyncAuth });
134
- const event = makeEvent('http://localhost/api/auth/sign-in/email', 'POST', { email: 'a@b.com' });
135
- const res = await authHandler(event);
136
- expect(res.status).toBe(200);
137
- expect(kernelWithAsyncAuth.getServiceAsync).toHaveBeenCalledWith('auth');
138
- expect(mockHandleRequest).toHaveBeenCalled();
139
- });
140
-
141
- it('falls back to dispatcher when getServiceAsync throws', async () => {
142
- const kernelWithFailingAsync = {
143
- ...mockKernel,
144
- getServiceAsync: vi.fn().mockRejectedValue(new Error("Service 'auth' not found")),
145
- };
146
- const authHandler = createRequestHandler({ kernel: kernelWithFailingAsync });
147
- const event = makeEvent('http://localhost/api/auth/login', 'POST', { email: 'a@b.com' });
148
- const res = await authHandler(event);
149
- expect(res.status).toBe(200);
150
- expect(mockDispatcher.handleAuth).toHaveBeenCalled();
151
- });
152
- });
153
-
154
- describe('GraphQL', () => {
155
- it('POST /api/graphql calls handleGraphQL', async () => {
156
- const body = { query: '{ objects { name } }' };
157
- const event = makeEvent('http://localhost/api/graphql', 'POST', body);
158
- const res = await handler(event);
159
- expect(res.status).toBe(200);
160
- const json = await res.json();
161
- expect(json.data).toBeDefined();
162
- expect(mockDispatcher.handleGraphQL).toHaveBeenCalledWith(
163
- body, expect.objectContaining({ request: expect.anything() }),
164
- );
165
- });
166
- });
167
-
168
- describe('Metadata', () => {
169
- it('GET /api/meta/objects delegates to dispatch()', async () => {
170
- const event = makeEvent('http://localhost/api/meta/objects');
171
- const res = await handler(event);
172
- expect(res.status).toBe(200);
173
- expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
174
- 'GET',
175
- '/meta/objects',
176
- undefined,
177
- expect.any(Object),
178
- expect.objectContaining({ request: expect.anything() }),
179
- '/api',
180
- );
181
- });
182
- });
183
-
184
- describe('Data', () => {
185
- it('GET /api/data/account delegates to dispatch()', async () => {
186
- const event = makeEvent('http://localhost/api/data/account');
187
- const res = await handler(event);
188
- expect(res.status).toBe(200);
189
- expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
190
- 'GET',
191
- '/data/account',
192
- undefined,
193
- expect.any(Object),
194
- expect.objectContaining({ request: expect.anything() }),
195
- '/api',
196
- );
197
- });
198
-
199
- it('POST /api/data/account parses body', async () => {
200
- const body = { name: 'Acme' };
201
- const event = makeEvent('http://localhost/api/data/account', 'POST', body);
202
- const res = await handler(event);
203
- expect(res.status).toBe(200);
204
- expect(mockDispatcher.dispatch).toHaveBeenCalledWith(
205
- 'POST',
206
- '/data/account',
207
- body,
208
- expect.any(Object),
209
- expect.objectContaining({ request: expect.anything() }),
210
- '/api',
211
- );
212
- });
213
-
214
- it('returns 404 when not handled', async () => {
215
- mockDispatcher.dispatch.mockResolvedValueOnce({ handled: false });
216
- const event = makeEvent('http://localhost/api/data/missing');
217
- const res = await handler(event);
218
- expect(res.status).toBe(404);
219
- });
220
- });
221
-
222
- describe('Storage', () => {
223
- it('GET /api/storage/files calls handleStorage', async () => {
224
- const event = makeEvent('http://localhost/api/storage/files');
225
- const res = await handler(event);
226
- expect(res.status).toBe(200);
227
- expect(mockDispatcher.handleStorage).toHaveBeenCalledWith(
228
- '/files', 'GET', undefined,
229
- expect.objectContaining({ request: expect.anything() }),
230
- );
231
- });
232
- });
233
-
234
- describe('Error handling', () => {
235
- it('returns 404 for unknown routes', async () => {
236
- mockDispatcher.dispatch.mockResolvedValueOnce({ handled: false });
237
- const event = makeEvent('http://localhost/api/unknown');
238
- const res = await handler(event);
239
- expect(res.status).toBe(404);
240
- });
241
-
242
- it('returns 500 on generic error', async () => {
243
- mockDispatcher.dispatch.mockRejectedValueOnce(new Error());
244
- const event = makeEvent('http://localhost/api/data/account');
245
- const res = await handler(event);
246
- expect(res.status).toBe(500);
247
- });
248
- });
249
-
250
- describe('toResponse', () => {
251
- it('handles redirect result', async () => {
252
- mockDispatcher.dispatch.mockResolvedValueOnce({
253
- handled: true,
254
- result: { type: 'redirect', url: 'https://example.com' },
255
- });
256
- const event = makeEvent('http://localhost/api/data/redir');
257
- const res = await handler(event);
258
- expect(res.status).toBe(302);
259
- expect(res.headers.get('location')).toBe('https://example.com');
260
- });
261
-
262
- it('handles generic result objects', async () => {
263
- mockDispatcher.dispatch.mockResolvedValueOnce({
264
- handled: true,
265
- result: { foo: 'bar' },
266
- });
267
- const event = makeEvent('http://localhost/api/data/custom');
268
- const res = await handler(event);
269
- expect(res.status).toBe(200);
270
- const json = await res.json();
271
- expect(json.foo).toBe('bar');
272
- });
273
- });
274
- });
275
-
276
- describe('createHandle', () => {
277
- it('attaches kernel to event.locals', async () => {
278
- const handle = createHandle({ kernel: mockKernel });
279
- const event: any = { locals: {} };
280
- const resolve = vi.fn().mockResolvedValue(new Response('ok'));
281
-
282
- await handle({ event, resolve });
283
-
284
- expect(event.locals.objectStack).toBe(mockKernel);
285
- expect(resolve).toHaveBeenCalledWith(event);
286
- });
287
- });
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "module": "NodeNext",
7
- "moduleResolution": "NodeNext",
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "lib": [
11
- "ES2020",
12
- "DOM",
13
- "DOM.Iterable"
14
- ],
15
- "types": [
16
- "node"
17
- ]
18
- },
19
- "include": [
20
- "src/**/*"
21
- ],
22
- "exclude": [
23
- "node_modules",
24
- "dist"
25
- ]
26
- }
package/vitest.config.ts DELETED
@@ -1,16 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { defineConfig } from 'vitest/config';
4
- import path from 'node:path';
5
-
6
- export default defineConfig({
7
- test: {
8
- globals: true,
9
- environment: 'node',
10
- },
11
- resolve: {
12
- alias: {
13
- '@objectstack/runtime': path.resolve(__dirname, 'src/__mocks__/runtime.ts'),
14
- },
15
- },
16
- });