@flowerforce/flowerbase 1.7.3 → 1.7.4-beta.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/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +7 -4
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +26 -13
- package/package.json +1 -1
- package/src/features/functions/controller.ts +9 -4
- package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +62 -0
- package/src/services/mongodb-atlas/index.ts +29 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AA8ChD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,kBAqKjC,CAAA"}
|
|
@@ -46,6 +46,7 @@ const isReturnedError = (value) => {
|
|
|
46
46
|
const candidate = value;
|
|
47
47
|
return typeof candidate.message === 'string' && typeof candidate.name === 'string';
|
|
48
48
|
};
|
|
49
|
+
const serializeEjson = (value) => JSON.stringify(bson_1.EJSON.serialize(value, { relaxed: false }));
|
|
49
50
|
/**
|
|
50
51
|
* > Creates a pre handler for every query
|
|
51
52
|
* @param app -> the fastify instance
|
|
@@ -89,7 +90,9 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
89
90
|
pipeline,
|
|
90
91
|
isClient: true
|
|
91
92
|
});
|
|
92
|
-
|
|
93
|
+
const serviceResult = yield operatorsByType[method]();
|
|
94
|
+
res.type('application/json');
|
|
95
|
+
return serializeEjson(serviceResult);
|
|
93
96
|
}
|
|
94
97
|
const currentFunction = functionsList[method];
|
|
95
98
|
if (!currentFunction) {
|
|
@@ -115,7 +118,7 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
115
118
|
return JSON.stringify({ message: result.message, name: result.name });
|
|
116
119
|
}
|
|
117
120
|
res.type('application/json');
|
|
118
|
-
return
|
|
121
|
+
return serializeEjson(result);
|
|
119
122
|
}
|
|
120
123
|
catch (error) {
|
|
121
124
|
res.status(400);
|
|
@@ -157,7 +160,7 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
157
160
|
const changeStream = streams[requestKey];
|
|
158
161
|
if (changeStream) {
|
|
159
162
|
changeStream.on('change', (change) => {
|
|
160
|
-
res.raw.write(`data: ${
|
|
163
|
+
res.raw.write(`data: ${serializeEjson(change)}\n\n`);
|
|
161
164
|
});
|
|
162
165
|
req.raw.on('close', () => {
|
|
163
166
|
var _a;
|
|
@@ -175,7 +178,7 @@ const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0
|
|
|
175
178
|
.collection(collection)
|
|
176
179
|
.watch([], { fullDocument: 'whenAvailable' });
|
|
177
180
|
streams[requestKey].on('change', (change) => {
|
|
178
|
-
res.raw.write(`data: ${
|
|
181
|
+
res.raw.write(`data: ${serializeEjson(change)}\n\n`);
|
|
179
182
|
});
|
|
180
183
|
}));
|
|
181
184
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EAGL,oBAAoB,EAErB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EAGL,oBAAoB,EAErB,MAAM,SAAS,CAAA;AA0uChB,QAAA,MAAM,YAAY,EAAE,oBA6BlB,CAAA;AAEF,eAAe,YAAY,CAAA"}
|
|
@@ -597,27 +597,40 @@ const getOperators = (collection, { rules, collName, user, run_as_system, monito
|
|
|
597
597
|
const safeQuery = Array.isArray(formattedQuery)
|
|
598
598
|
? (0, utils_3.normalizeQuery)(formattedQuery)
|
|
599
599
|
: formattedQuery;
|
|
600
|
-
const result = yield collection.findOne(buildAndQuery(safeQuery));
|
|
601
|
-
if (!result) {
|
|
602
|
-
throw new Error('Update not permitted');
|
|
603
|
-
}
|
|
604
|
-
const winningRole = (0, utils_2.getWinningRole)(result, user, roles);
|
|
605
600
|
const normalizedData = Array.isArray(data)
|
|
606
601
|
? data
|
|
607
602
|
: normalizeUpdatePayload(data);
|
|
603
|
+
const currentDoc = yield collection.findOne(buildAndQuery(safeQuery));
|
|
608
604
|
const updatedPaths = Array.isArray(normalizedData)
|
|
609
605
|
? []
|
|
610
606
|
: getUpdatedPaths(normalizedData);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
607
|
+
let docToCheck;
|
|
608
|
+
let validationType = 'write';
|
|
609
|
+
if (!currentDoc) {
|
|
610
|
+
if (!(normalizedOptions === null || normalizedOptions === void 0 ? void 0 : normalizedOptions.upsert) || Array.isArray(normalizedData)) {
|
|
611
|
+
throw new Error('Update not permitted');
|
|
612
|
+
}
|
|
613
|
+
const updateDocument = normalizedData;
|
|
614
|
+
const setOnInsertSeed = isPlainObject(updateDocument.$setOnInsert)
|
|
615
|
+
? updateDocument.$setOnInsert
|
|
616
|
+
: {};
|
|
617
|
+
docToCheck = applyDocumentUpdateOperators(setOnInsertSeed, updateDocument);
|
|
618
|
+
validationType = 'insert';
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
const [computedDoc] = Array.isArray(normalizedData)
|
|
622
|
+
? yield collection.aggregate([
|
|
623
|
+
{ $match: buildAndQuery(safeQuery) },
|
|
624
|
+
{ $limit: 1 },
|
|
625
|
+
...normalizedData
|
|
626
|
+
]).toArray()
|
|
627
|
+
: [applyDocumentUpdateOperators(currentDoc, normalizedData)];
|
|
628
|
+
docToCheck = computedDoc;
|
|
629
|
+
}
|
|
630
|
+
const winningRole = (0, utils_2.getWinningRole)(docToCheck, user, roles);
|
|
618
631
|
const { status, document } = winningRole
|
|
619
632
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
620
|
-
type:
|
|
633
|
+
type: validationType,
|
|
621
634
|
roles,
|
|
622
635
|
cursor: docToCheck,
|
|
623
636
|
expansions: {}
|
package/package.json
CHANGED
|
@@ -48,6 +48,9 @@ const isReturnedError = (value: unknown): value is { message: string; name: stri
|
|
|
48
48
|
return typeof candidate.message === 'string' && typeof candidate.name === 'string'
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const serializeEjson = (value: unknown) =>
|
|
52
|
+
JSON.stringify(EJSON.serialize(value, { relaxed: false }))
|
|
53
|
+
|
|
51
54
|
/**
|
|
52
55
|
* > Creates a pre handler for every query
|
|
53
56
|
* @param app -> the fastify instance
|
|
@@ -111,7 +114,9 @@ export const functionsController: FunctionController = async (
|
|
|
111
114
|
pipeline,
|
|
112
115
|
isClient: true
|
|
113
116
|
})
|
|
114
|
-
|
|
117
|
+
const serviceResult = await operatorsByType[method as keyof typeof operatorsByType]()
|
|
118
|
+
res.type('application/json')
|
|
119
|
+
return serializeEjson(serviceResult)
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
const currentFunction = functionsList[method]
|
|
@@ -141,7 +146,7 @@ export const functionsController: FunctionController = async (
|
|
|
141
146
|
return JSON.stringify({ message: result.message, name: result.name })
|
|
142
147
|
}
|
|
143
148
|
res.type('application/json')
|
|
144
|
-
return
|
|
149
|
+
return serializeEjson(result)
|
|
145
150
|
} catch (error) {
|
|
146
151
|
res.status(400)
|
|
147
152
|
res.type('application/json')
|
|
@@ -193,7 +198,7 @@ export const functionsController: FunctionController = async (
|
|
|
193
198
|
|
|
194
199
|
if (changeStream) {
|
|
195
200
|
changeStream.on('change', (change) => {
|
|
196
|
-
res.raw.write(`data: ${
|
|
201
|
+
res.raw.write(`data: ${serializeEjson(change)}\n\n`);
|
|
197
202
|
});
|
|
198
203
|
|
|
199
204
|
req.raw.on('close', () => {
|
|
@@ -214,7 +219,7 @@ export const functionsController: FunctionController = async (
|
|
|
214
219
|
|
|
215
220
|
|
|
216
221
|
streams[requestKey].on('change', (change) => {
|
|
217
|
-
res.raw.write(`data: ${
|
|
222
|
+
res.raw.write(`data: ${serializeEjson(change)}\n\n`);
|
|
218
223
|
});
|
|
219
224
|
})
|
|
220
225
|
}
|
|
@@ -125,4 +125,66 @@ describe('mongodb-atlas findOneAndUpdate', () => {
|
|
|
125
125
|
{ returnDocument: 'after' }
|
|
126
126
|
)
|
|
127
127
|
})
|
|
128
|
+
|
|
129
|
+
it('allows upsert when no document matches', async () => {
|
|
130
|
+
const id = new ObjectId()
|
|
131
|
+
const insertedDoc = { _id: id, title: 'Created', userId: 'user-1' }
|
|
132
|
+
const findOne = jest.fn().mockResolvedValue(null)
|
|
133
|
+
const findOneAndUpdate = jest.fn().mockResolvedValue(insertedDoc)
|
|
134
|
+
const collection = {
|
|
135
|
+
collectionName: 'todos',
|
|
136
|
+
findOne,
|
|
137
|
+
findOneAndUpdate
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const app = createAppWithCollection(collection)
|
|
141
|
+
const operators = MongoDbAtlas(app as any, {
|
|
142
|
+
rules: createRules(),
|
|
143
|
+
user: { id: 'user-1' }
|
|
144
|
+
})
|
|
145
|
+
.db('db')
|
|
146
|
+
.collection('todos')
|
|
147
|
+
|
|
148
|
+
const result = await operators.findOneAndUpdate(
|
|
149
|
+
{ _id: id },
|
|
150
|
+
{ $set: { title: 'Created' } },
|
|
151
|
+
{ upsert: true }
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
expect(result).toEqual(insertedDoc)
|
|
155
|
+
expect(findOneAndUpdate).toHaveBeenCalledWith(
|
|
156
|
+
{ $and: [{ _id: id }] },
|
|
157
|
+
{ $set: { title: 'Created' } },
|
|
158
|
+
{ upsert: true }
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('rejects upsert when insert permission is denied', async () => {
|
|
163
|
+
const id = new ObjectId()
|
|
164
|
+
const findOne = jest.fn().mockResolvedValue(null)
|
|
165
|
+
const findOneAndUpdate = jest.fn()
|
|
166
|
+
const collection = {
|
|
167
|
+
collectionName: 'todos',
|
|
168
|
+
findOne,
|
|
169
|
+
findOneAndUpdate
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const app = createAppWithCollection(collection)
|
|
173
|
+
const operators = MongoDbAtlas(app as any, {
|
|
174
|
+
rules: createRules({ insert: false }),
|
|
175
|
+
user: { id: 'user-1' }
|
|
176
|
+
})
|
|
177
|
+
.db('db')
|
|
178
|
+
.collection('todos')
|
|
179
|
+
|
|
180
|
+
await expect(
|
|
181
|
+
operators.findOneAndUpdate(
|
|
182
|
+
{ _id: id },
|
|
183
|
+
{ $set: { title: 'Created' }, $setOnInsert: { createdAt: new Date() } } as any,
|
|
184
|
+
{ upsert: true }
|
|
185
|
+
)
|
|
186
|
+
).rejects.toThrow('Update not permitted')
|
|
187
|
+
|
|
188
|
+
expect(findOneAndUpdate).not.toHaveBeenCalled()
|
|
189
|
+
})
|
|
128
190
|
})
|
|
@@ -687,33 +687,46 @@ const getOperators: GetOperatorsFunction = (
|
|
|
687
687
|
const safeQuery = Array.isArray(formattedQuery)
|
|
688
688
|
? normalizeQuery(formattedQuery)
|
|
689
689
|
: formattedQuery
|
|
690
|
-
|
|
691
|
-
const result = await collection.findOne(buildAndQuery(safeQuery))
|
|
692
|
-
|
|
693
|
-
if (!result) {
|
|
694
|
-
throw new Error('Update not permitted')
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const winningRole = getWinningRole(result, user, roles)
|
|
698
690
|
const normalizedData = Array.isArray(data)
|
|
699
691
|
? data
|
|
700
692
|
: normalizeUpdatePayload(data as Document)
|
|
693
|
+
const currentDoc = await collection.findOne(buildAndQuery(safeQuery))
|
|
701
694
|
const updatedPaths = Array.isArray(normalizedData)
|
|
702
695
|
? []
|
|
703
696
|
: getUpdatedPaths(normalizedData as Document)
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
697
|
+
let docToCheck: Document
|
|
698
|
+
let validationType: 'write' | 'insert' = 'write'
|
|
699
|
+
|
|
700
|
+
if (!currentDoc) {
|
|
701
|
+
if (!normalizedOptions?.upsert || Array.isArray(normalizedData)) {
|
|
702
|
+
throw new Error('Update not permitted')
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const updateDocument = normalizedData as Document
|
|
706
|
+
const setOnInsertSeed =
|
|
707
|
+
isPlainObject(updateDocument.$setOnInsert)
|
|
708
|
+
? (updateDocument.$setOnInsert as Document)
|
|
709
|
+
: {}
|
|
710
|
+
docToCheck = applyDocumentUpdateOperators(setOnInsertSeed, updateDocument)
|
|
711
|
+
validationType = 'insert'
|
|
712
|
+
} else {
|
|
713
|
+
const [computedDoc] = Array.isArray(normalizedData)
|
|
714
|
+
? await collection.aggregate([
|
|
715
|
+
{ $match: buildAndQuery(safeQuery) },
|
|
716
|
+
{ $limit: 1 },
|
|
717
|
+
...normalizedData
|
|
718
|
+
]).toArray()
|
|
719
|
+
: [applyDocumentUpdateOperators(currentDoc, normalizedData as Document)]
|
|
720
|
+
docToCheck = computedDoc
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const winningRole = getWinningRole(docToCheck, user, roles)
|
|
711
724
|
|
|
712
725
|
const { status, document } = winningRole
|
|
713
726
|
? await checkValidation(
|
|
714
727
|
winningRole,
|
|
715
728
|
{
|
|
716
|
-
type:
|
|
729
|
+
type: validationType,
|
|
717
730
|
roles,
|
|
718
731
|
cursor: docToCheck,
|
|
719
732
|
expansions: {}
|