@flowerforce/flowerbase 1.8.4-beta.3 → 1.8.4-beta.5

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 (39) 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/features/functions/controller.d.ts.map +1 -1
  5. package/dist/features/functions/controller.js +29 -2
  6. package/dist/features/functions/dtos.d.ts +2 -0
  7. package/dist/features/functions/dtos.d.ts.map +1 -1
  8. package/dist/features/functions/interface.d.ts +2 -0
  9. package/dist/features/functions/interface.d.ts.map +1 -1
  10. package/dist/features/functions/utils.d.ts +3 -1
  11. package/dist/features/functions/utils.d.ts.map +1 -1
  12. package/dist/features/functions/utils.js +3 -1
  13. package/dist/services/index.d.ts +8 -8
  14. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  15. package/dist/services/mongodb-atlas/index.js +112 -29
  16. package/dist/services/mongodb-atlas/model.d.ts +2 -0
  17. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  18. package/dist/services/mongodb-atlas/utils.d.ts +15 -0
  19. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  20. package/dist/services/mongodb-atlas/utils.js +74 -12
  21. package/dist/utils/context/helpers.d.ts +24 -24
  22. package/dist/utils/roles/machines/read/D/validators.d.ts +1 -1
  23. package/dist/utils/roles/machines/read/D/validators.d.ts.map +1 -1
  24. package/dist/utils/roles/machines/write/C/validators.d.ts +1 -1
  25. package/dist/utils/roles/machines/write/C/validators.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/features/functions/__tests__/controller.test.ts +42 -2
  28. package/src/features/functions/__tests__/utils.test.ts +44 -0
  29. package/src/features/functions/controller.ts +36 -1
  30. package/src/features/functions/dtos.ts +2 -0
  31. package/src/features/functions/interface.ts +2 -0
  32. package/src/features/functions/utils.ts +13 -0
  33. package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +116 -0
  34. package/src/services/mongodb-atlas/__tests__/utils.test.ts +86 -1
  35. package/src/services/mongodb-atlas/index.ts +164 -44
  36. package/src/services/mongodb-atlas/model.ts +8 -0
  37. package/src/services/mongodb-atlas/utils.ts +81 -12
  38. package/tsconfig.json +0 -5
  39. package/tsconfig.spec.json +2 -1
@@ -1,4 +1,4 @@
1
- import { ensureClientPipelineStages, getHiddenFieldsFromRulesConfig, prependUnsetStage, applyAccessControlToPipeline } from '../utils'
1
+ import { ensureClientPipelineStages, getHiddenFieldsFromRulesConfig, prependUnsetStage, applyAccessControlToPipeline, mergeProjections } from '../utils'
2
2
  import { Role } from '../../../utils/roles/interface'
3
3
 
4
4
  describe('MongoDB Atlas aggregate helpers', () => {
@@ -165,4 +165,89 @@ describe('MongoDB Atlas aggregate helpers', () => {
165
165
  })
166
166
  })
167
167
  })
