@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.
Files changed (45) hide show
  1. package/README.md +3 -0
  2. package/dist/auth/plugins/jwt.d.ts +1 -1
  3. package/dist/auth/plugins/jwt.d.ts.map +1 -1
  4. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  5. package/dist/auth/providers/custom-function/controller.js +2 -1
  6. package/dist/cli/call-function.js +2 -1
  7. package/dist/features/functions/controller.d.ts.map +1 -1
  8. package/dist/features/functions/controller.js +31 -3
  9. package/dist/features/functions/dtos.d.ts +2 -0
  10. package/dist/features/functions/dtos.d.ts.map +1 -1
  11. package/dist/features/functions/interface.d.ts +2 -0
  12. package/dist/features/functions/interface.d.ts.map +1 -1
  13. package/dist/features/functions/utils.d.ts +3 -1
  14. package/dist/features/functions/utils.d.ts.map +1 -1
  15. package/dist/features/functions/utils.js +3 -1
  16. package/dist/services/index.d.ts +8 -8
  17. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  18. package/dist/services/mongodb-atlas/index.js +91 -20
  19. package/dist/services/mongodb-atlas/model.d.ts +2 -0
  20. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  21. package/dist/utils/context/helpers.d.ts +35 -32
  22. package/dist/utils/context/helpers.d.ts.map +1 -1
  23. package/dist/utils/context/helpers.js +19 -2
  24. package/dist/utils/context/interface.d.ts +1 -1
  25. package/dist/utils/context/interface.d.ts.map +1 -1
  26. package/dist/utils/roles/machines/read/D/validators.d.ts +1 -1
  27. package/dist/utils/roles/machines/read/D/validators.d.ts.map +1 -1
  28. package/dist/utils/roles/machines/write/C/validators.d.ts +1 -1
  29. package/dist/utils/roles/machines/write/C/validators.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/auth/providers/custom-function/controller.ts +2 -1
  32. package/src/cli/call-function.ts +2 -1
  33. package/src/features/functions/__tests__/controller.test.ts +42 -2
  34. package/src/features/functions/__tests__/utils.test.ts +44 -0
  35. package/src/features/functions/controller.ts +38 -2
  36. package/src/features/functions/dtos.ts +2 -0
  37. package/src/features/functions/interface.ts +2 -0
  38. package/src/features/functions/utils.ts +13 -0
  39. package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +116 -0
  40. package/src/services/mongodb-atlas/index.ts +122 -25
  41. package/src/services/mongodb-atlas/model.ts +8 -0
  42. package/src/utils/context/helpers.ts +18 -2
  43. package/src/utils/context/interface.ts +1 -1
  44. package/tsconfig.json +0 -5
  45. package/tsconfig.spec.json +2 -1
@@ -492,6 +492,26 @@ const areUpdatedFieldsAllowed = (
492
492
  return updatedPaths.every((path) => isEqual(get(filtered, path), get(updated, path)))
493
493
  }
494
494
 
495
+ const appendDistinctValue = (values: unknown[], candidate: unknown) => {
496
+ if (typeof candidate === 'undefined') return
497
+ if (!values.some((entry) => isEqual(entry, candidate))) {
498
+ values.push(candidate)
499
+ }
500
+ }
501
+
502
+ const collectDistinctValues = (documents: Document[], key: string) =>
503
+ documents.reduce<unknown[]>((values, document) => {
504
+ const currentValue = get(document, key)
505
+
506
+ if (Array.isArray(currentValue)) {
507
+ currentValue.forEach((entry) => appendDistinctValue(values, entry))
508
+ return values
509
+ }
510
+
511
+ appendDistinctValue(values, currentValue)
512
+ return values
513
+ }, [])
514
+
495
515
  const getOperators: GetOperatorsFunction = (
496
516
  mongo,
497
517
  { rules, dbName, collName, user, run_as_system, monitoringOrigin }
@@ -535,7 +555,48 @@ const getOperators: GetOperatorsFunction = (
535
555
  })
536
556
  }
537
557
 
