@fjell/express-router 4.4.22 → 4.4.24

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.
@@ -0,0 +1,370 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { CItemRouter, createRegistry, PItemRouter } from '../src';
3
+ import { ComKey, Item, PriKey } from '@fjell/core';
4
+ import express from 'express';
5
+ import { Request, Response } from 'express';
6
+
7
+ // Define data models
8
+ interface User extends Item<'user'> {
9
+ id: string;
10
+ name: string;
11
+ email: string;
12
+ role: 'admin' | 'user' | 'guest';
13
+ }
14
+
15
+ interface Post extends Item<'post', 'user'> {
16
+ id: string;
17
+ title: string;
18
+ content: string;
19
+ authorId: string;
20
+ publishedAt?: Date;
21
+ }
22
+
23
+ // Mock operations for demonstration
24
+ const userOperations = {
25
+ create: async (item: User) => ({ ...item, id: `user_${Date.now()}` }),
26
+ get: async (ik: PriKey<'user'>) => ({ id: ik.pk, name: 'John Doe', email: 'john@example.com', role: 'user' as const }),
27
+ update: async (ik: PriKey<'user'>, item: Partial<User>) => ({ id: ik.pk, name: 'John Doe', email: 'john@example.com', role: 'user' as const, ...item }),
28
+ remove: async (ik: PriKey<'user'>) => ({ id: ik.pk, name: 'John Doe', email: 'john@example.com', role: 'user' as const }),
29
+ all: async () => [
30
+ { id: 'user_1', name: 'John Doe', email: 'john@example.com', role: 'user' as const },
31
+ { id: 'user_2', name: 'Jane Smith', email: 'jane@example.com', role: 'admin' as const }
32
+ ],
33
+ find: async () => [
34
+ { id: 'user_1', name: 'John Doe', email: 'john@example.com', role: 'user' as const }
35
+ ],
36
+ action: async (ik: PriKey<'user'>, action: string, body: any) => ({ message: `Library action: ${action}`, userId: ik.pk, body }),
37
+ facet: async (ik: PriKey<'user'>, facet: string, params: any) => ({ message: `Library facet: ${facet}`, userId: ik.pk, params }),
38
+ allAction: async (action: string, body: any) => ({ message: `Library all action: ${action}`, body }),
39
+ allFacet: async (facet: string, params: any) => ({ message: `Library all facet: ${facet}`, params })
40
+ };
41
+
42
+ const postOperations = {
43
+ create: async (item: Post, _options: { locations: any[] }) => ({ ...item, id: `post_${Date.now()}` }),
44
+ get: async (ik: ComKey<'post', 'user'>) => ({ id: ik.pk, title: 'Sample Post', content: 'Sample content', authorId: 'user_1' }),
45
+ update: async (ik: ComKey<'post', 'user'>, item: Partial<Post>) => ({ id: ik.pk, title: 'Sample Post', content: 'Sample content', authorId: 'user_1', ...item }),
46
+ remove: async (ik: ComKey<'post', 'user'>) => ({ id: ik.pk, title: 'Sample Post', content: 'Sample content', authorId: 'user_1' }),
47
+ all: async (_query: any, _locations: any[]) => [
48
+ { id: 'post_1', title: 'Sample Post 1', content: 'Sample content 1', authorId: 'user_1' },
49
+ { id: 'post_2', title: 'Sample Post 2', content: 'Sample content 2', authorId: 'user_1' }
50
+ ],
51
+ find: async (_finder: string, _params: any, _locations: any[]) => [
52
+ { id: 'post_1', title: 'Sample Post 1', content: 'Sample content 1', authorId: 'user_1' }
53
+ ],
54
+ action: async (ik: ComKey<'post', 'user'>, action: string, body: any) => ({ message: `Library action: ${action}`, postId: ik.pk, body }),
55
+ facet: async (ik: ComKey<'post', 'user'>, facet: string, params: any) => ({ message: `Library facet: ${facet}`, postId: ik.pk, params }),
56
+ allAction: async (action: string, body: any) => ({ message: `Library all action: ${action}`, body }),
57
+ allFacet: async (facet: string, params: any) => ({ message: `Library all facet: ${facet}`, params })
58
+ };
59
+
60
+ // Create Express app and registry
61
+ const app = express();
62
+ app.use(express.json());
63
+
64
+ const _registry = createRegistry();
65
+
66
+ // Create instances
67
+ const userInstance = {
68
+ operations: userOperations,
69
+ options: {
70
+ actions: {
71
+ activate: { description: 'Activate user account' },
72
+ deactivate: { description: 'Deactivate user account' }
73
+ },
74
+ facets: {
75
+ profile: { description: 'Get user profile' },
76
+ stats: { description: 'Get user statistics' }
77
+ },
78
+ allActions: {
79
+ bulkActivate: { description: 'Activate multiple users' },
80
+ bulkDeactivate: { description: 'Deactivate multiple users' }
81
+ },
82
+ allFacets: {
83
+ userStats: { description: 'Get overall user statistics' },
84
+ userCount: { description: 'Get user count' }
85
+ }
86
+ }
87
+ } as any;
88
+
89
+ const postInstance = {
90
+ operations: postOperations,
91
+ options: {
92
+ actions: {
93
+ publish: { description: 'Publish post' },
94
+ unpublish: { description: 'Unpublish post' }
95
+ },
96
+ facets: {
97
+ analytics: { description: 'Get post analytics' },
98
+ comments: { description: 'Get post comments' }
99
+ },
100
+ allActions: {
101
+ bulkPublish: { description: 'Publish multiple posts' },
102
+ bulkUnpublish: { description: 'Unpublish multiple posts' }
103
+ },
104
+ allFacets: {
105
+ postStats: { description: 'Get overall post statistics' },
106
+ postCount: { description: 'Get post count' }
107
+ }
108
+ }
109
+ } as any;
110
+
111
+ // Create routers with router-level handlers
112
+ const userRouter = new PItemRouter(userInstance, 'user', {
113
+ // Router-level action handlers
114
+ actions: {
115
+ activate: async (req: Request, res: Response, ik: PriKey<'user'>) => {
116
+ console.log('Router-level activate action called for user:', ik.pk);
117
+ // Custom logic: send activation email, update status, etc.
118
+ res.json({
119
+ message: 'User activated via router handler',
120
+ userId: ik.pk,
121
+ timestamp: new Date().toISOString(),
122
+ emailSent: true
123
+ });
124
+ },
125
+ deactivate: async (req: Request, res: Response, ik: PriKey<'user'>) => {
126
+ console.log('Router-level deactivate action called for user:', ik.pk);
127
+ // Custom logic: send deactivation notification, update status, etc.
128
+ res.json({
129
+ message: 'User deactivated via router handler',
130
+ userId: ik.pk,
131
+ timestamp: new Date().toISOString(),
132
+ notificationSent: true
133
+ });
134
+ }
135
+ },
136
+
137
+ // Router-level facet handlers
138
+ facets: {
139
+ profile: async (req: Request, res: Response, ik: PriKey<'user'>) => {
140
+ console.log('Router-level profile facet called for user:', ik.pk);
141
+ // Custom logic: aggregate data from multiple sources
142
+ res.json({
143
+ userId: ik.pk,
144
+ basicInfo: { name: 'John Doe', email: 'john@example.com' },
145
+ extendedInfo: { lastLogin: new Date(), preferences: { theme: 'dark' } },
146
+ socialInfo: { followers: 150, following: 75 }
147
+ });
148
+ },
149
+ stats: async (req: Request, res: Response, ik: PriKey<'user'>) => {
150
+ console.log('Router-level stats facet called for user:', ik.pk);
151
+ // Custom logic: calculate statistics from multiple data sources
152
+ res.json({
153
+ userId: ik.pk,
154
+ postsCount: 25,
155
+ commentsCount: 150,
156
+ likesReceived: 500,
157
+ lastActivity: new Date()
158
+ });
159
+ }
160
+ },
161
+
162
+ // Router-level all action handlers
163
+ allActions: {
164
+ bulkActivate: async (req: Request, res: Response) => {
165
+ console.log('Router-level bulk activate action called');
166
+ // Custom logic: batch processing, external service integration
167
+ const { userIds } = req.body;
168
+ res.json({
169
+ message: 'Bulk activation via router handler',
170
+ processedUsers: userIds.length,
171
+ timestamp: new Date().toISOString(),
172
+ externalServiceCalled: true
173
+ });
174
+ },
175
+ bulkDeactivate: async (req: Request, res: Response) => {
176
+ console.log('Router-level bulk deactivate action called');
177
+ // Custom logic: batch processing, audit logging
178
+ const { userIds } = req.body;
179
+ res.json({
180
+ message: 'Bulk deactivation via router handler',
181
+ processedUsers: userIds.length,
182
+ timestamp: new Date().toISOString(),
183
+ auditLogged: true
184
+ });
185
+ }
186
+ },
187
+
188
+ // Router-level all facet handlers
189
+ allFacets: {
190
+ userStats: async (req: Request, res: Response) => {
191
+ console.log('Router-level user stats facet called');
192
+ // Custom logic: aggregate statistics from multiple systems
193
+ res.json({
194
+ totalUsers: 1250,
195
+ activeUsers: 890,
196
+ newUsersThisMonth: 45,
197
+ topRoles: { admin: 15, user: 1200, guest: 35 },
198
+ systemHealth: 'excellent'
199
+ });
200
+ },
201
+ userCount: async (req: Request, res: Response) => {
202
+ console.log('Router-level user count facet called');
203
+ // Custom logic: real-time count from cache
204
+ res.json({
205
+ count: 1250,
206
+ lastUpdated: new Date().toISOString(),
207
+ source: 'cache'
208
+ });
209
+ }
210
+ }
211
+ } as any);
212
+
213
+ const postRouter = new CItemRouter(postInstance, 'post', userRouter, {
214
+ // Router-level action handlers for posts
215
+ actions: {
216
+ publish: async (req: Request, res: Response, ik: ComKey<'post', 'user'>) => {
217
+ console.log('Router-level publish action called for post:', ik.pk);
218
+ // Custom logic: publish to social media, send notifications
219
+ res.json({
220
+ message: 'Post published via router handler',
221
+ postId: ik.pk,
222
+ authorId: ik.loc[0].lk,
223
+ publishedAt: new Date().toISOString(),
224
+ socialMediaPosted: true,
225
+ notificationsSent: true
226
+ });
227
+ },
228
+ unpublish: async (req: Request, res: Response, ik: ComKey<'post', 'user'>) => {
229
+ console.log('Router-level unpublish action called for post:', ik.pk);
230
+ // Custom logic: remove from social media, send notifications
231
+ res.json({
232
+ message: 'Post unpublished via router handler',
233
+ postId: ik.pk,
234
+ authorId: ik.loc[0].lk,
235
+ unpublishedAt: new Date().toISOString(),
236
+ socialMediaRemoved: true,
237
+ notificationsSent: true
238
+ });
239
+ }
240
+ },
241
+
242
+ // Router-level facet handlers for posts
243
+ facets: {
244
+ analytics: async (req: Request, res: Response, ik: ComKey<'post', 'user'>) => {
245
+ console.log('Router-level analytics facet called for post:', ik.pk);
246
+ // Custom logic: aggregate analytics from multiple sources
247
+ res.json({
248
+ postId: ik.pk,
249
+ views: 1250,
250
+ likes: 89,
251
+ shares: 23,
252
+ comments: 15,
253
+ engagementRate: 0.12,
254
+ topReferrers: ['twitter.com', 'facebook.com', 'linkedin.com']
255
+ });
256
+ },
257
+ comments: async (req: Request, res: Response, ik: ComKey<'post', 'user'>) => {
258
+ console.log('Router-level comments facet called for post:', ik.pk);
259
+ // Custom logic: fetch comments from external service
260
+ res.json({
261
+ postId: ik.pk,
262
+ comments: [
263
+ { id: 'comment_1', text: 'Great post!', author: 'user_2', timestamp: new Date() },
264
+ { id: 'comment_2', text: 'Very informative', author: 'user_3', timestamp: new Date() }
265
+ ],
266
+ totalComments: 2
267
+ });
268
+ }
269
+ },
270
+
271
+ // Router-level all action handlers for posts
272
+ allActions: {
273
+ bulkPublish: async (req: Request, res: Response) => {
274
+ console.log('Router-level bulk publish action called');
275
+ // Custom logic: batch publishing to multiple platforms
276
+ const { postIds } = req.body;
277
+ res.json({
278
+ message: 'Bulk publish via router handler',
279
+ processedPosts: postIds.length,
280
+ timestamp: new Date().toISOString(),
281
+ platformsUpdated: ['twitter', 'facebook', 'linkedin']
282
+ });
283
+ },
284
+ bulkUnpublish: async (req: Request, res: Response) => {
285
+ console.log('Router-level bulk unpublish action called');
286
+ // Custom logic: batch unpublishing from multiple platforms
287
+ const { postIds } = req.body;
288
+ res.json({
289
+ message: 'Bulk unpublish via router handler',
290
+ processedPosts: postIds.length,
291
+ timestamp: new Date().toISOString(),
292
+ platformsUpdated: ['twitter', 'facebook', 'linkedin']
293
+ });
294
+ }
295
+ },
296
+
297
+ // Router-level all facet handlers for posts
298
+ allFacets: {
299
+ postStats: async (req: Request, res: Response) => {
300
+ console.log('Router-level post stats facet called');
301
+ // Custom logic: aggregate post statistics
302
+ res.json({
303
+ totalPosts: 450,
304
+ publishedPosts: 380,
305
+ draftPosts: 70,
306
+ averageViews: 850,
307
+ averageLikes: 45,
308
+ topCategories: ['technology', 'business', 'lifestyle']
309
+ });
310
+ },
311
+ postCount: async (req: Request, res: Response) => {
312
+ console.log('Router-level post count facet called');
313
+ // Custom logic: real-time count with filtering
314
+ res.json({
315
+ count: 450,
316
+ published: 380,
317
+ draft: 70,
318
+ lastUpdated: new Date().toISOString(),
319
+ source: 'database'
320
+ });
321
+ }
322
+ }
323
+ } as any);
324
+
325
+ // Mount routers
326
+ app.use('/api/users', userRouter.getRouter());
327
+ app.use('/api/posts', postRouter.getRouter());
328
+
329
+ // Example usage endpoints
330
+ app.get('/examples', (req, res) => {
331
+ res.json({
332
+ message: 'Router Handlers Example',
333
+ endpoints: {
334
+ // User endpoints with router handlers
335
+ 'POST /api/users/user_1/activate': 'Router-level activate action',
336
+ 'POST /api/users/user_1/deactivate': 'Router-level deactivate action',
337
+ 'GET /api/users/user_1/profile': 'Router-level profile facet',
338
+ 'GET /api/users/user_1/stats': 'Router-level stats facet',
339
+ 'POST /api/users/bulkActivate': 'Router-level bulk activate action',
340
+ 'POST /api/users/bulkDeactivate': 'Router-level bulk deactivate action',
341
+ 'GET /api/users/userStats': 'Router-level user stats facet',
342
+ 'GET /api/users/userCount': 'Router-level user count facet',
343
+
344
+ // Post endpoints with router handlers
345
+ 'POST /api/posts/post_1/publish': 'Router-level publish action',
346
+ 'POST /api/posts/post_1/unpublish': 'Router-level unpublish action',
347
+ 'GET /api/posts/post_1/analytics': 'Router-level analytics facet',
348
+ 'GET /api/posts/post_1/comments': 'Router-level comments facet',
349
+ 'POST /api/posts/bulkPublish': 'Router-level bulk publish action',
350
+ 'POST /api/posts/bulkUnpublish': 'Router-level bulk unpublish action',
351
+ 'GET /api/posts/postStats': 'Router-level post stats facet',
352
+ 'GET /api/posts/postCount': 'Router-level post count facet',
353
+
354
+ // Library fallback endpoints (these will use library handlers)
355
+ 'POST /api/users/user_1/someOtherAction': 'Library action (fallback)',
356
+ 'GET /api/users/user_1/someOtherFacet': 'Library facet (fallback)',
357
+ 'POST /api/users/someOtherAllAction': 'Library all action (fallback)',
358
+ 'GET /api/users/someOtherAllFacet': 'Library all facet (fallback)'
359
+ }
360
+ });
361
+ });
362
+
363
+ // Start server
364
+ const PORT = 3000;
365
+ app.listen(PORT, () => {
366
+ console.log(`Router Handlers Example server running on http://localhost:${PORT}`);
367
+ console.log('Visit http://localhost:3000/examples for endpoint documentation');
368
+ });
369
+
370
+ export { app, userRouter, postRouter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjell/express-router",
3
- "version": "4.4.22",
3
+ "version": "4.4.24",
4
4
  "license": "Apache-2.0",
5
5
  "keywords": [
6
6
  "express",
@@ -54,6 +54,7 @@
54
54
  "esbuild": "^0.25.8",
55
55
  "eslint": "^9.32.0",
56
56
  "nodemon": "^3.1.10",
57
+ "supertest": "^7.0.0",
57
58
  "typescript": "^5.8.3",
58
59
  "vitest": "^3.2.4"
59
60
  },