@nextage/nx-frame-be 1.0.36 → 1.0.38

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.
@@ -243,6 +243,26 @@ export declare class BaseModel<TAttrs, TDoc extends BaseMongoDoc, TMongoModel ex
243
243
  * @param {*} options
244
244
  */
245
245
  updateManyNative(filter: FilterParams, update: mongoose.UpdateQuery<TMongoModel>, options?: mongoose.mongo.UpdateOptions): Promise<mongoose.mongo.UpdateResult<Document>>;
246
+ /**
247
+ * Execute bulkWrite injecting version handling on each operation, so native bulk
248
+ * paths keep the same versioning guarantees as the Mongoose query hooks:
249
+ * - insertOne -> set versionKey to 0 when absent (matches save() baseline)
250
+ * - updateOne/updateMany -> add $inc:{version:1} unless the update already touches version
251
+ *
252
+ * Upsert ops that do not touch version fall back to $inc (an upsert insert lands at
253
+ * version 1). Other op kinds (replaceOne, deleteOne, deleteMany) pass through unchanged.
254
+ *
255
+ * @param {*} bulkOps
256
+ * @param {*} options
257
+ */
258
+ bulkWriteVersioned(bulkOps: mongoose.AnyBulkWriteOperation<TDoc>[], options?: mongoose.mongo.BulkWriteOptions): Promise<mongoose.mongo.BulkWriteResult>;
259
+ /**
260
+ * Inject the version key into a single bulkWrite operation (see bulkWriteVersioned).
261
+ *
262
+ * @param {*} op
263
+ * @param {*} versionKey
264
+ */
265
+ private applyVersionToBulkOp;
246
266
  /**
247
267
  * Update document using Mongoose wrapper method
248
268
  *
@@ -19,8 +19,12 @@ var __rest = (this && this.__rest) || function (s, e) {
19
19
  }
20
20
  return t;
21
21
  };
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
22
25
  Object.defineProperty(exports, "__esModule", { value: true });
23
26
  exports.BaseModel = void 0;
27
+ const mongoose_1 = __importDefault(require("mongoose"));
24
28
  const events_1 = require("events");
25
29
  const utils_1 = require("../utils");
26
30
  const constants_1 = require("../constants");
@@ -456,19 +460,35 @@ class BaseModel extends events_1.EventEmitter {
456
460
  item = yield this.beforeUpdate(item, context);
457
461
  //const res = await this.mgModel.findByIdAndUpdate(id, { $set: item }, { new: true });
458
462
  //const itemDoc = this.mgModel.build(Object.assign({ id, item }));
459
- const itemDoc = yield this.get(id, context);
460
- if (!itemDoc)
461
- throw new errors_1.AppError('messages.notFound');
462
- // complete merge
463
- if (mergeItem) {
464
- // item = merge (itemDoc.toObject(), item); // Merge with existing doc
465
- item = (0, utils_1.merge)(itemDoc.toObject(), item, (objValue, srcValue) => {
466
- if (Array.isArray(objValue))
467
- return srcValue; // sovrascrivi invece di mergiare
468
- });
463
+ // read-modify-write with optimistic concurrency: on a VersionError (a concurrent
464
+ // writer bumped the version between get() and save()) re-read the fresh document,
465
+ // re-apply the update payload and retry a bounded number of times.
466
+ const maxAttempts = 3;
467
+ let res;
468
+ for (let attempt = 1;; attempt++) {
469
+ const itemDoc = yield this.get(id, context);
470
+ if (!itemDoc)
471
+ throw new errors_1.AppError('messages.notFound');
472
+ let toApply = item;
473
+ // complete merge with the freshly read document
474
+ if (mergeItem) {
475
+ // item = merge (itemDoc.toObject(), item); // Merge with existing doc
476
+ toApply = (0, utils_1.merge)(itemDoc.toObject(), item, (objValue, srcValue) => {
477
+ if (Array.isArray(objValue))
478
+ return srcValue; // sovrascrivi invece di mergiare
479
+ });
480
+ }
481
+ itemDoc.set(toApply);
482
+ try {
483
+ res = yield itemDoc.save();
484
+ break;
485
+ }
486
+ catch (err) {
487
+ if (err instanceof mongoose_1.default.Error.VersionError && attempt < maxAttempts)
488
+ continue;
489
+ throw err;
490
+ }
469
491
  }
470
- itemDoc === null || itemDoc === void 0 ? void 0 : itemDoc.set(item);
471
- const res = yield itemDoc.save();
472
492
  if (context)
473
493
  context.evtItem = res;
474
494
  this.publishCRUDEvent({ type: enums_1.ModelCrudEventType.update, data: { uid: id, model: this.name } }, context);
@@ -506,6 +526,53 @@ class BaseModel extends events_1.EventEmitter {
506
526
  updateManyNative(filter, update, options) {
507
527
  return this.mgModel.collection.updateMany(filter, update, options);
508
528
  }
529
+ /**
530
+ * Execute bulkWrite injecting version handling on each operation, so native bulk
531
+ * paths keep the same versioning guarantees as the Mongoose query hooks:
532
+ * - insertOne -> set versionKey to 0 when absent (matches save() baseline)
533
+ * - updateOne/updateMany -> add $inc:{version:1} unless the update already touches version
534
+ *
535
+ * Upsert ops that do not touch version fall back to $inc (an upsert insert lands at
536
+ * version 1). Other op kinds (replaceOne, deleteOne, deleteMany) pass through unchanged.
537
+ *
538
+ * @param {*} bulkOps
539
+ * @param {*} options
540
+ */
541
+ bulkWriteVersioned(bulkOps, options) {
542
+ const versionKey = this.mgModel.schema.get('versionKey') || 'version';
543
+ const versioned = bulkOps.map(op => this.applyVersionToBulkOp(op, versionKey));
544
+ return this.mgModel.bulkWrite(versioned, options !== null && options !== void 0 ? options : {});
545
+ }
546
+ /**
547
+ * Inject the version key into a single bulkWrite operation (see bulkWriteVersioned).
548
+ *
549
+ * @param {*} op
550
+ * @param {*} versionKey
551
+ */
552
+ applyVersionToBulkOp(op, versionKey) {
553
+ var _a, _b;
554
+ const anyOp = op;
555
+ if ((_a = anyOp.insertOne) === null || _a === void 0 ? void 0 : _a.document) {
556
+ const doc = anyOp.insertOne.document;
557
+ if (doc[versionKey] === undefined)
558
+ doc[versionKey] = 0;
559
+ return op;
560
+ }
561
+ const writeOp = (_b = anyOp.updateOne) !== null && _b !== void 0 ? _b : anyOp.updateMany;
562
+ if (!writeOp)
563
+ return op;
564
+ const update = writeOp.update;
565
+ // update pipelines (arrays) are left untouched
566
+ if (!update || Array.isArray(update))
567
+ return op;
568
+ const nextUpdate = update;
569
+ const versionOperators = [nextUpdate, nextUpdate.$set, nextUpdate.$unset, nextUpdate.$inc];
570
+ const touchesVersion = versionOperators.some(operator => !!operator && Object.keys(operator).includes(versionKey));
571
+ if (touchesVersion)
572
+ return op;
573
+ nextUpdate.$inc = nextUpdate.$inc ? Object.assign(Object.assign({}, nextUpdate.$inc), { [versionKey]: 1 }) : { [versionKey]: 1 };
574
+ return op;
575
+ }
509
576
  /**
510
577
  * Update document using Mongoose wrapper method
511
578
  *
@@ -54,14 +54,16 @@ export declare function loadModel(model: BaseModel<any, any, any>): void;
54
54
  * @param { name, modelName, schema, collection, mergeSchema } param
55
55
  * @param load
56
56
  */
