@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.
- package/README.md +205 -0
- package/dist/CItemRouter.d.ts +3 -3
- package/dist/CItemRouter.d.ts.map +1 -1
- package/dist/CItemRouter.js +24 -9
- package/dist/CItemRouter.js.map +2 -2
- package/dist/Instance.d.ts +1 -1
- package/dist/Instance.d.ts.map +1 -1
- package/dist/Instance.js +1 -1
- package/dist/Instance.js.map +1 -1
- package/dist/InstanceFactory.d.ts +1 -1
- package/dist/InstanceFactory.d.ts.map +1 -1
- package/dist/InstanceFactory.js +2 -2
- package/dist/InstanceFactory.js.map +1 -1
- package/dist/ItemRouter.d.ts +13 -4
- package/dist/ItemRouter.d.ts.map +1 -1
- package/dist/ItemRouter.js +92 -16
- package/dist/ItemRouter.js.map +2 -2
- package/dist/PItemRouter.d.ts +3 -3
- package/dist/PItemRouter.d.ts.map +1 -1
- package/dist/PItemRouter.js +1 -1
- package/dist/PItemRouter.js.map +2 -2
- package/dist/Registry.js +1 -1
- package/dist/Registry.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/util/general.js.map +1 -1
- package/examples/README.md +47 -0
- package/examples/basic-router-example.ts +7 -6
- package/examples/full-application-example.ts +137 -12
- package/examples/nested-router-example.ts +154 -54
- package/examples/router-handlers-example.ts +370 -0
- package/package.json +2 -1
|
@@ -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.
|
|
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
|
},
|