@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 +56 -8
- package/package.json +32 -7
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -188
- package/src/__mocks__/runtime.ts +0 -4
- package/src/index.ts +0 -209
- package/src/sveltekit.test.ts +0 -287
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -16
package/README.md
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
# @objectstack/sveltekit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> SvelteKit adapter for ObjectStack — server endpoints and `handle` hook for the auto-generated REST API.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
- Full Auth/GraphQL/Metadata/Data/Storage routes
|
|
8
|
-
- AuthPlugin service support
|
|
9
|
-
- Handle hook for attaching kernel to event.locals
|
|
5
|
+
[](https://www.npmjs.com/package/@objectstack/sveltekit)
|
|
6
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
10
7
|
|
|
11
|
-
##
|
|
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
|
+
"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.
|
|
16
|
-
"@objectstack/runtime": "^4.0.
|
|
15
|
+
"@sveltejs/kit": "^2.58.0",
|
|
16
|
+
"@objectstack/runtime": "^4.0.5"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@sveltejs/kit": "^2.
|
|
20
|
-
"typescript": "^6.0.
|
|
21
|
-
"vitest": "^4.1.
|
|
22
|
-
"@objectstack/runtime": "4.0.
|
|
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",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -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
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
|
-
[34mCLI[39m Target: es2020
|
|
10
|
-
[34mCLI[39m Cleaning output folder
|
|
11
|
-
[34mESM[39m Build start
|
|
12
|
-
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m4.78 KB[39m
|
|
14
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[32m10.84 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in 27ms
|
|
16
|
-
[32mCJS[39m [1mdist/index.js [22m[32m5.83 KB[39m
|
|
17
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m10.88 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 27ms
|
|
19
|
-
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in 10309ms
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.65 KB[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.65 KB[39m
|
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
|
package/src/__mocks__/runtime.ts
DELETED
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
|
-
}
|
package/src/sveltekit.test.ts
DELETED
|
@@ -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
|
-
});
|