57
- export declare function createModel<TAttrs, TDoc extends BaseMongoDoc, TMongoModel extends BaseMongoModel<TAttrs, TDoc>, TNxModel>({ name, classDef, modelName, modelParams, collection, schema, mergeSchema }: ModelDef<TDoc, TMongoModel, TNxModel>, load: boolean, logger?: any): TNxModel;
57
+ export declare function createModel<TAttrs, TDoc extends BaseMongoDoc, TMongoModel extends BaseMongoModel<TAttrs, TDoc>, TNxModel>({ name, classDef, modelName, modelParams, collection, schema, mergeSchema, incrementOnSave }: ModelDef<TDoc, TMongoModel, TNxModel>, load: boolean, logger?: any): TNxModel;
58
58
  /**
59
59
  *
60
60
  * @param {*} modelName
61
61
  * @param {*} schema
62
62
  * @param {*} collection
63
+ * @param {*} version enable optimistic concurrency + query version increment
64
+ * @param {*} incrementOnSave opt-in version bump on save() (Mixed in-place mutations)
63
65
  */
64
- export declare function loadMongoModel<TAttrs extends BaseDBItem, TDoc extends BaseMongoDoc, TMongoModel extends BaseMongoModel<TAttrs, TDoc>>(modelName: string, schema: any, collection: string, version?: boolean): TMongoModel;
66
+ export declare function loadMongoModel<TAttrs extends BaseDBItem, TDoc extends BaseMongoDoc, TMongoModel extends BaseMongoModel<TAttrs, TDoc>>(modelName: string, schema: any, collection: string, version?: boolean, incrementOnSave?: boolean): TMongoModel;
65
67
  /**
66
68
  * transform string or string array to ObjectId or ObjectId array
67
69
  *
@@ -58,7 +58,7 @@ function loadModel(model) {
58
58
  * @param { name, modelName, schema, collection, mergeSchema } param
59
59
  * @param load
60
60
  */
