@objectstack/express 4.0.4 → 4.1.0
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 +67 -8
- package/package.json +31 -5
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -194
- package/src/__mocks__/runtime.ts +0 -4
- package/src/express.test.ts +0 -84
- package/src/index.ts +0 -188
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -16
package/README.md
CHANGED
|
@@ -1,22 +1,81 @@
|
|
|
1
1
|
# @objectstack/express
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Express adapter for ObjectStack — mounts the auto-generated REST API and route dispatcher onto an Express app.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
- Full Auth/GraphQL/Metadata/Data/Storage routes
|
|
8
|
-
- AuthPlugin service support with Web Request conversion
|
|
9
|
-
- Middleware mode for attaching kernel to requests
|
|
5
|
+
[](https://www.npmjs.com/package/@objectstack/express)
|
|
6
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
10
7
|
|
|
11
|
-
##
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Wraps `HttpDispatcher` from `@objectstack/runtime` as an Express `Router` (or request middleware). All ObjectStack routes — CRUD, batch, metadata, discovery, auth, storage, GraphQL — are handled by a catch-all that delegates to the dispatcher, so new protocol routes work automatically without adapter updates.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @objectstack/express express
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
12
19
|
|
|
13
20
|
```typescript
|
|
14
21
|
import express from 'express';
|
|
15
22
|
import { createExpressRouter } from '@objectstack/express';
|
|
23
|
+
import { kernel } from './my-kernel';
|
|
16
24
|
|
|
17
25
|
const app = express();
|
|
18
26
|
app.use(express.json());
|
|
19
27
|
app.use('/api', createExpressRouter({ kernel }));
|
|
20
|
-
|
|
21
28
|
app.listen(3000);
|
|
22
29
|
```
|
|
30
|
+
|
|
31
|
+
### Middleware mode
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { objectStackMiddleware } from '@objectstack/express';
|
|
35
|
+
|
|
36
|
+
app.use(objectStackMiddleware(kernel));
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Key Exports
|
|
40
|
+
|
|
41
|
+
| Export | Kind | Description |
|
|
42
|
+
|:---|:---|:---|
|
|
43
|
+
| `createExpressRouter(options)` | function | Returns a mounted `Router` with all ObjectStack dispatchers. |
|
|
44
|
+
| `objectStackMiddleware(kernel)` | function | Per-request middleware (use for custom routing/prefix). |
|
|
45
|
+
| `ExpressAdapterOptions` | interface | `{ kernel: ObjectKernel, prefix?: string }`. |
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
| Option | Type | Default | Notes |
|
|
50
|
+
|:---|:---|:---|:---|
|
|
51
|
+
| `kernel` | `ObjectKernel` | — | Bootstrapped kernel from `@objectstack/core`. |
|
|
52
|
+
| `prefix` | `string` | `'/api'` | Base path for the mounted router. |
|
|
53
|
+
|
|
54
|
+
## Middleware order
|
|
55
|
+
|
|
56
|
+
`express.json()` (or an equivalent body parser) MUST be registered **before** the ObjectStack router. Auth cookies require `cookie-parser` if you use cookie sessions. The adapter sets response headers directly; do not wrap with compression middleware that rewrites JSON bodies before the router.
|
|
57
|
+
|
|
58
|
+
## When to use
|
|
59
|
+
|
|
60
|
+
- ✅ Adding ObjectStack to an existing Express app.
|
|
61
|
+
- ✅ Node.js deployments without edge runtime constraints.
|
|
62
|
+
|
|
63
|
+
## When not to use
|
|
64
|
+
|
|
65
|
+
- ❌ Edge / Workers — use [`@objectstack/hono`](../hono) instead.
|
|
66
|
+
- ❌ Next.js App Router — use [`@objectstack/nextjs`](../nextjs).
|
|
67
|
+
|
|
68
|
+
## Related Packages
|
|
69
|
+
|
|
70
|
+
- [`@objectstack/runtime`](../../runtime) — provides `HttpDispatcher`.
|
|
71
|
+
- [`@objectstack/rest`](../../rest) — route registration used by the dispatcher.
|
|
72
|
+
- [`@objectstack/core`](../../core) — kernel.
|
|
73
|
+
|
|
74
|
+
## Links
|
|
75
|
+
|
|
76
|
+
- 📖 Docs: <https://objectstack.ai/docs>
|
|
77
|
+
- 📚 API Reference: <https://objectstack.ai/docs/references>
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
Apache-2.0 © ObjectStack
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/express",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,14 +13,40 @@
|
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"express": "^5.1.0",
|
|
16
|
-
"@objectstack/runtime": "^4.0
|
|
16
|
+
"@objectstack/runtime": "^4.1.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/express": "^5.0.6",
|
|
20
20
|
"express": "^5.2.1",
|
|
21
|
-
"typescript": "^6.0.
|
|
22
|
-
"vitest": "^4.1.
|
|
23
|
-
"@objectstack/runtime": "4.0
|
|
21
|
+
"typescript": "^6.0.3",
|
|
22
|
+
"vitest": "^4.1.7",
|
|
23
|
+
"@objectstack/runtime": "4.1.0"
|
|
24
|
+
},
|
|
25
|
+
"description": "Express adapter for ObjectStack — mounts the generated REST API onto an Express app.",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"objectstack",
|
|
28
|
+
"express",
|
|
29
|
+
"adapter",
|
|
30
|
+
"rest",
|
|
31
|
+
"node"
|
|
32
|
+
],
|
|
33
|
+
"author": "ObjectStack",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/objectstack-ai/framework.git",
|
|
37
|
+
"directory": "packages/adapters/express"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://objectstack.ai/docs",
|
|
40
|
+
"bugs": "https://github.com/objectstack-ai/framework/issues",
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
24
50
|
},
|
|
25
51
|
"scripts": {
|
|
26
52
|
"build": "tsup --config ../../../tsup.config.ts",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @objectstack/express@4.0.4 build /home/runner/work/framework/framework/packages/adapters/express
|
|
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
|
-
[32mCJS[39m [1mdist/index.js [22m[32m5.80 KB[39m
|
|
14
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m10.64 KB[39m
|
|
15
|
-
[32mCJS[39m ⚡️ Build success in 59ms
|
|
16
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m4.72 KB[39m
|
|
17
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[32m10.59 KB[39m
|
|
18
|
-
[32mESM[39m ⚡️ Build success in 60ms
|
|
19
|
-
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in 13153ms
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.18 KB[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.18 KB[39m
|
package/CHANGELOG.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# @objectstack/express
|
|
2
|
-
|
|
3
|
-
## 4.0.4
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- @objectstack/runtime@4.0.4
|
|
8
|
-
|
|
9
|
-
## 4.0.3
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- @objectstack/runtime@4.0.3
|
|
14
|
-
|
|
15
|
-
## 4.0.2
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- @objectstack/runtime@4.0.2
|
|
20
|
-
|
|
21
|
-
## 4.0.0
|
|
22
|
-
|
|
23
|
-
### Patch Changes
|
|
24
|
-
|
|
25
|
-
- f08ffc3: Fix discovery API endpoint routing and protocol consistency.
|
|
26
|
-
|
|
27
|
-
**Discovery route standardization:**
|
|
28
|
-
|
|
29
|
-
- All adapters (Express, Fastify, Hono, NestJS, Next.js, Nuxt, SvelteKit) now mount the discovery endpoint at `{prefix}/discovery` instead of `{prefix}` root.
|
|
30
|
-
- `.well-known/objectstack` redirects now point to `{prefix}/discovery`.
|
|
31
|
-
- Client `connect()` fallback URL changed from `/api/v1` to `/api/v1/discovery`.
|
|
32
|
-
- Runtime dispatcher handles both `/discovery` (standard) and `/` (legacy) for backward compatibility.
|
|
33
|
-
|
|
34
|
-
**Schema & route alignment:**
|
|
35
|
-
|
|
36
|
-
- Added `storage` (service: `file-storage`) and `feed` (service: `data`) routes to `DEFAULT_DISPATCHER_ROUTES`.
|
|
37
|
-
- Added `feed` and `discovery` fields to `ApiRoutesSchema`.
|
|
38
|
-
- Unified `GetDiscoveryResponseSchema` with `DiscoverySchema` as single source of truth.
|
|
39
|
-
- Client `getRoute('feed')` fallback updated from `/api/v1/data` to `/api/v1/feed`.
|
|
40
|
-
|
|
41
|
-
**Type safety:**
|
|
42
|
-
|
|
43
|
-
- Extracted `ApiRouteType` from `ApiRoutes` keys for type-safe client route resolution.
|
|
44
|
-
- Removed `as any` type casting in client route access.
|
|
45
|
-
|
|
46
|
-
- Updated dependencies [f08ffc3]
|
|
47
|
-
- Updated dependencies [e0b0a78]
|
|
48
|
-
- @objectstack/runtime@4.0.0
|
|
49
|
-
|
|
50
|
-
## 3.3.1
|
|
51
|
-
|
|
52
|
-
### Patch Changes
|
|
53
|
-
|
|
54
|
-
- @objectstack/runtime@3.3.1
|
|
55
|
-
|
|
56
|
-
## 3.3.0
|
|
57
|
-
|
|
58
|
-
### Patch Changes
|
|
59
|
-
|
|
60
|
-
- @objectstack/runtime@3.3.0
|
|
61
|
-
|
|
62
|
-
## 3.2.9
|
|
63
|
-
|
|
64
|
-
### Patch Changes
|
|
65
|
-
|
|
66
|
-
- @objectstack/runtime@3.2.9
|
|
67
|
-
|
|
68
|
-
## 3.2.8
|
|
69
|
-
|
|
70
|
-
### Patch Changes
|
|
71
|
-
|
|
72
|
-
- @objectstack/runtime@3.2.8
|
|
73
|
-
|
|
74
|
-
## 3.2.8
|
|
75
|
-
|
|
76
|
-
### Patch Changes
|
|
77
|
-
|
|
78
|
-
- fix: unified catch-all dispatch pattern — `createExpressRouter()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
|
|
79
|
-
- Only auth (service check), storage (file upload), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
|
|
80
|
-
|
|
81
|
-
## 3.2.7
|
|
82
|
-
|
|
83
|
-
### Patch Changes
|
|
84
|
-
|
|
85
|
-
- @objectstack/runtime@3.2.7
|
|
86
|
-
|
|
87
|
-
## 3.2.6
|
|
88
|
-
|
|
89
|
-
### Patch Changes
|
|
90
|
-
|
|
91
|
-
- @objectstack/runtime@3.2.6
|
|
92
|
-
|
|
93
|
-
## 3.2.5
|
|
94
|
-
|
|
95
|
-
### Patch Changes
|
|
96
|
-
|
|
97
|
-
- @objectstack/runtime@3.2.5
|
|
98
|
-
|
|
99
|
-
## 3.2.4
|
|
100
|
-
|
|
101
|
-
### Patch Changes
|
|
102
|
-
|
|
103
|
-
- @objectstack/runtime@3.2.4
|
|
104
|
-
|
|
105
|
-
## 3.2.3
|
|
106
|
-
|
|
107
|
-
### Patch Changes
|
|
108
|
-
|
|
109
|
-
- @objectstack/runtime@3.2.3
|
|
110
|
-
|
|
111
|
-
## 3.2.2
|
|
112
|
-
|
|
113
|
-
### Patch Changes
|
|
114
|
-
|
|
115
|
-
- @objectstack/runtime@3.2.2
|
|
116
|
-
|
|
117
|
-
## 3.2.1
|
|
118
|
-
|
|
119
|
-
### Patch Changes
|
|
120
|
-
|
|
121
|
-
- @objectstack/runtime@3.2.1
|
|
122
|
-
|
|
123
|
-
## 3.2.0
|
|
124
|
-
|
|
125
|
-
### Patch Changes
|
|
126
|
-
|
|
127
|
-
- @objectstack/runtime@3.2.0
|
|
128
|
-
|
|
129
|
-
## 3.1.1
|
|
130
|
-
|
|
131
|
-
### Patch Changes
|
|
132
|
-
|
|
133
|
-
- @objectstack/runtime@3.1.1
|
|
134
|
-
|
|
135
|
-
## 3.1.0
|
|
136
|
-
|
|
137
|
-
### Patch Changes
|
|
138
|
-
|
|
139
|
-
- @objectstack/runtime@3.1.0
|
|
140
|
-
|
|
141
|
-
## 3.0.11
|
|
142
|
-
|
|
143
|
-
### Patch Changes
|
|
144
|
-
|
|
145
|
-
- @objectstack/runtime@3.0.11
|
|
146
|
-
|
|
147
|
-
## 3.0.10
|
|
148
|
-
|
|
149
|
-
### Patch Changes
|
|
150
|
-
|
|
151
|
-
- @objectstack/runtime@3.0.10
|
|
152
|
-
|
|
153
|
-
## 3.0.9
|
|
154
|
-
|
|
155
|
-
### Patch Changes
|
|
156
|
-
|
|
157
|
-
- @objectstack/runtime@3.0.9
|
|
158
|
-
|
|
159
|
-
## 3.0.8
|
|
160
|
-
|
|
161
|
-
### Patch Changes
|
|
162
|
-
|
|
163
|
-
- @objectstack/runtime@3.0.8
|
|
164
|
-
|
|
165
|
-
## 3.0.7
|
|
166
|
-
|
|
167
|
-
### Patch Changes
|
|
168
|
-
|
|
169
|
-
- @objectstack/runtime@3.0.7
|
|
170
|
-
|
|
171
|
-
## 3.0.6
|
|
172
|
-
|
|
173
|
-
### Patch Changes
|
|
174
|
-
|
|
175
|
-
- @objectstack/runtime@3.0.6
|
|
176
|
-
|
|
177
|
-
## 3.0.5
|
|
178
|
-
|
|
179
|
-
### Patch Changes
|
|
180
|
-
|
|
181
|
-
- @objectstack/runtime@3.0.5
|
|
182
|
-
|
|
183
|
-
## 3.0.4
|
|
184
|
-
|
|
185
|
-
### Patch Changes
|
|
186
|
-
|
|
187
|
-
- @objectstack/runtime@3.0.4
|
|
188
|
-
|
|
189
|
-
## 3.0.3
|
|
190
|
-
|
|
191
|
-
### Patch Changes
|
|
192
|
-
|
|
193
|
-
- Updated dependencies [c7267f6]
|
|
194
|
-
- @objectstack/runtime@3.0.3
|
package/src/__mocks__/runtime.ts
DELETED
package/src/express.test.ts
DELETED
|
@@ -1,84 +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 { createExpressRouter, objectStackMiddleware } from './index';
|
|
23
|
-
|
|
24
|
-
const mockKernel = { name: 'test-kernel' } as any;
|
|
25
|
-
|
|
26
|
-
function createMockRes() {
|
|
27
|
-
const res: any = {
|
|
28
|
-
_status: 200,
|
|
29
|
-
_body: null,
|
|
30
|
-
_headers: {} as Record<string, string>,
|
|
31
|
-
_redirectUrl: null as string | null,
|
|
32
|
-
status(code: number) { res._status = code; return res; },
|
|
33
|
-
json(body: any) { res._body = body; return res; },
|
|
34
|
-
send(body: any) { res._body = body; return res; },
|
|
35
|
-
set(k: string, v: string) { res._headers[k] = v; return res; },
|
|
36
|
-
redirect(url: string) { res._redirectUrl = url; return res; },
|
|
37
|
-
};
|
|
38
|
-
return res;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createMockReq(overrides: any = {}) {
|
|
42
|
-
return {
|
|
43
|
-
method: 'GET',
|
|
44
|
-
path: '/',
|
|
45
|
-
url: '/',
|
|
46
|
-
originalUrl: '/',
|
|
47
|
-
body: {},
|
|
48
|
-
query: {},
|
|
49
|
-
headers: {},
|
|
50
|
-
params: {},
|
|
51
|
-
protocol: 'http',
|
|
52
|
-
get: (key: string) => key === 'host' ? 'localhost' : undefined,
|
|
53
|
-
...overrides,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
describe('createExpressRouter', () => {
|
|
58
|
-
let router: any;
|
|
59
|
-
|
|
60
|
-
beforeEach(() => {
|
|
61
|
-
vi.clearAllMocks();
|
|
62
|
-
router = createExpressRouter({ kernel: mockKernel });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('returns a router with registered routes', () => {
|
|
66
|
-
expect(router).toBeDefined();
|
|
67
|
-
expect(router.stack).toBeDefined();
|
|
68
|
-
expect(router.stack.length).toBeGreaterThan(0);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('objectStackMiddleware', () => {
|
|
73
|
-
it('attaches kernel to request', () => {
|
|
74
|
-
const middleware = objectStackMiddleware(mockKernel);
|
|
75
|
-
const req: any = {};
|
|
76
|
-
const res = createMockRes();
|
|
77
|
-
const next = vi.fn();
|
|
78
|
-
|
|
79
|
-
middleware(req, res, next);
|
|
80
|
-
|
|
81
|
-
expect(req.objectStack).toBe(mockKernel);
|
|
82
|
-
expect(next).toHaveBeenCalled();
|
|
83
|
-
});
|
|
84
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { type Router, type Request, type Response, type NextFunction, Router as createRouter } from 'express';
|
|
4
|
-
import { type ObjectKernel, HttpDispatcher, HttpDispatcherResult } from '@objectstack/runtime';
|
|
5
|
-
|
|
6
|
-
export interface ExpressAdapterOptions {
|
|
7
|
-
kernel: ObjectKernel;
|
|
8
|
-
prefix?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Auth service interface with handleRequest method
|
|
13
|
-
*/
|
|
14
|
-
interface AuthService {
|
|
15
|
-
handleRequest(request: globalThis.Request): Promise<globalThis.Response>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Creates an Express Router with all ObjectStack route dispatchers.
|
|
20
|
-
*
|
|
21
|
-
* Only routes that need framework-specific handling (auth service, storage
|
|
22
|
-
* file upload, GraphQL raw result, discovery wrapper) are registered explicitly.
|
|
23
|
-
* All other routes are handled by a catch-all that delegates to
|
|
24
|
-
* `HttpDispatcher.dispatch()`, making the adapter automatically support
|
|
25
|
-
* new routes added to the dispatcher.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* import express from 'express';
|
|
30
|
-
* import { createExpressRouter } from '@objectstack/express';
|
|
31
|
-
*
|
|
32
|
-
* const app = express();
|
|
33
|
-
* app.use('/api', createExpressRouter({ kernel }));
|
|
34
|
-
* app.listen(3000);
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export function createExpressRouter(options: ExpressAdapterOptions): Router {
|
|
38
|
-
const router = createRouter();
|
|
39
|
-
const dispatcher = new HttpDispatcher(options.kernel);
|
|
40
|
-
const prefix = options.prefix || '/api';
|
|
41
|
-
|
|
42
|
-
const sendResult = (result: HttpDispatcherResult, res: Response) => {
|
|
43
|
-
if (result.handled) {
|
|
44
|
-
if (result.response) {
|
|
45
|
-
if (result.response.headers) {
|
|
46
|
-
Object.entries(result.response.headers).forEach(([k, v]) => res.set(k, v as string));
|
|
47
|
-
}
|
|
48
|
-
return res.status(result.response.status).json(result.response.body);
|
|
49
|
-
}
|
|
50
|
-
if (result.result) {
|
|
51
|
-
const response = result.result;
|
|
52
|
-
if (response.type === 'redirect' && response.url) {
|
|
53
|
-
return res.redirect(response.url);
|
|
54
|
-
}
|
|
55
|
-
if (response.type === 'stream' && response.stream) {
|
|
56
|
-
if (response.headers) {
|
|
57
|
-
Object.entries(response.headers).forEach(([k, v]) => res.set(k, v as string));
|
|
58
|
-
}
|
|
59
|
-
response.stream.pipe(res);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
return res.status(200).json(response);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return res.status(404).json({ success: false, error: { message: 'Not Found', code: 404 } });
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const errorResponse = (err: any, res: Response) => {
|
|
69
|
-
return res.status(err.statusCode || 500).json({
|
|
70
|
-
success: false,
|
|
71
|
-
error: {
|
|
72
|
-
message: err.message || 'Internal Server Error',
|
|
73
|
-
code: err.statusCode || 500,
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// ─── Explicit routes (framework-specific handling required) ────────────────
|
|
79
|
-
|
|
80
|
-
// --- Discovery ---
|
|
81
|
-
router.get('/discovery', async (_req: Request, res: Response) => {
|
|
82
|
-
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// --- Auth (needs auth service integration) ---
|
|
86
|
-
router.all('/auth/{*path}', async (req: Request, res: Response) => {
|
|
87
|
-
try {
|
|
88
|
-
const path = (req.params as any).path;
|
|
89
|
-
const method = req.method;
|
|
90
|
-
|
|
91
|
-
// Try AuthPlugin service first (prefer async to support factory-based services)
|
|
92
|
-
let authService: AuthService | null = null;
|
|
93
|
-
try {
|
|
94
|
-
if (typeof options.kernel.getServiceAsync === 'function') {
|
|
95
|
-
authService = await options.kernel.getServiceAsync<AuthService>('auth');
|
|
96
|
-
} else if (typeof options.kernel.getService === 'function') {
|
|
97
|
-
authService = options.kernel.getService<AuthService>('auth');
|
|
98
|
-
}
|
|
99
|
-
} catch {
|
|
100
|
-
// Service not registered — fall through to dispatcher
|
|
101
|
-
authService = null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (authService && typeof authService.handleRequest === 'function') {
|
|
105
|
-
const protocol = req.protocol || 'http';
|
|
106
|
-
const host = req.get?.('host') || req.headers?.host || 'localhost';
|
|
107
|
-
const url = `${protocol}://${host}${req.originalUrl || req.url}`;
|
|
108
|
-
const headers = new Headers();
|
|
109
|
-
if (req.headers) {
|
|
110
|
-
Object.entries(req.headers).forEach(([k, v]) => {
|
|
111
|
-
if (typeof v === 'string') headers.set(k, v);
|
|
112
|
-
else if (Array.isArray(v)) headers.set(k, v.join(', '));
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
const init: RequestInit = { method, headers };
|
|
116
|
-
if (method !== 'GET' && method !== 'HEAD' && req.body) {
|
|
117
|
-
init.body = JSON.stringify(req.body);
|
|
118
|
-
if (!headers.has('content-type')) {
|
|
119
|
-
headers.set('content-type', 'application/json');
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const webRequest = new Request(url, init);
|
|
123
|
-
const response = await authService.handleRequest(webRequest);
|
|
124
|
-
res.status(response.status);
|
|
125
|
-
response.headers.forEach((v: string, k: string) => res.set(k, v));
|
|
126
|
-
const text = await response.text();
|
|
127
|
-
return res.send(text);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Fallback to dispatcher
|
|
131
|
-
const body = method === 'GET' || method === 'HEAD' ? {} : req.body || {};
|
|
132
|
-
const result = await dispatcher.handleAuth(path, method, body, { request: req, response: res });
|
|
133
|
-
return sendResult(result, res);
|
|
134
|
-
} catch (err: any) {
|
|
135
|
-
return errorResponse(err, res);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// --- GraphQL (returns raw result, not HttpDispatcherResult) ---
|
|
140
|
-
router.post('/graphql', async (req: Request, res: Response) => {
|
|
141
|
-
try {
|
|
142
|
-
const result = await dispatcher.handleGraphQL(req.body, { request: req });
|
|
143
|
-
return res.json(result);
|
|
144
|
-
} catch (err: any) {
|
|
145
|
-
return errorResponse(err, res);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// --- Storage (needs file upload handling) ---
|
|
150
|
-
router.all('/storage/{*path}', async (req: Request, res: Response) => {
|
|
151
|
-
try {
|
|
152
|
-
const subPath = '/' + (req.params as any).path;
|
|
153
|
-
const method = req.method;
|
|
154
|
-
const file = (req as any).file || (req as any).files?.file;
|
|
155
|
-
const result = await dispatcher.handleStorage(subPath, method, file, { request: req });
|
|
156
|
-
return sendResult(result, res);
|
|
157
|
-
} catch (err: any) {
|
|
158
|
-
return errorResponse(err, res);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────
|
|
163
|
-
// Handles meta, data, packages, analytics, automation, i18n, ui, openapi,
|
|
164
|
-
// custom API endpoints, and any future routes added to HttpDispatcher.
|
|
165
|
-
router.all('/{*path}', async (req: Request, res: Response) => {
|
|
166
|
-
try {
|
|
167
|
-
const subPath = '/' + (req.params as any).path;
|
|
168
|
-
const method = req.method;
|
|
169
|
-
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? req.body : undefined;
|
|
170
|
-
const result = await dispatcher.dispatch(method, subPath, body, req.query, { request: req, response: res }, prefix);
|
|
171
|
-
return sendResult(result, res);
|
|
172
|
-
} catch (err: any) {
|
|
173
|
-
return errorResponse(err, res);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
return router;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Middleware that attaches the ObjectStack kernel to the request.
|
|
182
|
-
*/
|
|
183
|
-
export function objectStackMiddleware(kernel: ObjectKernel) {
|
|
184
|
-
return (req: Request, _res: Response, next: NextFunction) => {
|
|
185
|
-
(req as any).objectStack = kernel;
|
|
186
|
-
next();
|
|
187
|
-
};
|
|
188
|
-
}
|
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
|
-
});
|