nicot 1.2.13 → 1.3.0

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/dist/index.mjs CHANGED
@@ -633,6 +633,7 @@ import { IsOptional } from "class-validator";
633
633
 
634
634
  // src/utility/metadata.ts
635
635
  import { MetadataSetter, Reflector } from "typed-reflector";
636
+ import _ from "lodash";
636
637
  var Metadata = new MetadataSetter();
637
638
  var reflector = new Reflector();
638
639
  function getSpecificFields(obj, type, filter = () => true) {
@@ -645,11 +646,11 @@ function getSpecificFields(obj, type, filter = () => true) {
645
646
  });
646
647
  }
647
648
  function getNotInResultFields(obj, keepEntityVersioningDates = false) {
648
- return getSpecificFields(
649
- obj,
650
- "notInResult",
651
- (meta) => !keepEntityVersioningDates || !meta.entityVersioningDate
652
- );
649
+ const res = getSpecificFields(obj, "notInResult");
650
+ if (keepEntityVersioningDates) {
651
+ return _.difference(res, getSpecificFields(obj, "entityVersioningDate"));
652
+ }
653
+ return res;
653
654
  }
654
655
 
655
656
  // src/decorators/access.ts
@@ -665,8 +666,12 @@ var NotCreatable = () => MergePropertyDecorators([
665
666
  var NotChangeable = () => MergePropertyDecorators([
666
667
  Metadata.set("notChangeable", true, "notChangeableFields")
667
668
  ]);
669
+ var NotUpsertable = () => MergePropertyDecorators([
670
+ IsOptional(),
671
+ Metadata.set("notUpsertable", true, "notUpsertableFields")
672
+ ]);
668
673
  var NotQueryable = () => Metadata.set("notQueryable", true, "notQueryableFields");
669
- var NotInResult = (options = {}) => Metadata.set("notInResult", options, "notInResultFields");
674
+ var NotInResult = () => Metadata.set("notInResult", true, "notInResultFields");
670
675
 
671
676
  // src/decorators/property.ts
672
677
  import { ApiProperty as ApiProperty2 } from "@nestjs/swagger";
@@ -1360,6 +1365,24 @@ var BindingValue = (bindingKey = DefaultBindingKey) => (obj, key, des) => {
1360
1365
  )(obj, key);
1361
1366
  };
1362
1367
 
1368
+ // src/decorators/upsert.ts
1369
+ import { Unique } from "typeorm";
1370
+ var UpsertColumn = () => Metadata.set("upsertColumn", true, "upsertColumnFields");
1371
+ var UpsertableEntity = () => (cls) => {
1372
+ const upsertColumns = getSpecificFields(cls, "upsertColumn");
1373
+ const bindingColumns = getSpecificFields(cls, "bindingColumn");
1374
+ if (!upsertColumns.length && !bindingColumns.length) {
1375
+ throw new Error(
1376
+ `UpsertableEntity ${cls.name} must have at least one UpsertColumn or BindingColumn defined.`
1377
+ );
1378
+ }
1379
+ Metadata.set("upsertableEntity", true)(cls);
1380
+ if (!bindingColumns.length && upsertColumns.length === 1 && upsertColumns[0] === "id") {
1381
+ return;
1382
+ }
1383
+ Unique([.../* @__PURE__ */ new Set([...bindingColumns, ...upsertColumns])])(cls);
1384
+ };
1385
+
1363
1386
  // src/dto/cursor-pagination.ts
1364
1387
  var CursorPaginationDto = class {
1365
1388
  };
@@ -1497,6 +1520,7 @@ __decorateClass([
1497
1520
  ], PageSettingsDto.prototype, "recordsPerPage", 2);
1498
1521
 
1499
1522
  // src/bases/time-base.ts
1523
+ var EntityVersioningDate = () => Metadata.set("entityVersioningDate", true, "entityVersioningDateFields");
1500
1524
  var TimeBase = class extends PageSettingsDto {
1501
1525
  isValidInCreate() {
1502
1526
  return;
@@ -1519,23 +1543,35 @@ var TimeBase = class extends PageSettingsDto {
1519
1543
  // eslint-disable-next-line @typescript-eslint/no-empty-function
1520
1544
  async beforeUpdate() {
1521
1545
  }
1546
+ isValidInUpsert() {
1547
+ return;
1548
+ }
1549
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1550
+ async beforeUpsert() {
1551
+ }
1552
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1553
+ async afterUpsert() {
1554
+ }
1522
1555
  };
1523
1556
  __decorateClass([
1524
1557
  CreateDateColumn({ select: false }),
1525
1558
  NotColumn(),
1526
- NotInResult({ entityVersioningDate: true }),
1559
+ NotInResult(),
1560
+ EntityVersioningDate(),
1527
1561
  Reflect.metadata("design:type", Date)
1528
1562
  ], TimeBase.prototype, "createTime", 2);
1529
1563
  __decorateClass([
1530
1564
  UpdateDateColumn({ select: false }),
1531
1565
  NotColumn(),
1532
- NotInResult({ entityVersioningDate: true }),
1566
+ NotInResult(),
1567
+ EntityVersioningDate(),
1533
1568
  Reflect.metadata("design:type", Date)
1534
1569
  ], TimeBase.prototype, "updateTime", 2);
1535
1570
  __decorateClass([
1536
1571
  DeleteDateColumn({ select: false }),
1537
1572
  NotColumn(),
1538
- NotInResult({ entityVersioningDate: true }),
1573
+ NotInResult(),
1574
+ EntityVersioningDate(),
1539
1575
  Reflect.metadata("design:type", Date)
1540
1576
  ], TimeBase.prototype, "deleteTime", 2);
1541
1577
 
@@ -1596,7 +1632,8 @@ function StringIdBase(idOptions) {
1596
1632
  ...idOptions.uuid ? [UuidColumn({ ...columnOptions, generated: true }), NotWritable()] : [
1597
1633
  StringColumn(idOptions.length || 255, columnOptions),
1598
1634
  IsNotEmpty2(),
1599
- NotChangeable()
1635
+ NotChangeable(),
1636
+ UpsertColumn()
1600
1637
  ]
1601
1638
  ];
1602
1639
  const dec = MergePropertyDecorators4(decs);
@@ -1605,9 +1642,9 @@ function StringIdBase(idOptions) {
1605
1642
  }
1606
1643
 
1607
1644
  // src/crud-base.ts
1608
- import { ConsoleLogger } from "@nestjs/common";
1645
+ import { ConsoleLogger, HttpException } from "@nestjs/common";
1609
1646
  import { camelCase } from "typeorm/util/StringUtils";
1610
- import _3, { omit } from "lodash";
1647
+ import _4, { omit } from "lodash";
1611
1648
  import {
1612
1649
  BlankReturnMessageDto as BlankReturnMessageDto2,
1613
1650
  GenericReturnMessageDto,
@@ -1617,7 +1654,7 @@ import {
1617
1654
 
1618
1655
  // src/utility/get-typeorm-relations.ts
1619
1656
  import { getMetadataArgsStorage } from "typeorm";
1620
- import _ from "lodash";
1657
+ import _2 from "lodash";
1621
1658
  function getTypeormRelations(cl) {
1622
1659
  const relations = getMetadataArgsStorage().relations.filter(
1623
1660
  (r) => r.target === cl
@@ -1658,7 +1695,7 @@ function getTypeormRelations(cl) {
1658
1695
  };
1659
1696
  }
1660
1697
  );
1661
- return _.uniqBy(
1698
+ return _2.uniqBy(
1662
1699
  [...typeormRelations, ...computedRelations],
1663
1700
  // Merge typeorm relations and computed relations
1664
1701
  (r) => r.propertyName
@@ -1667,7 +1704,7 @@ function getTypeormRelations(cl) {
1667
1704
 
1668
1705
  // src/utility/cursor-pagination-utils.ts
1669
1706
  import { Brackets as Brackets2 } from "typeorm";
1670
- import _2 from "lodash";
1707
+ import _3 from "lodash";
1671
1708
  import SJSON from "superjson";
1672
1709
 
1673
1710
  // src/utility/filter-relations.ts
@@ -1835,7 +1872,7 @@ async function getPaginatedResult(qb, entityClass, entityAliasName, take, cursor
1835
1872
  );
1836
1873
  if (keys.length) {
1837
1874
  const staircasedKeys = keys.map(
1838
- (key, i) => _2.range(i + 1).map((j) => keys[j])
1875
+ (key, i) => _3.range(i + 1).map((j) => keys[j])
1839
1876
  );
1840
1877
  const cursorKey = (key) => `_cursor_${key.replace(/\./g, "__").replace(/"/g, "")}`;
1841
1878
  const expressionMatrix = staircasedKeys.map(
@@ -2139,15 +2176,23 @@ var CrudBase = class {
2139
2176
  this._restoreBindings(snap);
2140
2177
  return res;
2141
2178
  }
2179
+ async _mayBeTransaction(cb, manager = this.repo.manager) {
2180
+ const hasActiveTx = !!manager.queryRunner?.isTransactionActive;
2181
+ const getRepo = (m) => m.getRepository(this.entityClass);
2182
+ if (hasActiveTx) {
2183
+ return cb(manager, getRepo(manager));
2184
+ } else {
2185
+ return manager.transaction(async (tdb) => await cb(tdb, getRepo(tdb)));
2186
+ }
2187
+ }
2142
2188
  async _batchCreate(ents, beforeCreate, skipErrors = false) {
2143
2189
  const entsWithId = ents.filter((ent) => ent.id != null);
2144
- return this.repo.manager.transaction(async (mdb) => {
2190
+ return this._mayBeTransaction(async (mdb, repo) => {
2145
2191
  let skipped = [];
2146
- const repo = mdb.getRepository(this.entityClass);
2147
2192
  let entsToSave = ents;
2148
2193
  if (entsWithId.length) {
2149
2194
  const entIds = entsWithId.map((ent) => ent.id);
2150
- const entIdChunks = _3.chunk(entIds, 65535);
2195
+ const entIdChunks = _4.chunk(entIds, 65535);
2151
2196
  const existingEnts = (await Promise.all(
2152
2197
  entIdChunks.map(
2153
2198
  (chunk) => repo.find({
@@ -2218,7 +2263,7 @@ var CrudBase = class {
2218
2263
  await beforeCreate(repo);
2219
2264
  }
2220
2265
  try {
2221
- const entChunksToSave = _3.chunk(
2266
+ const entChunksToSave = _4.chunk(
2222
2267
  entsToSave,
2223
2268
  Math.floor(
2224
2269
  65535 / Math.max(1, Object.keys(entsToSave[0] || {}).length)
@@ -2257,8 +2302,7 @@ var CrudBase = class {
2257
2302
  if (invalidReason) {
2258
2303
  throw new BlankReturnMessageDto2(400, invalidReason).toException();
2259
2304
  }
2260
- const savedEnt = await this.repo.manager.transaction(async (mdb) => {
2261
- const repo = mdb.getRepository(this.entityClass);
2305
+ const savedEnt = await this._mayBeTransaction(async (mdb, repo) => {
2262
2306
  if (ent.id != null) {
2263
2307
  const existingEnt = await repo.findOne({
2264
2308
  where: { id: ent.id },
@@ -2357,12 +2401,24 @@ var CrudBase = class {
2357
2401
  });
2358
2402
  }
2359
2403
  }
2404
+ _applyQueryKeepEntityVersioningDates(qb) {
2405
+ if (this.crudOptions.keepEntityVersioningDates) {
2406
+ const versioningDateFields = getSpecificFields(
2407
+ this.entityClass,
2408
+ "entityVersioningDate"
2409
+ );
2410
+ for (const field of versioningDateFields) {
2411
+ qb.addSelect(`${this.entityAliasName}.${field}`);
2412
+ }
2413
+ }
2414
+ }
2360
2415
  async findOne(id, extraQuery = () => {
2361
2416
  }) {
2362
2417
  const bindingEnt = await this.getBindingPartialEntity();
2363
2418
  const query = this.queryBuilder().where(`${this.entityAliasName}.id = :id`, { id }).take(1);
2364
2419
  this._applyQueryRelations(query);
2365
2420
  this._applyQueryFromBinding(bindingEnt, query);
2421
+ this._applyQueryKeepEntityVersioningDates(query);
2366
2422
  this.extraGetQuery(query);
2367
2423
  extraQuery(query);
2368
2424
  query.take(1);
@@ -2404,6 +2460,7 @@ var CrudBase = class {
2404
2460
  this._applyQueryRelations(query);
2405
2461
  this._applyQueryFilters(query, newEnt);
2406
2462
  this._applyQueryFromBinding(bindingEnt, query);
2463
+ this._applyQueryKeepEntityVersioningDates(query);
2407
2464
  const pageSettings = newEnt instanceof PageSettingsDto ? newEnt : Object.assign(new PageSettingsDto(), newEnt);
2408
2465
  this.extraGetQuery(query);
2409
2466
  extraQuery(query);
@@ -2496,6 +2553,116 @@ var CrudBase = class {
2496
2553
  }
2497
2554
  return new BlankReturnMessageDto2(200, "success");
2498
2555
  }
2556
+ async upsert(_ent) {
2557
+ const bindingEnt = await this.getBindingPartialEntity();
2558
+ if (!_ent) {
2559
+ throw new BlankReturnMessageDto2(400, "Invalid entity").toException();
2560
+ }
2561
+ const ent = new this.entityClass();
2562
+ Object.assign(
2563
+ ent,
2564
+ omit(_ent, ...this._typeormRelations.map((r) => r.propertyName))
2565
+ );
2566
+ const invalidReason = ent.isValidInUpsert();
2567
+ if (invalidReason) {
2568
+ throw new BlankReturnMessageDto2(400, invalidReason).toException();
2569
+ }
2570
+ const upsertColumns = getSpecificFields(this.entityClass, "upsertColumn");
2571
+ const conditions = {
2572
+ ..._4.pick(ent, upsertColumns),
2573
+ ...bindingEnt
2574
+ };
2575
+ const conditionKeys = [
2576
+ .../* @__PURE__ */ new Set([
2577
+ ...getSpecificFields(this.entityClass, "bindingColumn"),
2578
+ ...upsertColumns
2579
+ ])
2580
+ ];
2581
+ Object.assign(ent, conditions);
2582
+ let deleteColumnProperty = "";
2583
+ if (!this.crudOptions.hardDelete) {
2584
+ const deleteColumn = this.repo.manager.connection.getMetadata(
2585
+ this.entityClass
2586
+ ).deleteDateColumn;
2587
+ if (deleteColumn) {
2588
+ ent[deleteColumn.propertyName] = null;
2589
+ deleteColumnProperty = deleteColumn.propertyName;
2590
+ }
2591
+ }
2592
+ await ent.beforeUpsert?.();
2593
+ try {
2594
+ const savedEnt = await this._mayBeTransaction(async (mdb, repo) => {
2595
+ const res = await repo.upsert(ent, {
2596
+ conflictPaths: conditionKeys
2597
+ });
2598
+ const insertedId = res.identifiers[0]?.id;
2599
+ const fetchSaved = () => {
2600
+ const qb = repo.createQueryBuilder(this.entityAliasName);
2601
+ if (insertedId != null) {
2602
+ qb.where(`${this.entityAliasName}.id = :id`, { id: insertedId });
2603
+ } else {
2604
+ conditionKeys.forEach((key, i) => {
2605
+ const paramKey = `_cond_${key}`;
2606
+ qb[i === 0 ? "where" : "andWhere"](
2607
+ `${this.entityAliasName}.${key} = :${paramKey}`,
2608
+ {
2609
+ [paramKey]: conditions[key]
2610
+ }
2611
+ );
2612
+ });
2613
+ }
2614
+ qb.take(1);
2615
+ if (deleteColumnProperty) {
2616
+ if (!this.crudOptions.keepEntityVersioningDates) {
2617
+ qb.addSelect(`${this.entityAliasName}.${deleteColumnProperty}`);
2618
+ }
2619
+ qb.withDeleted();
2620
+ }
2621
+ this._applyQueryKeepEntityVersioningDates(qb);
2622
+ if (this.crudOptions.upsertIncludeRelations) {
2623
+ this._applyQueryRelations(qb);
2624
+ }
2625
+ return qb.getOne();
2626
+ };
2627
+ let saved = await fetchSaved();
2628
+ if (!saved) {
2629
+ this.log.error(
2630
+ `Failed to upsert entity ${JSON.stringify(
2631
+ ent
2632
+ )}: cannot find saved entity after upsert.`
2633
+ );
2634
+ throw new BlankReturnMessageDto2(500, "Internal error").toException();
2635
+ }
2636
+ if (deleteColumnProperty && saved[deleteColumnProperty]) {
2637
+ await repo.restore(insertedId ? { id: insertedId } : conditions);
2638
+ saved = await fetchSaved();
2639
+ if (!saved || saved[deleteColumnProperty]) {
2640
+ this.log.error(
2641
+ `Failed to upsert entity ${JSON.stringify(
2642
+ ent
2643
+ )}: cannot restore soft-deleted entity after upsert.`
2644
+ );
2645
+ throw new BlankReturnMessageDto2(
2646
+ 500,
2647
+ "Internal error"
2648
+ ).toException();
2649
+ }
2650
+ }
2651
+ return saved;
2652
+ });
2653
+ await savedEnt.afterUpsert?.();
2654
+ this.cleanEntityNotInResultFields(savedEnt);
2655
+ return new this.entityReturnMessageDto(200, "success", savedEnt);
2656
+ } catch (e) {
2657
+ if (e instanceof HttpException) {
2658
+ throw e;
2659
+ }
2660
+ this.log.error(
2661
+ `Failed to upsert entity ${JSON.stringify(ent)}: ${e.toString()}`
2662
+ );
2663
+ throw new BlankReturnMessageDto2(500, "Internal error").toException();
2664
+ }
2665
+ }
2499
2666
  async delete(id, cond = {}) {
2500
2667
  const bindingEnt = await this.getBindingPartialEntity();
2501
2668
  let result;
@@ -2528,7 +2695,7 @@ var CrudBase = class {
2528
2695
  );
2529
2696
  return newEnt;
2530
2697
  });
2531
- const invalidResults = _3.compact(
2698
+ const invalidResults = _4.compact(
2532
2699
  await Promise.all(
2533
2700
  ents.map(async (ent) => {
2534
2701
  const reason = ent.isValidInCreate();
@@ -2701,10 +2868,10 @@ var CrudBase = class {
2701
2868
  await flush();
2702
2869
  return result;
2703
2870
  };
2704
- const hasActiveTx = !!this.repo.manager.queryRunner?.isTransactionActive;
2705
- const res = await (options.repo ? op(options.repo) : hasActiveTx ? op(this.repo) : this.repo.manager.transaction(
2706
- (tdb) => op(tdb.getRepository(this.entityClass))
2707
- ));
2871
+ const res = await this._mayBeTransaction(
2872
+ (tdb, repo) => op(repo),
2873
+ options.repo?.manager || this.repo.manager
2874
+ );
2708
2875
  return res == null ? new BlankReturnMessageDto2(200, "success") : new GenericReturnMessageDto(200, "success", res);
2709
2876
  }
2710
2877
  async _loadFullTextIndex() {
@@ -2764,6 +2931,7 @@ import {
2764
2931
  ParseIntPipe,
2765
2932
  Patch,
2766
2933
  Post,
2934
+ Put,
2767
2935
  Query
2768
2936
  } from "@nestjs/common";
2769
2937
  import {
@@ -2786,7 +2954,7 @@ import {
2786
2954
  OmitType as OmitType2,
2787
2955
  PartialType
2788
2956
  } from "@nestjs/swagger";
2789
- import _5, { upperFirst } from "lodash";
2957
+ import _6, { upperFirst } from "lodash";
2790
2958
  import { RenameClass } from "nesties";
2791
2959
 
2792
2960
  // src/bases/base-restful-controller.ts
@@ -2795,6 +2963,7 @@ var RestfulMethods = [
2795
2963
  "findAll",
2796
2964
  "create",
2797
2965
  "update",
2966
+ "upsert",
2798
2967
  "delete",
2799
2968
  "import"
2800
2969
  ];
@@ -2829,6 +2998,9 @@ var BaseRestfulController = class {
2829
2998
  update(id, dto) {
2830
2999
  return this._service.update(id, dto);
2831
3000
  }
3001
+ upsert(dto) {
3002
+ return this._service.upsert(dto);
3003
+ }
2832
3004
  delete(id) {
2833
3005
  return this._service.delete(id);
2834
3006
  }
@@ -2860,7 +3032,7 @@ var PickTypeExpose = (cl, keys) => {
2860
3032
 
2861
3033
  // src/utility/patch-column-in-get.ts
2862
3034
  import { getApiProperty } from "nesties";
2863
- import _4 from "lodash";
3035
+ import _5 from "lodash";
2864
3036
  import { DECORATORS } from "@nestjs/swagger/dist/constants";
2865
3037
  var PatchColumnsInGet = (cl, originalCl = cl, fieldsToOmit = []) => {
2866
3038
  const omit2 = new Set(fieldsToOmit);
@@ -2886,7 +3058,7 @@ var PatchColumnsInGet = (cl, originalCl = cl, fieldsToOmit = []) => {
2886
3058
  field
2887
3059
  );
2888
3060
  }
2889
- const queryableFieldsRemaining = _4.difference(
3061
+ const queryableFieldsRemaining = _5.difference(
2890
3062
  getSpecificFields(useCl, "queryCondition"),
2891
3063
  mutateFields
2892
3064
  );
@@ -2947,7 +3119,7 @@ var _RestfulFactory = class _RestfulFactory {
2947
3119
  this.entityClass,
2948
3120
  {
2949
3121
  ...this.options,
2950
- fieldsToOmit: _5.uniq([...this.options.fieldsToOmit || [], ...fields])
3122
+ fieldsToOmit: _6.uniq([...this.options.fieldsToOmit || [], ...fields])
2951
3123
  },
2952
3124
  this.__resolveVisited
2953
3125
  );
@@ -2957,7 +3129,7 @@ var _RestfulFactory = class _RestfulFactory {
2957
3129
  this.entityClass,
2958
3130
  {
2959
3131
  ...this.options,
2960
- writeFieldsToOmit: _5.uniq([
3132
+ writeFieldsToOmit: _6.uniq([
2961
3133
  ...this.options.writeFieldsToOmit || [],
2962
3134
  ...fields
2963
3135
  ])
@@ -2970,7 +3142,7 @@ var _RestfulFactory = class _RestfulFactory {
2970
3142
  this.entityClass,
2971
3143
  {
2972
3144
  ...this.options,
2973
- createFieldsToOmit: _5.uniq([
3145
+ createFieldsToOmit: _6.uniq([
2974
3146
  ...this.options.createFieldsToOmit || [],
2975
3147
  ...fields
2976
3148
  ])
@@ -2983,7 +3155,7 @@ var _RestfulFactory = class _RestfulFactory {
2983
3155
  this.entityClass,
2984
3156
  {
2985
3157
  ...this.options,
2986
- updateFieldsToOmit: _5.uniq([
3158
+ updateFieldsToOmit: _6.uniq([
2987
3159
  ...this.options.updateFieldsToOmit || [],
2988
3160
  ...fields
2989
3161
  ])
@@ -2991,12 +3163,25 @@ var _RestfulFactory = class _RestfulFactory {
2991
3163
  this.__resolveVisited
2992
3164
  );
2993
3165
  }
3166
+ omitUpsert(...fields) {
3167
+ return new _RestfulFactory(
3168
+ this.entityClass,
3169
+ {
3170
+ ...this.options,
3171
+ upsertFieldsToOmit: _6.uniq([
3172
+ ...this.options.upsertFieldsToOmit || [],
3173
+ ...fields
3174
+ ])
3175
+ },
3176
+ this.__resolveVisited
3177
+ );
3178
+ }
2994
3179
  omitFindAll(...fields) {
2995
3180
  return new _RestfulFactory(
2996
3181
  this.entityClass,
2997
3182
  {
2998
3183
  ...this.options,
2999
- findAllFieldsToOmit: _5.uniq([
3184
+ findAllFieldsToOmit: _6.uniq([
3000
3185
  ...this.options.findAllFieldsToOmit || [],
3001
3186
  ...fields
3002
3187
  ])
@@ -3009,7 +3194,7 @@ var _RestfulFactory = class _RestfulFactory {
3009
3194
  this.entityClass,
3010
3195
  {
3011
3196
  ...this.options,
3012
- outputFieldsToOmit: _5.uniq([
3197
+ outputFieldsToOmit: _6.uniq([
3013
3198
  ...this.options.outputFieldsToOmit || [],
3014
3199
  ...fields
3015
3200
  ])
@@ -3071,7 +3256,7 @@ var _RestfulFactory = class _RestfulFactory {
3071
3256
  return this.options.entityClassName || this.entityClass.name;
3072
3257
  }
3073
3258
  get fieldsToOmit() {
3074
- return _5.uniq([
3259
+ return _6.uniq([
3075
3260
  ...getSpecificFields(this.entityClass, "notColumn"),
3076
3261
  ...this.options.fieldsToOmit || [],
3077
3262
  ...getTypeormRelations(this.entityClass).map(
@@ -3080,7 +3265,7 @@ var _RestfulFactory = class _RestfulFactory {
3080
3265
  ]);
3081
3266
  }
3082
3267
  get fieldsInCreateToOmit() {
3083
- return _5.uniq([
3268
+ return _6.uniq([
3084
3269
  ...this.fieldsToOmit,
3085
3270
  ...this.options.writeFieldsToOmit || [],
3086
3271
  ...this.options.createFieldsToOmit || [],
@@ -3095,7 +3280,7 @@ var _RestfulFactory = class _RestfulFactory {
3095
3280
  );
3096
3281
  }
3097
3282
  get fieldsInUpdateToOmit() {
3098
- return _5.uniq([
3283
+ return _6.uniq([
3099
3284
  ...this.fieldsToOmit,
3100
3285
  ...this.options.writeFieldsToOmit || [],
3101
3286
  ...this.options.updateFieldsToOmit || [],
@@ -3109,21 +3294,36 @@ var _RestfulFactory = class _RestfulFactory {
3109
3294
  `Update${this.entityClassName}Dto`
3110
3295
  );
3111
3296
  }
3297
+ get fieldsInUpsertToOmit() {
3298
+ return _6.uniq([
3299
+ ...this.fieldsToOmit,
3300
+ ...this.options.writeFieldsToOmit || [],
3301
+ ...this.options.upsertFieldsToOmit || [],
3302
+ ...getSpecificFields(this.entityClass, "notWritable"),
3303
+ ...getSpecificFields(this.entityClass, "notUpsertable")
3304
+ ]);
3305
+ }
3306
+ get upsertDto() {
3307
+ return RenameClass(
3308
+ OmitTypeExclude(this.entityClass, this.fieldsInUpsertToOmit),
3309
+ `Upsert${this.entityClassName}Dto`
3310
+ );
3311
+ }
3112
3312
  get importDto() {
3113
3313
  return ImportDataDto(this.createDto);
3114
3314
  }
3115
3315
  get fieldsInGetToOmit() {
3116
- return _5.uniq([
3316
+ return _6.uniq([
3117
3317
  ...this.fieldsToOmit,
3118
3318
  ...getSpecificFields(this.entityClass, "notQueryable"),
3119
- ..._5.difference(
3319
+ ..._6.difference(
3120
3320
  getSpecificFields(this.entityClass, "requireGetMutator"),
3121
3321
  getSpecificFields(this.entityClass, "getMutator")
3122
3322
  )
3123
3323
  ]);
3124
3324
  }
3125
3325
  get queryableFields() {
3126
- return _5.difference(
3326
+ return _6.difference(
3127
3327
  [
3128
3328
  ...getSpecificFields(this.entityClass, "queryCondition"),
3129
3329
  "pageCount",
@@ -3255,6 +3455,21 @@ var _RestfulFactory = class _RestfulFactory {
3255
3455
  `${this.entityClassName}CreateResultDto`
3256
3456
  );
3257
3457
  }
3458
+ get entityUpsertResultDto() {
3459
+ return RenameClass(
3460
+ OmitType2(this.entityResultDto, [
3461
+ ...this.options.upsertIncludeRelations ? [] : getTypeormRelations(this.entityClass).map(
3462
+ (r) => r.propertyName
3463
+ ),
3464
+ ...getSpecificFields(
3465
+ this.entityClass,
3466
+ "notColumn",
3467
+ (m) => m.hideInUpsert
3468
+ )
3469
+ ]),
3470
+ `${this.entityClassName}UpsertResultDto`
3471
+ );
3472
+ }
3258
3473
  get entityReturnMessageDto() {
3259
3474
  return ReturnMessageDto2(this.entityResultDto);
3260
3475
  }
@@ -3290,7 +3505,7 @@ var _RestfulFactory = class _RestfulFactory {
3290
3505
  HttpCode(200),
3291
3506
  ApiOperation({
3292
3507
  summary: `Create a new ${this.entityClassName}`,
3293
- ..._5.omit(extras, "prefix")
3508
+ ..._6.omit(extras, "prefix")
3294
3509
  }),
3295
3510
  ApiBody({ type: this.createDto }),
3296
3511
  ApiOkResponse({ type: this.entityCreateReturnMessageDto }),
@@ -3300,12 +3515,36 @@ var _RestfulFactory = class _RestfulFactory {
3300
3515
  createParam() {
3301
3516
  return Body(DataPipe(), OmitPipe(this.fieldsInCreateToOmit));
3302
3517
  }
3518
+ isUpsertable() {
3519
+ return !!reflector.get("upsertableEntity", this.entityClass);
3520
+ }
3521
+ upsert(extras = {}) {
3522
+ if (!this.isUpsertable()) {
3523
+ throw new Error(
3524
+ `Entity ${this.entityClass.name} is not upsertable. Please define at least one UpsertColumn or BindingColumn, and set @UpsertableEntity() decorator.`
3525
+ );
3526
+ }
3527
+ return MergeMethodDecorators([
3528
+ this.usePrefix(Put, extras.prefix),
3529
+ HttpCode(200),
3530
+ ApiOperation({
3531
+ summary: `Upsert a ${this.entityClassName}`,
3532
+ ..._6.omit(extras, "prefix")
3533
+ }),
3534
+ ApiBody({ type: this.upsertDto }),
3535
+ ApiOkResponse({ type: this.entityUpsertResultDto }),
3536
+ ApiError(400, `The ${this.entityClassName} is not valid`)
3537
+ ]);
3538
+ }
3539
+ upsertParam() {
3540
+ return Body(DataPipe(), OmitPipe(this.fieldsInUpsertToOmit));
3541
+ }
3303
3542
  findOne(extras = {}) {
3304
3543
  return MergeMethodDecorators([
3305
3544
  this.usePrefix(Get, extras.prefix, ":id"),
3306
3545
  ApiOperation({
3307
3546
  summary: `Find a ${this.entityClassName} by id`,
3308
- ..._5.omit(extras, "prefix")
3547
+ ..._6.omit(extras, "prefix")
3309
3548
  }),
3310
3549
  ApiParam({ name: "id", type: this.idType, required: true }),
3311
3550
  ApiOkResponse({ type: this.entityReturnMessageDto }),
@@ -3327,7 +3566,7 @@ var _RestfulFactory = class _RestfulFactory {
3327
3566
  this.usePrefix(Get, extras.prefix),
3328
3567
  ApiOperation({
3329
3568
  summary: `Find all ${this.entityClassName}`,
3330
- ..._5.omit(extras, "prefix")
3569
+ ..._6.omit(extras, "prefix")
3331
3570
  }),
3332
3571
  ApiOkResponse({ type: this.entityArrayReturnMessageDto })
3333
3572
  ]);
@@ -3337,14 +3576,14 @@ var _RestfulFactory = class _RestfulFactory {
3337
3576
  this.usePrefix(Get, extras.prefix),
3338
3577
  ApiOperation({
3339
3578
  summary: `Find all ${this.entityClassName}`,
3340
- ..._5.omit(extras, "prefix")
3579
+ ..._6.omit(extras, "prefix")
3341
3580
  }),
3342
3581
  ApiOkResponse({ type: this.entityCursorPaginationReturnMessageDto })
3343
3582
  ]);
3344
3583
  }
3345
3584
  getMutatorColumns() {
3346
3585
  const mutatorColumns = getSpecificFields(this.entityClass, "getMutator");
3347
- return _5.difference(mutatorColumns, this.fieldsInGetToOmit);
3586
+ return _6.difference(mutatorColumns, this.fieldsInGetToOmit);
3348
3587
  }
3349
3588
  findAllParam() {
3350
3589
  const mutatorColumns = this.getMutatorColumns();
@@ -3364,7 +3603,7 @@ var _RestfulFactory = class _RestfulFactory {
3364
3603
  HttpCode(200),
3365
3604
  ApiOperation({
3366
3605
  summary: `Update a ${this.entityClassName} by id`,
3367
- ..._5.omit(extras, "prefix")
3606
+ ..._6.omit(extras, "prefix")
3368
3607
  }),
3369
3608
  ApiParam({ name: "id", type: this.idType, required: true }),
3370
3609
  ApiBody({ type: this.updateDto }),
@@ -3386,7 +3625,7 @@ var _RestfulFactory = class _RestfulFactory {
3386
3625
  HttpCode(200),
3387
3626
  ApiOperation({
3388
3627
  summary: `Delete a ${this.entityClassName} by id`,
3389
- ..._5.omit(extras, "prefix")
3628
+ ..._6.omit(extras, "prefix")
3390
3629
  }),
3391
3630
  ApiParam({ name: "id", type: this.idType, required: true }),
3392
3631
  ApiBlankResponse(),
@@ -3403,7 +3642,7 @@ var _RestfulFactory = class _RestfulFactory {
3403
3642
  HttpCode(200),
3404
3643
  ApiOperation({
3405
3644
  summary: `Import ${this.entityClassName}`,
3406
- ..._5.omit(extras, "prefix")
3645
+ ..._6.omit(extras, "prefix")
3407
3646
  }),
3408
3647
  ApiBody({ type: this.importDto }),
3409
3648
  ApiOkResponse({ type: this.importReturnMessageDto }),
@@ -3416,7 +3655,7 @@ var _RestfulFactory = class _RestfulFactory {
3416
3655
  HttpCode(200),
3417
3656
  ApiOperation({
3418
3657
  summary: `${upperFirst(operationName)} a ${this.entityClassName} by id`,
3419
- ..._5.omit(options, "prefix", "returnType")
3658
+ ..._6.omit(options, "prefix", "returnType")
3420
3659
  }),
3421
3660
  options.returnType ? ApiTypeResponse(options.returnType) : ApiBlankResponse(),
3422
3661
  ApiError(
@@ -3440,6 +3679,7 @@ var _RestfulFactory = class _RestfulFactory {
3440
3679
  (m) => routeOptions?.routes?.[m]?.enabled === true
3441
3680
  );
3442
3681
  const validMethods = RestfulMethods.filter((m) => {
3682
+ if (m === "upsert" && !this.isUpsertable()) return false;
3443
3683
  const value = routeOptions?.routes?.[m]?.enabled;
3444
3684
  if (value === false) return false;
3445
3685
  if (value === true) return true;
@@ -3473,6 +3713,11 @@ var _RestfulFactory = class _RestfulFactory {
3473
3713
  paramDecorators: () => [this.idParam(), this.updateParam()],
3474
3714
  methodDecorators: () => [this.update()]
3475
3715
  },
3716
+ upsert: {
3717
+ paramTypes: [this.upsertDto],
3718
+ paramDecorators: () => [this.upsertParam()],
3719
+ methodDecorators: () => [this.upsert()]
3720
+ },
3476
3721
  delete: {
3477
3722
  paramTypes: [this.idType],
3478
3723
  paramDecorators: () => [this.idParam()],
@@ -3539,9 +3784,14 @@ var _RestfulFactory = class _RestfulFactory {
3539
3784
  return RenameClass(cl, `${this.entityClassName}Controller`);
3540
3785
  }
3541
3786
  crudService(options = {}) {
3787
+ const keysToMigrate = [
3788
+ "relations",
3789
+ "outputFieldsToOmit",
3790
+ "upsertIncludeRelations",
3791
+ "keepEntityVersioningDates"
3792
+ ];
3542
3793
  return CrudService(this.entityClass, {
3543
- relations: this.options.relations,
3544
- outputFieldsToOmit: this.options.outputFieldsToOmit,
3794
+ ..._6.pick(this.options, keysToMigrate),
3545
3795
  ...options
3546
3796
  });
3547
3797
  }
@@ -3561,6 +3811,12 @@ __decorateClass([
3561
3811
  __decorateClass([
3562
3812
  Memorize()
3563
3813
  ], _RestfulFactory.prototype, "updateDto", 1);
3814
+ __decorateClass([
3815
+ Memorize()
3816
+ ], _RestfulFactory.prototype, "fieldsInUpsertToOmit", 1);
3817
+ __decorateClass([
3818
+ Memorize()
3819
+ ], _RestfulFactory.prototype, "upsertDto", 1);
3564
3820
  __decorateClass([
3565
3821
  Memorize()
3566
3822
  ], _RestfulFactory.prototype, "importDto", 1);
@@ -3582,6 +3838,9 @@ __decorateClass([
3582
3838
  __decorateClass([
3583
3839
  Memorize()
3584
3840
  ], _RestfulFactory.prototype, "entityCreateResultDto", 1);
3841
+ __decorateClass([
3842
+ Memorize()
3843
+ ], _RestfulFactory.prototype, "entityUpsertResultDto", 1);
3585
3844
  __decorateClass([
3586
3845
  Memorize()
3587
3846
  ], _RestfulFactory.prototype, "entityReturnMessageDto", 1);
@@ -3668,8 +3927,8 @@ var applyQueryMatchBooleanMySQL = createQueryCondition(
3668
3927
  );
3669
3928
 
3670
3929
  // src/transactional-typeorm.module.ts
3671
- var import_typeorm8 = __toESM(require_typeorm());
3672
- import { EntityManager } from "typeorm";
3930
+ var import_typeorm9 = __toESM(require_typeorm());
3931
+ import { EntityManager as EntityManager2 } from "typeorm";
3673
3932
  import {
3674
3933
  Inject as Inject2,
3675
3934
  Injectable,
@@ -3726,7 +3985,7 @@ import { Observable } from "rxjs";
3726
3985
  var requestWeakMap = /* @__PURE__ */ new WeakMap();
3727
3986
  var normalizeDataSourceToken = (token) => typeof token === "string" ? token : token.name || token.toString();
3728
3987
  var TransactionalTypeOrmInterceptor = (dataSource) => {
3729
- const token = (0, import_typeorm8.getEntityManagerToken)(dataSource);
3988
+ const token = (0, import_typeorm9.getEntityManagerToken)(dataSource);
3730
3989
  const interceptorClass = class SpecificTransactionalTypeOrmInterceptor {
3731
3990
  constructor(entityManager) {
3732
3991
  this.entityManager = entityManager;
@@ -3737,7 +3996,7 @@ var TransactionalTypeOrmInterceptor = (dataSource) => {
3737
3996
  let innerSub = null;
3738
3997
  let finished = false;
3739
3998
  let abort;
3740
- const aborted = new Promise((_6, reject) => {
3999
+ const aborted = new Promise((_7, reject) => {
3741
4000
  abort = reject;
3742
4001
  });
3743
4002
  const run = this.entityManager.transaction(async (txEm) => {
@@ -3780,18 +4039,18 @@ var TransactionalTypeOrmInterceptor = (dataSource) => {
3780
4039
  });
3781
4040
  Reflect.defineMetadata(
3782
4041
  "design:paramtypes",
3783
- [EntityManager],
4042
+ [EntityManager2],
3784
4043
  interceptorClass
3785
4044
  );
3786
4045
  Inject2(token)(interceptorClass.prototype, void 0, 0);
3787
4046
  Injectable()(interceptorClass);
3788
4047
  return interceptorClass;
3789
4048
  };
3790
- var getTransactionalEntityManagerToken = (dataSource) => `Transactional${normalizeDataSourceToken((0, import_typeorm8.getEntityManagerToken)(dataSource))}`;
4049
+ var getTransactionalEntityManagerToken = (dataSource) => `Transactional${normalizeDataSourceToken((0, import_typeorm9.getEntityManagerToken)(dataSource))}`;
3791
4050
  var getTransactionalEntityManagerProvider = (dataSource) => createProvider(
3792
4051
  {
3793
4052
  provide: getTransactionalEntityManagerToken(dataSource),
3794
- inject: [(0, import_typeorm8.getEntityManagerToken)(dataSource), REQUEST],
4053
+ inject: [(0, import_typeorm9.getEntityManagerToken)(dataSource), REQUEST],
3795
4054
  scope: Scope.REQUEST
3796
4055
  },
3797
4056
  (entityManager, request) => {
@@ -3808,12 +4067,12 @@ var InjectTransactionalEntityManager = createInjectFromTokenFactory(
3808
4067
  getTransactionalEntityManagerToken
3809
4068
  );
3810
4069
  var getTransactionalRepositoryToken = (entity, dataSource) => `Transactional${normalizeDataSourceToken(
3811
- (0, import_typeorm8.getEntityManagerToken)(dataSource)
4070
+ (0, import_typeorm9.getEntityManagerToken)(dataSource)
3812
4071
  )}Repository_${entity.name || entity.toString()}`;
3813
4072
  var getTransactionalRepositoryProvider = (entity, dataSource) => createProvider(
3814
4073
  {
3815
4074
  provide: getTransactionalRepositoryToken(entity, dataSource),
3816
- inject: [(0, import_typeorm8.getEntityManagerToken)(dataSource), REQUEST],
4075
+ inject: [(0, import_typeorm9.getEntityManagerToken)(dataSource), REQUEST],
3817
4076
  scope: Scope.REQUEST
3818
4077
  },
3819
4078
  (entityManager, req) => {
@@ -3842,7 +4101,7 @@ var TransactionalTypeOrmModule = class {
3842
4101
  (entity) => getTransactionalRepositoryProvider(entity, dataSource)
3843
4102
  )
3844
4103
  ];
3845
- const moduleImports = entityArray.length ? [import_typeorm8.TypeOrmModule.forFeature(entityArray, dataSource)] : [];
4104
+ const moduleImports = entityArray.length ? [import_typeorm9.TypeOrmModule.forFeature(entityArray, dataSource)] : [];
3846
4105
  const moduleExports = [...providers, ...moduleImports];
3847
4106
  return {
3848
4107
  module: TransactionalTypeOrmModule,
@@ -3894,6 +4153,7 @@ export {
3894
4153
  NotCreatable,
3895
4154
  NotInResult,
3896
4155
  NotQueryable,
4156
+ NotUpsertable,
3897
4157
  NotWritable,
3898
4158
  OmitPipe,
3899
4159
  OptionalDataPipe,
@@ -3934,6 +4194,8 @@ export {
3934
4194
  TimeBase,
3935
4195
  TransactionalTypeOrmInterceptor,
3936
4196
  TransactionalTypeOrmModule,
4197
+ UpsertColumn,
4198
+ UpsertableEntity,
3937
4199
  UuidColumn,
3938
4200
  applyQueryMatchBoolean,
3939
4201
  applyQueryMatchBooleanMySQL,