@nextage/nx-frame-be 1.0.49 → 1.0.51

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.
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /* ────────────────────────────────────────────────────────────────────────────
3
+ * BaseController.update - the options a PARSE can decide
4
+ *
5
+ * An update payload cannot say everything about the write it wants. The parse step, which is
6
+ * where a subclass runs its own derivation, may conclude something the payload has no way to
7
+ * express - typically that a field must be REMOVED rather than left unwritten. `parseUpdateItem`
8
+ * can therefore return the options it decided, and `update` carries them to the model together
9
+ * with the caller's.
10
+ *
11
+ * WHAT THESE TESTS GUARD is mostly what does NOT change: an override that returns nothing - every
12
+ * one that exists today - must reach the model with the caller's own options object, by identity.
13
+ * A merge that quietly rebuilt that object would be invisible here and would break the callers
14
+ * that hand the same options around, so identity is asserted and not just deep equality.
15
+ *
16
+ * The model is a fake recording its arguments: this is about the controller, and there is nothing
17
+ * to persist.
18
+ * ──────────────────────────────────────────────────────────────────────────── */
19
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
20
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
21
+ return new (P || (P = Promise))(function (resolve, reject) {
22
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
23
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
24
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
25
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
26
+ });
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const base_controller_1 = require("../base.controller");
30
+ const doc = (over) => (Object.assign(Object.assign({}, over), { toObject: () => (Object.assign({}, over)) }));
31
+ /** Records what `update` hands to the model. */
32
+ const fakeModel = () => ({
33
+ get: jest.fn((id) => __awaiter(void 0, void 0, void 0, function* () { return doc({ id }); })),
34
+ update: jest.fn((id, item) => __awaiter(void 0, void 0, void 0, function* () { return doc(Object.assign({ id }, item)); })),
35
+ });
36
+ /** LEGACY: the shape of every override that exists today - declared `Promise<void>`. */
37
+ class LegacyCtrl extends base_controller_1.BaseController {
38
+ parseUpdateItem(id, item) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ item.parsed = true;
41
+ });
42
+ }
43
+ }
44
+ /** A parse that decides a removal of its own. */
45
+ class DecidingCtrl extends base_controller_1.BaseController {
46
+ constructor(model, decided) {
47
+ super({ model, mergeUpdate: true });
48
+ this.decided = decided;
49
+ }
50
+ parseUpdateItem() {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ return this.decided;
53
+ });
54
+ }
55
+ }
56
+ /** The options the model was handed, i.e. the fifth argument of `model.update`. */
57
+ const optionsSeenBy = (model) => model.update.mock.calls[0][4];
58
+ describe('BaseController.update - a parse that decides nothing changes nothing', () => {
59
+ it('hands the CALLER options to the model by identity', () => __awaiter(void 0, void 0, void 0, function* () {
60
+ const model = fakeModel();
61
+ const ctrl = new LegacyCtrl({ model, mergeUpdate: true });
62
+ const options = { unsetPaths: ['a'] };
63
+ yield ctrl.update('id-1', { name: 'N' }, {}, options);
64
+ expect(optionsSeenBy(model)).toBe(options); // the same object, not a copy
65
+ }));
66
+ it('hands over undefined when the caller passed nothing', () => __awaiter(void 0, void 0, void 0, function* () {
67
+ const model = fakeModel();
68
+ const ctrl = new LegacyCtrl({ model, mergeUpdate: true });
69
+ yield ctrl.update('id-1', { name: 'N' }, {});
70
+ expect(optionsSeenBy(model)).toBeUndefined();
71
+ }));
72
+ it('still runs the parse: a legacy override keeps mutating the item', () => __awaiter(void 0, void 0, void 0, function* () {
73
+ const model = fakeModel();
74
+ const ctrl = new LegacyCtrl({ model, mergeUpdate: true });
75
+ const item = { name: 'N' };
76
+ yield ctrl.update('id-1', item, {});
77
+ expect(item.parsed).toBe(true);
78
+ }));
79
+ });
80
+ describe('BaseController.update - a parse that returns options', () => {
81
+ it('carries them to the model when the caller asked for nothing', () => __awaiter(void 0, void 0, void 0, function* () {
82
+ const model = fakeModel();
83
+ const ctrl = new DecidingCtrl(model, { unsetPaths: ['confidentiality'] });
84
+ yield ctrl.update('id-1', { name: 'N' }, {});
85
+ expect(optionsSeenBy(model)).toEqual({ unsetPaths: ['confidentiality'] });
86
+ }));
87
+ it('unions the paths of the caller and of the parse', () => __awaiter(void 0, void 0, void 0, function* () {
88
+ const model = fakeModel();
89
+ const ctrl = new DecidingCtrl(model, { unsetPaths: ['confidentiality'] });
90
+ yield ctrl.update('id-1', { name: 'N' }, {}, { unsetPaths: ['legacyField'] });
91
+ expect(optionsSeenBy(model).unsetPaths).toEqual(['legacyField', 'confidentiality']);
92
+ }));
93
+ it('removes duplicates when both ask for the same path', () => __awaiter(void 0, void 0, void 0, function* () {
94
+ const model = fakeModel();
95
+ const ctrl = new DecidingCtrl(model, { unsetPaths: ['confidentiality', 'other'] });
96
+ yield ctrl.update('id-1', { name: 'N' }, {}, { unsetPaths: ['confidentiality'] });
97
+ expect(optionsSeenBy(model).unsetPaths).toEqual(['confidentiality', 'other']);
98
+ }));
99
+ it('leaves the two originals untouched, arrays included', () => __awaiter(void 0, void 0, void 0, function* () {
100
+ const model = fakeModel();
101
+ const decided = { unsetPaths: ['confidentiality'] };
102
+ const callerOptions = { unsetPaths: ['legacyField'] };
103
+ const ctrl = new DecidingCtrl(model, decided);
104
+ yield ctrl.update('id-1', { name: 'N' }, {}, callerOptions);
105
+ expect(callerOptions).toEqual({ unsetPaths: ['legacyField'] });
106
+ expect(decided).toEqual({ unsetPaths: ['confidentiality'] });
107
+ // and the merged one is a new object, not either of them
108
+ const seen = optionsSeenBy(model);
109
+ expect(seen).not.toBe(callerOptions);
110
+ expect(seen).not.toBe(decided);
111
+ }));
112
+ });
@@ -48,8 +48,14 @@ export declare abstract class BaseController<TAttrs, TDoc extends BaseMongoDoc,
48
48
  * @param id
