@loomcore/api 0.1.90 → 0.1.91

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.
@@ -1,4 +1,4 @@
1
- import { Application } from 'express';
1
+ import { Request, Response, Application, NextFunction } from 'express';
2
2
  import { IUserContext, IQueryOptions, IUser } from '@loomcore/common/models';
3
3
  import { ApiController } from '../controllers/api.controller.js';
4
4
  import { MultiTenantApiService } from '../services/multi-tenant-api.service.js';
@@ -31,16 +31,13 @@ export declare class CategoryController extends ApiController<ICategory> {
31
31
  }
32
32
  export declare function setupTestConfig(isMultiTenant: boolean | undefined, dbType: DbType): void;
33
33
  export declare class ProductService extends GenericApiService<IProduct> {
34
- private db;
35
34
  constructor(database: IDatabase);
36
- prepareQuery(userContext: IUserContext, queryObject: IQueryOptions, operations: Operation[]): {
37
- queryObject: IQueryOptions;
38
- operations: Operation[];
39
- };
40
- postProcessEntity(userContext: IUserContext, single: any): any;
41
35
  }
42
36
  export declare class ProductsController extends ApiController<IProduct> {
43
37
  constructor(app: Application, database: IDatabase);
38
+ get(req: Request, res: Response, next: NextFunction): Promise<void>;
39
+ getAll(req: Request, res: Response, next: NextFunction): Promise<void>;
40
+ getById(req: Request, res: Response, next: NextFunction): Promise<void>;
44
41
  }
45
42
  export declare class MultiTenantProductService extends MultiTenantApiService<IProduct> {
46
43
  private db;
@@ -2,6 +2,7 @@ import crypto from 'crypto';
2
2
  import jwt from 'jsonwebtoken';
3
3
  import { EmptyUserContext } from '@loomcore/common/models';
4
4
  import { Type } from '@sinclair/typebox';
5
+ import { Value } from '@sinclair/typebox/value';
5
6
  import { JwtService } from '../services/jwt.service.js';
6
7
  import { ApiController } from '../controllers/api.controller.js';
7
8
  import { MultiTenantApiService } from '../services/multi-tenant-api.service.js';
@@ -15,11 +16,13 @@ import * as testObjectsModule from './test-objects.js';
15
16
  const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId } = testObjectsModule;
16
17
  import { CategorySpec } from './models/category.model.js';
17
18
  import { ProductSpec } from './models/product.model.js';
19
+ import { ProductWithCategoryPublicSpec, ProductWithCategorySpec } from './models/product-with-category.model.js';
18
20
  import { setBaseApiConfig, config } from '../config/index.js';
19
21
  import { entityUtils } from '@loomcore/common/utils';
20
22
  import { getTestMetaOrgUserPerson, getTestOrgUser, getTestOrgUserPerson, setTestMetaOrgUserPersonId, setTestOrgUserPersonId } from './test-objects.js';
21
23
  import { TestEmailClient } from './test-email-client.js';
22
24
  import { PersonService } from '../services/person.service.js';
25
+ import { apiUtils } from '../utils/index.js';
23
26
  let deviceIdCookie;
24
27
  let authService;
25
28
  let organizationService;
@@ -234,39 +237,70 @@ export function setupTestConfig(isMultiTenant = true, dbType) {
234
237
  },
235
238
  });
236
239
  }
240
+ const prepareQueryCustom = (userContext, queryObject, operations) => {
241
+ return {
242
+ queryObject: queryObject,
243
+ operations: [
244
+ ...operations,
245
+ new Join('categories', 'categoryId', '_id', 'category')
246
+ ]
247
+ };
248
+ };
249
+ const postProcessEntityCustom = (userContext, entity) => {
250
+ return {
251
+ ...entity,
252
+ category: entity._joinData?.category
253
+ };
254
+ };
237
255
  export class ProductService extends GenericApiService {
238
- db;
239
256
  constructor(database) {
240
257
  super(database, 'products', 'product', ProductSpec);
241
- this.db = database;
242
- }
243
- prepareQuery(userContext, queryObject, operations) {
244
- const newOperations = [
245
- ...operations,
246
- new Join('categories', 'categoryId', '_id', 'category')
247
- ];
248
- return super.prepareQuery(userContext, queryObject, newOperations);
249
- }
250
- postProcessEntity(userContext, single) {
251
- if (single && single.category) {
252
- const categoryService = new CategoryService(this.db);
253
- single.category = categoryService.postProcessEntity(userContext, single.category);
254
- }
255
- return super.postProcessEntity(userContext, single);
256
258
  }
257
259
  }
