@clairejs/server 3.22.0 → 3.22.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/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  ## Change Log
2
2
 
3
- #### 3.22.0:
3
+ #### 3.22.2:
4
4
 
5
+ - fix ModelRepository return correct vector props nested fields
6
+ - fix uri mapper to map Identifiable classes
5
7
  - remove support of uri mapper for locale fields
6
8
  - support uri mapper for nested array field
7
9
 
@@ -2,10 +2,7 @@ import { ModelFieldMetadata, ModelMetadata } from "@clairejs/core";
2
2
  import { IPrincipal } from "./auth/IPrincipal";
3
3
  import { UriMapperHandler } from "../http/file-upload/types";
4
4
  export interface ServerModelFieldMetadata extends ModelFieldMetadata {
5
- uriMapper?: {
6
- mapper: UriMapperHandler;
7
- subkeys: string[];
8
- };
5
+ uriMapper?: UriMapperHandler;
9
6
  userResolver?: (principal: IPrincipal) => any;
10
7
  }
11
8
  export interface ServerModelMetadata extends ModelMetadata {
@@ -1,4 +1,4 @@
1
- import { type Constructor, HttpMethod, RecursiveKeys, AbstractModel } from "@clairejs/core";
1
+ import { type Constructor, HttpMethod, Identifiable } from "@clairejs/core";
2
2
  import { type UriMapperHandler } from "./file-upload/types";
3
3
  import { type HttpResponse } from "./common/HttpResponse";
4
4
  import { type IPrincipal } from "../common/auth/IPrincipal";
@@ -21,7 +21,7 @@ export declare const Queries: () => (prototype: AbstractHttpController, property
21
21
  export declare const Headers: () => (prototype: AbstractHttpController, propertyKey: string, paramIndex: number) => void;
22
22
  export declare const Socket: () => (prototype: AbstractHttpController, propertyKey: string, paramIndex: number) => void;
23
23
  export declare const Raw: () => (prototype: AbstractHttpController, propertyKey: string, paramIndex: number) => void;
24
- export declare const UriMapper: (mapper: UriMapperHandler) => <T extends AbstractModel>(prototype: T, propertyKey: RecursiveKeys<T>) => void;
24
+ export declare const UriMapper: (mapper: UriMapperHandler) => <T extends Identifiable>(prototype: T, propertyKey: keyof T) => void;
25
25
  /**
26
26
  * Current User decorator only has effect when being used with ICrudRepository.
27
27
  * */
@@ -61,9 +61,8 @@ export const Headers = () => RequestDeco("headers");
61
61
  export const Socket = () => RequestDeco("socket");
62
62
  export const Raw = () => RequestDeco("raw");
63
63
  export const UriMapper = (mapper) => (prototype, propertyKey) => {
64
- const [rootKey, ...keys] = propertyKey.split(".");
65
- const field = initFieldMetadata(prototype, rootKey);
66
- field.uriMapper = { mapper, subkeys: keys };
64
+ const field = initFieldMetadata(prototype, propertyKey);
65
+ field.uriMapper = mapper;
67
66
  };
68
67
  /**
69
68
  * Current User decorator only has effect when being used with ICrudRepository.
@@ -1,34 +1,14 @@
1
1
  import { DataType, getModelById, getServiceProvider, RangeQueryDto, uniqueReducer, leanData, getSystemLocale, Errors, omitData, MODEL_FIELD_SEPARATOR, } from "@clairejs/core";
2
- import { getDirectFields } from "@clairejs/orm";
2
+ import { getDirectFields, getSafeUpdate, } from "@clairejs/orm";
3
3
  import { AbstractFileUploadHandler } from "../file-upload/AbstractFileUploadHandler";
4
4
  import { AbstractRepository } from "./AbstractRepository";
5
5
  import { LocaleTranslation } from "../../system/locale/LocaleTranslation";
6
6
  import { LocaleEntry } from "../../system/locale/LocaleEntry";
7
- const resolveUris = (record, field, subkeys) => {
8
- return field.vectorProps
9
- ? field.vectorProps.elementDataType === DataType.STRING
10
- ? record[field.name]
11
- : record[field.name].map((obj) => subkeys.reduce((value, key) => value[key], obj))
12
- : [record[field.name]];
7
+ const resolveUris = (record, field) => {
8
+ return field.vectorProps?.elementDataType === DataType.STRING ? record[field.name] : [record[field.name]];
13
9
  };
14
- const assignUrls = (record, field, subkeys, urls) => {
15
- if (field.vectorProps) {
16
- if (field.vectorProps.elementDataType === DataType.STRING) {
17
- record[field.name] = urls;
18
- }
19
- else {
20
- record[field.name].forEach((obj, index) => {
21
- let currentObj = obj;
22
- for (let i = 0; i < subkeys.length - 1; i++) {
23
- currentObj = currentObj[subkeys[i]];
24
- }
25
- currentObj[subkeys[subkeys.length - 1]] = urls[index];
26
- });
27
- }
28
- }
29
- else {
30
- record[field.name] = urls[0];
31
- }
10
+ const assignUrls = (record, field, urls) => {
11
+ record[field.name] = field.vectorProps?.elementDataType === DataType.STRING ? urls : urls[0];
32
12
  };
33
13
  export class ModelRepository extends AbstractRepository {
34
14
  model;
@@ -157,12 +137,18 @@ export class ModelRepository extends AbstractRepository {
157
137
  //-- update record value and not persist yet
158
138
  return newUri;
159
139
  };
160
- if (fileUploadHandler) {
161
- for (const field of this.modelMetadata.fields) {
140
+ const mapRecords = (records, modelMetadata) => {
141
+ for (const field of modelMetadata.fields) {
162
142
  for (const record of records) {
163
- if (field.uriMapper && record[field.name]) {
164
- const { subkeys, mapper } = field.uriMapper;
165
- const tmpUris = resolveUris(record, field, subkeys);
143
+ const value = record[field.name];
144
+ if (!value)
145
+ continue;
146
+ if (field.elementDto) {
147
+ mapRecords(field.vectorProps ? value : [value], field.elementDto);
148
+ }
149
+ else if (field.uriMapper) {
150
+ const mapper = field.uriMapper;
151
+ const tmpUris = resolveUris(record, field);
166
152
  if (!tmpUris.length) {
167
153
  continue;
168
154
  }
@@ -171,11 +157,14 @@ export class ModelRepository extends AbstractRepository {
171
157
  }
172
158
  operations.push((async () => {
173
159
  const urls = await Promise.all(tmpUris.map((uri, index) => uriHandler(uri, index, mapper, getSystemLocale())));
174
- assignUrls(record, field, subkeys, urls);
160
+ assignUrls(record, field, urls);
175
161
  })());
176
162
  }
177
163
  }
178
164
  }
165
+ };
166
+ if (fileUploadHandler) {
167
+ mapRecords(records, this.modelMetadata);
179
168
  }
180
169
  //-- await all operations once to save time
181
170
  await Promise.all(operations);
@@ -192,22 +181,30 @@ export class ModelRepository extends AbstractRepository {
192
181
  return;
193
182
  }
194
183
  const mappingOperations = [];
195
- for (const record of records) {
196
- for (const field of this.modelMetadata.fields) {
197
- if (field.uriMapper && record[field.name]) {
198
- const { subkeys } = field.uriMapper;
199
- const uris = resolveUris(record, field, subkeys);
200
- mappingOperations.push((async () => {
201
- const urls = await Promise.all(uris.map(async (uri) => {
202
- return field.mimeProps?.public
203
- ? await fileUploadHandler.resolvePublicUrl(uri)
204
- : await fileUploadHandler.resolvePrivateUrl(uri);
205
- }));
206
- assignUrls(record, field, subkeys, urls);
207
- })());
184
+ const mapRecords = (records, modelMetadata) => {
185
+ for (const record of records) {
186
+ for (const field of modelMetadata.fields) {
187
+ const value = record[field.name];
188
+ if (!value)
189
+ continue;
190
+ if (field.elementDto && value) {
191
+ mapRecords(field.vectorProps ? value : [value], field.elementDto);
192
+ }
193
+ else if (field.uriMapper) {
194
+ const uris = resolveUris(record, field);
195
+ mappingOperations.push((async () => {
196
+ const urls = await Promise.all(uris.map(async (uri) => {
197
+ return field.mimeProps?.public
198
+ ? await fileUploadHandler.resolvePublicUrl(uri)
199
+ : await fileUploadHandler.resolvePrivateUrl(uri);
200
+ }));
201
+ assignUrls(record, field, urls);
202
+ })());
203
+ }
208
204
  }
209
205
  }
210
- }
206
+ };
207
+ mapRecords(records, this.modelMetadata);
211
208
  await Promise.all(mappingOperations);
212
209
  }
213
210
  async createMany({ principal, body, tx, logger, }) {
@@ -284,7 +281,11 @@ export class ModelRepository extends AbstractRepository {
284
281
  : [];
285
282
  await this.beforeReturning(records);
286
283
  const projection = this.modelMetadata.fields
287
- .filter((field) => !field.multiLocaleColumn && (field.pk || field.serverValue || field.mimeProps))
284
+ .filter((field) => !field.multiLocaleColumn &&
285
+ (field.pk ||
286
+ field.serverValue ||
287
+ field.mimeProps ||
288
+ field.vectorProps?.elementDataType === DataType.OBJECT))
288
289
  .map((field) => field.name);
289
290
  records = this.project(records, projection);
290
291
  //-- then create records for has many fields
@@ -335,9 +336,13 @@ export class ModelRepository extends AbstractRepository {
335
336
  const allConditions = ops || [];
336
337
  const hasManyFields = this.modelMetadata.fields.filter((f) => !!f.hasMany);
337
338
  const systemLocale = getSystemLocale();
339
+ const relevantIdentifiableVectorFields = this.modelMetadata.fields.filter((field) => field.vectorProps?.elementDataType === DataType.OBJECT &&
340
+ field.elementDto?.fields.some((f) => f.pk) &&
341
+ body.update[field.name]);
338
342
  //-- does not update multi locale columns
339
343
  const directUpdateFields = getDirectFields(this.modelMetadata).filter((f) => !f.multiLocaleColumn &&
340
- (body.update[f.name] !== undefined || (f.isMultiLocale && !!systemLocale)));
344
+ (body.update[f.name] !== undefined || (f.isMultiLocale && !!systemLocale)) &&
345
+ !relevantIdentifiableVectorFields.includes(f));
341
346
  const updatedFields = Object.keys(body.update);
342
347
  const localeOfFields = this.modelMetadata.fields.filter((f) => updatedFields.includes(f.name) && f.multiLocaleColumn);
343
348
  const cleanUp = await this.uriHandling([body.update]);
@@ -364,42 +369,57 @@ export class ModelRepository extends AbstractRepository {
364
369
  const nestedQueries = this.getNestedQueries(queries);
365
370
  let modified = [];
366
371
  let updatedRecords = [];
372
+ const projections = [
373
+ ...(queries?.returning || []),
374
+ ...relevantIdentifiableVectorFields.map((f) => f.name),
375
+ "id",
376
+ ];
367
377
  if (nestedQueries.length) {
368
378
  const tobeUpdated = await this.db
369
379
  .use(this.model, tx)
370
- .getMany(condition, { projection: [...(queries?.returning || []), "id"] }, nestedQueries);
371
- modified = tobeUpdated.records.map((r) => r.id);
380
+ .getRecords(condition, { projection: projections }, nestedQueries);
381
+ modified = tobeUpdated.map((r) => r.id);
372
382
  if (modified.length) {
373
383
  if (directUpdateFields.length) {
374
384
  updatedRecords = await this.db
375
385
  .use(this.model, tx)
376
- .updateMany({ _in: { id: modified } }, directUpdate, [
377
- ...(queries?.returning || []),
378
- "id",
379
- ]);
386
+ .updateMany({ _in: { id: modified } }, directUpdate, projections);
380
387
  }
381
388
  else {
382
- updatedRecords = tobeUpdated.records;
389
+ updatedRecords = tobeUpdated;
383
390
  }
384
391
  }
385
392
  }
386
393
  else {
387
394
  if (directUpdateFields.length) {
388
- updatedRecords = await this.db
389
- .use(this.model, tx)
390
- .updateMany(condition, directUpdate, [...(queries?.returning || []), "id"]);
395
+ updatedRecords = await this.db.use(this.model, tx).updateMany(condition, directUpdate, projections);
391
396
  modified = updatedRecords.map((re) => re.id);
392
397
  }
393
398
  else {
394
- const tobeUpdated = await this.db
395
- .use(this.model, tx)
396
- .getMany(condition, { projection: [...(queries?.returning || []), "id"] });
399
+ const tobeUpdated = await this.db.use(this.model, tx).getMany(condition, { projection: projections });
397
400
  modified = tobeUpdated.records.map((r) => r.id);
398
401
  updatedRecords = tobeUpdated.records;
399
402
  }
400
403
  }
404
+ for (const field of relevantIdentifiableVectorFields) {
405
+ const newValue = body.update[field.name];
406
+ for (const record of updatedRecords) {
407
+ const oldValue = record[field.name];
408
+ const tobeUpdated = newValue.map((newV) => {
409
+ const foundOld = oldValue?.find((v) => v.id === newV.id);
410
+ return {
411
+ ...foundOld,
412
+ ...newV,
413
+ };
414
+ });
415
+ const updatedRecord = await this.db
416
+ .use(this.model, tx)
417
+ .updateById(record.id, { [field.name]: tobeUpdated }, [field.name]);
418
+ Object.assign(record, updatedRecord);
419
+ }
420
+ }
401
421
  //-- body.update here had been modified by uri handling
402
- const records = updatedRecords.map((re) => ({ ...re, ...body.update, ...directUpdate }));
422
+ const records = updatedRecords.map((re) => ({ ...re, ...getSafeUpdate(body.update, this.modelMetadata) }));
403
423
  //-- update translations
404
424
  if (localeOfFields.length) {
405
425
  //-- check if there is missing locale entry for localeFields
@@ -510,19 +530,10 @@ export class ModelRepository extends AbstractRepository {
510
530
  .concat(updatedToBeKept.map((r) => r.modified[0]));
511
531
  theRecord[field.name] = (field.hasMany?.single ? finalInnerRecords[0] : finalInnerRecords);
512
532
  }
513
- let projection = ["id"];
514
- if (queries?.returning) {
515
- //-- return result
516
- projection = [
517
- ...projection,
518
- ...Object.keys(directUpdate).filter((key) => directUpdate[key] !== undefined),
519
- "lastModified",
520
- ];
521
- }
522
533
  //-- ok clean up
523
534
  cleanUp().catch((err) => logger?.error("Error in clean up", err));
524
535
  await this.beforeReturning(records);
525
- return { modified: this.project(records, projection) };
536
+ return { modified: this.project(records, projections) };
526
537
  }
527
538
  async getMany({ queries, ops, tx, logger: _logger, }) {
528
539
  const conditions = ops || [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clairejs/server",
3
- "version": "3.22.0",
3
+ "version": "3.22.2",
4
4
  "description": "Claire server NodeJs framework written in Typescript.",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
@@ -34,8 +34,8 @@
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@clairejs/client": "^3.4.4",
37
- "@clairejs/core": "^3.8.9",
38
- "@clairejs/orm": "^3.16.10"
37
+ "@clairejs/core": "^3.8.10",
38
+ "@clairejs/orm": "^3.16.15"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/cookie-parser": "^1.4.3",