49
49
  * @param item
50
50
  * @param context
51
+ * @returns the options this parse decided for THIS update, or nothing.
52
+ *
53
+ * A parse may conclude something that the payload cannot express - typically that a field must
54
+ * be REMOVED and not merely left unwritten. Returning it here is how that conclusion reaches the
55
+ * write, without a second call and without borrowing the context, which belongs to the operation
56
+ * and not to the mutation. Returning nothing is the ordinary case and changes nothing.
51
57
  */
52
- protected parseUpdateItem(id: string, item: TAttrs, context: NxContext): Promise<void>;
58
+ protected parseUpdateItem(id: string, item: TAttrs, context: NxContext): Promise<UpdateOptions | void>;
53
59
  /**
54
60
  *
55
61
  * @param item
@@ -10,6 +10,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.BaseController = void 0;
13
+ /**
14
+ * Merges the options the PARSE decided with the ones the CALLER asked for, leaving both untouched.
15
+ *
16
+ * Neither side owns the other: the caller states what it wants removed, the parse states what the
17
+ * derivation concluded must go, and an update carries the union. `unsetPaths` is unioned without
18
+ * duplicates - unsetting the same path twice is harmless, but a list that grows on every pass is
19
+ * noise nobody should have to read.
20
+ */
21
+ const mergeUpdateOptions = (caller, parsed) => {
22
+ var _a, _b;
23
+ const unsetPaths = [...new Set([...((_a = caller === null || caller === void 0 ? void 0 : caller.unsetPaths) !== null && _a !== void 0 ? _a : []), ...((_b = parsed.unsetPaths) !== null && _b !== void 0 ? _b : [])])];
24
+ return Object.assign(Object.assign(Object.assign({}, caller), parsed), { unsetPaths });
25
+ };
13
26
  class BaseController {
14
27
  constructor({ model, mergeUpdate }) {
15
28
  this.mergeUpdate = false;
@@ -67,6 +80,12 @@ class BaseController {
67
80
  * @param id
68
81
  * @param item
69
82
  * @param context
83
+ * @returns the options this parse decided for THIS update, or nothing.
84
+ *
85
+ * A parse may conclude something that the payload cannot express - typically that a field must
86
+ * be REMOVED and not merely left unwritten. Returning it here is how that conclusion reaches the
87
+ * write, without a second call and without borrowing the context, which belongs to the operation
88
+ * and not to the mutation. Returning nothing is the ordinary case and changes nothing.
70
89
  */
71
90
  parseUpdateItem(id, item, context) {
72
91
  return __awaiter(this, void 0, void 0, function* () {
@@ -114,10 +133,11 @@ class BaseController {
114
133
  */
115
134
  update(id, item, context, options) {
116
135
  return __awaiter(this, void 0, void 0, function* () {
117
- yield this.parseUpdateItem(id, item, context);
118
- // The options belong to THIS update and are handed over untouched: the controller does not
119
- // read them and does not interpret them.
120
- return this.model.update(id, item, this.mergeUpdate, context, options);
136
+ const parsed = yield this.parseUpdateItem(id, item, context);
137
+ // The options belong to THIS update. Those of the caller are handed over untouched - the very
138
+ // same object when the parse decided nothing, which is the ordinary case; when it did decide
139
+ // something, the two are merged into a NEW object and neither original is modified.
140
+ return this.model.update(id, item, this.mergeUpdate, context, parsed ? mergeUpdateOptions(options, parsed) : options);
121
141
  });
122
142
  }
123
143
  /**
@@ -85,11 +85,10 @@ export declare class BaseModel<TAttrs, TDoc extends BaseMongoDoc, TMongoModel ex
85
85
  aggregateNativeFiltered(pipeline: mongoose.PipelineStage[], params: FilterParams | undefined, context: NxContext): Promise<mongoose.mongo.AggregationCursor<any> | null>;
86
86
  /**
87
87
  *
88
- * @param {*} pipeline
89
- * @param {*} params
90
- * @param {*} context
88
+ * @param pipeline
89
+ * @param options
91
90
  */
92
- aggregateNative(pipeline: mongoose.PipelineStage[]): mongoose.mongo.AggregationCursor<any>;
91
+ aggregateNative(pipeline: mongoose.PipelineStage[], options?: mongoose.mongo.AggregateOptions): mongoose.mongo.AggregationCursor<any>;
93
92
  /**
94
93
  * Simple Find wrapper
95
94
  * @param {*} params
@@ -175,12 +175,11 @@ class BaseModel extends events_1.EventEmitter {
175
175
  }
176
176
  /**
177
177
  *
178
- * @param {*} pipeline
179
- * @param {*} params
180
- * @param {*} context
178
+ * @param pipeline
179
+ * @param options
181
180
  */
182
- aggregateNative(pipeline) {
183
- return this.mgModel.collection.aggregate(pipeline, { allowDiskUse: true });
181
+ aggregateNative(pipeline, options = {}) {
182
+ return this.mgModel.collection.aggregate(pipeline, Object.assign({ allowDiskUse: true }, options));
184
183
  }
185
184
  /**
186
185
  * Simple Find wrapper
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextage/nx-frame-be",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",