168
+
169
+ describe('mergeProjections', () => {
170
+ it('returns undefined when both sides are empty', () => {
171
+ expect(mergeProjections(undefined, undefined)).toBeUndefined()
172
+ expect(mergeProjections({}, null)).toBeUndefined()
173
+ })
174
+
175
+ it('returns the client projection when rules have none', () => {
176
+ expect(mergeProjections({ a: 1, b: 1 }, null)).toEqual({ a: 1, b: 1 })
177
+ })
178
+
179
+ it('normalizes the rules projection when client has none', () => {
180
+ // Mixed inclusion/exclusion rules are normalized to pure inclusion mode.
181
+ expect(
182
+ mergeProjections(undefined, { item: 1, status: 1, instock: 0 })
183
+ ).toEqual({ item: 1, status: 1 })
184
+ })
185
+
186
+ it('merges plain inclusion projections (rules wins on conflict)', () => {
187
+ expect(
188
+ mergeProjections(
189
+ { item: 1, price: 1 },
190
+ { item: 1, status: 1 }
191
+ )
192
+ ).toEqual({ item: 1, status: 1, price: 1 })
193
+ })
194
+
195
+ it('supports dotted client keys alongside plain rules keys', () => {
196
+ expect(
197
+ mergeProjections(
198
+ { price: 1 },
199
+ { item: 1, status: 1, 'instock.qty': 1 }
200
+ )
201
+ ).toEqual({ item: 1, status: 1, 'instock.qty': 1, price: 1 })
202
+ })
203
+
204
+ it('drops client dotted keys when rules exclude the top-level field', () => {
205
+ // Rules: include item/status, exclude the whole `instock` subtree.
206
+ // Client tries to read `instock.qty` — it must be stripped.
207
+ expect(
208
+ mergeProjections(
209
+ { item: 1, status: 1, 'instock.qty': 1 },
210
+ { item: 1, status: 1, instock: 0 }
211
+ )
212
+ ).toEqual({ item: 1, status: 1 })
213
+ })
214
+
215
+ it('drops every client inclusion whose top-level is excluded by rules', () => {
216
+ expect(
217
+ mergeProjections(
218
+ {
219
+ item: 1,
220
+ 'instock.qty': 1,
221
+ 'instock.warehouse': 1,
222
+ price: 1
223
+ },
224
+ { item: 1, instock: 0 }
225
+ )
226
+ ).toEqual({ item: 1, price: 1 })
227
+ })
228
+
229
+ it('produces pure exclusion output when neither side has inclusions', () => {
230
+ expect(
231
+ mergeProjections({ secretA: 0 }, { secretB: 0 })
232
+ ).toEqual({ secretA: 0, secretB: 0 })
233
+ })
234
+
235
+ it('drops non-_id client exclusions when switching to inclusion mode', () => {
236
+ // Can't mix `{ price: 0, item: 1 }` in MongoDB — rules force inclusion
237
+ // mode so the client exclusion is silently dropped (price is implicitly
238
+ // excluded because it is not included).
239
+ expect(
240
+ mergeProjections({ price: 0 }, { item: 1 })
241
+ ).toEqual({ item: 1 })
242
+ })
243
+
244
+ it('keeps _id: 0 alongside inclusion mode', () => {
245
+ expect(
246
+ mergeProjections({ _id: 0 }, { item: 1 })
247
+ ).toEqual({ _id: 0, item: 1 })
248
+ expect(
249
+ mergeProjections({ item: 1 }, { _id: 0 })
250
+ ).toEqual({ _id: 0, item: 1 })
251
+ })
252
+ })
168
253
  })
@@ -35,6 +35,7 @@ import {
35
35
  getFormattedProjection,
36
36
  getFormattedQuery,
37
37
  getHiddenFieldsFromRulesConfig,
38
+ mergeProjections,
38
39
  normalizeQuery
39
40
  } from './utils'
40
41
 
@@ -491,6 +492,26 @@ const areUpdatedFieldsAllowed = (
491
492
  return updatedPaths.every((path) => isEqual(get(filtered, path), get(updated, path)))
492
493
  }
493
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
+
494
515
  const getOperators: GetOperatorsFunction = (
495
516
  mongo,
496
517
  { rules, dbName, collName, user, run_as_system, monitoringOrigin }
@@ -534,7 +555,48 @@ const getOperators: GetOperatorsFunction = (
534
555
  })
535
556
  }
536
557
 
