@fjell/express-router 4.4.56 ā 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,601 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
/**
|
|
3
|
-
* Nested Router Example
|
|
4
|
-
*
|
|
5
|
-
* This example demonstrates the usage of fjell-express-router for hierarchical data structures
|
|
6
|
-
* using CItemRouter (Contained Item Router) alongside PItemRouter (Primary Item Router).
|
|
7
|
-
* It shows how to create nested routes that handle parent-child relationships in data models.
|
|
8
|
-
*
|
|
9
|
-
* Perfect for understanding how to handle complex data hierarchies with fjell-express-router.
|
|
10
|
-
*
|
|
11
|
-
* Run this example with: npx tsx examples/nested-router-example.ts
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import express, { Application } from 'express';
|
|
15
|
-
import { ComKey, Item, PriKey, UUID } from '@fjell/core';
|
|
16
|
-
import { CItemRouter, createRegistry, PItemRouter } from '../src';
|
|
17
|
-
import { NotFoundError } from '@fjell/lib';
|
|
18
|
-
|
|
19
|
-
// Define our hierarchical data models
|
|
20
|
-
export interface Organization extends Item<'organization'> {
|
|
21
|
-
id: string;
|
|
22
|
-
name: string;
|
|
23
|
-
type: 'startup' | 'enterprise' | 'nonprofit';
|
|
24
|
-
founded: Date;
|
|
25
|
-
industry: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface Department extends Item<'department', 'organization'> {
|
|
29
|
-
id: string;
|
|
30
|
-
name: string;
|
|
31
|
-
budget: number;
|
|
32
|
-
headCount: number;
|
|
33
|
-
organizationId: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface Employee extends Item<'employee', 'organization', 'department'> {
|
|
37
|
-
id: string;
|
|
38
|
-
name: string;
|
|
39
|
-
email: string;
|
|
40
|
-
position: string;
|
|
41
|
-
salary: number;
|
|
42
|
-
hireDate: Date;
|
|
43
|
-
organizationId: string;
|
|
44
|
-
departmentId: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Mock storage
|
|
48
|
-
const mockOrgStorage = new Map<string, Organization>();
|
|
49
|
-
const mockDeptStorage = new Map<string, Department>();
|
|
50
|
-
const mockEmpStorage = new Map<string, Employee>();
|
|
51
|
-
|
|
52
|
-
// Initialize sample data
|
|
53
|
-
const initializeSampleData = () => {
|
|
54
|
-
// Organizations
|
|
55
|
-
const orgs: Organization[] = [
|
|
56
|
-
{
|
|
57
|
-
key: { kt: 'organization', pk: 'org-1' as UUID },
|
|
58
|
-
id: 'org-1',
|
|
59
|
-
name: 'TechCorp Solutions',
|
|
60
|
-
type: 'enterprise',
|
|
61
|
-
founded: new Date('2010-03-15'),
|
|
62
|
-
industry: 'Technology',
|
|
63
|
-
events: {
|
|
64
|
-
created: { at: new Date('2010-03-15') },
|
|
65
|
-
updated: { at: new Date() },
|
|
66
|
-
deleted: { at: null }
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
key: { kt: 'organization', pk: 'org-2' as UUID },
|
|
71
|
-
id: 'org-2',
|
|
72
|
-
name: 'Green Future Initiative',
|
|
73
|
-
type: 'nonprofit',
|
|
74
|
-
founded: new Date('2018-07-22'),
|
|
75
|
-
industry: 'Environmental',
|
|
76
|
-
events: {
|
|
77
|
-
created: { at: new Date('2018-07-22') },
|
|
78
|
-
updated: { at: new Date() },
|
|
79
|
-
deleted: { at: null }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
// Departments
|
|
85
|
-
const depts: Department[] = [
|
|
86
|
-
{
|
|
87
|
-
key: {
|
|
88
|
-
kt: 'department',
|
|
89
|
-
pk: 'dept-1' as UUID,
|
|
90
|
-
loc: [{ kt: 'organization', lk: 'org-1' }]
|
|
91
|
-
},
|
|
92
|
-
id: 'dept-1',
|
|
93
|
-
name: 'Engineering',
|
|
94
|
-
budget: 2000000,
|
|
95
|
-
headCount: 25,
|
|
96
|
-
organizationId: 'org-1',
|
|
97
|
-
events: {
|
|
98
|
-
created: { at: new Date('2010-04-01') },
|
|
99
|
-
updated: { at: new Date() },
|
|
100
|
-
deleted: { at: null }
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
key: {
|
|
105
|
-
kt: 'department',
|
|
106
|
-
pk: 'dept-2' as UUID,
|
|
107
|
-
loc: [{ kt: 'organization', lk: 'org-1' }]
|
|
108
|
-
},
|
|
109
|
-
id: 'dept-2',
|
|
110
|
-
name: 'Marketing',
|
|
111
|
-
budget: 800000,
|
|
112
|
-
headCount: 12,
|
|
113
|
-
organizationId: 'org-1',
|
|
114
|
-
events: {
|
|
115
|
-
created: { at: new Date('2010-05-15') },
|
|
116
|
-
updated: { at: new Date() },
|
|
117
|
-
deleted: { at: null }
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
key: {
|
|
122
|
-
kt: 'department',
|
|
123
|
-
pk: 'dept-3' as UUID,
|
|
124
|
-
loc: [{ kt: 'organization', lk: 'org-2' }]
|
|
125
|
-
},
|
|
126
|
-
id: 'dept-3',
|
|
127
|
-
name: 'Research',
|
|
128
|
-
budget: 500000,
|
|
129
|
-
headCount: 8,
|
|
130
|
-
organizationId: 'org-2',
|
|
131
|
-
events: {
|
|
132
|
-
created: { at: new Date('2018-08-01') },
|
|
133
|
-
updated: { at: new Date() },
|
|
134
|
-
deleted: { at: null }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
// Employees
|
|
140
|
-
const employees: Employee[] = [
|
|
141
|
-
{
|
|
142
|
-
key: {
|
|
143
|
-
kt: 'employee',
|
|
144
|
-
pk: 'emp-1' as UUID,
|
|
145
|
-
loc: [
|
|
146
|
-
{ kt: 'organization', lk: 'org-1' },
|
|
147
|
-
{ kt: 'department', lk: 'dept-1' }
|
|
148
|
-
]
|
|
149
|
-
},
|
|
150
|
-
id: 'emp-1',
|
|
151
|
-
name: 'Alice Johnson',
|
|
152
|
-
email: 'alice.johnson@techcorp.com',
|
|
153
|
-
position: 'Senior Software Engineer',
|
|
154
|
-
salary: 120000,
|
|
155
|
-
hireDate: new Date('2015-09-01'),
|
|
156
|
-
organizationId: 'org-1',
|
|
157
|
-
departmentId: 'dept-1',
|
|
158
|
-
events: {
|
|
159
|
-
created: { at: new Date('2015-09-01') },
|
|
160
|
-
updated: { at: new Date() },
|
|
161
|
-
deleted: { at: null }
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
key: {
|
|
166
|
-
kt: 'employee',
|
|
167
|
-
pk: 'emp-2' as UUID,
|
|
168
|
-
loc: [
|
|
169
|
-
{ kt: 'organization', lk: 'org-1' },
|
|
170
|
-
{ kt: 'department', lk: 'dept-2' }
|
|
171
|
-
]
|
|
172
|
-
},
|
|
173
|
-
id: 'emp-2',
|
|
174
|
-
name: 'Bob Smith',
|
|
175
|
-
email: 'bob.smith@techcorp.com',
|
|
176
|
-
position: 'Marketing Manager',
|
|
177
|
-
salary: 85000,
|
|
178
|
-
hireDate: new Date('2017-03-15'),
|
|
179
|
-
organizationId: 'org-1',
|
|
180
|
-
departmentId: 'dept-2',
|
|
181
|
-
events: {
|
|
182
|
-
created: { at: new Date('2017-03-15') },
|
|
183
|
-
updated: { at: new Date() },
|
|
184
|
-
deleted: { at: null }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
orgs.forEach(org => mockOrgStorage.set(org.id, org));
|
|
190
|
-
depts.forEach(dept => mockDeptStorage.set(dept.id, dept));
|
|
191
|
-
employees.forEach(emp => mockEmpStorage.set(emp.id, emp));
|
|
192
|
-
|
|
193
|
-
console.log('š¦ Initialized hierarchical sample data:');
|
|
194
|
-
console.log(` Organizations: ${orgs.length} records`);
|
|
195
|
-
console.log(` Departments: ${depts.length} records`);
|
|
196
|
-
console.log(` Employees: ${employees.length} records`);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Create mock operations
|
|
200
|
-
const createOrgOperations = () => ({
|
|
201
|
-
async all() {
|
|
202
|
-
return Array.from(mockOrgStorage.values());
|
|
203
|
-
},
|
|
204
|
-
async get(key: PriKey<'organization'>) {
|
|
205
|
-
const org = mockOrgStorage.get(String(key.pk));
|
|
206
|
-
if (!org) throw new NotFoundError('get', { kta: ['organization', '', '', '', '', ''], scopes: [] }, key);
|
|
207
|
-
return org;
|
|
208
|
-
},
|
|
209
|
-
async create(item: Organization) {
|
|
210
|
-
// Validate required fields
|
|
211
|
-
if (!item.name || !item.type || !item.founded || !item.industry) {
|
|
212
|
-
throw new Error('Missing required fields: name, type, founded, and industry are required');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const id = `org-${Date.now()}`;
|
|
216
|
-
const newOrg: Organization = {
|
|
217
|
-
...item,
|
|
218
|
-
id,
|
|
219
|
-
key: { kt: 'organization', pk: id as UUID },
|
|
220
|
-
events: {
|
|
221
|
-
created: { at: new Date() },
|
|
222
|
-
updated: { at: new Date() },
|
|
223
|
-
deleted: { at: null }
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
mockOrgStorage.set(id, newOrg);
|
|
227
|
-
return newOrg;
|
|
228
|
-
},
|
|
229
|
-
async update(key: PriKey<'organization'>, updates: Partial<Organization>) {
|
|
230
|
-
const existing = mockOrgStorage.get(String(key.pk));
|
|
231
|
-
if (!existing) throw new NotFoundError('update', { kta: ['organization', '', '', '', '', ''], scopes: [] }, key);
|
|
232
|
-
const updated = {
|
|
233
|
-
...existing,
|
|
234
|
-
...updates,
|
|
235
|
-
events: {
|
|
236
|
-
...existing.events,
|
|
237
|
-
updated: { at: new Date() }
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
mockOrgStorage.set(String(key.pk), updated);
|
|
241
|
-
return updated;
|
|
242
|
-
},
|
|
243
|
-
async remove(key: PriKey<'organization'>) {
|
|
244
|
-
const existing = mockOrgStorage.get(String(key.pk));
|
|
245
|
-
if (!existing) throw new NotFoundError('remove', { kta: ['organization', '', '', '', '', ''], scopes: [] }, key);
|
|
246
|
-
mockOrgStorage.delete(String(key.pk));
|
|
247
|
-
return existing;
|
|
248
|
-
},
|
|
249
|
-
async find(finder: string, params: any) {
|
|
250
|
-
const orgs = Array.from(mockOrgStorage.values());
|
|
251
|
-
switch (finder) {
|
|
252
|
-
case 'byType':
|
|
253
|
-
return orgs.filter(org => org.type === params.type);
|
|
254
|
-
case 'byIndustry':
|
|
255
|
-
return orgs.filter(org => org.industry === params.industry);
|
|
256
|
-
default:
|
|
257
|
-
return orgs;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
const createDeptOperations = () => ({
|
|
263
|
-
async all(query?: any, locations?: any) {
|
|
264
|
-
const departments = Array.from(mockDeptStorage.values());
|
|
265
|
-
if (locations && locations.length >= 1) {
|
|
266
|
-
const orgId = String(locations[0]?.lk || '');
|
|
267
|
-
// Check if the parent organization exists
|
|
268
|
-
const org = mockOrgStorage.get(orgId);
|
|
269
|
-
if (!org) {
|
|
270
|
-
throw new NotFoundError('get', { kta: ['organization', '', '', '', '', ''], scopes: [] }, { kt: 'organization', pk: orgId as UUID });
|
|
271
|
-
}
|
|
272
|
-
return departments.filter(dept => dept.organizationId === orgId);
|
|
273
|
-
}
|
|
274
|
-
return departments;
|
|
275
|
-
},
|
|
276
|
-
async get(key: ComKey<'department', 'organization'>) {
|
|
277
|
-
const dept = mockDeptStorage.get(String(key.pk));
|
|
278
|
-
if (!dept) throw new NotFoundError('get', { kta: ['department', '', '', '', '', ''], scopes: [] }, key);
|
|
279
|
-
return dept;
|
|
280
|
-
},
|
|
281
|
-
async create(item: Department, context?: any) {
|
|
282
|
-
const id = `dept-${Date.now()}`;
|
|
283
|
-
const orgId = context?.locations?.[0]?.lk || item.organizationId || String((item.key as any)?.loc?.[0]?.lk || '');
|
|
284
|
-
const newDept: Department = {
|
|
285
|
-
...item,
|
|
286
|
-
id,
|
|
287
|
-
organizationId: orgId,
|
|
288
|
-
key: {
|
|
289
|
-
kt: 'department',
|
|
290
|
-
pk: id as UUID,
|
|
291
|
-
loc: context?.locations || (item.key as any)?.loc || []
|
|
292
|
-
},
|
|
293
|
-
events: {
|
|
294
|
-
created: { at: new Date() },
|
|
295
|
-
updated: { at: new Date() },
|
|
296
|
-
deleted: { at: null }
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
mockDeptStorage.set(id, newDept);
|
|
300
|
-
return newDept;
|
|
301
|
-
},
|
|
302
|
-
async update(key: ComKey<'department', 'organization'>, updates: Partial<Department>) {
|
|
303
|
-
const existing = mockDeptStorage.get(String(key.pk));
|
|
304
|
-
if (!existing) throw new NotFoundError('update', { kta: ['department', '', '', '', '', ''], scopes: [] }, key);
|
|
305
|
-
const updated = {
|
|
306
|
-
...existing,
|
|
307
|
-
...updates,
|
|
308
|
-
events: {
|
|
309
|
-
...existing.events,
|
|
310
|
-
updated: { at: new Date() }
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
mockDeptStorage.set(String(key.pk), updated);
|
|
314
|
-
return updated;
|
|
315
|
-
},
|
|
316
|
-
async remove(key: ComKey<'department', 'organization'>) {
|
|
317
|
-
const existing = mockDeptStorage.get(String(key.pk));
|
|
318
|
-
if (!existing) throw new NotFoundError('remove', { kta: ['department', '', '', '', '', ''], scopes: [] }, key);
|
|
319
|
-
mockDeptStorage.delete(String(key.pk));
|
|
320
|
-
return existing;
|
|
321
|
-
},
|
|
322
|
-
async find(finder: string, params: any, locations?: any) {
|
|
323
|
-
const departments = Array.from(mockDeptStorage.values());
|
|
324
|
-
let filtered = departments;
|
|
325
|
-
|
|
326
|
-
if (locations && locations.length >= 1) {
|
|
327
|
-
const orgId = String(locations[0]?.lk || '');
|
|
328
|
-
filtered = filtered.filter(dept => dept.organizationId === orgId);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
switch (finder) {
|
|
332
|
-
case 'byBudget':
|
|
333
|
-
return filtered.filter(dept => dept.budget >= params.minBudget);
|
|
334
|
-
case 'byHeadCount':
|
|
335
|
-
return filtered.filter(dept => dept.headCount >= params.minHeadCount);
|
|
336
|
-
default:
|
|
337
|
-
return filtered;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
const createEmpOperations = () => ({
|
|
343
|
-
async all(query?: any, locations?: any) {
|
|
344
|
-
const employees = Array.from(mockEmpStorage.values());
|
|
345
|
-
if (locations && locations.length >= 2) {
|
|
346
|
-
// The locations array is: [{kt: 'department', lk: 'dept-1'}, {kt: 'organization', lk: 'org-1'}]
|
|
347
|
-
const deptId = String(locations[0]?.lk || '');
|
|
348
|
-
const orgId = String(locations[1]?.lk || '');
|
|
349
|
-
|
|
350
|
-
// Check if the parent organization exists
|
|
351
|
-
const org = mockOrgStorage.get(orgId);
|
|
352
|
-
if (!org) {
|
|
353
|
-
throw new NotFoundError('get', { kta: ['organization', '', '', '', '', ''], scopes: [] }, { kt: 'organization', pk: orgId as UUID });
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Check if the parent department exists
|
|
357
|
-
const dept = mockDeptStorage.get(deptId);
|
|
358
|
-
if (!dept) {
|
|
359
|
-
throw new NotFoundError('get', { kta: ['department', '', '', '', '', ''], scopes: [] }, { kt: 'department', pk: deptId as UUID });
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return employees.filter(emp => emp.organizationId === orgId && emp.departmentId === deptId);
|
|
363
|
-
}
|
|
364
|
-
return employees;
|
|
365
|
-
},
|
|
366
|
-
async get(key: ComKey<'employee', 'organization', 'department'>) {
|
|
367
|
-
const emp = mockEmpStorage.get(String(key.pk));
|
|
368
|
-
if (!emp) throw new NotFoundError('get', { kta: ['employee', '', '', '', '', ''], scopes: [] }, key);
|
|
369
|
-
return emp;
|
|
370
|
-
},
|
|
371
|
-
async create(item: Employee, context?: any) {
|
|
372
|
-
const id = `emp-${Date.now()}`;
|
|
373
|
-
|
|
374
|
-
// Extract organization and department IDs from the locations context
|
|
375
|
-
// The locations array is: [{kt: 'department', lk: 'dept-1'}, {kt: 'organization', lk: 'org-1'}]
|
|
376
|
-
const deptId = context?.locations?.[0]?.lk || item.departmentId || String((item.key as any)?.loc?.[0]?.lk || '');
|
|
377
|
-
const orgId = context?.locations?.[1]?.lk || item.organizationId || String((item.key as any)?.loc?.[1]?.lk || '');
|
|
378
|
-
|
|
379
|
-
const newEmp: Employee = {
|
|
380
|
-
...item,
|
|
381
|
-
id,
|
|
382
|
-
organizationId: orgId,
|
|
383
|
-
departmentId: deptId,
|
|
384
|
-
key: {
|
|
385
|
-
kt: 'employee',
|
|
386
|
-
pk: id as UUID,
|
|
387
|
-
loc: context?.locations || (item.key as any)?.loc || []
|
|
388
|
-
},
|
|
389
|
-
events: {
|
|
390
|
-
created: { at: new Date() },
|
|
391
|
-
updated: { at: new Date() },
|
|
392
|
-
deleted: { at: null }
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
mockEmpStorage.set(id, newEmp);
|
|
396
|
-
return newEmp;
|
|
397
|
-
},
|
|
398
|
-
async update(key: ComKey<'employee', 'organization', 'department'>, updates: Partial<Employee>) {
|
|
399
|
-
const existing = mockEmpStorage.get(String(key.pk));
|
|
400
|
-
if (!existing) throw new NotFoundError('update', { kta: ['employee', '', '', '', '', ''], scopes: [] }, key);
|
|
401
|
-
const updated = {
|
|
402
|
-
...existing,
|
|
403
|
-
...updates,
|
|
404
|
-
events: {
|
|
405
|
-
...existing.events,
|
|
406
|
-
updated: { at: new Date() }
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
mockEmpStorage.set(String(key.pk), updated);
|
|
410
|
-
return updated;
|
|
411
|
-
},
|
|
412
|
-
async remove(key: ComKey<'employee', 'organization', 'department'>) {
|
|
413
|
-
const existing = mockEmpStorage.get(String(key.pk));
|
|
414
|
-
if (!existing) throw new NotFoundError('remove', { kta: ['employee', '', '', '', '', ''], scopes: [] }, key);
|
|
415
|
-
mockEmpStorage.delete(String(key.pk));
|
|
416
|
-
return existing;
|
|
417
|
-
},
|
|
418
|
-
async find(finder: string, params: any, locations?: any) {
|
|
419
|
-
const employees = Array.from(mockEmpStorage.values());
|
|
420
|
-
let filtered = employees;
|
|
421
|
-
|
|
422
|
-
if (locations && locations.length >= 2) {
|
|
423
|
-
const deptId = String(locations[0]?.lk || '');
|
|
424
|
-
const orgId = String(locations[1]?.lk || '');
|
|
425
|
-
filtered = filtered.filter(emp => emp.organizationId === orgId && emp.departmentId === deptId);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
switch (finder) {
|
|
429
|
-
case 'byDepartment':
|
|
430
|
-
return filtered.filter(emp => emp.departmentId === params.departmentId);
|
|
431
|
-
case 'byPosition':
|
|
432
|
-
return filtered.filter(emp => emp.position.includes(params.position));
|
|
433
|
-
default:
|
|
434
|
-
return filtered;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Main function demonstrating nested fjell-express-router usage
|
|
441
|
-
*/
|
|
442
|
-
export const runNestedRouterExample = async (): Promise<{ app: Application }> => {
|
|
443
|
-
console.log('š Starting Nested Express Router Example...\n');
|
|
444
|
-
|
|
445
|
-
initializeSampleData();
|
|
446
|
-
|
|
447
|
-
const registry = createRegistry();
|
|
448
|
-
|
|
449
|
-
// Create mock instances
|
|
450
|
-
const mockOrgInstance = {
|
|
451
|
-
operations: createOrgOperations(),
|
|
452
|
-
options: {}
|
|
453
|
-
} as any;
|
|
454
|
-
|
|
455
|
-
const mockDeptInstance = {
|
|
456
|
-
operations: createDeptOperations(),
|
|
457
|
-
options: {}
|
|
458
|
-
} as any;
|
|
459
|
-
|
|
460
|
-
const mockEmpInstance = {
|
|
461
|
-
operations: createEmpOperations(),
|
|
462
|
-
options: {}
|
|
463
|
-
} as any;
|
|
464
|
-
|
|
465
|
-
const app: Application = express();
|
|
466
|
-
app.use(express.json());
|
|
467
|
-
|
|
468
|
-
// Request logging middleware
|
|
469
|
-
app.use((req, res, next) => {
|
|
470
|
-
console.log(`š ${req.method} ${req.path}`, req.query);
|
|
471
|
-
next();
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Create routers
|
|
475
|
-
console.log('š¤ļø Creating nested Express routers...');
|
|
476
|
-
const orgRouter = new PItemRouter(mockOrgInstance, 'organization');
|
|
477
|
-
const deptRouter = new CItemRouter(mockDeptInstance, 'department', orgRouter);
|
|
478
|
-
const empRouter = new CItemRouter(mockEmpInstance, 'employee', deptRouter);
|
|
479
|
-
|
|
480
|
-
// Mount the routers to create nested endpoints:
|
|
481
|
-
// Primary routes for organizations:
|
|
482
|
-
// GET /api/organizations - Get all organizations
|
|
483
|
-
// GET /api/organizations/:organizationPk - Get specific organization
|
|
484
|
-
// POST /api/organizations - Create new organization
|
|
485
|
-
// PUT /api/organizations/:organizationPk - Update organization
|
|
486
|
-
// DELETE /api/organizations/:organizationPk - Delete organization
|
|
487
|
-
app.use('/api/organizations', orgRouter.getRouter());
|
|
488
|
-
|
|
489
|
-
// Nested routes for departments within organizations:
|
|
490
|
-
// GET /api/organizations/:organizationPk/departments - Get all departments for organization
|
|
491
|
-
// GET /api/organizations/:organizationPk/departments/:departmentPk - Get specific department
|
|
492
|
-
// POST /api/organizations/:organizationPk/departments - Create new department in organization
|
|
493
|
-
// PUT /api/organizations/:organizationPk/departments/:departmentPk - Update department
|
|
494
|
-
// DELETE /api/organizations/:organizationPk/departments/:departmentPk - Delete department
|
|
495
|
-
app.use('/api/organizations/:organizationPk/departments', deptRouter.getRouter());
|
|
496
|
-
|
|
497
|
-
// Deeply nested routes for employees within departments within organizations:
|
|
498
|
-
// GET /api/organizations/:organizationPk/departments/:departmentPk/employees - Get all employees for department
|
|
499
|
-
// GET /api/organizations/:organizationPk/departments/:departmentPk/employees/:employeePk - Get specific employee
|
|
500
|
-
// POST /api/organizations/:organizationPk/departments/:departmentPk/employees - Create new employee in department
|
|
501
|
-
// PUT /api/organizations/:organizationPk/departments/:departmentPk/employees/:employeePk - Update employee
|
|
502
|
-
// DELETE /api/organizations/:organizationPk/departments/:departmentPk/employees/:employeePk - Delete employee
|
|
503
|
-
app.use('/api/organizations/:organizationPk/departments/:departmentPk/employees', empRouter.getRouter());
|
|
504
|
-
|
|
505
|
-
// Additional hierarchy summary routes
|
|
506
|
-
app.get('/api/hierarchy', async (req, res) => {
|
|
507
|
-
try {
|
|
508
|
-
const orgs = await mockOrgInstance.operations.all();
|
|
509
|
-
const depts = await mockDeptInstance.operations.all();
|
|
510
|
-
const employees = await mockEmpInstance.operations.all();
|
|
511
|
-
|
|
512
|
-
const hierarchy = orgs.map((org: Organization) => {
|
|
513
|
-
const orgDepts = depts.filter((dept: Department) => dept.organizationId === org.id);
|
|
514
|
-
return {
|
|
515
|
-
organization: org,
|
|
516
|
-
departments: orgDepts.map((dept: Department) => {
|
|
517
|
-
const deptEmployees = employees.filter((emp: Employee) => emp.departmentId === dept.id);
|
|
518
|
-
return {
|
|
519
|
-
department: dept,
|
|
520
|
-
employees: deptEmployees
|
|
521
|
-
};
|
|
522
|
-
})
|
|
523
|
-
};
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
res.json(hierarchy);
|
|
527
|
-
} catch (error) {
|
|
528
|
-
res.status(500).json({ error: 'Failed to load hierarchy' });
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
app.get('/api/stats', async (req, res) => {
|
|
533
|
-
try {
|
|
534
|
-
const orgs = await mockOrgInstance.operations.all();
|
|
535
|
-
const depts = await mockDeptInstance.operations.all();
|
|
536
|
-
const employees = await mockEmpInstance.operations.all();
|
|
537
|
-
|
|
538
|
-
const stats = {
|
|
539
|
-
totals: {
|
|
540
|
-
organizations: orgs.length,
|
|
541
|
-
departments: depts.length,
|
|
542
|
-
employees: employees.length
|
|
543
|
-
},
|
|
544
|
-
organizationTypes: orgs.reduce((acc: any, org: Organization) => {
|
|
545
|
-
acc[org.type] = (acc[org.type] || 0) + 1;
|
|
546
|
-
return acc;
|
|
547
|
-
}, {}),
|
|
548
|
-
averageDepartmentBudget: depts.reduce((sum: number, dept: Department) => sum + dept.budget, 0) / depts.length,
|
|
549
|
-
totalPayroll: employees.reduce((sum: number, emp: Employee) => sum + emp.salary, 0)
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
res.json(stats);
|
|
553
|
-
} catch (error) {
|
|
554
|
-
res.status(500).json({ error: 'Failed to calculate stats' });
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
console.log('\nā
Nested Express Router Example setup complete!');
|
|
559
|
-
console.log('\nš Available nested endpoints:');
|
|
560
|
-
console.log(' š GET /api/hierarchy - Full organizational hierarchy');
|
|
561
|
-
console.log(' š GET /api/stats - Statistics summary');
|
|
562
|
-
console.log(' š¢ Organizations (Primary):');
|
|
563
|
-
console.log(' GET /api/organizations - List all organizations');
|
|
564
|
-
console.log(' GET /api/organizations/:organizationPk - Get specific organization');
|
|
565
|
-
console.log(' POST /api/organizations - Create new organization');
|
|
566
|
-
console.log(' š¬ Departments (Contained in Organizations):');
|
|
567
|
-
console.log(' GET /api/organizations/:organizationPk/departments - List departments for organization');
|
|
568
|
-
console.log(' GET /api/organizations/:organizationPk/departments/:departmentPk - Get specific department');
|
|
569
|
-
console.log(' POST /api/organizations/:organizationPk/departments - Create department in organization');
|
|
570
|
-
console.log(' š„ Employees (Contained in Departments):');
|
|
571
|
-
console.log(' GET /api/organizations/:organizationPk/departments/:departmentPk/employees - List employees for department');
|
|
572
|
-
console.log(' GET /api/organizations/:organizationPk/departments/:departmentPk/employees/:employeePk - Get specific employee');
|
|
573
|
-
console.log(' POST /api/organizations/:organizationPk/departments/:departmentPk/employees - Create employee in department');
|
|
574
|
-
|
|
575
|
-
// Error handling middleware
|
|
576
|
-
app.use((err: any, req: any, res: any, next: any) => {
|
|
577
|
-
console.error('Error:', err.message);
|
|
578
|
-
if (err instanceof NotFoundError || err.message.includes('not found') || err.message.includes('NotFoundError')) {
|
|
579
|
-
return res.status(404).json({ error: err.message });
|
|
580
|
-
}
|
|
581
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
return { app };
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
// If this file is run directly, start the server
|
|
588
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
589
|
-
runNestedRouterExample().then(({ app }) => {
|
|
590
|
-
const PORT = process.env.PORT || 3002;
|
|
591
|
-
app.listen(PORT, () => {
|
|
592
|
-
console.log(`\nš Server running on http://localhost:${PORT}`);
|
|
593
|
-
console.log('\nš” Try these example requests:');
|
|
594
|
-
console.log(` curl http://localhost:${PORT}/api/hierarchy`);
|
|
595
|
-
console.log(` curl http://localhost:${PORT}/api/stats`);
|
|
596
|
-
console.log(` curl http://localhost:${PORT}/api/organizations`);
|
|
597
|
-
console.log(` curl http://localhost:${PORT}/api/organizations/org-1/departments`);
|
|
598
|
-
console.log(` curl http://localhost:${PORT}/api/organizations/org-1/departments/dept-1/employees`);
|
|
599
|
-
});
|
|
600
|
-
}).catch(console.error);
|
|
601
|
-
}
|