@fjell/express-router 4.4.23 → 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 +1 -1
- package/dist/CItemRouter.d.ts.map +1 -1
- package/dist/CItemRouter.js +23 -8
- package/dist/CItemRouter.js.map +2 -2
- package/dist/Instance.d.ts.map +1 -1
- package/dist/Instance.js.map +2 -2
- package/dist/InstanceFactory.d.ts.map +1 -1
- package/dist/InstanceFactory.js.map +2 -2
- package/dist/ItemRouter.d.ts +12 -3
- package/dist/ItemRouter.d.ts.map +1 -1
- package/dist/ItemRouter.js +91 -15
- package/dist/ItemRouter.js.map +2 -2
- package/dist/PItemRouter.d.ts +1 -1
- package/dist/PItemRouter.d.ts.map +1 -1
- package/dist/PItemRouter.js.map +2 -2
- 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
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
import express, { Application } from 'express';
|
|
15
15
|
import { ComKey, Item, PriKey, UUID } from '@fjell/core';
|
|
16
16
|
import { CItemRouter, createRegistry, PItemRouter } from '../src';
|
|
17
|
+
import { NotFoundError } from '@fjell/lib';
|
|
17
18
|
|
|
18
19
|
// Define our hierarchical data models
|
|
19
|
-
interface Organization extends Item<'organization'> {
|
|
20
|
+
export interface Organization extends Item<'organization'> {
|
|
20
21
|
id: string;
|
|
21
22
|
name: string;
|
|
22
23
|
type: 'startup' | 'enterprise' | 'nonprofit';
|
|
@@ -24,7 +25,7 @@ interface Organization extends Item<'organization'> {
|
|
|
24
25
|
industry: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
interface Department extends Item<'department', 'organization'> {
|
|
28
|
+
export interface Department extends Item<'department', 'organization'> {
|
|
28
29
|
id: string;
|
|
29
30
|
name: string;
|
|
30
31
|
budget: number;
|
|
@@ -32,7 +33,7 @@ interface Department extends Item<'department', 'organization'> {
|
|
|
32
33
|
organizationId: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
interface Employee extends Item<'employee', 'organization', 'department'> {
|
|
36
|
+
export interface Employee extends Item<'employee', 'organization', 'department'> {
|
|
36
37
|
id: string;
|
|
37
38
|
name: string;
|
|
38
39
|
email: string;
|
|
@@ -202,10 +203,15 @@ const createOrgOperations = () => ({
|
|
|
202
203
|
},
|
|
203
204
|
async get(key: PriKey<'organization'>) {
|
|
204
205
|
const org = mockOrgStorage.get(String(key.pk));
|
|
205
|
-
if (!org) throw new
|
|
206
|
+
if (!org) throw new NotFoundError('get', { kta: ['organization', '', '', '', '', ''], scopes: [] }, key);
|
|
206
207
|
return org;
|
|
207
208
|
},
|
|
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
|
+
|
|
209
215
|
const id = `org-${Date.now()}`;
|
|
210
216
|
const newOrg: Organization = {
|
|
211
217
|
...item,
|
|
@@ -222,13 +228,23 @@ const createOrgOperations = () => ({
|
|
|
222
228
|
},
|
|
223
229
|
async update(key: PriKey<'organization'>, updates: Partial<Organization>) {
|
|
224
230
|
const existing = mockOrgStorage.get(String(key.pk));
|
|
225
|
-
if (!existing) throw new
|
|
226
|
-
const updated = {
|
|
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
|
+
};
|
|
227
240
|
mockOrgStorage.set(String(key.pk), updated);
|
|
228
241
|
return updated;
|
|
229
242
|
},
|
|
230
243
|
async remove(key: PriKey<'organization'>) {
|
|
231
|
-
|
|
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;
|
|
232
248
|
},
|
|
233
249
|
async find(finder: string, params: any) {
|
|
234
250
|
const orgs = Array.from(mockOrgStorage.values());
|
|
@@ -244,23 +260,35 @@ const createOrgOperations = () => ({
|
|
|
244
260
|
});
|
|
245
261
|
|
|
246
262
|
const createDeptOperations = () => ({
|
|
247
|
-
async all() {
|
|
248
|
-
|
|
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;
|
|
249
275
|
},
|
|
250
276
|
async get(key: ComKey<'department', 'organization'>) {
|
|
251
277
|
const dept = mockDeptStorage.get(String(key.pk));
|
|
252
|
-
if (!dept) throw new
|
|
278
|
+
if (!dept) throw new NotFoundError('get', { kta: ['department', '', '', '', '', ''], scopes: [] }, key);
|
|
253
279
|
return dept;
|
|
254
280
|
},
|
|
255
|
-
async create(item: Department) {
|
|
281
|
+
async create(item: Department, context?: any) {
|
|
256
282
|
const id = `dept-${Date.now()}`;
|
|
283
|
+
const orgId = context?.locations?.[0]?.lk || item.organizationId || String((item.key as any)?.loc?.[0]?.lk || '');
|
|
257
284
|
const newDept: Department = {
|
|
258
285
|
...item,
|
|
259
286
|
id,
|
|
287
|
+
organizationId: orgId,
|
|
260
288
|
key: {
|
|
261
289
|
kt: 'department',
|
|
262
290
|
pk: id as UUID,
|
|
263
|
-
loc: (item.key as any)?.loc || []
|
|
291
|
+
loc: context?.locations || (item.key as any)?.loc || []
|
|
264
292
|
},
|
|
265
293
|
events: {
|
|
266
294
|
created: { at: new Date() },
|
|
@@ -273,45 +301,90 @@ const createDeptOperations = () => ({
|
|
|
273
301
|
},
|
|
274
302
|
async update(key: ComKey<'department', 'organization'>, updates: Partial<Department>) {
|
|
275
303
|
const existing = mockDeptStorage.get(String(key.pk));
|
|
276
|
-
if (!existing) throw new
|
|
277
|
-
const updated = {
|
|
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
|
+
};
|
|
278
313
|
mockDeptStorage.set(String(key.pk), updated);
|
|
279
314
|
return updated;
|
|
280
315
|
},
|
|
281
316
|
async remove(key: ComKey<'department', 'organization'>) {
|
|
282
|
-
|
|
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;
|
|
283
321
|
},
|
|
284
|
-
async find(finder: string, params: any) {
|
|
285
|
-
const
|
|
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
|
+
|
|
286
331
|
switch (finder) {
|
|
287
|
-
case '
|
|
288
|
-
return
|
|
289
|
-
case '
|
|
290
|
-
return
|
|
332
|
+
case 'byBudget':
|
|
333
|
+
return filtered.filter(dept => dept.budget >= params.minBudget);
|
|
334
|
+
case 'byHeadCount':
|
|
335
|
+
return filtered.filter(dept => dept.headCount >= params.minHeadCount);
|
|
291
336
|
default:
|
|
292
|
-
return
|
|
337
|
+
return filtered;
|
|
293
338
|
}
|
|
294
339
|
}
|
|
295
340
|
});
|
|
296
341
|
|
|
297
342
|
const createEmpOperations = () => ({
|
|
298
|
-
async all() {
|
|
299
|
-
|
|
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;
|
|
300
365
|
},
|
|
301
366
|
async get(key: ComKey<'employee', 'organization', 'department'>) {
|
|
302
367
|
const emp = mockEmpStorage.get(String(key.pk));
|
|
303
|
-
if (!emp) throw new
|
|
368
|
+
if (!emp) throw new NotFoundError('get', { kta: ['employee', '', '', '', '', ''], scopes: [] }, key);
|
|
304
369
|
return emp;
|
|
305
370
|
},
|
|
306
|
-
async create(item: Employee) {
|
|
371
|
+
async create(item: Employee, context?: any) {
|
|
307
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
|
+
|
|
308
379
|
const newEmp: Employee = {
|
|
309
380
|
...item,
|
|
310
381
|
id,
|
|
382
|
+
organizationId: orgId,
|
|
383
|
+
departmentId: deptId,
|
|
311
384
|
key: {
|
|
312
385
|
kt: 'employee',
|
|
313
386
|
pk: id as UUID,
|
|
314
|
-
loc: (item.key as any)?.loc || []
|
|
387
|
+
loc: context?.locations || (item.key as any)?.loc || []
|
|
315
388
|
},
|
|
316
389
|
events: {
|
|
317
390
|
created: { at: new Date() },
|
|
@@ -324,23 +397,41 @@ const createEmpOperations = () => ({
|
|
|
324
397
|
},
|
|
325
398
|
async update(key: ComKey<'employee', 'organization', 'department'>, updates: Partial<Employee>) {
|
|
326
399
|
const existing = mockEmpStorage.get(String(key.pk));
|
|
327
|
-
if (!existing) throw new
|
|
328
|
-
const updated = {
|
|
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
|
+
};
|
|
329
409
|
mockEmpStorage.set(String(key.pk), updated);
|
|
330
410
|
return updated;
|
|
331
411
|
},
|
|
332
412
|
async remove(key: ComKey<'employee', 'organization', 'department'>) {
|
|
333
|
-
|
|
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;
|
|
334
417
|
},
|
|
335
|
-
async find(finder: string, params: any) {
|
|
418
|
+
async find(finder: string, params: any, locations?: any) {
|
|
336
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
|
+
|
|
337
428
|
switch (finder) {
|
|
338
429
|
case 'byDepartment':
|
|
339
|
-
return
|
|
430
|
+
return filtered.filter(emp => emp.departmentId === params.departmentId);
|
|
340
431
|
case 'byPosition':
|
|
341
|
-
return
|
|
432
|
+
return filtered.filter(emp => emp.position.includes(params.position));
|
|
342
433
|
default:
|
|
343
|
-
return
|
|
434
|
+
return filtered;
|
|
344
435
|
}
|
|
345
436
|
}
|
|
346
437
|
});
|
|
@@ -389,26 +480,26 @@ export const runNestedRouterExample = async (): Promise<{ app: Application }> =>
|
|
|
389
480
|
// Mount the routers to create nested endpoints:
|
|
390
481
|
// Primary routes for organizations:
|
|
391
482
|
// GET /api/organizations - Get all organizations
|
|
392
|
-
// GET /api/organizations/:
|
|
483
|
+
// GET /api/organizations/:organizationPk - Get specific organization
|
|
393
484
|
// POST /api/organizations - Create new organization
|
|
394
|
-
// PUT /api/organizations/:
|
|
395
|
-
// DELETE /api/organizations/:
|
|
485
|
+
// PUT /api/organizations/:organizationPk - Update organization
|
|
486
|
+
// DELETE /api/organizations/:organizationPk - Delete organization
|
|
396
487
|
app.use('/api/organizations', orgRouter.getRouter());
|
|
397
488
|
|
|
398
489
|
// Nested routes for departments within organizations:
|
|
399
|
-
// GET /api/organizations/:
|
|
400
|
-
// GET /api/organizations/:
|
|
401
|
-
// POST /api/organizations/:
|
|
402
|
-
// PUT /api/organizations/:
|
|
403
|
-
// DELETE /api/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
|
|
404
495
|
app.use('/api/organizations/:organizationPk/departments', deptRouter.getRouter());
|
|
405
496
|
|
|
406
497
|
// Deeply nested routes for employees within departments within organizations:
|
|
407
|
-
// GET /api/organizations/:
|
|
408
|
-
// GET /api/organizations/:
|
|
409
|
-
// POST /api/organizations/:
|
|
410
|
-
// PUT /api/organizations/:
|
|
411
|
-
// DELETE /api/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
|
|
412
503
|
app.use('/api/organizations/:organizationPk/departments/:departmentPk/employees', empRouter.getRouter());
|
|
413
504
|
|
|
414
505
|
// Additional hierarchy summary routes
|
|
@@ -470,16 +561,25 @@ export const runNestedRouterExample = async (): Promise<{ app: Application }> =>
|
|
|
470
561
|
console.log(' 📈 GET /api/stats - Statistics summary');
|
|
471
562
|
console.log(' 🏢 Organizations (Primary):');
|
|
472
563
|
console.log(' GET /api/organizations - List all organizations');
|
|
473
|
-
console.log(' GET /api/organizations/:
|
|
564
|
+
console.log(' GET /api/organizations/:organizationPk - Get specific organization');
|
|
474
565
|
console.log(' POST /api/organizations - Create new organization');
|
|
475
566
|
console.log(' 🏬 Departments (Contained in Organizations):');
|
|
476
|
-
console.log(' GET /api/organizations/:
|
|
477
|
-
console.log(' GET /api/organizations/:
|
|
478
|
-
console.log(' POST /api/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');
|
|
479
570
|
console.log(' 👥 Employees (Contained in Departments):');
|
|
480
|
-
console.log(' GET /api/organizations/:
|
|
481
|
-
console.log(' GET /api/organizations/:
|
|
482
|
-
console.log(' POST /api/organizations/:
|
|
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
|
+
});
|
|
483
583
|
|
|
484
584
|
return { app };
|
|
485
585
|
};
|