@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.
- package/build/common/base/__test__/base.controller.update-options.test.d.ts +1 -0
- package/build/common/base/__test__/base.controller.update-options.test.js +112 -0
- package/build/common/base/base.controller.d.ts +7 -1
- package/build/common/base/base.controller.js +24 -4
- package/build/common/base/base.model.d.ts +3 -4
- package/build/common/base/base.model.js +4 -5
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
|
119
|
-
//
|
|
120
|
-
|
|
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
|
|
89
|
-
* @param
|
|
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
|
|
179
|
-
* @param
|
|
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
|