@flowerforce/flowerbase 1.8.4-beta.4 → 1.8.4-beta.6
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/README.md +3 -0
- package/dist/auth/plugins/jwt.d.ts +1 -1
- package/dist/auth/plugins/jwt.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +2 -1
- package/dist/cli/call-function.js +2 -1
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +31 -3
- package/dist/features/functions/dtos.d.ts +2 -0
- package/dist/features/functions/dtos.d.ts.map +1 -1
- package/dist/features/functions/interface.d.ts +2 -0
- package/dist/features/functions/interface.d.ts.map +1 -1
- package/dist/features/functions/utils.d.ts +3 -1
- package/dist/features/functions/utils.d.ts.map +1 -1
- package/dist/features/functions/utils.js +3 -1
- package/dist/services/index.d.ts +8 -8
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +91 -20
- package/dist/services/mongodb-atlas/model.d.ts +2 -0
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/utils/context/helpers.d.ts +35 -32
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/helpers.js +19 -2
- package/dist/utils/context/interface.d.ts +1 -1
- package/dist/utils/context/interface.d.ts.map +1 -1
- package/dist/utils/roles/machines/read/D/validators.d.ts +1 -1
- package/dist/utils/roles/machines/read/D/validators.d.ts.map +1 -1
- package/dist/utils/roles/machines/write/C/validators.d.ts +1 -1
- package/dist/utils/roles/machines/write/C/validators.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/auth/providers/custom-function/controller.ts +2 -1
- package/src/cli/call-function.ts +2 -1
- package/src/features/functions/__tests__/controller.test.ts +42 -2
- package/src/features/functions/__tests__/utils.test.ts +44 -0
- package/src/features/functions/controller.ts +38 -2
- package/src/features/functions/dtos.ts +2 -0
- package/src/features/functions/interface.ts +2 -0
- package/src/features/functions/utils.ts +13 -0
- package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +116 -0
- package/src/services/mongodb-atlas/index.ts +122 -25
- package/src/services/mongodb-atlas/model.ts +8 -0
- package/src/utils/context/helpers.ts +18 -2
- package/src/utils/context/interface.ts +1 -1
- package/tsconfig.json +0 -5
- package/tsconfig.spec.json +2 -1
|
@@ -109,7 +109,7 @@ const createJwtUtils = () => {
|
|
|
109
109
|
* @param functionsList -> the list of all functions
|
|
110
110
|
*/
|
|
111
111
|
const generateContextData = ({ user, services, app, rules, currentFunction, functionName, functionsList, GenerateContextSync, request }) => {
|
|
112
|
-
var _a;
|
|
112
|
+
var _a, _b, _c, _d, _e, _f;
|
|
113
113
|
const BSON = mongodb_1.mongodb.BSON;
|
|
114
114
|
const Binary = BSON === null || BSON === void 0 ? void 0 : BSON.Binary;
|
|
115
115
|
if (Binary && typeof Binary.fromBase64 !== 'function') {
|
|
@@ -160,7 +160,24 @@ const generateContextData = ({ user, services, app, rules, currentFunction, func
|
|
|
160
160
|
}
|
|
161
161
|
},
|
|
162
162
|
context: {
|
|
163
|
-
request:
|
|
163
|
+
request: {
|
|
164
|
+
remoteIPAddress: (_b = request === null || request === void 0 ? void 0 : request.ip) !== null && _b !== void 0 ? _b : '',
|
|
165
|
+
requestHeaders: (request === null || request === void 0 ? void 0 : request.headers)
|
|
166
|
+
? Object.fromEntries(Object.entries(request.headers).map(([key, value]) => [
|
|
167
|
+
key,
|
|
168
|
+
Array.isArray(value) ? value : value !== undefined ? [value] : []
|
|
169
|
+
]))
|
|
170
|
+
: {},
|
|
171
|
+
webhookUrl: (_c = request === null || request === void 0 ? void 0 : request.url) === null || _c === void 0 ? void 0 : _c.split('?')[0],
|
|
172
|
+
httpMethod: request === null || request === void 0 ? void 0 : request.method,
|
|
173
|
+
rawQueryString: ((_d = request === null || request === void 0 ? void 0 : request.url) === null || _d === void 0 ? void 0 : _d.includes('?'))
|
|
174
|
+
? request.url.substring(request.url.indexOf('?'))
|
|
175
|
+
: '',
|
|
176
|
+
httpReferrer: (_e = request === null || request === void 0 ? void 0 : request.headers) === null || _e === void 0 ? void 0 : _e.referer,
|
|
177
|
+
httpUserAgent: (_f = request === null || request === void 0 ? void 0 : request.headers) === null || _f === void 0 ? void 0 : _f['user-agent'],
|
|
178
|
+
service: '',
|
|
179
|
+
action: ''
|
|
180
|
+
},
|
|
164
181
|
user,
|
|
165
182
|
environment: {
|
|
166
183
|
tag: process.env.NODE_ENV
|
|
@@ -17,7 +17,7 @@ export interface GenerateContextParams {
|
|
|
17
17
|
enqueue?: boolean;
|
|
18
18
|
request?: ContextRequest;
|
|
19
19
|
}
|
|
20
|
-
type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id">;
|
|
20
|
+
type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id" | "headers">;
|
|
21
21
|
export interface GenerateContextDataParams extends Omit<GenerateContextParams, 'args'> {
|
|
22
22
|
GenerateContext: (params: GenerateContextParams) => Promise<unknown>;
|
|
23
23
|
GenerateContextSync: (params: GenerateContextParams) => unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/utils/context/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,eAAe,EAAE,QAAQ,CAAA;IACzB,aAAa,EAAE,SAAS,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/utils/context/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,eAAe,EAAE,QAAQ,CAAA;IACzB,aAAa,EAAE,SAAS,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AACpH,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACpE,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAA;CAChE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { MachineContext } from '../../interface';
|
|
2
2
|
export declare const checkAdditionalFieldsFn: ({ role }: MachineContext) => boolean;
|
|
3
|
-
export declare const checkIsValidFieldNameFn: (context: MachineContext) => Promise<import("bson
|
|
3
|
+
export declare const checkIsValidFieldNameFn: (context: MachineContext) => Promise<import("bson").Document>;
|
|
4
4
|
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../../../src/utils/roles/machines/read/D/validators.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,eAAO,MAAM,uBAAuB,GAAI,UAAU,cAAc,YAE/D,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAU,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../../../src/utils/roles/machines/read/D/validators.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,eAAO,MAAM,uBAAuB,GAAI,UAAU,cAAc,YAE/D,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAU,SAAS,cAAc,qCAOpE,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { MachineContext } from '../../interface';
|
|
2
2
|
export declare const checkAdditionalFieldsFn: ({ role }: MachineContext) => boolean;
|
|
3
|
-
export declare const checkIsValidFieldNameFn: (context: MachineContext) => Promise<import("bson
|
|
3
|
+
export declare const checkIsValidFieldNameFn: (context: MachineContext) => Promise<import("bson").Document>;
|
|
4
4
|
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../../../src/utils/roles/machines/write/C/validators.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,eAAO,MAAM,uBAAuB,GAAI,UAAU,cAAc,YAE/D,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAU,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../../../src/utils/roles/machines/write/C/validators.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,eAAO,MAAM,uBAAuB,GAAI,UAAU,cAAc,YAE/D,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAU,SAAS,cAAc,qCAIpE,CAAA"}
|
package/package.json
CHANGED
package/src/cli/call-function.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Fastify, { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
import { services } from '../../../services'
|
|
2
3
|
import { GenerateContext } from '../../../utils/context'
|
|
3
4
|
import { functionsController } from '../controller'
|
|
4
5
|
|
|
@@ -8,18 +9,19 @@ jest.mock('../../../utils/context', () => ({
|
|
|
8
9
|
|
|
9
10
|
describe('functionsController', () => {
|
|
10
11
|
let app: FastifyInstance
|
|
12
|
+
const originalMongoService = services['mongodb-atlas']
|
|
11
13
|
|
|
12
14
|
beforeEach(async () => {
|
|
13
15
|
app = Fastify()
|
|
14
16
|
|
|
15
17
|
app.decorate('jwtAuthentication', async (request: FastifyRequest, _reply: FastifyReply) => {
|
|
16
|
-
;(request as any).user = {
|
|
18
|
+
; (request as any).user = {
|
|
17
19
|
id: '507f191e810c19729de860ea',
|
|
18
20
|
typ: 'access'
|
|
19
21
|
}
|
|
20
22
|
})
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
; (GenerateContext as jest.Mock).mockResolvedValue({ ok: true })
|
|
23
25
|
|
|
24
26
|
await app.register(functionsController, {
|
|
25
27
|
functionsList: {
|
|
@@ -33,6 +35,7 @@ describe('functionsController', () => {
|
|
|
33
35
|
})
|
|
34
36
|
|
|
35
37
|
afterEach(async () => {
|
|
38
|
+
services['mongodb-atlas'] = originalMongoService
|
|
36
39
|
await app.close()
|
|
37
40
|
jest.clearAllMocks()
|
|
38
41
|
})
|
|
@@ -57,4 +60,41 @@ describe('functionsController', () => {
|
|
|
57
60
|
})
|
|
58
61
|
)
|
|
59
62
|
})
|
|
63
|
+
|
|
64
|
+
it('passes mongodb-atlas distinct service arguments through POST /call', async () => {
|
|
65
|
+
const distinct = jest.fn().mockResolvedValue(['open'])
|
|
66
|
+
services['mongodb-atlas'] = jest.fn(() => ({
|
|
67
|
+
db: jest.fn().mockReturnValue({
|
|
68
|
+
collection: jest.fn().mockReturnValue({
|
|
69
|
+
distinct
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})) as any
|
|
73
|
+
|
|
74
|
+
const response = await app.inject({
|
|
75
|
+
method: 'POST',
|
|
76
|
+
url: '/call',
|
|
77
|
+
payload: {
|
|
78
|
+
service: 'mongodb-atlas',
|
|
79
|
+
name: 'distinct',
|
|
80
|
+
arguments: [
|
|
81
|
+
{
|
|
82
|
+
database: 'app',
|
|
83
|
+
collection: 'todos',
|
|
84
|
+
key: 'status',
|
|
85
|
+
query: { archived: false },
|
|
86
|
+
options: { maxTimeMS: 250 }
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(response.statusCode).toBe(200)
|
|
93
|
+
expect(JSON.parse(response.body)).toEqual(['open'])
|
|
94
|
+
expect(distinct).toHaveBeenCalledWith(
|
|
95
|
+
'status',
|
|
96
|
+
{ archived: false },
|
|
97
|
+
{ maxTimeMS: 250 }
|
|
98
|
+
)
|
|
99
|
+
})
|
|
60
100
|
})
|
|
@@ -30,4 +30,48 @@ describe('executeQuery', () => {
|
|
|
30
30
|
{ upsert: true }
|
|
31
31
|
)
|
|
32
32
|
})
|
|
33
|
+
|
|
34
|
+
it('passes distinct arguments through to the service method', async () => {
|
|
35
|
+
const currentMethod = jest.fn().mockResolvedValue(['open'])
|
|
36
|
+
|
|
37
|
+
const operators = await executeQuery({
|
|
38
|
+
currentMethod,
|
|
39
|
+
key: 'status',
|
|
40
|
+
query: { archived: false },
|
|
41
|
+
update: {},
|
|
42
|
+
options: { maxTimeMS: 500 }
|
|
43
|
+
} as any)
|
|
44
|
+
|
|
45
|
+
await operators.distinct()
|
|
46
|
+
|
|
47
|
+
expect(currentMethod).toHaveBeenCalledWith(
|
|
48
|
+
'status',
|
|
49
|
+
{ archived: false },
|
|
50
|
+
{ maxTimeMS: 500 }
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('passes bulkWrite operations through to the service method', async () => {
|
|
55
|
+
const currentMethod = jest.fn().mockResolvedValue({ acknowledged: true })
|
|
56
|
+
const operations = [
|
|
57
|
+
{
|
|
58
|
+
updateOne: {
|
|
59
|
+
filter: { archived: false },
|
|
60
|
+
update: { $set: { archived: true } }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const operators = await executeQuery({
|
|
66
|
+
currentMethod,
|
|
67
|
+
query: {},
|
|
68
|
+
update: {},
|
|
69
|
+
operations,
|
|
70
|
+
options: { ordered: false }
|
|
71
|
+
} as any)
|
|
72
|
+
|
|
73
|
+
await operators.bulkWrite()
|
|
74
|
+
|
|
75
|
+
expect(currentMethod).toHaveBeenCalledWith(operations, { ordered: false })
|
|
76
|
+
})
|
|
33
77
|
})
|
|
@@ -9,6 +9,29 @@ import { Base64Function, FunctionCallBase64Dto, FunctionCallDto } from './dtos'
|
|
|
9
9
|
import { FunctionController } from './interface'
|
|
10
10
|
import { executeQuery } from './utils'
|
|
11
11
|
|
|
12
|
+
const SUPPORTED_QUERY_METHODS = [
|
|
13
|
+
'find',
|
|
14
|
+
'findOne',
|
|
15
|
+
'count',
|
|
16
|
+
'countDocuments',
|
|
17
|
+
'distinct',
|
|
18
|
+
'deleteOne',
|
|
19
|
+
'insertOne',
|
|
20
|
+
'updateOne',
|
|
21
|
+
'findOneAndUpdate',
|
|
22
|
+
'aggregate',
|
|
23
|
+
'insertMany',
|
|
24
|
+
'bulkWrite',
|
|
25
|
+
'updateMany',
|
|
26
|
+
'deleteMany'
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
type SupportedQueryMethod = (typeof SUPPORTED_QUERY_METHODS)[number]
|
|
30
|
+
|
|
31
|
+
const isSupportedQueryMethod = (value: unknown): value is SupportedQueryMethod =>
|
|
32
|
+
typeof value === 'string' &&
|
|
33
|
+
(SUPPORTED_QUERY_METHODS as readonly string[]).includes(value)
|
|
34
|
+
|
|
12
35
|
const normalizeUser = (payload: Record<string, any> | undefined) => {
|
|
13
36
|
if (!payload) return undefined
|
|
14
37
|
const nestedUser =
|
|
@@ -352,6 +375,7 @@ export const functionsController: FunctionController = async (
|
|
|
352
375
|
const [{
|
|
353
376
|
database,
|
|
354
377
|
collection,
|
|
378
|
+
key,
|
|
355
379
|
query,
|
|
356
380
|
filter,
|
|
357
381
|
update,
|
|
@@ -360,9 +384,14 @@ export const functionsController: FunctionController = async (
|
|
|
360
384
|
returnNewDocument,
|
|
361
385
|
document,
|
|
362
386
|
documents,
|
|
387
|
+
operations,
|
|
363
388
|
pipeline = []
|
|
364
389
|
}] = args
|
|
365
390
|
|
|
391
|
+
if (!isSupportedQueryMethod(method)) {
|
|
392
|
+
throw new Error(`Unsupported service method "${String(method)}"`)
|
|
393
|
+
}
|
|
394
|
+
|
|
366
395
|
const currentMethod = serviceFn(app, { rules, user })
|
|
367
396
|
.db(database)
|
|
368
397
|
.collection(collection)[method]
|
|
@@ -371,6 +400,7 @@ export const functionsController: FunctionController = async (
|
|
|
371
400
|
const operatorsByType = await executeQuery({
|
|
372
401
|
currentMethod,
|
|
373
402
|
query,
|
|
403
|
+
key,
|
|
374
404
|
filter,
|
|
375
405
|
update,
|
|
376
406
|
projection,
|
|
@@ -378,10 +408,15 @@ export const functionsController: FunctionController = async (
|
|
|
378
408
|
returnNewDocument,
|
|
379
409
|
document,
|
|
380
410
|
documents,
|
|
411
|
+
operations,
|
|
381
412
|
pipeline,
|
|
382
413
|
isClient: true
|
|
383
414
|
})
|
|
384
|
-
const
|
|
415
|
+
const operator = operatorsByType[method]
|
|
416
|
+
if (typeof operator !== 'function') {
|
|
417
|
+
throw new Error(`Unsupported service method "${method}"`)
|
|
418
|
+
}
|
|
419
|
+
const serviceResult = await operator()
|
|
385
420
|
res.type('application/json')
|
|
386
421
|
return serializeEjson(serviceResult)
|
|
387
422
|
}
|
|
@@ -406,7 +441,8 @@ export const functionsController: FunctionController = async (
|
|
|
406
441
|
currentFunction,
|
|
407
442
|
functionName: String(method),
|
|
408
443
|
functionsList,
|
|
409
|
-
services
|
|
444
|
+
services,
|
|
445
|
+
request: req
|
|
410
446
|
})
|
|
411
447
|
const normalizedResult = await normalizeFunctionResult(result)
|
|
412
448
|
if (isReturnedError(normalizedResult)) {
|
|
@@ -23,6 +23,7 @@ export type FunctionCallBase64Dto = {
|
|
|
23
23
|
type ArgumentsData = Arguments<{
|
|
24
24
|
database: string
|
|
25
25
|
collection: string
|
|
26
|
+
key?: string
|
|
26
27
|
filter?: Document
|
|
27
28
|
query: Parameters<GetOperatorsFunction>
|
|
28
29
|
update: Document
|
|
@@ -31,6 +32,7 @@ type ArgumentsData = Arguments<{
|
|
|
31
32
|
returnNewDocument?: boolean
|
|
32
33
|
document: Document
|
|
33
34
|
documents: Document[]
|
|
35
|
+
operations?: Document[]
|
|
34
36
|
pipeline?: Document[]
|
|
35
37
|
}>
|
|
36
38
|
|
|
@@ -27,12 +27,14 @@ export type ExecuteQueryParams = {
|
|
|
27
27
|
currentMethod: ReturnType<GetOperatorsFunction>[keyof ReturnType<GetOperatorsFunction>]
|
|
28
28
|
query: Parameters<GetOperatorsFunction>
|
|
29
29
|
update: Document
|
|
30
|
+
key?: string
|
|
30
31
|
filter?: Document
|
|
31
32
|
projection?: Document
|
|
32
33
|
options?: Document
|
|
33
34
|
returnNewDocument?: boolean
|
|
34
35
|
document: Document
|
|
35
36
|
documents: Document[]
|
|
37
|
+
operations?: Document[]
|
|
36
38
|
pipeline: Document[]
|
|
37
39
|
isClient?: boolean
|
|
38
40
|
}
|
|
@@ -45,12 +45,14 @@ export const executeQuery = async ({
|
|
|
45
45
|
currentMethod,
|
|
46
46
|
query,
|
|
47
47
|
update,
|
|
48
|
+
key,
|
|
48
49
|
filter,
|
|
49
50
|
projection,
|
|
50
51
|
options,
|
|
51
52
|
returnNewDocument,
|
|
52
53
|
document,
|
|
53
54
|
documents,
|
|
55
|
+
operations,
|
|
54
56
|
pipeline,
|
|
55
57
|
isClient = false
|
|
56
58
|
}: ExecuteQueryParams) => {
|
|
@@ -113,6 +115,12 @@ export const executeQuery = async ({
|
|
|
113
115
|
EJSON.deserialize(resolvedQuery),
|
|
114
116
|
parsedOptions
|
|
115
117
|
),
|
|
118
|
+
distinct: () =>
|
|
119
|
+
(currentMethod as ReturnType<GetOperatorsFunction>['distinct'])(
|
|
120
|
+
key ?? '',
|
|
121
|
+
EJSON.deserialize(resolvedQuery),
|
|
122
|
+
parsedOptions
|
|
123
|
+
),
|
|
116
124
|
deleteOne: () =>
|
|
117
125
|
(currentMethod as ReturnType<GetOperatorsFunction>['deleteOne'])(
|
|
118
126
|
EJSON.deserialize(resolvedQuery),
|
|
@@ -144,6 +152,11 @@ export const executeQuery = async ({
|
|
|
144
152
|
(currentMethod as ReturnType<GetOperatorsFunction>['insertMany'])(
|
|
145
153
|
EJSON.deserialize(documents)
|
|
146
154
|
),
|
|
155
|
+
bulkWrite: () =>
|
|
156
|
+
(currentMethod as ReturnType<GetOperatorsFunction>['bulkWrite'])(
|
|
157
|
+
EJSON.deserialize(operations ?? []),
|
|
158
|
+
parsedOptions
|
|
159
|
+
),
|
|
147
160
|
updateMany: () =>
|
|
148
161
|
(currentMethod as ReturnType<GetOperatorsFunction>['updateMany'])(
|
|
149
162
|
EJSON.deserialize(resolvedQuery),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
|
|
1
3
|
import { ObjectId } from 'mongodb'
|
|
2
4
|
import MongoDbAtlas from '..'
|
|
3
5
|
import { Rules } from '../../../features/rules/interface'
|
|
@@ -178,6 +180,70 @@ describe('mongodb-atlas Realm compatibility', () => {
|
|
|
178
180
|
)
|
|
179
181
|
})
|
|
180
182
|
|
|
183
|
+
it('forwards bulkWrite to the underlying collection in system mode', async () => {
|
|
184
|
+
const operations = [
|
|
185
|
+
{
|
|
186
|
+
updateOne: {
|
|
187
|
+
filter: { done: false },
|
|
188
|
+
update: { $set: { done: true } }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
const bulkWriteResult = {
|
|
193
|
+
acknowledged: true,
|
|
194
|
+
matchedCount: 1,
|
|
195
|
+
modifiedCount: 1,
|
|
196
|
+
insertedCount: 0,
|
|
197
|
+
deletedCount: 0,
|
|
198
|
+
upsertedCount: 0,
|
|
199
|
+
insertedIds: {},
|
|
200
|
+
upsertedIds: {}
|
|
201
|
+
}
|
|
202
|
+
const bulkWrite = jest.fn().mockResolvedValue(bulkWriteResult)
|
|
203
|
+
const collection = {
|
|
204
|
+
collectionName: 'todos',
|
|
205
|
+
bulkWrite
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
209
|
+
run_as_system: true
|
|
210
|
+
}).db('db').collection('todos')
|
|
211
|
+
|
|
212
|
+
const result = await operators.bulkWrite(operations as any, { ordered: false } as any)
|
|
213
|
+
|
|
214
|
+
expect(result).toEqual(bulkWriteResult)
|
|
215
|
+
expect(bulkWrite).toHaveBeenCalledWith(operations, { ordered: false })
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('rejects bulkWrite outside run_as_system mode', async () => {
|
|
219
|
+
const bulkWrite = jest.fn()
|
|
220
|
+
const collection = {
|
|
221
|
+
collectionName: 'todos',
|
|
222
|
+
bulkWrite
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
226
|
+
rules: createRules(),
|
|
227
|
+
user: { id: 'user-1' }
|
|
228
|
+
}).db('db').collection('todos')
|
|
229
|
+
|
|
230
|
+
await expect(
|
|
231
|
+
operators.bulkWrite(
|
|
232
|
+
[
|
|
233
|
+
{
|
|
234
|
+
updateOne: {
|
|
235
|
+
filter: { done: false },
|
|
236
|
+
update: { $set: { done: true } }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
] as any,
|
|
240
|
+
{ ordered: false } as any
|
|
241
|
+
)
|
|
242
|
+
).rejects.toThrow('bulkWrite is available only when run_as_system is enabled')
|
|
243
|
+
|
|
244
|
+
expect(bulkWrite).not.toHaveBeenCalled()
|
|
245
|
+
})
|
|
246
|
+
|
|
181
247
|
it('supports operator updates in updateMany without using invalid aggregate stages', async () => {
|
|
182
248
|
const id = new ObjectId()
|
|
183
249
|
const find = jest.fn().mockReturnValue({
|
|
@@ -513,6 +579,56 @@ describe('mongodb-atlas Realm compatibility', () => {
|
|
|
513
579
|
expect(result.insertedIds).toEqual([id0, id1])
|
|
514
580
|
})
|
|
515
581
|
|
|
582
|
+
it('computes distinct values from readable documents only', async () => {
|
|
583
|
+
const find = jest.fn().mockReturnValue({
|
|
584
|
+
toArray: jest.fn().mockResolvedValue([
|
|
585
|
+
{ _id: new ObjectId(), visible: 'A', secret: 'internal-1' },
|
|
586
|
+
{ _id: new ObjectId(), visible: 'A', secret: 'internal-2' },
|
|
587
|
+
{ _id: new ObjectId(), visible: 'B', secret: 'internal-1' }
|
|
588
|
+
])
|
|
589
|
+
})
|
|
590
|
+
const collection = {
|
|
591
|
+
collectionName: 'todos',
|
|
592
|
+
find
|
|
593
|
+
}
|
|
594
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
595
|
+
rules: createRules({
|
|
596
|
+
roles: [
|
|
597
|
+
{
|
|
598
|
+
name: 'reader',
|
|
599
|
+
apply_when: {},
|
|
600
|
+
insert: true,
|
|
601
|
+
delete: true,
|
|
602
|
+
search: true,
|
|
603
|
+
read: true,
|
|
604
|
+
write: true,
|
|
605
|
+
fields: {
|
|
606
|
+
visible: { read: true },
|
|
607
|
+
secret: { read: false, write: false }
|
|
608
|
+
}
|
|
609
|
+
} as any
|
|
610
|
+
]
|
|
611
|
+
}),
|
|
612
|
+
user: { id: 'user-1' }
|
|
613
|
+
}).db('db').collection('todos')
|
|
614
|
+
|
|
615
|
+
const visibleResult = await operators.distinct('visible', { archived: false })
|
|
616
|
+
const secretResult = await operators.distinct('secret', { archived: false })
|
|
617
|
+
|
|
618
|
+
expect(visibleResult).toEqual(['A', 'B'])
|
|
619
|
+
expect(secretResult).toEqual([])
|
|
620
|
+
expect(find).toHaveBeenNthCalledWith(
|
|
621
|
+
1,
|
|
622
|
+
{ $and: [{ archived: false }] },
|
|
623
|
+
{ projection: { _id: 1, visible: 1 } }
|
|
624
|
+
)
|
|
625
|
+
expect(find).toHaveBeenNthCalledWith(
|
|
626
|
+
2,
|
|
627
|
+
{ $and: [{ archived: false }] },
|
|
628
|
+
{ projection: { _id: 1, secret: 1 } }
|
|
629
|
+
)
|
|
630
|
+
})
|
|
631
|
+
|
|
516
632
|
it('exposes startSession and delegates to the underlying MongoClient', async () => {
|
|
517
633
|
const mockSession = { withTransaction: jest.fn() }
|
|
518
634
|
const startSession = jest.fn().mockReturnValue(mockSession)
|