@flowerforce/flowerbase 1.9.0 → 1.9.1-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.
@@ -24,6 +24,7 @@ export declare const generateContextData: ({ user, services, app, rules, current
24
24
  deserialize: (ejson: mongodb.BSON.Document, options?: mongodb.BSON.EJSONOptions) => any;
25
25
  };
26
26
  Buffer: BufferConstructor;
27
+ fetch: typeof fetch;
27
28
  utils: {
28
29
  jwt: JwtUtils;
29
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/context/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAEvD,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzC,MAAM,CAAA;IACX,MAAM,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,EACtB,sBAAsB,CAAC,EAAE,MAAM,EAAE,KAC9B,OAAO,CAAA;CACb,CAAA;AAgFD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,4GAUjC,yBAAyB;;;;;;;;;;;;;uBA4DP,SAAS;yBAGP,SAAS;;;;;;;;;;;0BAoBmB,MAAM,GAAG,SAAS;2BACV,MAAM,GAAG,SAAS;;;;;;;;;uBAS1D,MAAM;;;;;;+BA5DU,MAAM,OAAO,QAAQ;;;;sCA1HrC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAsGT,CAAC;iCAAa,CAAC;;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;4BAyFF,MAAM,OAAO,aAAa,WAAW,SAAS;;;CAkBrE,CAAA"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/context/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAEvD,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzC,MAAM,CAAA;IACX,MAAM,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,EACtB,sBAAsB,CAAC,EAAE,MAAM,EAAE,KAC9B,OAAO,CAAA;CACb,CAAA;AAgFD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,4GAUjC,yBAAyB;;;;;;;;;;;;;;uBA6DP,SAAS;yBAGP,SAAS;;;;;;;;;;;0BAoBmB,MAAM,GAAG,SAAS;2BACV,MAAM,GAAG,SAAS;;;;;;;;;uBAS1D,MAAM;;;;;;+BA7DU,MAAM,OAAO,QAAQ;;;;sCA1HrC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAsGT,CAAC;iCAAa,CAAC;;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;4BA0FF,MAAM,OAAO,aAAa,WAAW,SAAS;;;CAkBrE,CAAA"}
@@ -150,6 +150,7 @@ const generateContextData = ({ user, services, app, rules, currentFunction, func
150
150
  BSON,
151
151
  EJSON: bson_1.EJSON,
152
152
  Buffer,
153
+ fetch,
153
154
  utils,
154
155
  console: {
155
156
  log: (...args) => {
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils/rules-matcher/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAe,MAAM,aAAa,CAAA;AAmEvE;;GAEG;AACH,QAAA,MAAM,iBAAiB,EAAE,iBAkOxB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,SAyDvB,CAAA;AAID,eAAe,iBAAiB,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils/rules-matcher/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAe,MAAM,aAAa,CAAA;AA2EvE;;GAEG;AACH,QAAA,MAAM,iBAAiB,EAAE,iBAkOxB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,SAyDvB,CAAA;AAID,eAAe,iBAAiB,CAAA"}
@@ -45,6 +45,12 @@ const includesWithSemanticEquality = (value, candidate) => rulesMatcherUtils
45
45
  .some((sourceItem) => rulesMatcherUtils
46
46
  .forceArray(item)
47
47
  .some((candidateItem) => areSemanticallyEqual(sourceItem, candidateItem))));
48
+ const hasScalarArrayMembershipMatch = (left, right) => {
49
+ if (Array.isArray(left) === Array.isArray(right)) {
50
+ return false;
51
+ }
52
+ return includesWithSemanticEquality(left, right);
53
+ };
48
54
  const resolveRefPath = (data, refPath, prefix) => {
49
55
  const exactMatch = (0, get_1.default)(data, refPath, undefined);
50
56
  if (exactMatch !== undefined) {
@@ -241,7 +247,7 @@ const rulesMatcherUtils = {
241
247
  exports.operators = {
242
248
  $exists: (a, b) => !rulesMatcherUtils.isEmpty(a) === b,
243
249
  '%exists': (a, b) => !rulesMatcherUtils.isEmpty(a) === b,
244
- $eq: (a, b) => areSemanticallyEqual(a, b),
250
+ $eq: (a, b) => areSemanticallyEqual(a, b) || hasScalarArrayMembershipMatch(a, b),
245
251
  $ne: (a, b) => !areSemanticallyEqual(a, b),
246
252
  $gt: (a, b) => rulesMatcherUtils.forceNumber(a) > parseFloat(b),
247
253
  $gte: (a, b) => rulesMatcherUtils.forceNumber(a) >= parseFloat(b),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.9.0",
3
+ "version": "1.9.1-beta.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,7 +1,58 @@
1
- import { ensureClientPipelineStages, getHiddenFieldsFromRulesConfig, prependUnsetStage, applyAccessControlToPipeline, mergeProjections } from '../utils'
1
+ import {
2
+ ensureClientPipelineStages,
3
+ getHiddenFieldsFromRulesConfig,
4
+ prependUnsetStage,
5
+ applyAccessControlToPipeline,
6
+ mergeProjections,
7
+ getValidRule
8
+ } from '../utils'
2
9
  import { Role } from '../../../utils/roles/interface'
3
10
 
4
11
  describe('MongoDB Atlas aggregate helpers', () => {
12
+ describe('getValidRule', () => {
13
+ it('matches legacy scalar apply_when syntax against array-valued custom data', () => {
14
+ const validRules = getValidRule({
15
+ filters: [
16
+ {
17
+ apply_when: {
18
+ '%%user.custom_data.roles': 'Admin'
19
+ },
20
+ query: {}
21
+ }
22
+ ] as any,
23
+ user: {
24
+ custom_data: {
25
+ roles: ['Admin', 'SCM']
26
+ }
27
+ } as any,
28
+ record: null
29
+ })
30
+
31
+ expect(validRules).toHaveLength(1)
32
+ })
33
+
34
+ it('does not match legacy scalar apply_when syntax when the array does not contain the value', () => {
35
+ const validRules = getValidRule({
36
+ filters: [
37
+ {
38
+ apply_when: {
39
+ '%%user.custom_data.roles': 'Admin'
40
+ },
41
+ query: {}
42
+ }
43
+ ] as any,
44
+ user: {
45
+ custom_data: {
46
+ roles: ['Editor']
47
+ }
48
+ } as any,
49
+ record: null
50
+ })
51
+
52
+ expect(validRules).toHaveLength(0)
53
+ })
54
+ })
55
+
5
56
  describe('ensureClientPipelineStages', () => {
6
57
  it('allows safe stages', () => {
7
58
  expect(() =>
@@ -157,4 +157,25 @@ describe('context.functions.execute compatibility', () => {
157
157
  expect(result).toEqual({ address: 'rome', total: 10 })
158
158
  fs.rmSync(tempDir, { recursive: true, force: true })
159
159
  })
160
+
161
+ it('exposes native fetch in the sandbox global scope', () => {
162
+ const functionsList = {
163
+ caller: {
164
+ code: 'module.exports = function() { return fetch === globalThis.fetch && typeof fetch === "function" }'
165
+ }
166
+ } as Functions
167
+
168
+ const result = GenerateContextSync({
169
+ args: [],
170
+ app: {} as any,
171
+ rules: {} as any,
172
+ user: {} as any,
173
+ currentFunction: functionsList.caller,
174
+ functionsList,
175
+ services: mockServices,
176
+ functionName: 'caller'
177
+ })
178
+
179
+ expect(result).toBe(true)
180
+ })
160
181
  })
@@ -119,4 +119,27 @@ describe('evaluateExpression', () => {
119
119
  })
120
120
  )
121
121
  })
122
+
123
+ it('supports scalar equality against array-valued custom data', async () => {
124
+ const expression = {
125
+ '%%user.custom_data.roles': 'Admin'
126
+ }
127
+
128
+ const params = {
129
+ type: 'read',
130
+ cursor: { _id: 'doc-1' },
131
+ expansions: {
132
+ '%%prevRoot': undefined
133
+ },
134
+ roles: []
135
+ } as Params
136
+
137
+ const user = {
138
+ custom_data: {
139
+ roles: ['Admin', 'SCM']
140
+ }
141
+ }
142
+
143
+ await expect(evaluateExpression(params, expression, user as never)).resolves.toBe(true)
144
+ })
122
145
  })
@@ -47,7 +47,7 @@ describe('generateContextData', () => {
47
47
 
48
48
  it('should return an object with context configuration', async () => {
49
49
  const mockApp = Fastify()
50
- const { context, console: contextConsole, BSON } = generateContextData({
50
+ const { context, console: contextConsole, BSON, fetch } = generateContextData({
51
51
  services,
52
52
  app: mockApp,
53
53
  functionsList: mockFunctions,
@@ -66,6 +66,8 @@ describe('generateContextData', () => {
66
66
 
67
67
  expect(context.user).toEqual(mockUser)
68
68
 
69
+ expect(fetch).toBe(global.fetch)
70
+
69
71
  const mockedLog = jest.spyOn(console, 'log').mockImplementation(() => { })
70
72
  contextConsole.log('Test', 'generateContextData')
71
73
  expect(mockedLog).toHaveBeenCalledWith('Test', 'generateContextData')
@@ -86,7 +86,7 @@ describe('rule function', () => {
86
86
  expect(result.name).toBe('user.authId___%oidToString')
87
87
  })
88
88
 
89
- it('does not treat scalar equality as array membership in compact rules', () => {
89
+ it('treats scalar equality as array membership in compact rules', () => {
90
90
  const data = {
91
91
  doc: {
92
92
  owners: ['user-1', 'user-2']
@@ -97,7 +97,7 @@ describe('rule function', () => {
97
97
  prefix: 'doc'
98
98
  })
99
99
 
100
- expect(result.valid).toBe(false)
100
+ expect(result.valid).toBe(true)
101
101
  expect(result.name).toBe('doc.owners___$eq')
102
102
  })
103
103
 
@@ -27,8 +27,8 @@ describe('rulesMatcherUtils', () => {
27
27
  // isFunction
28
28
  expect(isFunction(2)).toBe(false)
29
29
  expect(isFunction('ciao')).toBe(false)
30
- expect(isFunction(() => {})).toBe(true)
31
- expect(isFunction(function test() {})).toBe(true)
30
+ expect(isFunction(() => { })).toBe(true)
31
+ expect(isFunction(function test() { })).toBe(true)
32
32
  // isString
33
33
  expect(isString(2)).toBe(false)
34
34
  expect(isString('2')).toBe(true)
@@ -94,4 +94,13 @@ describe('rulesMatcherUtils', () => {
94
94
  )
95
95
  ).toBe(false)
96
96
  })
97
+
98
+ it('matches scalar equality against array-valued fields', () => {
99
+ const data = {
100
+ '%%user': { custom_data: { roles: ['Admin', 'SCM'] } }
101
+ }
102
+
103
+ expect(checkRule({ '%%user.custom_data.roles': 'Admin' } as any, data, {})).toBe(true)
104
+ expect(checkRule({ '%%user.custom_data.roles': 'Manager' } as any, data, {})).toBe(false)
105
+ })
97
106
  })
@@ -176,6 +176,7 @@ export const generateContextData = ({
176
176
  BSON,
177
177
  EJSON,
178
178
  Buffer,
179
+ fetch,
179
180
  utils,
180
181
  console: {
181
182
  log: (...args: Arguments) => {
@@ -58,6 +58,14 @@ const includesWithSemanticEquality = (value: unknown, candidate: unknown): boole
58
58
  )
59
59
  )
60
60
 
61
+ const hasScalarArrayMembershipMatch = (left: unknown, right: unknown): boolean => {
62
+ if (Array.isArray(left) === Array.isArray(right)) {
63
+ return false
64
+ }
65
+
66
+ return includesWithSemanticEquality(left, right)
67
+ }
68
+
61
69
  const resolveRefPath = (data: unknown, refPath: string, prefix?: string): unknown => {
62
70
  const exactMatch = _get(data, refPath, undefined)
63
71
 
@@ -306,7 +314,7 @@ export const operators: Operators = {
306
314
  $exists: (a, b) => !rulesMatcherUtils.isEmpty(a) === b,
307
315
  '%exists': (a, b) => !rulesMatcherUtils.isEmpty(a) === b,
308
316
 
309
- $eq: (a, b) => areSemanticallyEqual(a, b),
317
+ $eq: (a, b) => areSemanticallyEqual(a, b) || hasScalarArrayMembershipMatch(a, b),
310
318
 
311
319
  $ne: (a, b) => !areSemanticallyEqual(a, b),
312
320