typeowl-example-backend 1.0.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 +68 -0
- package/package.json +22 -0
- package/src/server.ts +239 -0
- package/src/types/Blog.ts +11 -0
- package/src/types/Product.ts +7 -0
- package/src/types/anothertype.ts +22 -0
- package/src/types/index.ts +5 -0
- package/tsconfig.json +14 -0
- package/typeowl.server.config.ts +201 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="../frontend/public/typeOwl.logo.png" alt="TypeOwl Logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# TypeOwl Example Backend
|
|
6
|
+
|
|
7
|
+
A Fastify API server that exposes types via TypeOwl.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install
|
|
13
|
+
npm run dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The server starts on `http://localhost:3001`.
|
|
17
|
+
|
|
18
|
+
## API Endpoints
|
|
19
|
+
|
|
20
|
+
| Method | Path | Description |
|
|
21
|
+
|--------|------|-------------|
|
|
22
|
+
| GET | `/api/users` | List all users |
|
|
23
|
+
| GET | `/api/users/:id` | Get user by ID |
|
|
24
|
+
| POST | `/api/users` | Create a new user |
|
|
25
|
+
| DELETE | `/api/users/:id` | Delete a user |
|
|
26
|
+
| GET | `/api/posts` | List all posts |
|
|
27
|
+
| GET | `/api/posts/:id` | Get post by ID |
|
|
28
|
+
| POST | `/api/posts` | Create a new post |
|
|
29
|
+
|
|
30
|
+
## TypeOwl Endpoints
|
|
31
|
+
|
|
32
|
+
| Path | Description |
|
|
33
|
+
|------|-------------|
|
|
34
|
+
| `/__typeowl` | JSON manifest (metadata + file pointers) |
|
|
35
|
+
| `/__typeowl/types/users.d.ts` | User domain types |
|
|
36
|
+
| `/__typeowl/types/posts.d.ts` | Post domain types |
|
|
37
|
+
| `/__typeowl/types/common.d.ts` | Shared types (ApiError, etc.) |
|
|
38
|
+
| `/__typeowl/types/index.d.ts` | All types + ApiEndpoints |
|
|
39
|
+
|
|
40
|
+
## How It Works
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createTypeOwl } from 'typeowl/server';
|
|
44
|
+
import { z } from 'zod';
|
|
45
|
+
|
|
46
|
+
const typeowl = createTypeOwl({ version: '1.0.0' });
|
|
47
|
+
|
|
48
|
+
// Register types by domain
|
|
49
|
+
typeowl
|
|
50
|
+
.domain('users')
|
|
51
|
+
.registerZod('User', UserSchema)
|
|
52
|
+
.registerZod('CreateUserInput', CreateUserSchema);
|
|
53
|
+
|
|
54
|
+
// Register endpoints
|
|
55
|
+
typeowl
|
|
56
|
+
.domain('main')
|
|
57
|
+
.get('/api/users', 'User[]')
|
|
58
|
+
.post('/api/users', 'User', { body: 'CreateUserInput' });
|
|
59
|
+
|
|
60
|
+
// Handle TypeOwl requests in your server
|
|
61
|
+
app.get('/__typeowl/*', (req, reply) => {
|
|
62
|
+
const response = typeowl.handleRequest(req.url);
|
|
63
|
+
if (response) {
|
|
64
|
+
return reply.type(response.contentType).send(response.body);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typeowl-example-backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Example backend using TypeOwl for type sharing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/server.ts",
|
|
8
|
+
"start": "node dist/server.js",
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fastify/cors": "^9.0.1",
|
|
13
|
+
"fastify": "^4.26.0",
|
|
14
|
+
"typeowl": "file:../..",
|
|
15
|
+
"zod": "^4.3.5"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.10.0",
|
|
19
|
+
"tsx": "^4.7.0",
|
|
20
|
+
"typescript": "^5.3.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦉 TypeOwl Example Backend
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates THREE ways to define API types:
|
|
5
|
+
*
|
|
6
|
+
* 🔴 OPTION 1: No TypeOwl - Raw handlers, no type exposure
|
|
7
|
+
* → Frontend uses `any` or manual types
|
|
8
|
+
*
|
|
9
|
+
* 🟡 OPTION 2: Static types only - Types extracted from src/types/
|
|
10
|
+
* → Frontend can force-cast using exposed static types
|
|
11
|
+
*
|
|
12
|
+
* 🟢 OPTION 3: typeowl.endpoint() - Full type generation + validation
|
|
13
|
+
* → Frontend gets auto-generated endpoint types
|
|
14
|
+
*
|
|
15
|
+
* Run: npm run dev
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import Fastify from 'fastify';
|
|
19
|
+
import cors from '@fastify/cors';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { initTypeOwl } from 'typeowl/server';
|
|
22
|
+
|
|
23
|
+
// Import types from our types folder (for use in this file)
|
|
24
|
+
import type { Blog, Product } from './types/index.js';
|
|
25
|
+
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
// 🦉 TYPEOWL - Auto-loads from typeowl.server.config.ts
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
const typeowl = await initTypeOwl();
|
|
31
|
+
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
33
|
+
// 📦 ZOD SCHEMAS (for typeowl.endpoint validation)
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
// Common params schema
|
|
37
|
+
const IdParamsSchema = z.object({ id: z.string() });
|
|
38
|
+
|
|
39
|
+
// User schemas
|
|
40
|
+
const UserSchema = z.object({
|
|
41
|
+
id: z.string(),
|
|
42
|
+
email: z.string().email(),
|
|
43
|
+
name: z.string(),
|
|
44
|
+
role: z.enum(['admin', 'user', 'guest']),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const CreateUserSchema = z.object({
|
|
48
|
+
email: z.string().email(),
|
|
49
|
+
name: z.string(),
|
|
50
|
+
role: z.enum(['admin', 'user', 'guest']).default('user'),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Health check (untyped endpoint)
|
|
54
|
+
const HealthSchema = z.object({
|
|
55
|
+
status: z.string(),
|
|
56
|
+
timestamp: z.string(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
60
|
+
// 🌐 FASTIFY SERVER
|
|
61
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
62
|
+
|
|
63
|
+
const app = Fastify({ logger: false });
|
|
64
|
+
|
|
65
|
+
await app.register(cors, { origin: true });
|
|
66
|
+
|
|
67
|
+
// Mount TypeOwl routes (from config)
|
|
68
|
+
await typeowl.mount(app);
|
|
69
|
+
|
|
70
|
+
// ─── Sample Data ─────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const blogs: Blog[] = [
|
|
73
|
+
{ id: '1', title: 'Hello World', content: 'Welcome to TypeOwl!', published: true, createdAt: new Date().toISOString() },
|
|
74
|
+
{ id: '2', title: 'Getting Started', content: 'Let me show you how...', published: true, createdAt: new Date().toISOString() },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const products: Product[] = [
|
|
78
|
+
{ id: '1', name: 'TypeOwl Pro', price: 99, description: 'Full-featured type sharing', inStock: true },
|
|
79
|
+
{ id: '2', name: 'TypeOwl Starter', price: 0, inStock: true },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
type User = z.infer<typeof UserSchema>;
|
|
83
|
+
const users: User[] = [
|
|
84
|
+
{ id: '1', email: 'alice@example.com', name: 'Alice', role: 'admin' },
|
|
85
|
+
{ id: '2', email: 'bob@example.com', name: 'Bob', role: 'user' },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
89
|
+
// 🔴 OPTION 1: No TypeOwl - Raw Fastify handlers
|
|
90
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
91
|
+
// These endpoints are NOT registered with TypeOwl.
|
|
92
|
+
// Frontend must use `any` or define types manually.
|
|
93
|
+
|
|
94
|
+
app.get('/api/health', async () => {
|
|
95
|
+
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
99
|
+
// 🟡 OPTION 2: Static types only - Types exposed via extract config
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
|
+
// Blog type is extracted from src/types/Blog.ts via typeowl.server.config.ts
|
|
102
|
+
// The TYPE is exposed, but endpoints are NOT mapped.
|
|
103
|
+
// Frontend can force-cast responses using the exposed Blog type.
|
|
104
|
+
|
|
105
|
+
app.get('/api/blogs', async () => blogs);
|
|
106
|
+
|
|
107
|
+
app.get('/api/blogs/:id', async (request, reply) => {
|
|
108
|
+
const { id } = request.params as { id: string };
|
|
109
|
+
const blog = blogs.find(b => b.id === id);
|
|
110
|
+
if (!blog) return reply.code(404).send({ error: 'Blog not found' });
|
|
111
|
+
return blog;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
115
|
+
// 🟢 OPTION 3: typeowl.endpoint() - Full type generation + validation
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
117
|
+
// These endpoints have AUTOMATIC validation and type registration!
|
|
118
|
+
// Schemas are extracted to TypeOwl AND used for runtime validation.
|
|
119
|
+
// Frontend gets fully typed ApiEndpoints with body/params/response.
|
|
120
|
+
|
|
121
|
+
// GET /api/users - List all users
|
|
122
|
+
typeowl.endpoint(app, 'GET', '/api/users', {
|
|
123
|
+
response: z.array(UserSchema),
|
|
124
|
+
description: 'List all users',
|
|
125
|
+
}, async () => {
|
|
126
|
+
return users;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// GET /api/users/:id - Get user by ID
|
|
130
|
+
typeowl.endpoint(app, 'GET', '/api/users/:id', {
|
|
131
|
+
params: IdParamsSchema,
|
|
132
|
+
response: UserSchema,
|
|
133
|
+
description: 'Get user by ID',
|
|
134
|
+
}, async ({ params, reply }) => {
|
|
135
|
+
const user = users.find(u => u.id === params.id);
|
|
136
|
+
if (!user) {
|
|
137
|
+
(reply as { code: (n: number) => { send: (d: unknown) => void } }).code(404).send({ error: 'User not found' });
|
|
138
|
+
return undefined as never;
|
|
139
|
+
}
|
|
140
|
+
return user;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// POST /api/users - Create user (with body validation!)
|
|
144
|
+
typeowl.endpoint(app, 'POST', '/api/users', {
|
|
145
|
+
body: CreateUserSchema,
|
|
146
|
+
response: UserSchema,
|
|
147
|
+
description: 'Create a new user',
|
|
148
|
+
}, async ({ body }) => {
|
|
149
|
+
// body is already validated and typed as { email: string, name: string, role: 'admin'|'user'|'guest' }
|
|
150
|
+
const newUser: User = {
|
|
151
|
+
id: String(users.length + 1),
|
|
152
|
+
email: body.email,
|
|
153
|
+
name: body.name,
|
|
154
|
+
role: body.role,
|
|
155
|
+
};
|
|
156
|
+
users.push(newUser);
|
|
157
|
+
return newUser;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// DELETE /api/users/:id - Delete user
|
|
161
|
+
typeowl.endpoint(app, 'DELETE', '/api/users/:id', {
|
|
162
|
+
params: IdParamsSchema,
|
|
163
|
+
response: z.object({ success: z.boolean(), message: z.string() }),
|
|
164
|
+
description: 'Delete a user',
|
|
165
|
+
}, async ({ params, reply }) => {
|
|
166
|
+
const index = users.findIndex(u => u.id === params.id);
|
|
167
|
+
if (index === -1) {
|
|
168
|
+
(reply as { code: (n: number) => { send: (d: unknown) => void } }).code(404).send({ error: 'User not found' });
|
|
169
|
+
return undefined as never;
|
|
170
|
+
}
|
|
171
|
+
users.splice(index, 1);
|
|
172
|
+
return { success: true, message: 'User deleted' };
|
|
173
|
+
});
|
|
174
|
+
// POST /api/blogs - Create a new blog
|
|
175
|
+
typeowl.endpoint(app, 'POST', '/api/blogs', {
|
|
176
|
+
body:'BlogInput',
|
|
177
|
+
response: 'Blog',
|
|
178
|
+
description: 'Create a new blog',
|
|
179
|
+
}, async ({ body }) => console.log(body));
|
|
180
|
+
|
|
181
|
+
// GET /api/products - List all products
|
|
182
|
+
typeowl.endpoint(app, 'GET', '/api/products', {
|
|
183
|
+
response: 'Product[]',
|
|
184
|
+
description: 'List all products',
|
|
185
|
+
}, async () => products);
|
|
186
|
+
|
|
187
|
+
// GET /api/products/:id - Get product by ID
|
|
188
|
+
typeowl.endpoint(app, 'GET', '/api/products/:id', {
|
|
189
|
+
params: IdParamsSchema,
|
|
190
|
+
response: 'Product | null', // Using extracted type reference!
|
|
191
|
+
description: 'Get product by ID',
|
|
192
|
+
}, async ({ params, reply }) => {
|
|
193
|
+
const product = products.find(p => p.id === params.id);
|
|
194
|
+
if (!product) {
|
|
195
|
+
(reply as { code: (n: number) => { send: (d: unknown) => void } }).code(404).send({ error: 'Product not found' });
|
|
196
|
+
return undefined as never;
|
|
197
|
+
}
|
|
198
|
+
return product;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ─── Start Server ────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
const PORT = 3001;
|
|
204
|
+
|
|
205
|
+
app.listen({ port: PORT, host: '0.0.0.0' }, (err) => {
|
|
206
|
+
if (err) {
|
|
207
|
+
console.error(err);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`
|
|
212
|
+
\x1b[36m🦉 TypeOwl Example Backend\x1b[0m
|
|
213
|
+
\x1b[2m──────────────────────────────\x1b[0m
|
|
214
|
+
|
|
215
|
+
Server: \x1b[32mhttp://localhost:${PORT}\x1b[0m
|
|
216
|
+
|
|
217
|
+
\x1b[31m🔴 No TypeOwl (raw handlers):\x1b[0m
|
|
218
|
+
GET /api/health
|
|
219
|
+
|
|
220
|
+
\x1b[33m🟡 Static types only (Blog exposed, endpoint not mapped):\x1b[0m
|
|
221
|
+
GET /api/blogs
|
|
222
|
+
GET /api/blogs/:id
|
|
223
|
+
|
|
224
|
+
\x1b[32m🟢 typeowl.endpoint() (full type + validation):\x1b[0m
|
|
225
|
+
GET /api/products
|
|
226
|
+
GET /api/products/:id
|
|
227
|
+
GET /api/users
|
|
228
|
+
GET /api/users/:id
|
|
229
|
+
POST /api/users \x1b[33m← Try invalid body!\x1b[0m
|
|
230
|
+
DELETE /api/users/:id
|
|
231
|
+
|
|
232
|
+
TypeOwl Endpoints:
|
|
233
|
+
\x1b[33mhttp://localhost:${PORT}/__typeowl\x1b[0m
|
|
234
|
+
\x1b[33mhttp://localhost:${PORT}/__typeowl/types/content.d.ts\x1b[0m
|
|
235
|
+
\x1b[33mhttp://localhost:${PORT}/__typeowl/types/endpoints.d.ts\x1b[0m
|
|
236
|
+
|
|
237
|
+
\x1b[2mPress Ctrl+C to stop\x1b[0m
|
|
238
|
+
`);
|
|
239
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type Developer = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
email: string;
|
|
5
|
+
role: DeveloperRole;
|
|
6
|
+
};
|
|
7
|
+
enum DeveloperRole {
|
|
8
|
+
ADMIN = 'admin',
|
|
9
|
+
USER = 'user',
|
|
10
|
+
GUEST = 'guest',
|
|
11
|
+
}
|
|
12
|
+
type customeType<T extends string | number> = `custom_${T}`;
|
|
13
|
+
type Author = {
|
|
14
|
+
isDeveloper: boolean;
|
|
15
|
+
developers: Map<string, Developer>;
|
|
16
|
+
customType: customeType<string>;
|
|
17
|
+
};
|
|
18
|
+
type NoUsageType = {
|
|
19
|
+
id: string;
|
|
20
|
+
noUsage: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type { Author , NoUsageType};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src"
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦉 TypeOwl Server Configuration
|
|
3
|
+
*
|
|
4
|
+
* This is the central configuration file for TypeOwl.
|
|
5
|
+
* All settings for type extraction, serving, and security are defined here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineServerConfig } from 'typeowl/server';
|
|
9
|
+
import type { FastifyInstance } from 'fastify';
|
|
10
|
+
|
|
11
|
+
// Helper to cast app to your framework type
|
|
12
|
+
const asFastify = (app: unknown) => app as FastifyInstance;
|
|
13
|
+
|
|
14
|
+
export default defineServerConfig({
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
// 📍 BASE CONFIGURATION
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Base path for TypeOwl endpoints.
|
|
21
|
+
* All type endpoints will be served under this path.
|
|
22
|
+
*
|
|
23
|
+
* @default '/__typeowl'
|
|
24
|
+
* @example '/__typeowl' -> http://localhost:3001/__typeowl/types/content.d.ts
|
|
25
|
+
*/
|
|
26
|
+
basePath: '/__typeowl',
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Version string for cache invalidation.
|
|
30
|
+
* Change this when your types change to bust client caches.
|
|
31
|
+
*
|
|
32
|
+
* @example '1.0.0', 'v2', process.env.npm_package_version
|
|
33
|
+
*/
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Include git commit hash in the manifest.
|
|
38
|
+
* Useful for debugging which version is deployed.
|
|
39
|
+
*
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
includeGitCommit: false,
|
|
43
|
+
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
+
// 📦 TYPE EXTRACTION
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 🔒 Allowed source files/directories for type extraction.
|
|
50
|
+
* Only files within these paths can have their types extracted.
|
|
51
|
+
* This is a security measure to prevent accidental exposure of sensitive types.
|
|
52
|
+
*
|
|
53
|
+
* Can be:
|
|
54
|
+
* - Single file: './src/types.ts'
|
|
55
|
+
* - Single directory: './src/types/'
|
|
56
|
+
* - Multiple paths: ['./src/types/', './src/models/']
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ Required for extractAndRegister() to work.
|
|
59
|
+
*/
|
|
60
|
+
typeSources: './src/types/',
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Types to extract from source files.
|
|
64
|
+
* Maps domain names to source locations and type names.
|
|
65
|
+
*
|
|
66
|
+
* Each domain becomes a separate .d.ts file:
|
|
67
|
+
* - content -> /__typeowl/types/content.d.ts
|
|
68
|
+
* - models -> /__typeowl/types/models.d.ts
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* extract: {
|
|
72
|
+
* content: { from: './src/types/', types: ['Blog', 'Product'] },
|
|
73
|
+
* models: { from: './src/models/', types: ['User', 'Order'] },
|
|
74
|
+
* }
|
|
75
|
+
*/
|
|
76
|
+
extract: {
|
|
77
|
+
content: {
|
|
78
|
+
from: './src/types/',
|
|
79
|
+
types: '*', // Extract ALL exported types automatically!
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
84
|
+
// 🛡️ GUARD
|
|
85
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Guard settings for TypeOwl endpoints.
|
|
89
|
+
*/
|
|
90
|
+
guard: {
|
|
91
|
+
/**
|
|
92
|
+
* When to enable TypeOwl endpoints:
|
|
93
|
+
* - true: Always enabled (use with caution in production!)
|
|
94
|
+
* - false: Always disabled
|
|
95
|
+
* - 'development': Only when NODE_ENV !== 'production'
|
|
96
|
+
*
|
|
97
|
+
* @default 'development'
|
|
98
|
+
*/
|
|
99
|
+
enabled: 'development',
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* API key required to access type endpoints.
|
|
103
|
+
* If set, clients must include the key via:
|
|
104
|
+
* - Header: X-TypeOwl-Key: your-api-key
|
|
105
|
+
* - Query param: ?key=your-api-key
|
|
106
|
+
*
|
|
107
|
+
* @example process.env.TYPEOWL_API_KEY
|
|
108
|
+
*/
|
|
109
|
+
// apiKey: process.env.TYPEOWL_API_KEY,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
113
|
+
// 🚀 SERVING MODE
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* How TypeOwl serves types:
|
|
118
|
+
*
|
|
119
|
+
* - 'dynamic': Types served at runtime via registerRoutes.
|
|
120
|
+
* Your server handles requests to /__typeowl/*.
|
|
121
|
+
* Requires registerRoutes to be defined.
|
|
122
|
+
*
|
|
123
|
+
* - 'static': Types generated as static files by CLI.
|
|
124
|
+
* Run: npx typeowl generate
|
|
125
|
+
* Files output to basePath (e.g., ./public/__typeowl/).
|
|
126
|
+
* registerRoutes is optional (not needed for static hosting).
|
|
127
|
+
*
|
|
128
|
+
* @default 'dynamic'
|
|
129
|
+
*/
|
|
130
|
+
mode: 'dynamic',
|
|
131
|
+
|
|
132
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
133
|
+
// 🔌 ROUTE REGISTRATION
|
|
134
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register TypeOwl routes on your server framework.
|
|
138
|
+
*
|
|
139
|
+
* This function receives:
|
|
140
|
+
* - appInstance: Your server instance (Fastify, Express, Hono, etc.)
|
|
141
|
+
* - typeowl: The TypeOwl handler for processing requests
|
|
142
|
+
* - config: This config object for reference
|
|
143
|
+
*
|
|
144
|
+
* Called when you run: typeowl.mount(app)
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* // Fastify
|
|
148
|
+
* registerRoutes: (app, typeowl, config) => {
|
|
149
|
+
* app.get('/__typeowl/*', (req, reply) => { ... });
|
|
150
|
+
* }
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* // Express
|
|
154
|
+
* registerRoutes: (app, typeowl, config) => {
|
|
155
|
+
* app.use('/__typeowl', (req, res) => { ... });
|
|
156
|
+
* }
|
|
157
|
+
*/
|
|
158
|
+
registerRoutes: (appInstance, typeowl, config) => {
|
|
159
|
+
const app = asFastify(appInstance);
|
|
160
|
+
const basePath = typeowl.getBasePath();
|
|
161
|
+
const guard = config.guard;
|
|
162
|
+
|
|
163
|
+
// Guard check helper
|
|
164
|
+
const checkGuard = (request: { headers: Record<string, string | string[] | undefined>; query: Record<string, string> }) => {
|
|
165
|
+
return typeowl.validateRequest(
|
|
166
|
+
{ path: '', headers: request.headers, query: request.query },
|
|
167
|
+
guard
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Manifest endpoint: /__typeowl
|
|
172
|
+
app.get(`${basePath}`, async (request, reply) => {
|
|
173
|
+
const auth = checkGuard(request as never);
|
|
174
|
+
if (!auth.valid) return reply.code(401).send({ error: auth.error });
|
|
175
|
+
const response = typeowl.handleRequest(basePath);
|
|
176
|
+
if (response) return reply.type(response.contentType).send(response.body);
|
|
177
|
+
return reply.code(404).send({ error: 'Not found' });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Manifest JSON endpoint: /__typeowl/manifest.json
|
|
181
|
+
app.get(`${basePath}/manifest.json`, async (request, reply) => {
|
|
182
|
+
const auth = checkGuard(request as never);
|
|
183
|
+
if (!auth.valid) return reply.code(401).send({ error: auth.error });
|
|
184
|
+
const response = typeowl.handleRequest(`${basePath}/manifest.json`);
|
|
185
|
+
if (response) return reply.type(response.contentType).send(response.body);
|
|
186
|
+
return reply.code(404).send({ error: 'Not found' });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Type files endpoint: /__typeowl/types/{file}.d.ts
|
|
190
|
+
app.get(`${basePath}/types/:file`, async (request, reply) => {
|
|
191
|
+
const auth = checkGuard(request as never);
|
|
192
|
+
if (!auth.valid) return reply.code(401).send({ error: auth.error });
|
|
193
|
+
const { file } = request.params as { file: string };
|
|
194
|
+
const response = typeowl.handleRequest(`${basePath}/types/${file}`);
|
|
195
|
+
if (response) return reply.type(response.contentType).send(response.body);
|
|
196
|
+
return reply.code(404).send({ error: 'Not found' });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
console.log(` 🦉 TypeOwl registered at ${basePath}`);
|
|
200
|
+
},
|
|
201
|
+
});
|