537
- 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> = {
538
600
  /**
539
601
  * Finds a single document in a MongoDB collection with optional role-based filtering and validation.
540
602
  *
@@ -558,13 +620,6 @@ const getOperators: GetOperatorsFunction = (
558
620
  projectionOrOptions,
559
621
  options
560
622
  )
561
- const resolvedOptions =
562
- projection || normalizedOptions
563
- ? {
564
- ...(normalizedOptions ?? {}),
565
- ...(projection ? { projection } : {})
566
- }
567
- : undefined
568
623
  const resolvedQuery = query ?? {}
569
624
  if (!run_as_system) {
570
625
  checkDenyOperation(
@@ -574,6 +629,17 @@ const getOperators: GetOperatorsFunction = (
574
629
  )
575
630
  // Apply access control filters to the query
576
631
  const formattedQuery = getFormattedQuery(filters, resolvedQuery, user)
632
+ // Rules-level projection has priority over client-provided projection.
633
+ // The merged projection is passed natively to MongoDB.
634
+ const rulesProjection = getFormattedProjection(filters, user)
635
+ const finalProjection = mergeProjections(projection, rulesProjection)
636
+ const resolvedOptions =
637
+ finalProjection || normalizedOptions
638
+ ? {
639
+ ...(normalizedOptions ?? {}),
640
+ ...(finalProjection ? { projection: finalProjection } : {})
641
+ }
642
+ : undefined
577
643
  logDebug('update formattedQuery', {
578
644
  collection: collName,
579
645
  query,
@@ -629,8 +695,15 @@ const getOperators: GetOperatorsFunction = (
629
695
  emitMongoEvent('findOne')
630
696
  return Promise.resolve(response)
631
697
  }
632
- // System mode: no validation applied
633
- const response = await collection.findOne(resolvedQuery, resolvedOptions)
698
+ // System mode: no validation applied, only client-provided projection/options.
699
+ const systemOptions =
700
+ projection || normalizedOptions
701
+ ? {
702
+ ...(normalizedOptions ?? {}),
703
+ ...(projection ? { projection } : {})
704
+ }
705
+ : undefined
706
+ const response = await collection.findOne(resolvedQuery, systemOptions)
634
707
  emitMongoEvent('findOne')
635
708
  return response
636
709
  } catch (error) {
@@ -1023,13 +1096,6 @@ const getOperators: GetOperatorsFunction = (
1023
1096
  projectionOrOptions,
1024
1097
  options
1025
1098
  )
1026
- const resolvedOptions =
1027
- projection || normalizedOptions
1028
- ? {
1029
- ...(normalizedOptions ?? {}),
1030
- ...(projection ? { projection } : {})
1031
- }
1032
- : undefined
1033
1099
  if (!run_as_system) {
1034
1100
  checkDenyOperation(
1035
1101
  normalizedRules,
@@ -1039,6 +1105,17 @@ const getOperators: GetOperatorsFunction = (
1039
1105
  // Pre-query filtering based on access control rules
1040
1106
  const formattedQuery = getFormattedQuery(filters, query, user)
1041
1107
  const currentQuery = formattedQuery.length ? { $and: formattedQuery } : {}
1108
+ // Rules-level projection has priority over client-provided projection.
1109
+ // The merged projection is passed natively to MongoDB.
1110
+ const rulesProjection = getFormattedProjection(filters, user)
1111
+ const finalProjection = mergeProjections(projection, rulesProjection)
1112
+ const resolvedOptions =
1113
+ finalProjection || normalizedOptions
1114
+ ? {
1115
+ ...(normalizedOptions ?? {}),
1116
+ ...(finalProjection ? { projection: finalProjection } : {})
1117
+ }
1118
+ : undefined
1042
1119
  // aggiunto filter per evitare questo errore: $and argument's entries must be objects
1043
1120
  const cursor = collection.find(currentQuery, resolvedOptions)
1044
1121
  const originalToArray = cursor.toArray.bind(cursor)
@@ -1052,30 +1129,7 @@ const getOperators: GetOperatorsFunction = (
1052
1129
  const response = await originalToArray()
1053
1130
 
1054
1131
  const filteredResponse = await Promise.all(
1055
- response.map(async (currentDoc) => {
1056
- const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1057
-
1058
- logDebug('find winningRole', {
1059
- collection: collName,
1060
- userId: getUserId(user),
1061
- winningRoleName: winningRole?.name ?? null,
1062
- rolesLength: roles.length
1063
- })
1064
- const { status, document } = winningRole
1065
- ? await checkValidation(
1066
- winningRole,
1067
- {
1068
- type: 'read',
1069
- roles,
1070
- cursor: currentDoc,
1071
- expansions: getValidationExpansions(currentDoc)
1072
- },
1073
- user
1074
- )
1075
- : fallbackAccess(currentDoc)
1076
-
1077
- return status ? document : undefined
1078
- })
1132
+ response.map((currentDoc) => validateReadableDocument(currentDoc))
1079
1133
  )
1080
1134
 
1081
1135
  return filteredResponse.filter(Boolean) as WithId<Document>[]
@@ -1084,8 +1138,15 @@ const getOperators: GetOperatorsFunction = (
1084
1138
  emitMongoEvent('find')
1085
1139
  return cursor
1086
1140
  }
1087
- // System mode: return original unfiltered cursor
1088
- const cursor = collection.find(query, resolvedOptions)
1141
+ // System mode: return original unfiltered cursor (only client projection/options).
1142
+ const systemOptions =
1143
+ projection || normalizedOptions
1144
+ ? {
1145
+ ...(normalizedOptions ?? {}),
1146
+ ...(projection ? { projection } : {})
1147
+ }
1148
+ : undefined
1149
+ const cursor = collection.find(query, systemOptions)
1089
1150
  emitMongoEvent('find')
1090
1151
  return cursor
1091
1152
  } catch (error) {
@@ -1141,6 +1202,45 @@ const getOperators: GetOperatorsFunction = (
1141
1202
  throw error
1142
1203
  }
1143
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
+ },
1144
1244
  /**
1145
1245
  * Watches changes on a MongoDB collection with optional role-based filtering of change events.
1146
1246
  *
@@ -1327,7 +1427,7 @@ const getOperators: GetOperatorsFunction = (
1327
1427
  formattedQuery,
1328
1428
  pipeline
1329
1429
  })
1330
- const projection = getFormattedProjection(filters)
1430
+ const projection = getFormattedProjection(filters, user)
1331
1431
  const hiddenFields = getHiddenFieldsFromRulesConfig(rulesConfig)
1332
1432
 
1333
1433
  const sanitizedPipeline = applyAccessControlToPipeline(
@@ -1427,6 +1527,24 @@ const getOperators: GetOperatorsFunction = (
1427
1527
  throw error
1428
1528
  }
1429
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
+ },
1430
1548
  updateMany: async (query, data, options) => {
1431
1549
  try {
1432
1550
  const normalizedData = normalizeUpdatePayload(data as Document)
@@ -1579,6 +1697,8 @@ const getOperators: GetOperatorsFunction = (
1579
1697
  }
1580
1698
  }
1581
1699
  }
1700
+
1701
+ return operators
1582
1702
  }
1583
1703
 
1584
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'>>
@@ -76,20 +76,89 @@ export const getFormattedProjection = (
76
76
  filters: Filter[] = [],
77
77
  user?: User
78
78
  ): Projection | null => {
79
- const projections = filters
80
- .filter((filter) => {
81
- if (filter.projection) {
82
- const preFilter = getValidRule({ filters, user })
83
- const isValidPreFilter = !!preFilter?.length
84
- return isValidPreFilter
85
- }
86
- return false
87
- })
88
- .map((f) => f.projection)
79
+ const projections = getValidRule({ filters, user })
80
+ .filter((f) => !!f.projection)
81
+ .map((f) => f.projection as Projection)
89
82
  if (!projections.length) return null
90
83
  return Object.assign({}, ...projections)
91
84
  }
92
85
 
86
+ /**
87
+ * Merges a client-provided projection with the one computed from rules filters.
88
+ *
89
+ * Rules have higher priority over the client:
90
+ * - If rules exclude a top-level field (e.g. `{ instock: 0 }`), every client
91
+ * reference to that field — including dotted sub-paths such as
92
+ * `"instock.qty": 1` — is dropped from the final projection.
93
+ * - If rules include a field (value `1`), it is always part of the final
94
+ * projection and overrides any conflicting client value.
95
+ * - The returned projection is always a valid MongoDB projection (no mixing of
96
+ * inclusion and exclusion on non-`_id` keys), so it can be passed as-is to
97
+ * native MongoDB methods.
98
+ * - Returns `undefined` when neither side provided a meaningful projection.
99
+ */
100
+ export const mergeProjections = (
101
+ clientProjection: Projection | Document | undefined,
102
+ rulesProjection: Projection | null | undefined
103
+ ): Projection | Document | undefined => {
104
+ const hasClient = !!clientProjection && Object.keys(clientProjection).length > 0
105
+ const hasRules = !!rulesProjection && Object.keys(rulesProjection).length > 0
106
+ if (!hasClient && !hasRules) return undefined
107
+
108
+ const client = (hasClient ? (clientProjection as Projection) : {}) as Projection
109
+ const rules = (hasRules ? (rulesProjection as Projection) : {}) as Projection
110
+
111
+ const getTopLevel = (key: string) => key.split('.')[0]
112
+
113
+ const rulesEntries = Object.entries(rules)
114
+ const rulesIncludeKeys = rulesEntries
115
+ .filter(([, value]) => value === 1)
116
+ .map(([key]) => key)
117
+ const rulesExcludeKeys = rulesEntries
118
+ .filter(([, value]) => value === 0)
119
+ .map(([key]) => key)
120
+
121
+ // Top-level fields excluded by rules (excluding `_id` which has special
122
+ // MongoDB semantics and is allowed alongside inclusion projections).
123
+ const excludedTopLevel = new Set(
124
+ rulesExcludeKeys.map(getTopLevel).filter((key) => key !== '_id')
125
+ )
126
+
127
+ const filteredClient: Record<string, 0 | 1> = {}
128
+ for (const [key, value] of Object.entries(client)) {
129
+ if (excludedTopLevel.has(getTopLevel(key))) continue
130
+ filteredClient[key] = value as 0 | 1
131
+ }
132
+
133
+ const hasInclusion =
134
+ rulesIncludeKeys.some((key) => key !== '_id') ||
135
+ Object.entries(filteredClient).some(([key, value]) => value === 1 && key !== '_id')
136
+
137
+ const merged: Record<string, 0 | 1> = {}
138
+
139
+ if (hasInclusion) {
140
+ // Inclusion mode: keep only client inclusions, then overlay rules inclusions.
141
+ // Client exclusions (other than `_id: 0`) are incompatible with inclusion
142
+ // mode and are dropped; not-included fields are implicitly excluded anyway.
143
+ for (const [key, value] of Object.entries(filteredClient)) {
144
+ if (value === 1 || key === '_id') merged[key] = value
145
+ }
146
+ for (const key of rulesIncludeKeys) merged[key] = 1
147
+ // Allow `_id: 0` to be forced by rules in inclusion mode.
148
+ for (const key of rulesExcludeKeys) {
149
+ if (key === '_id') merged[key] = 0
150
+ }
151
+ } else {
152
+ // Pure exclusion mode: combine all exclusions from both sides.
153
+ for (const [key, value] of Object.entries(filteredClient)) {
154
+ if (value === 0) merged[key] = 0
155
+ }
156
+ for (const key of rulesExcludeKeys) merged[key] = 0
157
+ }
158
+
159
+ return Object.keys(merged).length > 0 ? merged : undefined
160
+ }
161
+
93
162
  export const applyAccessControlToPipeline = (
94
163
  pipeline: AggregationPipeline,
95
164
  rules: Record<
@@ -120,7 +189,7 @@ export const applyAccessControlToPipeline = (
120
189
  checkDenyOperation(rules as Rules, currentCollection, CRUD_OPERATIONS.READ)
121
190
  const lookupRules = rules[currentCollection] || {}
122
191
  const formattedQuery = getFormattedQuery(lookupRules.filters, {}, user)
123
- const projection = getFormattedProjection(lookupRules.filters)
192
+ const projection = getFormattedProjection(lookupRules.filters, user)
124
193
 
125
194
  const nestedPipeline = applyAccessControlToPipeline(
126
195
  lookUpStage.pipeline || [],
@@ -155,7 +224,7 @@ export const applyAccessControlToPipeline = (
155
224
  checkDenyOperation(rules as Rules, currentCollection, CRUD_OPERATIONS.READ)
156
225
  const unionRules = rules[currentCollection] || {}
157
226
  const formattedQuery = getFormattedQuery(unionRules.filters, {}, user)
158
- const projection = getFormattedProjection(unionRules.filters)
227
+ const projection = getFormattedProjection(unionRules.filters, user)
159
228
 
160
229
  if (isSimpleStage) {
161
230
  return stage
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
  }