538
- return {
558
+ const validateReadableDocument = async (currentDoc: Document) => {
559
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
560
+
561
+ logDebug('find winningRole', {
562
+ collection: collName,
563
+ userId: getUserId(user),
564
+ winningRoleName: winningRole?.name ?? null,
565
+ rolesLength: roles.length
566
+ })
567
+
568
+ const { status, document } = winningRole
569
+ ? await checkValidation(
570
+ winningRole,
571
+ {
572
+ type: 'read',
573
+ roles,
574
+ cursor: currentDoc,
575
+ expansions: getValidationExpansions(currentDoc)
576
+ },
577
+ user
578
+ )
579
+ : fallbackAccess(currentDoc)
580
+
581
+ return status ? document : undefined
582
+ }
583
+
584
+ const getScopedQuery = (query: MongoFilter<Document> = {}) => {
585
+ const formattedQuery = getFormattedQuery(filters, query, user)
586
+ const currentQuery = formattedQuery.length ? { $and: formattedQuery } : {}
587
+ const safeQuery = Array.isArray(formattedQuery)
588
+ ? normalizeQuery(formattedQuery)
589
+ : formattedQuery
590
+
591
+ return {
592
+ formattedQuery,
593
+ currentQuery,
594
+ safeQuery,
595
+ builtQuery: buildAndQuery(safeQuery)
596
+ }
597
+ }
598
+
599
+ const operators: ReturnType<GetOperatorsFunction> = {
539
600
  /**
540
601
  * Finds a single document in a MongoDB collection with optional role-based filtering and validation.
541
602
  *
@@ -1068,30 +1129,7 @@ const getOperators: GetOperatorsFunction = (
1068
1129
  const response = await originalToArray()
1069
1130
 
1070
1131
  const filteredResponse = await Promise.all(
1071
- response.map(async (currentDoc) => {
1072
- const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1073
-
1074
- logDebug('find winningRole', {
1075
- collection: collName,
1076
- userId: getUserId(user),
1077
- winningRoleName: winningRole?.name ?? null,
1078
- rolesLength: roles.length
1079
- })
1080
- const { status, document } = winningRole
1081
- ? await checkValidation(
1082
- winningRole,
1083
- {
1084
- type: 'read',
1085
- roles,
1086
- cursor: currentDoc,
1087
- expansions: getValidationExpansions(currentDoc)
1088
- },
1089
- user
1090
- )
1091
- : fallbackAccess(currentDoc)
1092
-
1093
- return status ? document : undefined
1094
- })
1132
+ response.map((currentDoc) => validateReadableDocument(currentDoc))
1095
1133
  )
1096
1134
 
1097
1135
  return filteredResponse.filter(Boolean) as WithId<Document>[]
@@ -1164,6 +1202,45 @@ const getOperators: GetOperatorsFunction = (
1164
1202
  throw error
1165
1203
  }
1166
1204
  },
1205
+ distinct: async (key, query = {}, options) => {
1206
+ try {
1207
+ if (!key) {
1208
+ throw new Error('distinct key is required')
1209
+ }
1210
+
1211
+ if (!run_as_system) {
1212
+ checkDenyOperation(
1213
+ normalizedRules,
1214
+ collection.collectionName,
1215
+ CRUD_OPERATIONS.READ
1216
+ )
1217
+
1218
+ const { currentQuery } = getScopedQuery(query)
1219
+ const projectedOptions = {
1220
+ ...(options ?? {}),
1221
+ projection: { _id: 1, [key]: 1 }
1222
+ } as FindOptions
1223
+ const documents = await collection.find(currentQuery, projectedOptions).toArray()
1224
+ const readableDocuments = (
1225
+ await Promise.all(documents.map((currentDoc) => validateReadableDocument(currentDoc)))
1226
+ ).filter(Boolean) as Document[]
1227
+ const result = collectDistinctValues(readableDocuments, key)
1228
+
1229
+ emitMongoEvent('distinct')
1230
+ return result
1231
+ }
1232
+
1233
+ const result =
1234
+ typeof options === 'undefined'
1235
+ ? await collection.distinct(key, query)
1236
+ : await collection.distinct(key, query, options)
1237
+ emitMongoEvent('distinct')
1238
+ return result
1239
+ } catch (error) {
1240
+ emitMongoEvent('distinct', undefined, error)
1241
+ throw error
1242
+ }
1243
+ },
1167
1244
  /**
1168
1245
  * Watches changes on a MongoDB collection with optional role-based filtering of change events.
1169
1246
  *
@@ -1450,6 +1527,24 @@ const getOperators: GetOperatorsFunction = (
1450
1527
  throw error
1451
1528
  }
1452
1529
  },
1530
+ bulkWrite: async (operations, options) => {
1531
+ try {
1532
+ if (!run_as_system) {
1533
+ throw new Error('bulkWrite is available only when run_as_system is enabled')
1534
+ }
1535
+
1536
+ const result = await collection.bulkWrite(operations, options)
1537
+ emitMongoEvent('bulkWrite', { operations: operations.length })
1538
+ return result
1539
+ } catch (error) {
1540
+ emitMongoEvent(
1541
+ 'bulkWrite',
1542
+ { operations: Array.isArray(operations) ? operations.length : 0 },
1543
+ error
1544
+ )
1545
+ throw error
1546
+ }
1547
+ },
1453
1548
  updateMany: async (query, data, options) => {
1454
1549
  try {
1455
1550
  const normalizedData = normalizeUpdatePayload(data as Document)
@@ -1602,6 +1697,8 @@ const getOperators: GetOperatorsFunction = (
1602
1697
  }
1603
1698
  }
1604
1699
  }
1700
+
1701
+ return operators
1605
1702
  }
1606
1703
 
1607
1704
  const MongodbAtlas: MongodbAtlasFunction = (
@@ -93,6 +93,14 @@ export type GetOperatorsFunction = (
93
93
  aggregate: (
94
94
  ...params: [...Parameters<Method<'aggregate'>>, isClient: boolean]
95
95
  ) => ReturnType<Method<'aggregate'>>
96
+ distinct: (
97
+ key: Parameters<Method<'distinct'>>[0],
98
+ filter?: Parameters<Method<'distinct'>>[1],
99
+ options?: Parameters<Method<'distinct'>>[2]
100
+ ) => ReturnType<Method<'distinct'>>
101
+ bulkWrite: (
102
+ ...params: Parameters<Method<'bulkWrite'>>
103
+ ) => ReturnType<Method<'bulkWrite'>>
96
104
  insertMany: (
97
105
  ...params: Parameters<Method<'insertMany'>>
98
106
  ) => ReturnType<Method<'insertMany'>>
@@ -187,8 +187,24 @@ export const generateContextData = ({
187
187
  },
188
188
  context: {
189
189
  request: {
190
- ...request,
191
- remoteIPAddress: request?.ip
190
+ remoteIPAddress: request?.ip ?? '',
191
+ requestHeaders: request?.headers
192
+ ? Object.fromEntries(
193
+ Object.entries(request.headers).map(([key, value]) => [
194
+ key,
195
+ Array.isArray(value) ? value : value !== undefined ? [value] : []
196
+ ])
197
+ )
198
+ : {},
199
+ webhookUrl: request?.url?.split('?')[0],
200
+ httpMethod: request?.method,
201
+ rawQueryString: request?.url?.includes('?')
202
+ ? request.url.substring(request.url.indexOf('?'))
203
+ : '',
204
+ httpReferrer: request?.headers?.referer as string | undefined,
205
+ httpUserAgent: request?.headers?.['user-agent'] as string | undefined,
206
+ service: '',
207
+ action: ''
192
208
  },
193
209
  user,
194
210
  environment: {
@@ -19,7 +19,7 @@ export interface GenerateContextParams {
19
19
  request?: ContextRequest
20
20
  }
21
21
 
22
- type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id">
22
+ type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id" | "headers">
23
23
  export interface GenerateContextDataParams extends Omit<GenerateContextParams, 'args'> {
24
24
  GenerateContext: (params: GenerateContextParams) => Promise<unknown>
25
25
  GenerateContextSync: (params: GenerateContextParams) => unknown
package/tsconfig.json CHANGED
@@ -8,13 +8,8 @@
8
8
  "declarationMap": true,
9
9
  "noImplicitAny": true,
10
10
  "strict": true,
11
- "moduleResolution": "node",
12
11
  "esModuleInterop": true,
13
12
  "skipLibCheck": true,
14
- "baseUrl": ".",
15
- "paths": {
16
- "*": ["../../node_modules/*"]
17
- },
18
13
  "lib": ["ES2021", "DOM"]
19
14
  },
20
15
  "include": ["src/**/*"],
@@ -3,5 +3,6 @@
3
3
  "compilerOptions": {
4
4
  "types": ["node", "jest"]
5
5
  },
6
- "include": ["src/**/*.test.ts", "src/**/*.spec.ts"]
6
+ "include": ["src/**/*.test.ts", "src/**/*.spec.ts"],
7
+ "exclude": ["node_modules", "dist"]
7
8
  }