61
- function createModel({ name, classDef, modelName, modelParams, collection, schema, mergeSchema }, load, logger = constants_1.APP.logger) {
61
+ function createModel({ name, classDef, modelName, modelParams, collection, schema, mergeSchema, incrementOnSave }, load, logger = constants_1.APP.logger) {
62
62
  let entitySchema;
63
63
  if (!name)
64
64
  throw new Error('Missing param to create model');
@@ -68,7 +68,7 @@ function createModel({ name, classDef, modelName, modelParams, collection, schem
68
68
  entitySchema = exports.codedEntitySchema;
69
69
  else
70
70
  entitySchema = mergeSchema ? mergeDBSchema(exports.codedEntitySchema, schema) : schema;
71
- const mgModel = loadMongoModel(modelName, entitySchema, collection);
71
+ const mgModel = loadMongoModel(modelName, entitySchema, collection, true, !!incrementOnSave);
72
72
  const model = classDef ? new classDef({ name, model: mgModel, params: modelParams, logger }) : new base_model_1.BaseModel({ name, model: mgModel, params: modelParams, logger });
73
73
  if (load)
74
74
  loadModel(model);
@@ -76,7 +76,7 @@ function createModel({ name, classDef, modelName, modelParams, collection, schem
76
76
  //console.log(`created model ${modelName} (load: ${!!load})`);
77
77
  return model;
78
78
  }
79
- function enableQueryVersioning(mgSchema, version) {
79
+ function enableQueryVersioning(mgSchema, version, incrementOnSave = false) {
80
80
  if (!version)
81
81
  return;
82
82
  const versionKey = mgSchema.get('versionKey');
@@ -97,6 +97,24 @@ function enableQueryVersioning(mgSchema, version) {
97
97
  mgSchema.pre('findOneAndUpdate', addVersionIncrement);
98
98
  mgSchema.pre('updateOne', addVersionIncrement);
99
99
  mgSchema.pre('updateMany', addVersionIncrement);
100
+ if (!incrementOnSave)
101
+ return;
102
+ // Opt-in (per-model) version bump on save().
103
+ //
104
+ // With optimisticConcurrency mongoose already increments the version when a
105
+ // tracked path is modified. This hook covers the case of Mixed fields mutated
106
+ // in-place (doc.data.x = y) WITHOUT markModified(): mongoose does not see them
107
+ // as modified, so no increment would happen and the published event would carry
108
+ // a stale version. Enable it only on event-source entities that expose Mixed
109
+ // fields (e.g. companyAsset). increment() shares the same internal version flag
110
+ // as optimisticConcurrency, so the net effect is at most +1 per save().
111
+ mgSchema.pre('save', function () {
112
+ if (this.isNew) // creation: versionKey default acts as v0
113
+ return;
114
+ if (this.isModified(versionKey)) // version explicitly set: respect it
115
+ return;
116
+ this.increment();
117
+ });
100
118
  }
101
119
  ;
102
120
  /**
@@ -104,8 +122,10 @@ function enableQueryVersioning(mgSchema, version) {
104
122
  * @param {*} modelName
105
123
  * @param {*} schema
106
124
  * @param {*} collection
125
+ * @param {*} version enable optimistic concurrency + query version increment
126
+ * @param {*} incrementOnSave opt-in version bump on save() (Mixed in-place mutations)
107
127
  */
108
- function loadMongoModel(modelName, schema, collection, version = true) {
128
+ function loadMongoModel(modelName, schema, collection, version = true, incrementOnSave = false) {
109
129
  const mgSchema = new mongoose_1.default.Schema(schema, {
110
130
  optimisticConcurrency: version,
111
131
  versionKey: 'version',
@@ -120,7 +140,7 @@ function loadMongoModel(modelName, schema, collection, version = true) {
120
140
  });
121
141
  // change documents' version key from __v to version
122
142
  mgSchema.set('versionKey', 'version');
123
- enableQueryVersioning(mgSchema, version);
143
+ enableQueryVersioning(mgSchema, version, incrementOnSave);
124
144
  mgSchema.statics.build = (attrs) => {
125
145
  const item = { _id: attrs.id };
126
146
  delete attrs.id;
@@ -7,6 +7,7 @@ export type ModelDef<TDoc, TMongoModel, TNxModel> = {
7
7
  collection: string;
8
8
  schema: NxObject;
9
9
  mergeSchema?: boolean;
10
+ incrementOnSave?: boolean;
10
11
  };
11
12
  export type ModelData<TDoc, TMongoModel> = {
12
13
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextage/nx-frame-be",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -26,6 +26,7 @@
26
26
  "dependencies": {
27
27
  "@apollo/server": "^5.5.0",
28
28
  "@apollo/subgraph": "^2.11.2",
29
+ "@apollo/federation-internals": "^2.11.2",
29
30
  "@as-integrations/express5": "^1.1.2",
30
31
  "@aws-sdk/client-ecs": "^3.592.0",
31
32
  "@aws-sdk/client-s3": "^3.1017.0",