@fjell/express-router 4.4.0 → 4.4.2
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/.kodrdriv/config.yaml +10 -0
- package/.kodrdriv/context/content.md +1 -0
- package/dist/CItemRouter.cjs +105 -0
- package/dist/CItemRouter.cjs.map +1 -0
- package/dist/CItemRouter.d.ts +2 -2
- package/dist/CItemRouter.js +21 -5
- package/dist/CItemRouter.js.map +1 -0
- package/dist/ItemRouter.cjs +338 -0
- package/dist/ItemRouter.cjs.map +1 -0
- package/dist/ItemRouter.d.ts +10 -6
- package/dist/ItemRouter.js +54 -9
- package/dist/ItemRouter.js.map +1 -0
- package/dist/PItemRouter.cjs +80 -0
- package/dist/PItemRouter.cjs.map +1 -0
- package/dist/PItemRouter.d.ts +2 -2
- package/dist/PItemRouter.js +15 -6
- package/dist/PItemRouter.js.map +1 -0
- package/dist/index.cjs +507 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -0
- package/dist/logger.cjs +10 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -0
- package/package.json +23 -45
- package/src/CItemRouter.ts +13 -6
- package/src/ItemRouter.ts +59 -11
- package/src/PItemRouter.ts +13 -7
package/package.json
CHANGED
|
@@ -1,71 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjell/express-router",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Express Router for Fjell",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=21"
|
|
8
8
|
},
|
|
9
|
-
"main": "dist/index.
|
|
9
|
+
"main": "dist/index.cjs",
|
|
10
|
+
"module": "dist/index.js",
|
|
10
11
|
"exports": {
|
|
11
12
|
".": {
|
|
12
13
|
"types": "./dist/index.d.ts",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
15
16
|
}
|
|
16
17
|
},
|
|
17
18
|
"type": "module",
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@fjell/core": "^4.4",
|
|
20
|
-
"@fjell/lib": "^4.4",
|
|
21
|
-
"@fjell/logging": "^4.4",
|
|
22
|
-
"@google-cloud/firestore": "^7.11.0",
|
|
23
|
-
"cors": "^2.8.5",
|
|
24
|
-
"dayjs": "^1.11.13",
|
|
20
|
+
"@fjell/core": "^4.4.3",
|
|
21
|
+
"@fjell/lib": "^4.4.2",
|
|
22
|
+
"@fjell/logging": "^4.4.3",
|
|
25
23
|
"deepmerge": "^4.3.1",
|
|
26
|
-
"
|
|
27
|
-
"express": "^4.21.2",
|
|
28
|
-
"multer": "^1.4.5-lts.1",
|
|
29
|
-
"nodemailer": "^6.10.0",
|
|
30
|
-
"response-time": "^2.3.3",
|
|
31
|
-
"specifier-resolution-node": "^1.1.4",
|
|
32
|
-
"supertest": "^7.0.0",
|
|
33
|
-
"winston": "^3.17.0"
|
|
24
|
+
"express": "^5.1.0"
|
|
34
25
|
},
|
|
35
26
|
"devDependencies": {
|
|
36
|
-
"@babel/preset-env": "^7.26.9",
|
|
37
|
-
"@babel/preset-react": "^7.26.3",
|
|
38
|
-
"@babel/preset-typescript": "^7.26.0",
|
|
39
27
|
"@eslint/eslintrc": "^3.3.1",
|
|
40
|
-
"@eslint/js": "^9.
|
|
41
|
-
"@
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"@types/
|
|
45
|
-
"@
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"@types/response-time": "^2.3.8",
|
|
52
|
-
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
|
53
|
-
"@typescript-eslint/parser": "^8.24.1",
|
|
54
|
-
"@vitest/coverage-v8": "^3.1.4",
|
|
55
|
-
"@vitest/ui": "^3.1.4",
|
|
56
|
-
"concurrently": "^9.1.2",
|
|
57
|
-
"eslint": "^9.21.0",
|
|
58
|
-
"jest": "^29.7.0",
|
|
59
|
-
"nodemon": "^3.1.9",
|
|
28
|
+
"@eslint/js": "^9.29.0",
|
|
29
|
+
"@swc/core": "^1.12.5",
|
|
30
|
+
"@tsconfig/recommended": "^1.0.10",
|
|
31
|
+
"@types/express": "^5.0.3",
|
|
32
|
+
"@types/node": "^24.0.3",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
|
34
|
+
"@typescript-eslint/parser": "^8.34.1",
|
|
35
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
36
|
+
"@vitest/ui": "^3.2.4",
|
|
37
|
+
"eslint": "^9.29.0",
|
|
38
|
+
"nodemon": "^3.1.10",
|
|
60
39
|
"rimraf": "^6.0.1",
|
|
61
|
-
"ts-jest": "^29.2.5",
|
|
62
40
|
"ts-node": "^10.9.2",
|
|
63
|
-
"
|
|
64
|
-
"typescript": "^5.7.3",
|
|
41
|
+
"typescript": "^5.8.3",
|
|
65
42
|
"vite": "^6.3.5",
|
|
66
43
|
"vite-plugin-dts": "^4.5.4",
|
|
67
44
|
"vite-plugin-node": "^5.0.1",
|
|
68
|
-
"vitest": "^3.
|
|
45
|
+
"vitest": "^3.2.4"
|
|
69
46
|
},
|
|
70
47
|
"repository": {
|
|
71
48
|
"type": "git",
|
|
@@ -73,6 +50,7 @@
|
|
|
73
50
|
},
|
|
74
51
|
"scripts": {
|
|
75
52
|
"build": "tsc --noEmit && vite build",
|
|
53
|
+
"dev": "nodemon --watch src --ext ts --exec 'pnpm build'",
|
|
76
54
|
"lint": "eslint . --ext .ts --fix",
|
|
77
55
|
"clean": "rimraf dist",
|
|
78
56
|
"test": "pnpm run lint && vitest run --coverage"
|
package/src/CItemRouter.ts
CHANGED
|
@@ -66,30 +66,37 @@ export class CItemRouter<
|
|
|
66
66
|
validatePK(await this.lib.create(
|
|
67
67
|
itemToCreate, { locations: this.getLocations(res) }), this.getPkType()) as Item<S, L1, L2, L3, L4, L5>;
|
|
68
68
|
item = await this.postCreateItem(item);
|
|
69
|
-
|
|
69
|
+
res.json(item);
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
/* eslint-disable */
|
|
73
72
|
protected findItems = async (req: Request, res: Response) => {
|
|
74
73
|
logger.trace('Finding Items', { query: req.query, params: req.params, locals: res.locals });
|
|
75
74
|
|
|
76
75
|
const query: ParsedQuery = req.query as unknown as ParsedQuery;
|
|
77
76
|
const finder = query['finder'] as string;
|
|
78
77
|
const finderParams = query['finderParams'] as string;
|
|
78
|
+
const one = query['one'] as string;
|
|
79
79
|
|
|
80
80
|
let items: Item<S, L1, L2, L3, L4, L5>[] = [];
|
|
81
81
|
|
|
82
|
-
if(
|
|
82
|
+
if (finder) {
|
|
83
83
|
// If finder is defined? Call a finder.
|
|
84
|
-
|
|
84
|
+
logger.trace('Finding Items with a finder', { finder, finderParams, one });
|
|
85
|
+
|
|
86
|
+
if (one === 'true') {
|
|
87
|
+
const item = await (this.lib as any).findOne(finder, JSON.parse(finderParams), this.getLocations(res));
|
|
88
|
+
items = item ? [item] : [];
|
|
89
|
+
} else {
|
|
90
|
+
items = await this.lib.find(finder, JSON.parse(finderParams), this.getLocations(res));
|
|
91
|
+
}
|
|
85
92
|
} else {
|
|
93
|
+
logger.trace('Finding Items with a query', { query: req.query });
|
|
86
94
|
// TODO: This is once of the more important places to perform some validaation and feedback
|
|
87
95
|
const itemQuery: ItemQuery = paramsToQuery(req.query as QueryParams);
|
|
88
96
|
items = await this.lib.all(itemQuery, this.getLocations(res));
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
|
|
99
|
+
res.json(items.map((item: Item<S, L1, L2, L3, L4, L5>) => validatePK(item, this.getPkType())));
|
|
92
100
|
};
|
|
93
|
-
/* eslint-enable */
|
|
94
101
|
|
|
95
102
|
}
|
package/src/ItemRouter.ts
CHANGED
|
@@ -30,6 +30,17 @@ export type ActionMethod = <
|
|
|
30
30
|
// TODO: body is in the request, it's not needed in the parameters
|
|
31
31
|
export type AllActionMethods = Array<RequestHandler>;
|
|
32
32
|
|
|
33
|
+
// TODO: body is in the request, it's not needed in the parameters
|
|
34
|
+
export type FacetMethod = <
|
|
35
|
+
S extends string,
|
|
36
|
+
L1 extends string = never,
|
|
37
|
+
L2 extends string = never,
|
|
38
|
+
L3 extends string = never,
|
|
39
|
+
L4 extends string = never,
|
|
40
|
+
L5 extends string = never
|
|
41
|
+
>(req: Request, res: Response, item: Item<S, L1, L2, L3, L4, L5>, params: any) =>
|
|
42
|
+
Promise<any>;
|
|
43
|
+
|
|
33
44
|
export class ItemRouter<
|
|
34
45
|
S extends string,
|
|
35
46
|
L1 extends string = never,
|
|
@@ -45,6 +56,7 @@ export class ItemRouter<
|
|
|
45
56
|
private childRouters: Record<string, Router> = {};
|
|
46
57
|
private logger;
|
|
47
58
|
private itemActions: Record<string, ActionMethod> | undefined;
|
|
59
|
+
private itemFacets: Record<string, FacetMethod> | undefined;
|
|
48
60
|
|
|
49
61
|
constructor(
|
|
50
62
|
lib: Operations<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>,
|
|
@@ -96,15 +108,35 @@ export class ItemRouter<
|
|
|
96
108
|
const actionKey = req.path.substring(req.path.lastIndexOf('/') + 1);
|
|
97
109
|
if (!this.itemActions) {
|
|
98
110
|
this.logger.error('Item Actions are not configured');
|
|
99
|
-
|
|
111
|
+
res.status(500).json({ error: 'Item Actions are not configured' });
|
|
112
|
+
return;
|
|
100
113
|
}
|
|
101
114
|
try {
|
|
102
115
|
const item =
|
|
103
116
|
validatePK(await this.lib.get(ik), this.getPkType()) as Item<S, L1, L2, L3, L4, L5>;
|
|
104
|
-
|
|
117
|
+
res.json(await this.itemActions[actionKey](req, res, item, req.params, req.body));
|
|
105
118
|
} catch (err: any) {
|
|
106
119
|
this.logger.error('Error in Item Action', { message: err?.message, stack: err?.stack });
|
|
107
|
-
|
|
120
|
+
res.status(500).json(err);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected getItemFacet = async (req: Request, res: Response) => {
|
|
125
|
+
this.logger.default('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
|
|
126
|
+
const ik = this.getIk(res);
|
|
127
|
+
const facetKey = req.path.substring(req.path.lastIndexOf('/') + 1);
|
|
128
|
+
if (!this.itemFacets) {
|
|
129
|
+
this.logger.error('Item Facets are not configured');
|
|
130
|
+
res.status(500).json({ error: 'Item Facets are not configured' });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const item =
|
|
135
|
+
validatePK(await this.lib.get(ik), this.getPkType()) as Item<S, L1, L2, L3, L4, L5>;
|
|
136
|
+
await this.itemFacets[facetKey](req, res, item, req.params);
|
|
137
|
+
} catch (err: any) {
|
|
138
|
+
this.logger.error('Error in Item Facet', { message: err?.message, stack: err?.stack });
|
|
139
|
+
res.status(500).json(err);
|
|
108
140
|
}
|
|
109
141
|
}
|
|
110
142
|
|
|
@@ -138,6 +170,16 @@ export class ItemRouter<
|
|
|
138
170
|
});
|
|
139
171
|
}
|
|
140
172
|
|
|
173
|
+
this.itemFacets = this.configureItemFacets();
|
|
174
|
+
this.logger.debug('Item Facets supplied to Router', { itemFacets: this.itemFacets });
|
|
175
|
+
if (this.itemFacets) {
|
|
176
|
+
Object.keys(this.itemFacets).forEach((facetKey) => {
|
|
177
|
+
this.logger.default('Configuring Item Facet', { facetKey });
|
|
178
|
+
// TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
|
|
179
|
+
itemRouter.get(`/${facetKey}`, this.getItemFacet)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
141
183
|
this.logger.default('Configuring Item Operations under PK Param', { pkParam: this.getPkParam() });
|
|
142
184
|
router.use(`/:${this.getPkParam()}`, this.validatePrimaryKeyValue, itemRouter);
|
|
143
185
|
|
|
@@ -177,6 +219,12 @@ export class ItemRouter<
|
|
|
177
219
|
return {};
|
|
178
220
|
}
|
|
179
221
|
|
|
222
|
+
/* istanbul ignore next */
|
|
223
|
+
protected configureItemFacets(): Record<string, FacetMethod> {
|
|
224
|
+
this.logger.debug('ARouter - No Item Facets Configured');
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
|
|
180
228
|
/* istanbul ignore next */
|
|
181
229
|
protected configureAllActions(): Record<string, AllActionMethods> {
|
|
182
230
|
this.logger.debug('ARouter - No All Actions Configured');
|
|
@@ -192,7 +240,7 @@ export class ItemRouter<
|
|
|
192
240
|
|
|
193
241
|
/* istanbul ignore next */
|
|
194
242
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
195
|
-
protected createItem = async (req: Request, res: Response): Promise<
|
|
243
|
+
protected createItem = async (req: Request, res: Response): Promise<void> => {
|
|
196
244
|
throw new Error('Method not implemented in an abstract router');
|
|
197
245
|
};
|
|
198
246
|
|
|
@@ -203,17 +251,17 @@ export class ItemRouter<
|
|
|
203
251
|
return item;
|
|
204
252
|
};
|
|
205
253
|
|
|
206
|
-
protected deleteItem = async (req: Request, res: Response): Promise<
|
|
254
|
+
protected deleteItem = async (req: Request, res: Response): Promise<void> => {
|
|
207
255
|
this.logger.default('Deleting Item', { query: req.query, params: req.params, locals: res.locals });
|
|
208
256
|
const ik = this.getIk(res);
|
|
209
257
|
const removedItem = await this.lib.remove(ik);
|
|
210
258
|
const item = validatePK(removedItem, this.getPkType());
|
|
211
|
-
|
|
259
|
+
res.json(item);
|
|
212
260
|
};
|
|
213
261
|
|
|
214
262
|
/* eslint-disable */
|
|
215
263
|
/* istanbul ignore next */
|
|
216
|
-
protected findItems = async (req: Request, res: Response): Promise<
|
|
264
|
+
protected findItems = async (req: Request, res: Response): Promise<void> => {
|
|
217
265
|
throw new Error('Method not implemented in an abstract router');
|
|
218
266
|
};
|
|
219
267
|
/* eslint-enable */
|
|
@@ -224,17 +272,17 @@ export class ItemRouter<
|
|
|
224
272
|
try {
|
|
225
273
|
// TODO: What error does validate PK throw, when can that fail?
|
|
226
274
|
const item = validatePK(await this.lib.get(ik), this.getPkType());
|
|
227
|
-
|
|
275
|
+
res.json(item);
|
|
228
276
|
} catch (err: any) {
|
|
229
277
|
if (err instanceof NotFoundError) {
|
|
230
278
|
this.logger.error('Item Not Found', { ik, message: err?.message, stack: err?.stack });
|
|
231
|
-
|
|
279
|
+
res.status(404).json({
|
|
232
280
|
ik,
|
|
233
281
|
message: "Item Not Found",
|
|
234
282
|
});
|
|
235
283
|
} else {
|
|
236
284
|
this.logger.error('General Error', { ik, message: err?.message, stack: err?.stack });
|
|
237
|
-
|
|
285
|
+
res.status(500).json({
|
|
238
286
|
ik,
|
|
239
287
|
message: "General Error",
|
|
240
288
|
});
|
|
@@ -248,7 +296,7 @@ export class ItemRouter<
|
|
|
248
296
|
const ik = this.getIk(res);
|
|
249
297
|
const itemToUpdate = this.convertDates(req.body as ItemProperties<S, L1, L2, L3, L4, L5>);
|
|
250
298
|
const retItem = validatePK(await this.lib.update(ik, itemToUpdate), this.getPkType());
|
|
251
|
-
|
|
299
|
+
res.json(retItem);
|
|
252
300
|
};
|
|
253
301
|
|
|
254
302
|
public convertDates = (item: Item<S, L1, L2, L3, L4, L5> | ItemProperties<S, L1, L2, L3, L4, L5>):
|
package/src/PItemRouter.ts
CHANGED
|
@@ -27,10 +27,9 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
|
|
|
27
27
|
let item =
|
|
28
28
|
validatePK(await this.lib.create(itemToCreate), this.getPkType()) as Item<S>;
|
|
29
29
|
item = await this.postCreateItem(item);
|
|
30
|
-
|
|
30
|
+
res.json(item);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
/* eslint-disable */
|
|
34
33
|
protected findItems = async (req: Request, res: Response) => {
|
|
35
34
|
logger.default('Finding Items', { query: req.query, params: req.params, locals: res.locals });
|
|
36
35
|
|
|
@@ -39,11 +38,18 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
|
|
|
39
38
|
const query: ParsedQuery = req.query as unknown as ParsedQuery;
|
|
40
39
|
const finder = query['finder'] as string;
|
|
41
40
|
const finderParams = query['finderParams'] as string;
|
|
41
|
+
const one = query['one'] as string;
|
|
42
42
|
|
|
43
|
-
if(
|
|
43
|
+
if (finder) {
|
|
44
44
|
// If finder is defined? Call a finder.
|
|
45
|
-
logger.default('Finding Items with a finder', { finder, finderParams });
|
|
46
|
-
|
|
45
|
+
logger.default('Finding Items with a finder', { finder, finderParams, one });
|
|
46
|
+
|
|
47
|
+
if (one === 'true') {
|
|
48
|
+
const item = await (this.lib as any).findOne(finder, JSON.parse(finderParams));
|
|
49
|
+
items = item ? [item] : [];
|
|
50
|
+
} else {
|
|
51
|
+
items = await this.lib.find(finder, JSON.parse(finderParams));
|
|
52
|
+
}
|
|
47
53
|
} else {
|
|
48
54
|
logger.default('Finding Items with a query', { query: req.query });
|
|
49
55
|
// TODO: This is once of the more important places to perform some validaation and feedback
|
|
@@ -51,7 +57,7 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
|
|
|
51
57
|
items = await this.lib.all(itemQuery);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
res.json(items.map((item: Item<S>) => validatePK(item, this.getPkType())));
|
|
55
61
|
};
|
|
56
|
-
|
|
62
|
+
|
|
57
63
|
}
|