258
260
  export class ProductsController extends ApiController {
259
261
  constructor(app, database) {
260
262
  const productService = new ProductService(database);
261
- const AggregatedProductSchema = Type.Intersect([
262
- ProductSpec.fullSchema,
263
- Type.Partial(Type.Object({
264
- category: CategorySpec.fullSchema
265
- }))
266
- ]);
267
- const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
268
- const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
269
- super('products', app, productService, 'product', ProductSpec, PublicAggregatedProductSpec);
263
+ super('products', app, productService, 'product', ProductSpec);
264
+ }
265
+ async get(req, res, next) {
266
+ res.set('Content-Type', 'application/json');
267
+ const userContext = req.userContext;
268
+ if (!userContext) {
269
+ throw new Error('User context not found');
270
+ }
271
+ const queryOptions = apiUtils.getQueryOptionsFromRequest(req);
272
+ const pagedResult = await this.service.get(userContext, queryOptions, prepareQueryCustom, postProcessEntityCustom);
273
+ apiUtils.apiResponse(res, 200, { data: pagedResult }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
274
+ }
275
+ async getAll(req, res, next) {
276
+ res.set('Content-Type', 'application/json');
277
+ const userContext = req.userContext;
278
+ if (!userContext) {
279
+ throw new Error('User context not found');
280
+ }
281
+ const entities = await this.service.getAll(userContext, prepareQueryCustom, postProcessEntityCustom);
282
+ apiUtils.apiResponse(res, 200, { data: entities }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
283
+ }
284
+ async getById(req, res, next) {
285
+ res.set('Content-Type', 'application/json');
286
+ const userContext = req.userContext;
287
+ if (!userContext) {
288
+ throw new Error('User context not found');
289
+ }
290
+ const idParam = req.params?.id;
291
+ if (!idParam) {
292
+ throw new Error('ID parameter is required');
293
+ }
294
+ try {
295
+ const id = Value.Convert(this.idSchema, idParam);
296
+ console.log('got to Id response');
297
+ const entity = await this.service.getById(userContext, id, prepareQueryCustom, postProcessEntityCustom);
298
+ console.log('got to Id response 2');
299
+ apiUtils.apiResponse(res, 200, { data: entity }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
300
+ }
301
+ catch (error) {
302
+ throw new Error(`Invalid ID format: ${error.message || error}`);
303
+ }
270
304
  }
271
305
  }
272
306
  export class MultiTenantProductService extends MultiTenantApiService {
@@ -0,0 +1,21 @@
1
+ import { ICategory } from "./category.model.js";
2
+ import { IAuditable, IEntity } from "@loomcore/common/models";
3
+ export interface IProductWithCategory extends IEntity, IAuditable {
4
+ name: string;
5
+ description?: string;
6
+ internalNumber?: string;
7
+ category: ICategory;
8
+ }
9
+ export declare const ProductWithCategorySchema: import("@sinclair/typebox").TObject<{
10
+ name: import("@sinclair/typebox").TString;
11
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
12
+ internalNumber: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
13
+ category: import("@sinclair/typebox").TSchema;
14
+ }>;
15
+ export declare const ProductWithCategorySpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
16
+ export declare const PublicProductWithCategorySchema: import("@sinclair/typebox").TObject<{
17
+ name: import("@sinclair/typebox").TString;
18
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
19
+ category: import("@sinclair/typebox").TSchema;
20
+ }>;
21
+ export declare const ProductWithCategoryPublicSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
@@ -0,0 +1,12 @@
1
+ import { entityUtils } from "@loomcore/common/utils";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { CategorySpec } from "./category.model.js";
4
+ export const ProductWithCategorySchema = Type.Object({
5
+ name: Type.String(),
6
+ description: Type.Optional(Type.String()),
7
+ internalNumber: Type.Optional(Type.String()),
8
+ category: CategorySpec.fullSchema,
9
+ });
10
+ export const ProductWithCategorySpec = entityUtils.getModelSpec(ProductWithCategorySchema, { isAuditable: true });
11
+ export const PublicProductWithCategorySchema = Type.Omit(ProductWithCategorySchema, ['internalNumber']);
12
+ export const ProductWithCategoryPublicSpec = entityUtils.getModelSpec(PublicProductWithCategorySchema);
@@ -1,12 +1,10 @@
1
1
  import { IAuditable, IEntity } from "@loomcore/common/models";
2
- import { ICategory } from "./category.model.js";
3
2
  import { AppIdType } from "@loomcore/common/types";
4
3
  export interface IProduct extends IEntity, IAuditable {
5
4
  name: string;
6
5
  description?: string;
7
6
  internalNumber?: string;
8
7
  categoryId: AppIdType;
9
- category?: ICategory;
10
8
  }
11
9
  export declare const ProductSchema: import("@sinclair/typebox").TObject<{
12
10
  name: import("@sinclair/typebox").TString;
@@ -8,3 +8,4 @@ export * from './roles.controller.js';
8
8
  export * from './authorizations.controller.js';
9
9
  export * from './features.controller.js';
10
10
  export * from './persons.controller.js';
11
+ export * from './types.js';
@@ -8,3 +8,4 @@ export * from './roles.controller.js';
8
8
  export * from './authorizations.controller.js';
9
9
  export * from './features.controller.js';
10
10
  export * from './persons.controller.js';
11
+ export * from './types.js';
@@ -0,0 +1,7 @@
1
+ import { IEntity, IQueryOptions, IUserContext } from "@loomcore/common/models";
2
+ import { Operation } from "../databases/index.js";
3
+ export type PrepareQueryCustomFunction = (userContext: IUserContext | undefined, queryObject: IQueryOptions, operations: Operation[]) => {
4
+ queryObject: IQueryOptions;
5
+ operations: Operation[];
6
+ };
7
+ export type PostProcessEntityCustomFunction<TIn extends IEntity, TOut extends IEntity> = (userContext: IUserContext, entity: TIn) => TOut;
@@ -0,0 +1 @@
1
+ export {};
@@ -2,21 +2,49 @@ import { Join } from '../../operations/join.operation.js';
2
2
  import { JoinMany } from '../../operations/join-many.operation.js';
3
3
  import { JoinThrough } from '../../operations/join-through.operation.js';
4
4
  import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
5
+ function resolveLocalFieldPath(localField, processedOperations) {
6
+ if (!localField.includes('.')) {
7
+ return localField;
8
+ }
9
+ const [parentAlias] = localField.split('.');
10
+ const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany) &&
11
+ op.as === parentAlias);
12
+ if (parentJoin) {
13
+ return `_joinData.${localField}`;
14
+ }
15
+ return localField;
16
+ }
5
17
  export function convertOperationsToPipeline(operations) {
6
18
  let pipeline = [];
7
19
  const processedOperations = [];
20
+ const joinAliases = operations
21
+ .filter(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany)
22
+ .map(op => op.as);
23
+ if (joinAliases.length > 0) {
24
+ pipeline.push({
25
+ $set: {
26
+ _joinData: {}
27
+ }
28
+ });
29
+ }
8
30
  operations.forEach(operation => {
9
31
  if (operation instanceof Join) {
10
32
  const needsObjectIdConversion = operation.foreignField === '_id';
33
+ const isNestedField = operation.localField.includes('.');
34
+ const resolvedLocalField = resolveLocalFieldPath(operation.localField, processedOperations);
11
35
  if (needsObjectIdConversion) {
12
36
  pipeline.push({
13
37
  $lookup: {
14
38
  from: operation.from,
15
- let: { localId: { $cond: [
16
- { $eq: [{ $type: `$${operation.localField}` }, 'string'] },
17
- { $toObjectId: `$${operation.localField}` },
18
- `$${operation.localField}`
19
- ] } },
39
+ let: {
40
+ localId: {
41
+ $cond: [
42
+ { $eq: [{ $type: `$${resolvedLocalField}` }, 'string'] },
43
+ { $toObjectId: `$${resolvedLocalField}` },
44
+ `$${resolvedLocalField}`
45
+ ]
46
+ }
47
+ },
20
48
  pipeline: [
21
49
  {
22
50
  $match: {
@@ -35,7 +63,7 @@ export function convertOperationsToPipeline(operations) {
35
63
  }
36
64
  }, {
37
65
  $addFields: {
38
- [operation.as]: `$${operation.as}Arr`
66
+ [`_joinData.${operation.as}`]: `$${operation.as}Arr`
39
67
  }
40
68
  }, {
41
69
  $project: {
@@ -47,7 +75,7 @@ export function convertOperationsToPipeline(operations) {
47
75
  pipeline.push({
48
76
  $lookup: {
49
77
  from: operation.from,
50
- localField: operation.localField,
78
+ localField: resolvedLocalField,
51
79
  foreignField: operation.foreignField,
52
80
  as: `${operation.as}Arr`
53
81
  }
@@ -58,7 +86,7 @@ export function convertOperationsToPipeline(operations) {
58
86
  }
59
87
  }, {
60
88
  $addFields: {
61
- [operation.as]: `$${operation.as}Arr`
89
+ [`_joinData.${operation.as}`]: `$${operation.as}Arr`
62
90
  }
63
91
  }, {
64
92
  $project: {
@@ -66,19 +94,43 @@ export function convertOperationsToPipeline(operations) {
66
94
  }
67
95
  });
68
96
  }
97
+ if (isNestedField) {
98
+ const [parentAlias] = operation.localField.split('.');
99
+ const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany) && op.as === parentAlias);
100
+ if (parentJoin) {
101
+ pipeline.push({
102
+ $set: {
103
+ [`_joinData.${parentAlias}`]: {
104
+ $mergeObjects: [
105
+ { $ifNull: [`$_joinData.${parentAlias}`, {}] },
106
+ { [operation.as]: `$_joinData.${operation.as}` }
107
+ ]
108
+ }
109
+ }
110
+ });
111
+ pipeline.push({
112
+ $unset: `_joinData.${operation.as}`
113
+ });
114
+ }
115
+ }
69
116
  }
70
117
  else if (operation instanceof JoinMany) {
71
118
  const needsObjectIdConversion = operation.foreignField === '_id';
72
119
  const isNestedField = operation.localField.includes('.');
120
+ const resolvedLocalField = resolveLocalFieldPath(operation.localField, processedOperations);
73
121
  if (needsObjectIdConversion || isNestedField) {
74
122
  pipeline.push({
75
123
  $lookup: {
76
124
  from: operation.from,
77
- let: { localId: { $cond: [
78
- { $eq: [{ $type: `$${operation.localField}` }, 'string'] },
79
- { $toObjectId: `$${operation.localField}` },
80
- `$${operation.localField}`
81
- ] } },
125
+ let: {
126
+ localId: {
127
+ $cond: [
128
+ { $eq: [{ $type: `$${resolvedLocalField}` }, 'string'] },
129
+ { $toObjectId: `$${resolvedLocalField}` },
130
+ `$${resolvedLocalField}`
131
+ ]
132
+ }
133
+ },
82
134
  pipeline: [
83
135
  {
84
136
  $match: {
@@ -88,27 +140,33 @@ export function convertOperationsToPipeline(operations) {
88
140
  }
89
141
  }
90
142
  ],
91
- as: operation.as
143
+ as: `${operation.as}_temp`
144
+ }
145
+ }, {
146
+ $addFields: {
147
+ [`_joinData.${operation.as}`]: `$${operation.as}_temp`
148
+ }
149
+ }, {
150
+ $project: {
151
+ [`${operation.as}_temp`]: 0
92
152
  }
93
153
  });
94
154
  if (isNestedField) {
95
155
  const [parentAlias] = operation.localField.split('.');
96
- const parentJoin = processedOperations.find(op => op instanceof Join && op.as === parentAlias);
156
+ const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany) && op.as === parentAlias);
97
157
  if (parentJoin) {
98
158
  pipeline.push({
99
- $addFields: {
100
- [parentAlias]: {
159
+ $set: {
160
+ [`_joinData.${parentAlias}`]: {
101
161
  $mergeObjects: [
102
- `$${parentAlias}`,
103
- { [operation.as]: `$${operation.as}` }
162
+ { $ifNull: [`$_joinData.${parentAlias}`, {}] },
163
+ { [operation.as]: `$_joinData.${operation.as}` }
104
164
  ]
105
165
  }
106
166
  }
107
167
  });
108
168
  pipeline.push({
109
- $project: {
110
- [operation.as]: 0
111
- }
169
+ $unset: `_joinData.${operation.as}`
112
170
  });
113
171
  }
114
172
  }
@@ -117,9 +175,17 @@ export function convertOperationsToPipeline(operations) {
117
175
  pipeline.push({
118
176
  $lookup: {
119
177
  from: operation.from,
120
- localField: operation.localField,
178
+ localField: resolvedLocalField,
121
179
  foreignField: operation.foreignField,
122
- as: operation.as
180
+ as: `${operation.as}_temp`
181
+ }
182
+ }, {
183
+ $addFields: {
184
+ [`_joinData.${operation.as}`]: `$${operation.as}_temp`
185
+ }
186
+ }, {
187
+ $project: {
188
+ [`${operation.as}_temp`]: 0
123
189
  }
124
190
  });
125
191
  }
@@ -127,15 +193,20 @@ export function convertOperationsToPipeline(operations) {
127
193
  else if (operation instanceof JoinThrough) {
128
194
  const needsObjectIdConversion = operation.foreignField === '_id';
129
195
  const isNestedField = operation.localField.includes('.');
196
+ const resolvedLocalField = resolveLocalFieldPath(operation.localField, processedOperations);
130
197
  if (needsObjectIdConversion || isNestedField) {
131
198
  pipeline.push({
132
199
  $lookup: {
133
200
  from: operation.through,
134
- let: { localId: { $cond: [
135
- { $eq: [{ $type: `$${operation.localField}` }, 'string'] },
136
- { $toObjectId: `$${operation.localField}` },
137
- `$${operation.localField}`
138
- ] } },
201
+ let: {
202
+ localId: {
203
+ $cond: [
204
+ { $eq: [{ $type: `$${resolvedLocalField}` }, 'string'] },
205
+ { $toObjectId: `$${resolvedLocalField}` },
206
+ `$${resolvedLocalField}`
207
+ ]
208
+ }
209
+ },
139
210
  pipeline: [
140
211
  {
141
212
  $match: {
@@ -165,11 +236,15 @@ export function convertOperationsToPipeline(operations) {
165
236
  }, {
166
237
  $lookup: {
167
238
  from: operation.from,
168
- let: { foreignId: { $cond: [
239
+ let: {
240
+ foreignId: {
241
+ $cond: [
169
242
  { $eq: [{ $type: `$${operation.as}_through.${operation.throughForeignField}` }, 'string'] },
170
243
  { $toObjectId: `$${operation.as}_through.${operation.throughForeignField}` },
171
244
  `$${operation.as}_through.${operation.throughForeignField}`
172
- ] } },
245
+ ]
246
+ }
247
+ },
173
248
  pipeline: [
174
249
  {
175
250
  $match: {
@@ -197,7 +272,7 @@ export function convertOperationsToPipeline(operations) {
197
272
  }
198
273
  }, {
199
274
  $addFields: {
200
- [operation.as]: `$${operation.as}_temp`
275
+ [`_joinData.${operation.as}`]: `$${operation.as}_temp`
201
276
  }
202
277
  }, {
203
278
  $project: {
@@ -211,27 +286,28 @@ export function convertOperationsToPipeline(operations) {
211
286
  if (parentJoin) {
212
287
  pipeline.push({
213
288
  $set: {
214
- [parentAlias]: {
289
+ [`_joinData.${parentAlias}`]: {
215
290
  $mergeObjects: [
216
- { $ifNull: [`$${parentAlias}`, {}] },
217
- { [operation.as]: `$${operation.as}` }
291
+ { $ifNull: [`$_joinData.${parentAlias}`, {}] },
292
+ { [operation.as]: `$_joinData.${operation.as}` }
218
293
  ]
219
294
  }
220
295
  }
221
296
  });
222
297
  pipeline.push({
223
- $unset: operation.as
298
+ $unset: `_joinData.${operation.as}`
224
299
  });
225
300
  }
226
301
  }
227
302
  }
228
303
  else {
229
304
  const isNestedFieldElse = operation.localField.includes('.');
305
+ const resolvedLocalFieldElse = resolveLocalFieldPath(operation.localField, processedOperations);
230
306
  if (isNestedFieldElse) {
231
307
  pipeline.push({
232
308
  $lookup: {
233
309
  from: operation.through,
234
- let: { localId: `$${operation.localField}` },
310
+ let: { localId: `$${resolvedLocalFieldElse}` },
235
311
  pipeline: [
236
312
  {
237
313
  $match: {
@@ -255,7 +331,7 @@ export function convertOperationsToPipeline(operations) {
255
331
  pipeline.push({
256
332
  $lookup: {
257
333
  from: operation.through,
258
- localField: operation.localField,
334
+ localField: resolvedLocalFieldElse,
259
335
  foreignField: operation.throughLocalField,
260
336
  as: `${operation.as}_through`
261
337
  }
@@ -280,7 +356,7 @@ export function convertOperationsToPipeline(operations) {
280
356
  }
281
357
  }, {
282
358
  $addFields: {
283
- [operation.as]: `$${operation.as}_temp`
359
+ [`_joinData.${operation.as}`]: `$${operation.as}_temp`
284
360
  }
285
361
  }, {
286
362
  $project: {
@@ -293,19 +369,17 @@ export function convertOperationsToPipeline(operations) {
293
369
  const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
294
370
  if (parentJoin) {
295
371
  pipeline.push({
296
- $addFields: {
297
- [parentAlias]: {
372
+ $set: {
373
+ [`_joinData.${parentAlias}`]: {
298
374
  $mergeObjects: [
299
- `$${parentAlias}`,
300
- { [operation.as]: `$${operation.as}` }
375
+ { $ifNull: [`$_joinData.${parentAlias}`, {}] },
376
+ { [operation.as]: `$_joinData.${operation.as}` }
301
377
  ]
302
378
  }
303
379
  }
304
380
  });
305
381
  pipeline.push({
306
- $project: {
307
- [operation.as]: 0
308
- }
382
+ $unset: `_joinData.${operation.as}`
309
383
  });
310
384
  }
311
385
  }
@@ -314,15 +388,20 @@ export function convertOperationsToPipeline(operations) {
314
388
  else if (operation instanceof JoinThroughMany) {
315
389
  const needsObjectIdConversion = operation.foreignField === '_id';
316
390
  const isNestedField = operation.localField.includes('.');
391
+ const resolvedLocalField = resolveLocalFieldPath(operation.localField, processedOperations);
317
392
  if (needsObjectIdConversion || isNestedField) {
318
393
  pipeline.push({
319
394
  $lookup: {
320
395
  from: operation.through,
321
- let: { localId: { $cond: [
322
- { $eq: [{ $type: `$${operation.localField}` }, 'string'] },
323
- { $toObjectId: `$${operation.localField}` },
324
- `$${operation.localField}`
325
- ] } },
396
+ let: {
397
+ localId: {
398
+ $cond: [
399
+ { $eq: [{ $type: `$${resolvedLocalField}` }, 'string'] },
400
+ { $toObjectId: `$${resolvedLocalField}` },
401
+ `$${resolvedLocalField}`
402
+ ]
403
+ }
404
+ },
326
405
  pipeline: [
327
406
  {
328
407
  $match: {
@@ -351,11 +430,15 @@ export function convertOperationsToPipeline(operations) {
351
430
  }, {
352
431
  $lookup: {
353
432
  from: operation.from,
354
- let: { foreignId: { $cond: [
433
+ let: {
434
+ foreignId: {
435
+ $cond: [
355
436
  { $eq: [{ $type: `$${operation.as}_through.${operation.throughForeignField}` }, 'string'] },
356
437
  { $toObjectId: `$${operation.as}_through.${operation.throughForeignField}` },
357
438
  `$${operation.as}_through.${operation.throughForeignField}`
358
- ] } },
439
+ ]
440
+ }
441
+ },
359
442
  pipeline: [
360
443
  {
361
444
  $match: {
@@ -380,18 +463,19 @@ export function convertOperationsToPipeline(operations) {
380
463
  $group: {
381
464
  _id: '$_id',
382
465
  root: { $first: '$$ROOT' },
383
- [operation.as]: { $push: { $arrayElemAt: [`$${operation.as}_temp`, 0] } }
466
+ [`${operation.as}_temp_grouped`]: { $push: { $arrayElemAt: [`$${operation.as}_temp`, 0] } }
384
467
  }
385
468
  }, {
386
469
  $replaceRoot: {
387
470
  newRoot: {
388
- $mergeObjects: ['$root', { [operation.as]: `$${operation.as}` }]
471
+ $mergeObjects: ['$root', { [`_joinData.${operation.as}`]: `$${operation.as}_temp_grouped` }]
389
472
  }
390
473
  }
391
474
  }, {
392
475
  $project: {
393
476
  [`${operation.as}_through`]: 0,
394
- [`${operation.as}_temp`]: 0
477
+ [`${operation.as}_temp`]: 0,
478
+ [`${operation.as}_temp_grouped`]: 0
395
479
  }
396
480
  });
397
481
  if (isNestedField) {
@@ -400,27 +484,28 @@ export function convertOperationsToPipeline(operations) {
400
484
  if (parentJoin) {
401
485
  pipeline.push({
402
486
  $set: {
403
- [parentAlias]: {
487
+ [`_joinData.${parentAlias}`]: {
404
488
  $mergeObjects: [
405
- { $ifNull: [`$${parentAlias}`, {}] },
406
- { [operation.as]: `$${operation.as}` }
489
+ { $ifNull: [`$_joinData.${parentAlias}`, {}] },
490
+ { [operation.as]: `$_joinData.${operation.as}` }
407
491
  ]
408
492
  }
409
493
  }
410
494
  });
411
495
  pipeline.push({
412
- $unset: operation.as
496
+ $unset: `_joinData.${operation.as}`
413
497
  });
414
498
  }
415
499
  }
416
500
  }
417
501
  else {
418
502
  const isNestedFieldElse = operation.localField.includes('.');
503
+ const resolvedLocalFieldElse = resolveLocalFieldPath(operation.localField, processedOperations);
419
504
  if (isNestedFieldElse) {
420
505
  pipeline.push({
421
506
  $lookup: {
422
507
  from: operation.through,
423
- let: { localId: `$${operation.localField}` },
508
+ let: { localId: `$${resolvedLocalFieldElse}` },
424
509
  pipeline: [
425
510
  {
426
511
  $match: {
@@ -443,7 +528,7 @@ export function convertOperationsToPipeline(operations) {
443
528
  pipeline.push({
444
529
  $lookup: {
445
530
  from: operation.through,
446
- localField: operation.localField,
531
+ localField: resolvedLocalFieldElse,
447
532
  foreignField: operation.throughLocalField,
448
533
  as: `${operation.as}_through`
449
534
  }
@@ -465,18 +550,19 @@ export function convertOperationsToPipeline(operations) {
465
550
  $group: {
466
551
  _id: '$_id',
467
552
  root: { $first: '$$ROOT' },
468
- [operation.as]: { $push: { $arrayElemAt: [`$${operation.as}_temp`, 0] } }
553
+ [`${operation.as}_temp_grouped`]: { $push: { $arrayElemAt: [`$${operation.as}_temp`, 0] } }
469
554
  }
470
555
  }, {
471
556
  $replaceRoot: {
472
557
  newRoot: {
473
- $mergeObjects: ['$root', { [operation.as]: `$${operation.as}` }]
558
+ $mergeObjects: ['$root', { [`_joinData.${operation.as}`]: `$${operation.as}_temp_grouped` }]
474
559
  }
475
560
  }
476
561
  }, {
477
562
  $project: {
478
563
  [`${operation.as}_through`]: 0,
479
- [`${operation.as}_temp`]: 0
564
+ [`${operation.as}_temp`]: 0,
565
+ [`${operation.as}_temp_grouped`]: 0
480
566
  }
481
567
  });
482
568
  if (isNestedFieldElse) {
@@ -484,19 +570,17 @@ export function convertOperationsToPipeline(operations) {
484
570
  const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
485
571
  if (parentJoin) {
486
572
  pipeline.push({
487
- $addFields: {
488
- [parentAlias]: {
573
+ $set: {
574
+ [`_joinData.${parentAlias}`]: {
489
575
  $mergeObjects: [
490
- `$${parentAlias}`,
491
- { [operation.as]: `$${operation.as}` }
576
+ { $ifNull: [`$_joinData.${parentAlias}`, {}] },
577
+ { [operation.as]: `$_joinData.${operation.as}` }
492
578
  ]
493
579
  }
494
580
  }
495
581
  });
496
582
  pipeline.push({
497
- $project: {
498
- [operation.as]: 0
499
- }
583
+ $unset: `_joinData.${operation.as}`
500
584
  });
501
585
  }
502
586
  }
@@ -83,6 +83,7 @@ export function transformJoinResults(rows, operations) {
83
83
  }
84
84
  return rows.map(row => {
85
85
  const transformed = {};
86
+ const joinData = {};
86
87
  for (const key of Object.keys(row)) {
87
88
  const hasJoinPrefix = joinOperations.some(join => key.startsWith(`${join.as}__`));
88
89
  const isJoinAlias = allJoinAliases.includes(key);
@@ -113,14 +114,14 @@ export function transformJoinResults(rows, operations) {
113
114
  const relatedJoin = joinOperations.find(j => j.as === tableAlias);
114
115
  const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
115
116
  let targetObject = null;
116
- if (relatedJoin && transformed[relatedJoin.as]) {
117
- targetObject = transformed[relatedJoin.as];
117
+ if (relatedJoin && joinData[relatedJoin.as]) {
118
+ targetObject = joinData[relatedJoin.as];
118
119
  }
119
- else if (relatedJoinThrough && transformed[relatedJoinThrough.as]) {
120
- targetObject = transformed[relatedJoinThrough.as];
120
+ else if (relatedJoinThrough && joinData[relatedJoinThrough.as]) {
121
+ targetObject = joinData[relatedJoinThrough.as];
121
122
  }
122
123
  else {
123
- const found = findNestedObject(transformed, tableAlias);
124
+ const found = findNestedObject(joinData, tableAlias);
124
125
  if (found) {
125
126
  targetObject = found.obj;
126
127
  }
@@ -129,11 +130,11 @@ export function transformJoinResults(rows, operations) {
129
130
  targetObject[operation.as] = hasAnyData ? joinedData : null;
130
131
  }
131
132
  else {
132
- transformed[operation.as] = hasAnyData ? joinedData : null;
133
+ joinData[operation.as] = hasAnyData ? joinedData : null;
133
134
  }
134
135
  }
135
136
  else {
136
- transformed[operation.as] = hasAnyData ? joinedData : null;
137
+ joinData[operation.as] = hasAnyData ? joinedData : null;
137
138
  }
138
139
  }
139
140
  else if (operation instanceof JoinThrough) {
@@ -143,11 +144,11 @@ export function transformJoinResults(rows, operations) {
143
144
  const relatedJoin = joinOperations.find(j => j.as === tableAlias);
144
145
  const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
145
146
  let targetObject = null;
146
- if (relatedJoin && transformed[relatedJoin.as]) {
147
- targetObject = transformed[relatedJoin.as];
147
+ if (relatedJoin && joinData[relatedJoin.as]) {
148
+ targetObject = joinData[relatedJoin.as];
148
149
  }
149
150
  else if (relatedJoinThrough) {
150
- const found = findNestedObject(transformed, relatedJoinThrough.as);
151
+ const found = findNestedObject(joinData, relatedJoinThrough.as);
151
152
  if (found) {
152
153
  targetObject = found.obj;
153
154
  }
@@ -156,11 +157,11 @@ export function transformJoinResults(rows, operations) {
156
157
  targetObject[operation.as] = jsonValue;
157
158
  }
158
159
  else {
159
- transformed[operation.as] = jsonValue;
160
+ joinData[operation.as] = jsonValue;
160
161
  }
161
162
  }
162
163
  else {
163
- transformed[operation.as] = jsonValue;
164
+ joinData[operation.as] = jsonValue;
164
165
  }
165
166
  }
166
167
  else if (operation instanceof JoinMany) {
@@ -191,30 +192,30 @@ export function transformJoinResults(rows, operations) {
191
192
  const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
192
193
  const relatedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
193
194
  let targetObject = null;
194
- if (relatedJoin && transformed[relatedJoin.as]) {
195
- targetObject = transformed[relatedJoin.as];
195
+ if (relatedJoin && joinData[relatedJoin.as]) {
196
+ targetObject = joinData[relatedJoin.as];
196
197
  }
197
198
  else if (relatedJoinThrough) {
198
- const found = findNestedObject(transformed, relatedJoinThrough.as);
199
+ const found = findNestedObject(joinData, relatedJoinThrough.as);
199
200
  if (found) {
200
201
  targetObject = found.obj;
201
202
  }
202
203
  }
203
- else if (relatedJoinMany && transformed[relatedJoinMany.as]) {
204
- targetObject = transformed[relatedJoinMany.as];
204
+ else if (relatedJoinMany && joinData[relatedJoinMany.as]) {
205
+ targetObject = joinData[relatedJoinMany.as];
205
206
  }
206
- else if (relatedJoinThroughMany && transformed[relatedJoinThroughMany.as]) {
207
- targetObject = transformed[relatedJoinThroughMany.as];
207
+ else if (relatedJoinThroughMany && joinData[relatedJoinThroughMany.as]) {
208
+ targetObject = joinData[relatedJoinThroughMany.as];
208
209
  }
209
210
  if (targetObject) {
210
211
  targetObject[operation.as] = parsedValue;
211
212
  }
212
213
  else {
213
- transformed[operation.as] = parsedValue;
214
+ joinData[operation.as] = parsedValue;
214
215
  }
215
216
  }
216
217
  else {
217
- transformed[operation.as] = parsedValue;
218
+ joinData[operation.as] = parsedValue;
218
219
  }
219
220
  }
220
221
  else if (operation instanceof JoinThroughMany) {
@@ -245,33 +246,36 @@ export function transformJoinResults(rows, operations) {
245
246
  const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
246
247
  const relatedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
247
248
  let targetObject = null;
248
- if (relatedJoin && transformed[relatedJoin.as]) {
249
- targetObject = transformed[relatedJoin.as];
249
+ if (relatedJoin && joinData[relatedJoin.as]) {
250
+ targetObject = joinData[relatedJoin.as];
250
251
  }
251
252
  else if (relatedJoinThrough) {
252
- const found = findNestedObject(transformed, relatedJoinThrough.as);
253
+ const found = findNestedObject(joinData, relatedJoinThrough.as);
253
254
  if (found) {
254
255
  targetObject = found.obj;
255
256
  }
256
257
  }
257
- else if (relatedJoinMany && transformed[relatedJoinMany.as]) {
258
- targetObject = transformed[relatedJoinMany.as];
258
+ else if (relatedJoinMany && joinData[relatedJoinMany.as]) {
259
+ targetObject = joinData[relatedJoinMany.as];
259
260
  }
260
- else if (relatedJoinThroughMany && transformed[relatedJoinThroughMany.as]) {
261
- targetObject = transformed[relatedJoinThroughMany.as];
261
+ else if (relatedJoinThroughMany && joinData[relatedJoinThroughMany.as]) {
262
+ targetObject = joinData[relatedJoinThroughMany.as];
262
263
  }
263
264
  if (targetObject) {
264
265
  targetObject[operation.as] = parsedValue;
265
266
  }
266
267
  else {
267
- transformed[operation.as] = parsedValue;
268
+ joinData[operation.as] = parsedValue;
268
269
  }
269
270
  }
270
271
  else {
271
- transformed[operation.as] = parsedValue;
272
+ joinData[operation.as] = parsedValue;
272
273
  }
273
274
  }
274
275
  }
276
+ if (Object.keys(joinData).length > 0) {
277
+ transformed._joinData = joinData;
278
+ }
275
279
  return transformed;
276
280
  });
277
281
  }
@@ -3,6 +3,7 @@ import { IUserContext, IEntity, IPagedResult, IQueryOptions } from '@loomcore/co
3
3
  import type { AppIdType } from '@loomcore/common/types';
4
4
  import { DeleteResult } from '../../databases/models/delete-result.js';
5
5
  import { Operation } from '../../databases/operations/operation.js';
6
+ import { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from '../../controllers/types.js';
6
7
  export interface IGenericApiService<T extends IEntity> {
7
8
  validate(doc: any, isPartial?: boolean): ValueError[] | null;
8
9
  validateMany(docs: any[], isPartial?: boolean): ValueError[] | null;
@@ -13,8 +14,11 @@ export interface IGenericApiService<T extends IEntity> {
13
14
  preProcessEntity(userContext: IUserContext, entity: Partial<T>, isCreate: boolean, allowId: boolean): Promise<Partial<T>>;
14
15
  postProcessEntity(userContext: IUserContext, entity: T): T;
15
16
  getAll(userContext: IUserContext): Promise<T[]>;
16
- get(userContext: IUserContext, queryOptions: IQueryOptions): Promise<IPagedResult<T>>;
17
+ getAll<TCustom extends IEntity>(userContext: IUserContext, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<TCustom[]>;
18
+ get(userContext: IUserContext, queryOptions?: IQueryOptions): Promise<IPagedResult<T>>;
19
+ get<TCustom extends IEntity>(userContext: IUserContext, queryOptions: IQueryOptions, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<IPagedResult<TCustom>>;
17
20
  getById(userContext: IUserContext, id: AppIdType): Promise<T>;
21
+ getById<TCustom extends IEntity>(userContext: IUserContext, id: AppIdType, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<TCustom>;
18
22
  getCount(userContext: IUserContext): Promise<number>;
19
23
  create(userContext: IUserContext, entity: Partial<T>): Promise<T | null>;
20
24
  createMany(userContext: IUserContext, entities: Partial<T>[]): Promise<T[]>;
@@ -5,6 +5,7 @@ import { IGenericApiService } from './generic-api-service.interface.js';
5
5
  import { Operation } from '../../databases/operations/operation.js';
6
6
  import { DeleteResult } from '../../databases/models/delete-result.js';
7
7
  import { IDatabase } from '../../databases/models/index.js';
8
+ import { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from '../../controllers/types.js';
8
9
  export declare class GenericApiService<T extends IEntity> implements IGenericApiService<T> {
9
10
  protected database: IDatabase;
10
11
  protected pluralResourceName: string;
@@ -12,6 +13,7 @@ export declare class GenericApiService<T extends IEntity> implements IGenericApi
12
13
  protected modelSpec: IModelSpec;
13
14
  constructor(database: IDatabase, pluralResourceName: string, singularResourceName: string, modelSpec: IModelSpec);
14
15
  getAll(userContext: IUserContext): Promise<T[]>;
16
+ getAll<TCustom extends IEntity>(userContext: IUserContext, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<TCustom[]>;
15
17
  prepareQuery(userContext: IUserContext | undefined, queryObject: IQueryOptions, operations: Operation[]): {
16
18
  queryObject: IQueryOptions;
17
19
  operations: Operation[];
@@ -21,8 +23,10 @@ export declare class GenericApiService<T extends IEntity> implements IGenericApi
21
23
  preProcessEntity(userContext: IUserContext, entity: Partial<T>, isCreate: boolean, allowId?: boolean): Promise<Partial<T>>;
22
24
  postProcessEntity(userContext: IUserContext, entity: T): T;
23
25
  get(userContext: IUserContext, queryOptions?: IQueryOptions): Promise<IPagedResult<T>>;
26
+ get<TCustom extends IEntity>(userContext: IUserContext, queryOptions: IQueryOptions, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<IPagedResult<TCustom>>;
24
27
  protected prepareQueryOptions(userContext: IUserContext | undefined, queryOptions: IQueryOptions): IQueryOptions;
25
28
  getById(userContext: IUserContext, id: AppIdType): Promise<T>;
29
+ getById<TCustom extends IEntity>(userContext: IUserContext, id: AppIdType, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<TCustom>;
26
30
  getCount(userContext: IUserContext): Promise<number>;
27
31
  create(userContext: IUserContext, entity: Partial<T>): Promise<T | null>;
28
32
  createMany(userContext: IUserContext, entities: Partial<T>[]): Promise<T[]>;
@@ -16,10 +16,22 @@ export class GenericApiService {
16
16
  this.modelSpec = modelSpec;
17
17
  this.database = database;
18
18
  }
19
- async getAll(userContext) {
20
- const { operations } = this.prepareQuery(userContext, {}, []);
19
+ async getAll(userContext, prepareQueryCustom, postProcessEntityCustom) {
20
+ let operations = [];
21
+ if (prepareQueryCustom) {
22
+ operations = prepareQueryCustom(userContext, {}, []).operations;
23
+ }
24
+ else {
25
+ operations = this.prepareQuery(userContext, {}, []).operations;
26
+ }
21
27
  const entities = await this.database.getAll(operations, this.pluralResourceName);
22
- return entities.map(entity => this.postProcessEntity(userContext, entity));
28
+ return entities.map(entity => {
29
+ const dbProcessed = this.postProcessEntity(userContext, entity);
30
+ if (postProcessEntityCustom) {
31
+ return postProcessEntityCustom(userContext, dbProcessed);
32
+ }
33
+ return dbProcessed;
34
+ });
23
35
  }
24
36
  prepareQuery(userContext, queryObject, operations) {
25
37
  return { queryObject, operations };
@@ -63,11 +75,23 @@ export class GenericApiService {
63
75
  postProcessEntity(userContext, entity) {
64
76
  return this.database.postProcessEntity(entity, this.modelSpec.fullSchema);
65
77
  }
66
- async get(userContext, queryOptions = { ...DefaultQueryOptions }) {
78
+ async get(userContext, queryOptions = { ...DefaultQueryOptions }, prepareQueryCustom, postProcessEntityCustom) {
67
79
  const preparedOptions = this.prepareQueryOptions(userContext, queryOptions);
68
- const { operations } = this.prepareQuery(userContext, {}, []);
80
+ let operations = [];
81
+ if (prepareQueryCustom) {
82
+ operations = prepareQueryCustom(userContext, {}, []).operations;
83
+ }
84
+ else {
85
+ operations = this.prepareQuery(userContext, {}, []).operations;
86
+ }
69
87
  const pagedResult = await this.database.get(operations, preparedOptions, this.modelSpec, this.pluralResourceName);
70
- const transformedEntities = (pagedResult.entities || []).map(entity => this.postProcessEntity(userContext, entity));
88
+ const transformedEntities = (pagedResult.entities || []).map(entity => {
89
+ const transformedEntity = this.postProcessEntity(userContext, entity);
90
+ if (postProcessEntityCustom) {
91
+ return postProcessEntityCustom(userContext, transformedEntity);
92
+ }
93
+ return transformedEntity;
94
+ });
71
95
  return {
72
96
  ...pagedResult,
73
97
  entities: transformedEntities
@@ -76,13 +100,30 @@ export class GenericApiService {
76
100
  prepareQueryOptions(userContext, queryOptions) {
77
101
  return queryOptions;
78
102
  }
79
- async getById(userContext, id) {
80
- const { operations, queryObject } = this.prepareQuery(userContext, {}, []);
103
+ async getById(userContext, id, prepareQueryCustom, postProcessEntityCustom) {
104
+ let operations = [];
105
+ let queryObject = {};
106
+ if (prepareQueryCustom) {
107
+ const result = prepareQueryCustom(userContext, {}, []);
108
+ operations = result.operations;
109
+ queryObject = result.queryObject;
110
+ }
111
+ else {
112
+ const result = this.prepareQuery(userContext, {}, []);
113
+ operations = result.operations;
114
+ queryObject = result.queryObject;
115
+ }
81
116
  const entity = await this.database.getById(operations, queryObject, id, this.pluralResourceName);
82
117
  if (!entity) {
83
118
  throw new IdNotFoundError();
84
119
  }
85
- return this.postProcessEntity(userContext, entity);
120
+ const transformedEntity = this.postProcessEntity(userContext, entity);
121
+ if (postProcessEntityCustom) {
122
+ return postProcessEntityCustom(userContext, transformedEntity);
123
+ }
124
+ else {
125
+ return transformedEntity;
126
+ }
86
127
  }
87
128
  async getCount(userContext) {
88
129
  this.prepareQuery(userContext, {}, []);
@@ -5,14 +5,21 @@ function apiResponse(response, status, options = {}, modelSpec, publicSpec) {
5
5
  const specForEncoding = publicSpec ?? modelSpec;
6
6
  if (specForEncoding && options.data) {
7
7
  if (Array.isArray(options.data)) {
8
- options.data = options.data.map((item) => specForEncoding.encode(item));
8
+ options.data = options.data.map((item) => {
9
+ delete item._joinData;
10
+ return specForEncoding.encode(item);
11
+ });
9
12
  }
10
13
  else if (typeof options.data === 'object' && options.data !== null && 'entities' in options.data && Array.isArray(options.data.entities)) {
11
14
  const pagedResult = options.data;
12
- pagedResult.entities = pagedResult.entities.map((item) => specForEncoding.encode(item));
15
+ pagedResult.entities = pagedResult.entities.map((item) => {
16
+ delete item._joinData;
17
+ return specForEncoding?.encode(item);
18
+ });
13
19
  options.data = pagedResult;
14
20
  }
15
21
  else {
22
+ delete options.data._joinData;
16
23
  const encodedData = specForEncoding.encode(options.data);
17
24
  options.data = encodedData;
18
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {
@@ -57,7 +57,7 @@
57
57
  "qs": "^6.15.0"
58
58
  },
59
59
  "peerDependencies": {
60
- "@loomcore/common": "^0.0.50",
60
+ "@loomcore/common": "^0.0.51",
61
61
  "@sinclair/typebox": "0.34.33",
62
62
  "cookie-parser": "^1.4.6",
63
63
  "cors": "^2.8.5",