@fjell/express-router 4.4.4 → 4.4.5

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/src/ItemRouter.ts CHANGED
@@ -3,13 +3,12 @@ import {
3
3
  cPK,
4
4
  Item,
5
5
  ItemEvent,
6
- ItemProperties,
7
6
  LocKey,
8
7
  LocKeyArray,
9
8
  PriKey,
10
9
  validatePK
11
10
  } from "@fjell/core";
12
- import { NotFoundError, Operations } from "@fjell/lib";
11
+ import { Instance, NotFoundError } from "@fjell/lib";
13
12
  import deepmerge from "deepmerge";
14
13
  import { Request, Response, Router } from "express";
15
14
  import LibLogger from "./logger";
@@ -25,14 +24,14 @@ export class ItemRouter<
25
24
  L5 extends string = never
26
25
  > {
27
26
 
28
- protected lib: Operations<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>;
27
+ protected lib: Instance<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>;
29
28
  private keyType: S;
30
29
  protected options: ItemRouterOptions;
31
30
  private childRouters: Record<string, Router> = {};
32
- private logger;
31
+ protected logger;
33
32
 
34
33
  constructor(
35
- lib: Operations<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>,
34
+ lib: Instance<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>,
36
35
  keyType: S,
37
36
  options: ItemRouterOptions = {}
38
37
  ) {
@@ -76,21 +75,23 @@ export class ItemRouter<
76
75
  }
77
76
 
78
77
  protected postAllAction = async (req: Request, res: Response) => {
79
- this.logger.default('Posting All Action', { query: req?.query, params: req?.params, locals: res?.locals });
78
+ const libOptions = this.lib.definition.options;
79
+ const libOperations = this.lib.operations;
80
+ this.logger.debug('Posting All Action', { query: req?.query, params: req?.params, locals: res?.locals });
80
81
  const allActionKey = req.path.substring(req.path.lastIndexOf('/') + 1);
81
- if (!this.lib.allActions) {
82
+ if (!libOptions.allActions) {
82
83
  this.logger.error('Item Actions are not configured');
83
84
  res.status(500).json({ error: 'Item Actions are not configured' });
84
85
  return;
85
86
  }
86
- const allAction = this.lib.allActions[allActionKey];
87
+ const allAction = libOptions.allActions[allActionKey];
87
88
  if (!allAction) {
88
89
  this.logger.error('All Action is not configured', { allActionKey });
89
90
  res.status(500).json({ error: 'Item Action is not configured' });
90
91
  return;
91
92
  }
92
93
  try {
93
- res.json(await this.lib.allAction(allActionKey, req.body));
94
+ res.json(await libOperations.allAction(allActionKey, req.body));
94
95
  } catch (err: any) {
95
96
  this.logger.error('Error in All Action', { message: err?.message, stack: err?.stack });
96
97
  res.status(500).json(err);
@@ -98,14 +99,16 @@ export class ItemRouter<
98
99
  }
99
100
 
100
101
  protected getAllFacet = async (req: Request, res: Response) => {
101
- this.logger.default('Getting All Facet', { query: req?.query, params: req?.params, locals: res?.locals });
102
+ const libOptions = this.lib.definition.options;
103
+ const libOperations = this.lib.operations;
104
+ this.logger.debug('Getting All Facet', { query: req?.query, params: req?.params, locals: res?.locals });
102
105
  const facetKey = req.path.substring(req.path.lastIndexOf('/') + 1);
103
- if (!this.lib.allFacets) {
106
+ if (!libOptions.allFacets) {
104
107
  this.logger.error('Item Facets are not configured');
105
108
  res.status(500).json({ error: 'Item Facets are not configured' });
106
109
  return;
107
110
  }
108
- const facet = this.lib.allFacets[facetKey];
111
+ const facet = libOptions.allFacets[facetKey];
109
112
  if (!facet) {
110
113
  this.logger.error('Item Facet is not configured', { facetKey });
111
114
  res.status(500).json({ error: 'Item Facet is not configured' });
@@ -113,7 +116,7 @@ export class ItemRouter<
113
116
  }
114
117
  try {
115
118
  const combinedQueryParams = { ...req.query, ...req.params } as Record<string, string | number | boolean | Date | Array<string | number | boolean | Date>>;
116
- res.json(await this.lib.allFacet(facetKey, combinedQueryParams));
119
+ res.json(await libOperations.allFacet(facetKey, combinedQueryParams));
117
120
  } catch (err: any) {
118
121
  this.logger.error('Error in All Facet', { message: err?.message, stack: err?.stack });
119
122
  res.status(500).json(err);
@@ -121,22 +124,24 @@ export class ItemRouter<
121
124
  }
122
125
 
123
126
  protected postItemAction = async (req: Request, res: Response) => {
124
- this.logger.default('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
127
+ const libOptions = this.lib.definition.options;
128
+ const libOperations = this.lib.operations;
129
+ this.logger.debug('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
125
130
  const ik = this.getIk(res);
126
131
  const actionKey = req.path.substring(req.path.lastIndexOf('/') + 1);
127
- if (!this.lib.actions) {
132
+ if (!libOptions.actions) {
128
133
  this.logger.error('Item Actions are not configured');
129
134
  res.status(500).json({ error: 'Item Actions are not configured' });
130
135
  return;
131
136
  }
132
- const action = this.lib.actions[actionKey];
137
+ const action = libOptions.actions[actionKey];
133
138
  if (!action) {
134
139
  this.logger.error('Item Action is not configured', { actionKey });
135
140
  res.status(500).json({ error: 'Item Action is not configured' });
136
141
  return;
137
142
  }
138
143
  try {
139
- res.json(await this.lib.action(ik, actionKey, req.body));
144
+ res.json(await libOperations.action(ik, actionKey, req.body));
140
145
  } catch (err: any) {
141
146
  this.logger.error('Error in Item Action', { message: err?.message, stack: err?.stack });
142
147
  res.status(500).json(err);
@@ -144,15 +149,17 @@ export class ItemRouter<
144
149
  }
145
150
 
146
151
  protected getItemFacet = async (req: Request, res: Response) => {
147
- this.logger.default('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
152
+ const libOptions = this.lib.definition.options;
153
+ const libOperations = this.lib.operations;
154
+ this.logger.debug('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
148
155
  const ik = this.getIk(res);
149
156
  const facetKey = req.path.substring(req.path.lastIndexOf('/') + 1);
150
- if (!this.lib.facets) {
157
+ if (!libOptions.facets) {
151
158
  this.logger.error('Item Facets are not configured');
152
159
  res.status(500).json({ error: 'Item Facets are not configured' });
153
160
  return;
154
161
  }
155
- const facet = this.lib.facets[facetKey];
162
+ const facet = libOptions.facets[facetKey];
156
163
  if (!facet) {
157
164
  this.logger.error('Item Facet is not configured', { facetKey });
158
165
  res.status(500).json({ error: 'Item Facet is not configured' });
@@ -160,7 +167,7 @@ export class ItemRouter<
160
167
  }
161
168
  try {
162
169
  const combinedQueryParams = { ...req.query, ...req.params } as Record<string, string | number | boolean | Date | Array<string | number | boolean | Date>>;
163
- res.json(await this.lib.facet(ik, facetKey, combinedQueryParams));
170
+ res.json(await libOperations.facet(ik, facetKey, combinedQueryParams));
164
171
  } catch (err: any) {
165
172
  this.logger.error('Error in Item Facet', { message: err?.message, stack: err?.stack });
166
173
  res.status(500).json(err);
@@ -168,23 +175,24 @@ export class ItemRouter<
168
175
  }
169
176
 
170
177
  private configure = (router: Router) => {
171
- this.logger.default('Configuring Router', { pkType: this.getPkType() });
178
+ const libOptions = this.lib.definition.options;
179
+ this.logger.debug('Configuring Router', { pkType: this.getPkType() });
172
180
  router.get('/', this.findItems);
173
181
  router.post('/', this.createItem);
174
182
 
175
- this.logger.debug('All Actions supplied to Router', { allActions: this.lib.allActions });
176
- if (this.lib.allActions) {
177
- Object.keys(this.lib.allActions).forEach((actionKey) => {
178
- this.logger.default('Configuring All Action', { actionKey });
183
+ this.logger.default('All Actions supplied to Router', { allActions: libOptions.allActions });
184
+ if (libOptions.allActions) {
185
+ Object.keys(libOptions.allActions).forEach((actionKey) => {
186
+ this.logger.debug('Configuring All Action %s', actionKey);
179
187
  // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
180
188
  router.post(`/${actionKey}`, this.postAllAction);
181
189
  });
182
190
  }
183
191
 
184
- this.logger.debug('All Facets supplied to Router', { allFacets: this.lib.allFacets });
185
- if (this.lib.allFacets) {
186
- Object.keys(this.lib.allFacets).forEach((facetKey) => {
187
- this.logger.default('Configuring All Facet', { facetKey });
192
+ this.logger.default('All Facets supplied to Router', { allFacets: libOptions.allFacets });
193
+ if (libOptions.allFacets) {
194
+ Object.keys(libOptions.allFacets).forEach((facetKey) => {
195
+ this.logger.debug('Configuring All Facet %s', facetKey);
188
196
  // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
189
197
  router.get(`/${facetKey}`, this.getAllFacet);
190
198
  });
@@ -195,25 +203,25 @@ export class ItemRouter<
195
203
  itemRouter.put('/', this.updateItem);
196
204
  itemRouter.delete('/', this.deleteItem);
197
205
 
198
- this.logger.debug('Item Actions supplied to Router', { itemActions: this.lib.actions });
199
- if (this.lib.actions) {
200
- Object.keys(this.lib.actions).forEach((actionKey) => {
201
- this.logger.default('Configuring Item Action', { actionKey });
206
+ this.logger.default('Item Actions supplied to Router', { itemActions: libOptions.actions });
207
+ if (libOptions.actions) {
208
+ Object.keys(libOptions.actions).forEach((actionKey) => {
209
+ this.logger.debug('Configuring Item Action %s', actionKey);
202
210
  // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
203
211
  itemRouter.post(`/${actionKey}`, this.postItemAction)
204
212
  });
205
213
  }
206
214
 
207
- this.logger.debug('Item Facets supplied to Router', { itemFacets: this.lib.facets });
208
- if (this.lib.facets) {
209
- Object.keys(this.lib.facets).forEach((facetKey) => {
210
- this.logger.default('Configuring Item Facet', { facetKey });
215
+ this.logger.default('Item Facets supplied to Router', { itemFacets: libOptions.facets });
216
+ if (libOptions.facets) {
217
+ Object.keys(libOptions.facets).forEach((facetKey) => {
218
+ this.logger.debug('Configuring Item Facet %s', facetKey);
211
219
  // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
212
220
  itemRouter.get(`/${facetKey}`, this.getItemFacet)
213
221
  });
214
222
  }
215
223
 
216
- this.logger.default('Configuring Item Operations under PK Param', { pkParam: this.getPkParam() });
224
+ this.logger.debug('Configuring Item Operations under PK Param %s', this.getPkParam());
217
225
  router.use(`/:${this.getPkParam()}`, this.validatePrimaryKeyValue, itemRouter);
218
226
 
219
227
  if (this.childRouters) {
@@ -228,14 +236,14 @@ export class ItemRouter<
228
236
  res.locals[this.getPkParam()] = pkParamValue;
229
237
  next();
230
238
  } else {
231
- this.logger.error('Invalid Primary Key', { pkParamValue, path: req?.path });
232
- res.status(500).json({ error: 'Invalid Primary Key', path: req?.path });
239
+ this.logger.error('Invalid Primary Key', { pkParamValue, path: req?.originalUrl });
240
+ res.status(500).json({ error: 'Invalid Primary Key', path: req?.originalUrl });
233
241
  }
234
242
  }
235
243
 
236
244
  private configureChildRouters = (router: Router, childRouters: Record<string, Router>) => {
237
245
  for (const path in childRouters) {
238
- this.logger.default('Configuring Child Router at Path', { path });
246
+ this.logger.debug('Configuring Child Router at Path %s', path);
239
247
 
240
248
  router.use(`/${path}`, childRouters[path]);
241
249
  }
@@ -262,14 +270,16 @@ export class ItemRouter<
262
270
  // TODO: Probably a better way to do this, but this postCreate hook only needs the item.
263
271
  /* istanbul ignore next */
264
272
  public postCreateItem = async (item: Item<S, L1, L2, L3, L4, L5>): Promise<Item<S, L1, L2, L3, L4, L5>> => {
265
- this.logger.default('Post Create Item', { item });
273
+ this.logger.debug('Post Create Item', { item });
266
274
  return item;
267
275
  };
268
276
 
269
277
  protected deleteItem = async (req: Request, res: Response): Promise<void> => {
270
- this.logger.default('Deleting Item', { query: req.query, params: req.params, locals: res.locals });
278
+ const libOperations = this.lib.operations;
279
+
280
+ this.logger.debug('Deleting Item', { query: req.query, params: req.params, locals: res.locals });
271
281
  const ik = this.getIk(res);
272
- const removedItem = await this.lib.remove(ik);
282
+ const removedItem = await libOperations.remove(ik);
273
283
  const item = validatePK(removedItem, this.getPkType());
274
284
  res.json(item);
275
285
  };
@@ -282,11 +292,12 @@ export class ItemRouter<
282
292
  /* eslint-enable */
283
293
 
284
294
  protected getItem = async (req: Request, res: Response) => {
285
- this.logger.default('Getting Item', { query: req.query, params: req.params, locals: res.locals });
295
+ const libOperations = this.lib.operations;
296
+ this.logger.debug('Getting Item', { query: req.query, params: req.params, locals: res.locals });
286
297
  const ik = this.getIk(res);
287
298
  try {
288
299
  // TODO: What error does validate PK throw, when can that fail?
289
- const item = validatePK(await this.lib.get(ik), this.getPkType());
300
+ const item = validatePK(await libOperations.get(ik), this.getPkType());
290
301
  res.json(item);
291
302
  } catch (err: any) {
292
303
  if (err instanceof NotFoundError) {
@@ -306,18 +317,18 @@ export class ItemRouter<
306
317
  }
307
318
 
308
319
  protected updateItem = async (req: Request, res: Response) => {
309
- this.logger.default('Updating Item',
320
+ const libOperations = this.lib.operations;
321
+ this.logger.debug('Updating Item',
310
322
  { body: req?.body, query: req?.query, params: req?.params, locals: res?.locals });
311
323
  const ik = this.getIk(res);
312
- const itemToUpdate = this.convertDates(req.body as ItemProperties<S, L1, L2, L3, L4, L5>);
313
- const retItem = validatePK(await this.lib.update(ik, itemToUpdate), this.getPkType());
324
+ const itemToUpdate = this.convertDates(req.body as Partial<Item<S, L1, L2, L3, L4, L5>>);
325
+ const retItem = validatePK(await libOperations.update(ik, itemToUpdate), this.getPkType());
314
326
  res.json(retItem);
315
327
  };
316
328
 
317
- public convertDates = (item: Item<S, L1, L2, L3, L4, L5> | ItemProperties<S, L1, L2, L3, L4, L5>):
318
- Item<S, L1, L2, L3, L4, L5> | ItemProperties<S, L1, L2, L3, L4, L5> => {
329
+ public convertDates = (item: Partial<Item<S, L1, L2, L3, L4, L5>>): Partial<Item<S, L1, L2, L3, L4, L5>> => {
319
330
  const events = item.events as Record<string, ItemEvent>;
320
- this.logger.default('Converting Dates', { item });
331
+ this.logger.debug('Converting Dates', { item });
321
332
  if (events) {
322
333
  Object.keys(events).forEach((key: string) => {
323
334
  Object.assign(events, {
@@ -2,9 +2,6 @@ import { Item, ItemQuery, paramsToQuery, PriKey, QueryParams, validatePK } from
2
2
  import { Primary } from "@fjell/lib";
3
3
  import { ItemRouter, ItemRouterOptions } from "@/ItemRouter";
4
4
  import { Request, Response } from "express";
5
- import LibLogger from "@/logger";
6
-
7
- const logger = LibLogger.get('PItemRouter');
8
5
 
9
6
  interface ParsedQuery {
10
7
  [key: string]: undefined | string | string[] | ParsedQuery | ParsedQuery[];
@@ -12,7 +9,7 @@ interface ParsedQuery {
12
9
 
13
10
  export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter<S> {
14
11
 
15
- constructor(lib: Primary.Operations<T, S>, keyType: S, options: ItemRouterOptions = {}) {
12
+ constructor(lib: Primary.Instance<T, S>, keyType: S, options: ItemRouterOptions = {}) {
16
13
  super(lib as any, keyType, options);
17
14
  }
18
15
 
@@ -22,16 +19,18 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
22
19
  }
23
20
 
24
21
  public createItem = async (req: Request, res: Response) => {
25
- logger.default('Creating Item 2', { body: req.body, query: req.query, params: req.params, locals: res.locals });
22
+ const libOperations = this.lib.operations;
23
+ this.logger.default('Creating Item', { body: req.body, query: req.query, params: req.params, locals: res.locals });
26
24
  const itemToCreate = this.convertDates(req.body as Item<S>);
27
- let item =
28
- validatePK(await this.lib.create(itemToCreate), this.getPkType()) as Item<S>;
25
+ let item = validatePK(await libOperations.create(itemToCreate), this.getPkType()) as Item<S>;
29
26
  item = await this.postCreateItem(item);
27
+ this.logger.default('Created Item %j', item);
30
28
  res.json(item);
31
29
  };
32
30
 
33
31
  protected findItems = async (req: Request, res: Response) => {
34
- logger.default('Finding Items', { query: req.query, params: req.params, locals: res.locals });
32
+ const libOperations = this.lib.operations;
33
+ this.logger.default('Finding Items', { query: req.query, params: req.params, locals: res.locals });
35
34
 
36
35
  let items: Item<S>[] = [];
37
36
 
@@ -42,19 +41,19 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
42
41
 
43
42
  if (finder) {
44
43
  // If finder is defined? Call a finder.
45
- logger.default('Finding Items with a finder', { finder, finderParams, one });
44
+ this.logger.default('Finding Items with Finder %s %j one:%s', finder, finderParams, one);
46
45
 
47
46
  if (one === 'true') {
48
47
  const item = await (this.lib as any).findOne(finder, JSON.parse(finderParams));
49
48
  items = item ? [item] : [];
50
49
  } else {
51
- items = await this.lib.find(finder, JSON.parse(finderParams));
50
+ items = await libOperations.find(finder, JSON.parse(finderParams));
52
51
  }
53
52
  } else {
54
- logger.default('Finding Items with a query', { query: req.query });
55
53
  // TODO: This is once of the more important places to perform some validaation and feedback
56
54
  const itemQuery: ItemQuery = paramsToQuery(req.query as QueryParams);
57
- items = await this.lib.all(itemQuery);
55
+ this.logger.default('Finding Items with a query %j', itemQuery);
56
+ items = await libOperations.all(itemQuery);
58
57
  }
59
58
 
60
59
  res.json(items.map((item: Item<S>) => validatePK(item, this.getPkType())));
@@ -0,0 +1,65 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable no-undefined */
3
+ export const clean = (obj: any) => {
4
+ return Object.fromEntries(
5
+ Object.entries(obj).filter(([_, v]) => v !== undefined)
6
+ );
7
+ }
8
+
9
+ //Recursive implementation of jSON.stringify;
10
+ export const stringifyJSON = function (obj: any, visited: Set<any> = new Set()): string {
11
+ const arrOfKeyVals: string[] = [];
12
+ const arrVals: string[] = [];
13
+ let objKeys: string[] = [];
14
+
15
+ /*********CHECK FOR PRIMITIVE TYPES**********/
16
+ if (typeof obj === 'number' || typeof obj === 'boolean' || obj === null)
17
+ return '' + obj;
18
+ else if (typeof obj === 'string')
19
+ return '"' + obj + '"';
20
+
21
+ /*********DETECT CIRCULAR REFERENCES**********/
22
+ if (obj instanceof Object && visited.has(obj)) {
23
+ return '"(circular)"';
24
+ }
25
+
26
+ /*********CHECK FOR ARRAY**********/
27
+ else if (Array.isArray(obj)) {
28
+ //check for empty array
29
+ if (obj[0] === undefined)
30
+ return '[]';
31
+ else {
32
+ // Add array to visited before processing its elements
33
+ visited.add(obj);
34
+ obj.forEach(function (el) {
35
+ arrVals.push(stringifyJSON(el, visited));
36
+ });
37
+ return '[' + arrVals + ']';
38
+ }
39
+ }
40
+ /*********CHECK FOR OBJECT**********/
41
+ else if (obj instanceof Object) {
42
+ // Add object to visited before processing its properties
43
+ visited.add(obj);
44
+ //get object keys
45
+ objKeys = Object.keys(obj);
46
+ //set key output;
47
+ objKeys.forEach(function (key) {
48
+ const keyOut = '"' + key + '":';
49
+ const keyValOut = obj[key];
50
+ //skip functions and undefined properties
51
+ if (keyValOut instanceof Function || keyValOut === undefined)
52
+ return; // Skip this entry entirely instead of pushing an empty string
53
+ else if (typeof keyValOut === 'string')
54
+ arrOfKeyVals.push(keyOut + '"' + keyValOut + '"');
55
+ else if (typeof keyValOut === 'boolean' || typeof keyValOut === 'number' || keyValOut === null)
56
+ arrOfKeyVals.push(keyOut + keyValOut);
57
+ //check for nested objects, call recursively until no more objects
58
+ else if (keyValOut instanceof Object) {
59
+ arrOfKeyVals.push(keyOut + stringifyJSON(keyValOut, visited));
60
+ }
61
+ });
62
+ return '{' + arrOfKeyVals + '}';
63
+ }
64
+ return '';
65
+ };