@fjell/express-router 4.4.55 ā 4.4.57
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/dist/util/general.js +1 -1
- package/dist/util/general.js.map +2 -2
- package/package.json +11 -11
- package/MIGRATION_v3.md +0 -255
- package/build.js +0 -4
- package/docs/docs.config.ts +0 -44
- package/docs/index.html +0 -18
- package/docs/package.json +0 -34
- package/docs/public/README.md +0 -332
- package/docs/public/examples-README.md +0 -339
- package/docs/public/fjell-icon.svg +0 -1
- package/docs/public/pano.png +0 -0
- package/docs/tsconfig.node.json +0 -6
- package/examples/README.md +0 -386
- package/examples/basic-router-example.ts +0 -391
- package/examples/full-application-example.ts +0 -768
- package/examples/nested-router-example.ts +0 -601
- package/examples/router-handlers-example.ts +0 -394
- package/examples/router-options-example.ts +0 -214
- package/vitest.config.ts +0 -39
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
/**
|
|
3
|
-
* Basic Express Router Example
|
|
4
|
-
*
|
|
5
|
-
* This example demonstrates the fundamental usage of fjell-express-router for setting up
|
|
6
|
-
* Express routes that automatically handle CRUD operations for data models.
|
|
7
|
-
* It shows how to create routers, integrate with Express apps, and handle basic HTTP operations.
|
|
8
|
-
*
|
|
9
|
-
* Perfect for understanding the basics of fjell-express-router before moving to advanced features.
|
|
10
|
-
*
|
|
11
|
-
* Run this example with: npx tsx examples/basic-router-example.ts
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Item, PriKey, UUID } from '@fjell/core';
|
|
15
|
-
import { NotFoundError } from '@fjell/lib';
|
|
16
|
-
import express, { Application } from 'express';
|
|
17
|
-
import { createRegistry, PItemRouter } from '../src';
|
|
18
|
-
|
|
19
|
-
// Define our data models
|
|
20
|
-
export interface User extends Item<'user'> {
|
|
21
|
-
id: string;
|
|
22
|
-
name: string;
|
|
23
|
-
email: string;
|
|
24
|
-
role: 'admin' | 'user' | 'guest';
|
|
25
|
-
lastLogin?: Date;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface Task extends Item<'task'> {
|
|
29
|
-
id: string;
|
|
30
|
-
title: string;
|
|
31
|
-
description: string;
|
|
32
|
-
assignedTo?: string;
|
|
33
|
-
status: 'pending' | 'in-progress' | 'completed';
|
|
34
|
-
priority: 'low' | 'medium' | 'high';
|
|
35
|
-
dueDate?: Date;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Mock storage for demonstration (in real apps, this would be your database/API)
|
|
39
|
-
const mockUserStorage = new Map<string, User>();
|
|
40
|
-
const mockTaskStorage = new Map<string, Task>();
|
|
41
|
-
|
|
42
|
-
// Counters for unique ID generation
|
|
43
|
-
let userIdCounter = 1000;
|
|
44
|
-
let taskIdCounter = 1000;
|
|
45
|
-
|
|
46
|
-
// Initialize with some sample data
|
|
47
|
-
const initializeSampleData = () => {
|
|
48
|
-
const users: User[] = [
|
|
49
|
-
{
|
|
50
|
-
key: { kt: 'user', pk: 'user-1' as UUID },
|
|
51
|
-
id: 'user-1',
|
|
52
|
-
name: 'Alice Johnson',
|
|
53
|
-
email: 'alice@example.com',
|
|
54
|
-
role: 'admin',
|
|
55
|
-
lastLogin: new Date('2025-01-15T10:30:00Z'),
|
|
56
|
-
events: {
|
|
57
|
-
created: { at: new Date('2025-01-01T00:00:00Z') },
|
|
58
|
-
updated: { at: new Date('2025-01-15T10:30:00Z') },
|
|
59
|
-
deleted: { at: null }
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
key: { kt: 'user', pk: 'user-2' as UUID },
|
|
64
|
-
id: 'user-2',
|
|
65
|
-
name: 'Bob Smith',
|
|
66
|
-
email: 'bob@example.com',
|
|
67
|
-
role: 'user',
|
|
68
|
-
lastLogin: new Date('2025-01-14T15:20:00Z'),
|
|
69
|
-
events: {
|
|
70
|
-
created: { at: new Date('2025-01-02T00:00:00Z') },
|
|
71
|
-
updated: { at: new Date('2025-01-14T15:20:00Z') },
|
|
72
|
-
deleted: { at: null }
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
const tasks: Task[] = [
|
|
78
|
-
{
|
|
79
|
-
key: { kt: 'task', pk: 'task-1' as UUID },
|
|
80
|
-
id: 'task-1',
|
|
81
|
-
title: 'Setup project documentation',
|
|
82
|
-
description: 'Create comprehensive documentation for the new project',
|
|
83
|
-
assignedTo: 'user-1',
|
|
84
|
-
status: 'in-progress',
|
|
85
|
-
priority: 'high',
|
|
86
|
-
dueDate: new Date('2025-01-30T00:00:00Z'),
|
|
87
|
-
events: {
|
|
88
|
-
created: { at: new Date('2025-01-10T00:00:00Z') },
|
|
89
|
-
updated: { at: new Date('2025-01-15T00:00:00Z') },
|
|
90
|
-
deleted: { at: null }
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
key: { kt: 'task', pk: 'task-2' as UUID },
|
|
95
|
-
id: 'task-2',
|
|
96
|
-
title: 'Review code changes',
|
|
97
|
-
description: 'Review and approve pending pull requests',
|
|
98
|
-
assignedTo: 'user-2',
|
|
99
|
-
status: 'pending',
|
|
100
|
-
priority: 'medium',
|
|
101
|
-
dueDate: new Date('2025-01-25T00:00:00Z'),
|
|
102
|
-
events: {
|
|
103
|
-
created: { at: new Date('2025-01-12T00:00:00Z') },
|
|
104
|
-
updated: { at: new Date('2025-01-12T00:00:00Z') },
|
|
105
|
-
deleted: { at: null }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
users.forEach(user => mockUserStorage.set(user.id, user));
|
|
111
|
-
tasks.forEach(task => mockTaskStorage.set(task.id, task));
|
|
112
|
-
|
|
113
|
-
console.log('š¦ Initialized sample data:');
|
|
114
|
-
console.log(` Users: ${users.length} records`);
|
|
115
|
-
console.log(` Tasks: ${tasks.length} records`);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Create mock operations for Users and Tasks
|
|
119
|
-
const createUserOperations = () => {
|
|
120
|
-
return {
|
|
121
|
-
async all() {
|
|
122
|
-
console.log('š¦ UserOperations.all() - Fetching all users...');
|
|
123
|
-
return Array.from(mockUserStorage.values());
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
async get(key: PriKey<'user'>) {
|
|
127
|
-
console.log(`š UserOperations.get(${key.pk}) - Fetching user...`);
|
|
128
|
-
const user = mockUserStorage.get(String(key.pk));
|
|
129
|
-
if (!user) {
|
|
130
|
-
throw new NotFoundError('get', { kta: ['user', '', '', '', '', ''], scopes: [] }, key);
|
|
131
|
-
}
|
|
132
|
-
return user;
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
async create(item: User) {
|
|
136
|
-
console.log(`⨠UserOperations.create() - Creating user: ${item.name}`);
|
|
137
|
-
const id = `user-${++userIdCounter}`;
|
|
138
|
-
const newUser: User = {
|
|
139
|
-
...item,
|
|
140
|
-
id,
|
|
141
|
-
key: { kt: 'user', pk: id as UUID },
|
|
142
|
-
events: {
|
|
143
|
-
created: { at: new Date() },
|
|
144
|
-
updated: { at: new Date() },
|
|
145
|
-
deleted: { at: null }
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
mockUserStorage.set(id, newUser);
|
|
149
|
-
return newUser;
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
async update(key: PriKey<'user'>, updates: Partial<User>) {
|
|
153
|
-
console.log(`š UserOperations.update(${key.pk}) - Updating user...`);
|
|
154
|
-
const existing = mockUserStorage.get(String(key.pk));
|
|
155
|
-
if (!existing) {
|
|
156
|
-
throw new NotFoundError('update', { kta: ['user', '', '', '', '', ''], scopes: [] }, key);
|
|
157
|
-
}
|
|
158
|
-
const updated: User = {
|
|
159
|
-
...existing,
|
|
160
|
-
...updates,
|
|
161
|
-
events: {
|
|
162
|
-
...existing.events,
|
|
163
|
-
updated: { at: new Date() }
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
mockUserStorage.set(String(key.pk), updated);
|
|
167
|
-
return updated;
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
async remove(key: PriKey<'user'>) {
|
|
171
|
-
console.log(`šļø UserOperations.remove(${key.pk}) - Removing user...`);
|
|
172
|
-
const existing = mockUserStorage.get(String(key.pk));
|
|
173
|
-
if (!existing) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
mockUserStorage.delete(String(key.pk));
|
|
177
|
-
return existing;
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
async find(finder: string, params: any) {
|
|
181
|
-
console.log(`š UserOperations.find(${finder}) - Finding users...`, params);
|
|
182
|
-
const users = Array.from(mockUserStorage.values());
|
|
183
|
-
|
|
184
|
-
switch (finder) {
|
|
185
|
-
case 'byRole':
|
|
186
|
-
return users.filter(user => user.role === params.role);
|
|
187
|
-
case 'byEmail':
|
|
188
|
-
return users.filter(user => user.email.includes(params.email));
|
|
189
|
-
default:
|
|
190
|
-
return users;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const createTaskOperations = () => {
|
|
197
|
-
return {
|
|
198
|
-
async all() {
|
|
199
|
-
console.log('š¦ TaskOperations.all() - Fetching all tasks...');
|
|
200
|
-
return Array.from(mockTaskStorage.values());
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
async get(key: PriKey<'task'>) {
|
|
204
|
-
console.log(`š TaskOperations.get(${key.pk}) - Fetching task...`);
|
|
205
|
-
const task = mockTaskStorage.get(String(key.pk));
|
|
206
|
-
if (!task) {
|
|
207
|
-
throw new NotFoundError('get', { kta: ['task', '', '', '', '', ''], scopes: [] }, key);
|
|
208
|
-
}
|
|
209
|
-
return task;
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
async create(item: Task) {
|
|
213
|
-
console.log(`⨠TaskOperations.create() - Creating task: ${item.title}`);
|
|
214
|
-
const id = `task-${++taskIdCounter}`;
|
|
215
|
-
const newTask: Task = {
|
|
216
|
-
...item,
|
|
217
|
-
id,
|
|
218
|
-
key: { kt: 'task', pk: id as UUID },
|
|
219
|
-
events: {
|
|
220
|
-
created: { at: new Date() },
|
|
221
|
-
updated: { at: new Date() },
|
|
222
|
-
deleted: { at: null }
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
mockTaskStorage.set(id, newTask);
|
|
226
|
-
return newTask;
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
async update(key: PriKey<'task'>, updates: Partial<Task>) {
|
|
230
|
-
console.log(`š TaskOperations.update(${key.pk}) - Updating task...`);
|
|
231
|
-
const existing = mockTaskStorage.get(String(key.pk));
|
|
232
|
-
if (!existing) {
|
|
233
|
-
throw new NotFoundError('update', { kta: ['task', '', '', '', '', ''], scopes: [] }, key);
|
|
234
|
-
}
|
|
235
|
-
const updated: Task = {
|
|
236
|
-
...existing,
|
|
237
|
-
...updates,
|
|
238
|
-
events: {
|
|
239
|
-
...existing.events,
|
|
240
|
-
updated: { at: new Date() }
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
mockTaskStorage.set(String(key.pk), updated);
|
|
244
|
-
return updated;
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
async remove(key: PriKey<'task'>) {
|
|
248
|
-
console.log(`šļø TaskOperations.remove(${key.pk}) - Removing task...`);
|
|
249
|
-
const existing = mockTaskStorage.get(String(key.pk));
|
|
250
|
-
if (!existing) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
mockTaskStorage.delete(String(key.pk));
|
|
254
|
-
return existing;
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
async find(finder: string, params: any) {
|
|
258
|
-
console.log(`š TaskOperations.find(${finder}) - Finding tasks...`, params);
|
|
259
|
-
const tasks = Array.from(mockTaskStorage.values());
|
|
260
|
-
|
|
261
|
-
switch (finder) {
|
|
262
|
-
case 'byStatus':
|
|
263
|
-
return tasks.filter(task => task.status === params.status);
|
|
264
|
-
case 'byAssignee':
|
|
265
|
-
return tasks.filter(task => task.assignedTo === params.assignedTo);
|
|
266
|
-
case 'byPriority':
|
|
267
|
-
return tasks.filter(task => task.priority === params.priority);
|
|
268
|
-
default:
|
|
269
|
-
return tasks;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Main function demonstrating basic fjell-express-router usage
|
|
277
|
-
*/
|
|
278
|
-
export const runBasicRouterExample = async (): Promise<{ app: Application; userRouter: PItemRouter<User, 'user'>; taskRouter: PItemRouter<Task, 'task'> }> => {
|
|
279
|
-
console.log('š Starting Basic Express Router Example...\n');
|
|
280
|
-
|
|
281
|
-
// Initialize sample data
|
|
282
|
-
initializeSampleData();
|
|
283
|
-
|
|
284
|
-
// Create registry and instances
|
|
285
|
-
console.log('š Creating registry and instances...');
|
|
286
|
-
const registry = createRegistry();
|
|
287
|
-
|
|
288
|
-
// Create mock instances that simulate the fjell pattern
|
|
289
|
-
const mockUserInstance = {
|
|
290
|
-
operations: createUserOperations(),
|
|
291
|
-
options: {}
|
|
292
|
-
} as any;
|
|
293
|
-
|
|
294
|
-
const mockTaskInstance = {
|
|
295
|
-
operations: createTaskOperations(),
|
|
296
|
-
options: {}
|
|
297
|
-
} as any;
|
|
298
|
-
|
|
299
|
-
// Create Express app
|
|
300
|
-
const app: Application = express();
|
|
301
|
-
app.use(express.json());
|
|
302
|
-
|
|
303
|
-
// Add request logging middleware
|
|
304
|
-
app.use((req, res, next) => {
|
|
305
|
-
console.log(`š ${req.method} ${req.path}`, req.query);
|
|
306
|
-
next();
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// Create PItemRouters for our models
|
|
310
|
-
console.log('š¤ļø Creating Express routers...');
|
|
311
|
-
const userRouter = new PItemRouter(mockUserInstance, 'user');
|
|
312
|
-
const taskRouter = new PItemRouter(mockTaskInstance, 'task');
|
|
313
|
-
|
|
314
|
-
// Mount the routers on Express app
|
|
315
|
-
// These will automatically create REST endpoints:
|
|
316
|
-
// GET /api/users - Get all users
|
|
317
|
-
// GET /api/users/:userPk - Get specific user
|
|
318
|
-
// POST /api/users - Create new user
|
|
319
|
-
// PUT /api/users/:userPk - Update user
|
|
320
|
-
// DELETE /api/users/:userPk - Delete user
|
|
321
|
-
app.use('/api/users', userRouter.getRouter());
|
|
322
|
-
app.use('/api/tasks', taskRouter.getRouter());
|
|
323
|
-
|
|
324
|
-
// Add some custom routes to demonstrate business logic
|
|
325
|
-
app.get('/api/health', (req, res) => {
|
|
326
|
-
res.json({
|
|
327
|
-
status: 'healthy',
|
|
328
|
-
timestamp: new Date().toISOString(),
|
|
329
|
-
users: mockUserStorage.size,
|
|
330
|
-
tasks: mockTaskStorage.size
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Dashboard route showing summary data
|
|
335
|
-
app.get('/api/dashboard', async (req, res) => {
|
|
336
|
-
try {
|
|
337
|
-
const users = await mockUserInstance.operations.all();
|
|
338
|
-
const tasks = await mockTaskInstance.operations.all();
|
|
339
|
-
|
|
340
|
-
const dashboard = {
|
|
341
|
-
summary: {
|
|
342
|
-
totalUsers: users.length,
|
|
343
|
-
totalTasks: tasks.length,
|
|
344
|
-
completedTasks: tasks.filter((t: Task) => t.status === 'completed').length,
|
|
345
|
-
pendingTasks: tasks.filter((t: Task) => t.status === 'pending').length,
|
|
346
|
-
inProgressTasks: tasks.filter((t: Task) => t.status === 'in-progress').length
|
|
347
|
-
},
|
|
348
|
-
users: users.map((u: User) => ({ id: u.id, name: u.name, role: u.role })),
|
|
349
|
-
recentTasks: tasks
|
|
350
|
-
.sort((a: Task, b: Task) => b.events.created.at.getTime() - a.events.created.at.getTime())
|
|
351
|
-
.slice(0, 5)
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
res.json(dashboard);
|
|
355
|
-
} catch (error) {
|
|
356
|
-
res.status(500).json({ error: 'Failed to load dashboard' });
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
console.log('\nā
Basic Express Router Example setup complete!');
|
|
361
|
-
console.log('\nš Available endpoints:');
|
|
362
|
-
console.log(' GET /api/health - Health check');
|
|
363
|
-
console.log(' GET /api/dashboard - Dashboard summary');
|
|
364
|
-
console.log(' GET /api/users - List all users');
|
|
365
|
-
console.log(' GET /api/users/:userPk - Get specific user');
|
|
366
|
-
console.log(' POST /api/users - Create new user');
|
|
367
|
-
console.log(' PUT /api/users/:userPk - Update user');
|
|
368
|
-
console.log(' DELETE /api/users/:userPk - Delete user');
|
|
369
|
-
console.log(' GET /api/tasks - List all tasks');
|
|
370
|
-
console.log(' GET /api/tasks/:taskPk - Get specific task');
|
|
371
|
-
console.log(' POST /api/tasks - Create new task');
|
|
372
|
-
console.log(' PUT /api/tasks/:taskPk - Update task');
|
|
373
|
-
console.log(' DELETE /api/tasks/:taskPk - Delete task');
|
|
374
|
-
|
|
375
|
-
return { app, userRouter, taskRouter };
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
// If this file is run directly, start the server
|
|
379
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
380
|
-
runBasicRouterExample().then(({ app }) => {
|
|
381
|
-
const PORT = process.env.PORT || 3001;
|
|
382
|
-
app.listen(PORT, () => {
|
|
383
|
-
console.log(`\nš Server running on http://localhost:${PORT}`);
|
|
384
|
-
console.log('\nš” Try these example requests:');
|
|
385
|
-
console.log(` curl http://localhost:${PORT}/api/health`);
|
|
386
|
-
console.log(` curl http://localhost:${PORT}/api/dashboard`);
|
|
387
|
-
console.log(` curl http://localhost:${PORT}/api/users`);
|
|
388
|
-
console.log(` curl http://localhost:${PORT}/api/tasks`);
|
|
389
|
-
});
|
|
390
|
-
}).catch(console.error);
|
|
391
|
-
}
|