@prosopo/user-access-policy 3.11.2 → 3.12.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.
Files changed (69) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +38 -37
  2. package/.turbo/turbo-build$colon$tsc.log +14 -14
  3. package/.turbo/turbo-build.log +39 -38
  4. package/CHANGELOG.md +30 -0
  5. package/dist/api/delete/deleteRules.d.ts +2 -2
  6. package/dist/api/delete/deleteRules.d.ts.map +1 -1
  7. package/dist/api/delete/deleteRules.js.map +1 -1
  8. package/dist/api/read/findRuleIds.d.ts +1 -1
  9. package/dist/api/read/findRuleIds.d.ts.map +1 -1
  10. package/dist/api/read/findRuleIds.js.map +1 -1
  11. package/dist/cjs/redis/reader/redisRulesQuery.cjs +3 -2
  12. package/dist/cjs/redis/reader/redisRulesReader.cjs +101 -1
  13. package/dist/cjs/redis/reader/redisRulesSplitQuery.cjs +78 -0
  14. package/dist/cjs/redis/redisRuleIndex.cjs +3 -1
  15. package/dist/cjs/redis/redisRulesWriter.cjs +17 -6
  16. package/dist/cjs/rule.cjs +2 -0
  17. package/dist/cjs/ruleInput/policyInput.cjs +9 -1
  18. package/dist/redis/reader/redisRulesQuery.d.ts.map +1 -1
  19. package/dist/redis/reader/redisRulesQuery.js +4 -3
  20. package/dist/redis/reader/redisRulesQuery.js.map +1 -1
  21. package/dist/redis/reader/redisRulesReader.d.ts +1 -0
  22. package/dist/redis/reader/redisRulesReader.d.ts.map +1 -1
  23. package/dist/redis/reader/redisRulesReader.js +101 -1
  24. package/dist/redis/reader/redisRulesReader.js.map +1 -1
  25. package/dist/redis/reader/redisRulesSplitQuery.d.ts +8 -0
  26. package/dist/redis/reader/redisRulesSplitQuery.d.ts.map +1 -0
  27. package/dist/redis/reader/redisRulesSplitQuery.js +78 -0
  28. package/dist/redis/reader/redisRulesSplitQuery.js.map +1 -0
  29. package/dist/redis/redisRuleIndex.d.ts +1 -0
  30. package/dist/redis/redisRuleIndex.d.ts.map +1 -1
  31. package/dist/redis/redisRuleIndex.js +2 -0
  32. package/dist/redis/redisRuleIndex.js.map +1 -1
  33. package/dist/redis/redisRulesWriter.d.ts +1 -1
  34. package/dist/redis/redisRulesWriter.d.ts.map +1 -1
  35. package/dist/redis/redisRulesWriter.js +14 -3
  36. package/dist/redis/redisRulesWriter.js.map +1 -1
  37. package/dist/rule.d.ts +1 -0
  38. package/dist/rule.d.ts.map +1 -1
  39. package/dist/rule.js +3 -1
  40. package/dist/rule.js.map +1 -1
  41. package/dist/ruleInput/policyInput.d.ts +2 -2
  42. package/dist/ruleInput/policyInput.d.ts.map +1 -1
  43. package/dist/ruleInput/policyInput.js +10 -2
  44. package/dist/ruleInput/policyInput.js.map +1 -1
  45. package/dist/ruleInput/ruleInput.d.ts +6 -6
  46. package/dist/ruleInput/ruleInput.d.ts.map +1 -1
  47. package/dist/ruleInput/ruleInput.js.map +1 -1
  48. package/dist/tests/redis/reader/redisRulesSplitQuery.unit.test.d.ts +2 -0
  49. package/dist/tests/redis/reader/redisRulesSplitQuery.unit.test.d.ts.map +1 -0
  50. package/dist/tests/redis/reader/redisRulesSplitQuery.unit.test.js +79 -0
  51. package/dist/tests/redis/reader/redisRulesSplitQuery.unit.test.js.map +1 -0
  52. package/dist/tests/redis/redisRulesReaderLoad.benchmark.integration.test.d.ts +2 -0
  53. package/dist/tests/redis/redisRulesReaderLoad.benchmark.integration.test.d.ts.map +1 -0
  54. package/dist/tests/redis/redisRulesReaderLoad.benchmark.integration.test.js +288 -0
  55. package/dist/tests/redis/redisRulesReaderLoad.benchmark.integration.test.js.map +1 -0
  56. package/package.json +3 -3
  57. package/src/api/delete/deleteRules.ts +7 -1
  58. package/src/api/read/findRuleIds.ts +3 -1
  59. package/src/redis/reader/redisRulesQuery.ts +12 -2
  60. package/src/redis/reader/redisRulesReader.ts +145 -7
  61. package/src/redis/reader/redisRulesSplitQuery.ts +170 -0
  62. package/src/redis/redisRuleIndex.ts +6 -0
  63. package/src/redis/redisRulesWriter.ts +19 -5
  64. package/src/rule.ts +12 -0
  65. package/src/ruleInput/policyInput.ts +17 -3
  66. package/src/ruleInput/ruleInput.ts +7 -1
  67. package/src/tests/redis/reader/redisRulesSplitQuery.unit.test.ts +132 -0
  68. package/src/tests/redis/redisRulesReaderLoad.benchmark.integration.test.ts +516 -0
  69. package/tsconfig.tsbuildinfo +1 -1
@@ -1,5 +1,5 @@
1
1
 
2
- > @prosopo/user-access-policy@3.11.2 build:cjs
2
+ > @prosopo/user-access-policy@3.12.0 build:cjs
3
3
  > NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
4
4
 
5
5
  ViteCommonJSConfig: .
@@ -33,40 +33,41 @@ ViteCommonJSConfig: .
33
33
  Bundle build started
34
34
  transforming...
35
35
  Build end
36
- ✓ 34 modules transformed.
36
+ ✓ 35 modules transformed.
37
37
  rendering chunks...
38
- dist/cjs/api/delete/.export.cjs  0.01 kB
39
- dist/cjs/api/read/.export.cjs  0.01 kB
40
- dist/cjs/api/write/.export.cjs  0.01 kB
41
- dist/cjs/mongoose/.export.cjs  0.23 kB
42
- dist/cjs/rulesStorage.cjs  0.34 kB
43
- dist/cjs/rule.cjs  0.34 kB
44
- dist/cjs/redis/.export.cjs  0.36 kB
45
- dist/cjs/ruleInput/.export.cjs  0.46 kB
46
- dist/cjs/api/.export.cjs  0.57 kB
47
- dist/cjs/classifyOs.cjs  0.62 kB
48
- dist/cjs/api/delete/deleteAllRules.cjs  0.77 kB
49
- dist/cjs/ruleRecord.cjs  0.84 kB
50
- dist/cjs/api/read/getMissingIds.cjs  1.15 kB
51
- dist/cjs/api/read/fetchRules.cjs  1.22 kB
52
- dist/cjs/redis/redisRulesStorage.cjs  1.35 kB
53
- dist/cjs/.export.cjs  1.37 kB
54
- dist/cjs/api/delete/deleteRules.cjs  1.51 kB
55
- dist/cjs/mongoose/mongooseRuleSchema.cjs  1.52 kB
56
- dist/cjs/api/delete/deleteRuleGroups.cjs  1.57 kB
57
- dist/cjs/api/write/rehashRules.cjs  1.64 kB
58
- dist/cjs/api/read/findRuleIds.cjs  1.65 kB
59
- dist/cjs/ruleInput/policyInput.cjs  1.70 kB
60
- dist/cjs/redis/redisClient.cjs  2.09 kB
61
- dist/cjs/ruleInput/ruleInput.cjs  2.11 kB
62
- dist/cjs/api/rulesApiClient.cjs  2.60 kB
63
- dist/cjs/redis/reader/redisAggregate.cjs  2.61 kB
64
- dist/cjs/ruleInput/userScopeInput.cjs  2.67 kB
65
- dist/cjs/redis/redisRuleIndex.cjs  2.95 kB
66
- dist/cjs/transformRule.cjs  3.05 kB
67
- dist/cjs/api/write/insertRules.cjs  3.38 kB
68
- dist/cjs/redis/redisRulesWriter.cjs  3.44 kB
69
- dist/cjs/redis/reader/redisRulesQuery.cjs  4.47 kB
70
- dist/cjs/api/ruleApiRoutes.cjs  4.55 kB
71
- dist/cjs/redis/reader/redisRulesReader.cjs 11.59 kB
72
- ✓ built in 490ms
38
+ dist/cjs/api/delete/.export.cjs  0.01 kB
39
+ dist/cjs/api/read/.export.cjs  0.01 kB
40
+ dist/cjs/api/write/.export.cjs  0.01 kB
41
+ dist/cjs/mongoose/.export.cjs  0.23 kB
42
+ dist/cjs/rulesStorage.cjs  0.34 kB
43
+ dist/cjs/redis/.export.cjs  0.36 kB
44
+ dist/cjs/rule.cjs  0.46 kB
45
+ dist/cjs/ruleInput/.export.cjs  0.46 kB
46
+ dist/cjs/api/.export.cjs  0.57 kB
47
+ dist/cjs/classifyOs.cjs  0.62 kB
48
+ dist/cjs/api/delete/deleteAllRules.cjs  0.77 kB
49
+ dist/cjs/ruleRecord.cjs  0.84 kB
50
+ dist/cjs/api/read/getMissingIds.cjs  1.15 kB
51
+ dist/cjs/api/read/fetchRules.cjs  1.22 kB
52
+ dist/cjs/redis/redisRulesStorage.cjs  1.35 kB
53
+ dist/cjs/.export.cjs  1.37 kB
54
+ dist/cjs/api/delete/deleteRules.cjs  1.51 kB
55
+ dist/cjs/mongoose/mongooseRuleSchema.cjs  1.52 kB
56
+ dist/cjs/api/delete/deleteRuleGroups.cjs  1.57 kB
57
+ dist/cjs/api/write/rehashRules.cjs  1.64 kB
58
+ dist/cjs/api/read/findRuleIds.cjs  1.65 kB
59
+ dist/cjs/redis/redisClient.cjs  2.09 kB
60
+ dist/cjs/ruleInput/ruleInput.cjs  2.11 kB
61
+ dist/cjs/ruleInput/policyInput.cjs  2.14 kB
62
+ dist/cjs/redis/reader/redisRulesSplitQuery.cjs  2.43 kB
63
+ dist/cjs/api/rulesApiClient.cjs  2.60 kB
64
+ dist/cjs/redis/reader/redisAggregate.cjs  2.61 kB
65
+ dist/cjs/ruleInput/userScopeInput.cjs  2.67 kB
66
+ dist/cjs/transformRule.cjs  3.05 kB
67
+ dist/cjs/redis/redisRuleIndex.cjs  3.07 kB
68
+ dist/cjs/api/write/insertRules.cjs  3.38 kB
69
+ dist/cjs/redis/redisRulesWriter.cjs  3.68 kB
70
+ dist/cjs/api/ruleApiRoutes.cjs  4.55 kB
71
+ dist/cjs/redis/reader/redisRulesQuery.cjs  4.60 kB
72
+ dist/cjs/redis/reader/redisRulesReader.cjs 15.32 kB
73
+ ✓ built in 540ms
@@ -1,8 +1,8 @@
1
1
 
2
- > @prosopo/user-access-policy@3.11.2 build:tsc
2
+ > @prosopo/user-access-policy@3.12.0 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 9:21:58 AM - Projects in this build:
5
+ 11:41:51 AM - Projects in this build:
6
6
  * ../../dev/config/tsconfig.json
7
7
  * ../locale/tsconfig.json
8
8
  * ../util/tsconfig.json
@@ -15,27 +15,27 @@
15
15
  * ../api/tsconfig.json
16
16
  * tsconfig.json
17
17
 
18
- 9:21:58 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/vite-plugin-fix-absolute-imports.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
18
+ 11:41:52 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/NodejsPolarsNativeFilePlugin.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
19
19
 
20
- 9:21:58 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/useTranslation.ts' is older than output '../locale/tsconfig.tsbuildinfo'
20
+ 11:41:52 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/useTranslation.ts' is older than output '../locale/tsconfig.tsbuildinfo'
21
21
 
22
- 9:21:58 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
22
+ 11:41:52 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
23
23
 
24
- 9:21:58 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
24
+ 11:41:52 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
25
25
 
26
- 9:21:58 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
26
+ 11:41:52 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
27
27
 
28
- 9:21:58 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/provider/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
28
+ 11:41:52 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/provider/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
29
29
 
30
- 9:21:58 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
30
+ 11:41:52 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
31
31
 
32
- 9:21:58 AM - Project '../api-route/tsconfig.json' is up to date because newest input '../api-route/src/apiRoutes.ts' is older than output '../api-route/tsconfig.tsbuildinfo'
32
+ 11:41:52 AM - Project '../api-route/tsconfig.json' is up to date because newest input '../api-route/src/apiRoutes.ts' is older than output '../api-route/tsconfig.tsbuildinfo'
33
33
 
34
- 9:21:58 AM - Project '../redis-client/tsconfig.json' is up to date because newest input '../redis-client/src/index.ts' is older than output '../redis-client/tsconfig.tsbuildinfo'
34
+ 11:41:52 AM - Project '../redis-client/tsconfig.json' is up to date because newest input '../redis-client/src/index.ts' is older than output '../redis-client/tsconfig.tsbuildinfo'
35
35
 
36
- 9:21:58 AM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/index.ts' is older than output '../api/tsconfig.tsbuildinfo'
36
+ 11:41:52 AM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/index.ts' is older than output '../api/tsconfig.tsbuildinfo'
37
37
 
38
- 9:21:58 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
38
+ 11:41:52 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
39
39
 
40
- 9:21:58 AM - Building project '/home/runner/work/captcha/captcha/packages/user-access-policy/tsconfig.json'...
40
+ 11:41:52 AM - Building project '/home/runner/work/captcha/captcha/packages/user-access-policy/tsconfig.json'...
41
41
 
@@ -1,9 +1,9 @@
1
1
 
2
- > @prosopo/user-access-policy@3.11.2 build
2
+ > @prosopo/user-access-policy@3.12.0 build
3
3
  > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
4
 
5
5
 
6
- > @prosopo/user-access-policy@3.11.2 build:cross-env
6
+ > @prosopo/user-access-policy@3.12.0 build:cross-env
7
7
  > vite build --config vite.esm.config.ts --mode production
8
8
 
9
9
  ViteEsmConfig: .
@@ -37,40 +37,41 @@ ViteEsmConfig: .
37
37
  Bundle build started
38
38
  transforming...
39
39
  Build end
40
- ✓ 34 modules transformed.
40
+ ✓ 35 modules transformed.
41
41
  rendering chunks...
42
- dist/api/delete/.export.js  0.00 kB
43
- dist/api/read/.export.js  0.00 kB
44
- dist/api/write/.export.js  0.00 kB
45
- dist/mongoose/.export.js  0.11 kB
46
- dist/redis/.export.js  0.20 kB
47
- dist/rulesStorage.js  0.23 kB
48
- dist/rule.js  0.24 kB
49
- dist/ruleInput/.export.js  0.27 kB
50
- dist/api/.export.js  0.37 kB
51
- dist/classifyOs.js  0.51 kB
52
- dist/ruleRecord.js  0.62 kB
53
- dist/api/delete/deleteAllRules.js  0.67 kB
54
- dist/.export.js  0.93 kB
55
- dist/api/read/getMissingIds.js  1.01 kB
56
- dist/api/read/fetchRules.js  1.08 kB
57
- dist/redis/redisRulesStorage.js  1.21 kB
58
- dist/mongoose/mongooseRuleSchema.js  1.41 kB
59
- dist/api/delete/deleteRules.js  1.44 kB
60
- dist/api/delete/deleteRuleGroups.js  1.44 kB
61
- dist/ruleInput/policyInput.js  1.50 kB
62
- dist/api/read/findRuleIds.js  1.54 kB
63
- dist/api/write/rehashRules.js  1.54 kB
64
- dist/ruleInput/ruleInput.js  1.76 kB
65
- dist/redis/redisClient.js  1.90 kB
66
- dist/api/rulesApiClient.js  2.36 kB
67
- dist/ruleInput/userScopeInput.js  2.44 kB
68
- dist/redis/reader/redisAggregate.js  2.46 kB
69
- dist/redis/redisRuleIndex.js  2.49 kB
70
- dist/transformRule.js  2.76 kB
71
- dist/api/write/insertRules.js  3.22 kB
72
- dist/redis/redisRulesWriter.js  3.22 kB
73
- dist/redis/reader/redisRulesQuery.js  4.29 kB
74
- dist/api/ruleApiRoutes.js  4.33 kB
75
- dist/redis/reader/redisRulesReader.js 10.71 kB
76
- ✓ built in 440ms
42
+ dist/api/delete/.export.js  0.00 kB
43
+ dist/api/read/.export.js  0.00 kB
44
+ dist/api/write/.export.js  0.00 kB
45
+ dist/mongoose/.export.js  0.11 kB
46
+ dist/redis/.export.js  0.20 kB
47
+ dist/rulesStorage.js  0.23 kB
48
+ dist/ruleInput/.export.js  0.27 kB
49
+ dist/rule.js  0.32 kB
50
+ dist/api/.export.js  0.37 kB
51
+ dist/classifyOs.js  0.51 kB
52
+ dist/ruleRecord.js  0.62 kB
53
+ dist/api/delete/deleteAllRules.js  0.67 kB
54
+ dist/.export.js  0.93 kB
55
+ dist/api/read/getMissingIds.js  1.01 kB
56
+ dist/api/read/fetchRules.js  1.08 kB
57
+ dist/redis/redisRulesStorage.js  1.21 kB
58
+ dist/mongoose/mongooseRuleSchema.js  1.41 kB
59
+ dist/api/delete/deleteRules.js  1.44 kB
60
+ dist/api/delete/deleteRuleGroups.js  1.44 kB
61
+ dist/api/read/findRuleIds.js  1.54 kB
62
+ dist/api/write/rehashRules.js  1.54 kB
63
+ dist/ruleInput/ruleInput.js  1.76 kB
64
+ dist/redis/redisClient.js  1.90 kB
65
+ dist/ruleInput/policyInput.js  1.96 kB
66
+ dist/redis/reader/redisRulesSplitQuery.js  2.35 kB
67
+ dist/api/rulesApiClient.js  2.36 kB
68
+ dist/ruleInput/userScopeInput.js  2.44 kB
69
+ dist/redis/reader/redisAggregate.js  2.46 kB
70
+ dist/redis/redisRuleIndex.js  2.58 kB
71
+ dist/transformRule.js  2.76 kB
72
+ dist/api/write/insertRules.js  3.22 kB
73
+ dist/redis/redisRulesWriter.js  3.47 kB
74
+ dist/api/ruleApiRoutes.js  4.33 kB
75
+ dist/redis/reader/redisRulesQuery.js  4.45 kB
76
+ dist/redis/reader/redisRulesReader.js 14.33 kB
77
+ ✓ built in 569ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @prosopo/user-access-policy
2
2
 
3
+ ## 3.12.0
4
+ ### Minor Changes
5
+
6
+ - 7d7e767: perf(access-rules): split-query hot path + verdict cache with LRU and singleflight
7
+
8
+ Reworks the block-lookup Redis path so per-request latency is bounded by matching-rules-per-request rather than total rules in the tenant. Rule populations in the 10k+ range no longer degrade tail latency.
9
+
10
+ Key changes:
11
+
12
+ - **Split-query read path**: `redisRulesSplitQuery.ts` builds one FT sub-query per populated request field (numericIp exact + CIDR, ja4Hash, userId, headHash, coords, countryCode, asn), each hitting a discriminating posting-list index instead of a single wide FT.AGGREGATE that scaled linearly in total rule count.
13
+ - **`clientId="global"` sentinel**: writer stamps the sentinel on rules with no clientId so queries probe `@clientId:{X|global|ismissing}` instead of the expensive `ismissing()` set-difference walk. Transition-safe — legacy rules match via the ismissing branch until a rehash migrates them.
14
+ - **HardBlockVerdictCache**: bounded LRU + TTL (10 s / 50k entries) with real LRU move-to-tail on hit and **singleflight `getOrCompute`** dedupe of concurrent misses — the wave-1 stampede fix for the frontend retry-loop shape.
15
+ - **Request-scoped memo**: attached to `req` so blockMiddleware + downstream in-request checks share one Redis round-trip.
16
+
17
+ Measured impact under 100-concurrent × 10-wave retry storm against a 19.3k-rule population: wave-1 p99 drops from 23 ms → 3 ms, throughput jumps from 28.5k → 61.5k req/s per process, 50 identical concurrent misses collapse to 1 storage call.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [b3f351b]
22
+ - Updated dependencies [17bc76e]
23
+ - @prosopo/types@4.9.6
24
+ - @prosopo/api@3.5.13
25
+
26
+ ## 3.11.3
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [6cb3218]
30
+ - @prosopo/types@4.9.5
31
+ - @prosopo/api@3.5.12
32
+
3
33
  ## 3.11.2
4
34
  ### Patch Changes
5
35
 
@@ -1,9 +1,9 @@
1
1
  import { type ApiEndpoint, type ApiEndpointResponse } from "@prosopo/api-route";
2
2
  import type { Logger } from "@prosopo/logger";
3
- import { type ZodType } from "zod";
3
+ import { type ZodType, z } from "zod";
4
4
  import { type AccessRulesFilterInput } from "#policy/ruleInput/ruleInput.js";
5
5
  import type { AccessRulesStorage } from "#policy/rulesStorage.js";
6
- type DeleteRulesSchema = ZodType<AccessRulesFilterInput[]>;
6
+ type DeleteRulesSchema = ZodType<AccessRulesFilterInput[], z.ZodTypeDef, unknown>;
7
7
  export declare class DeleteRulesEndpoint implements ApiEndpoint<DeleteRulesSchema> {
8
8
  private readonly accessRulesStorage;
9
9
  private readonly logger;
@@ -1 +1 @@
1
- {"version":3,"file":"deleteRules.d.ts","sourceRoot":"","sources":["../../../src/api/delete/deleteRules.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAExB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAK,MAAM,KAAK,CAAC;AACtC,OAAO,EACN,KAAK,sBAAsB,EAG3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,KAAK,iBAAiB,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAE3D,qBAAa,mBAAoB,YAAW,WAAW,CAAC,iBAAiB,CAAC;IAExE,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM;IAGzB,oBAAoB,IAAI,iBAAiB;IAI1C,cAAc,CACnB,IAAI,EAAE,sBAAsB,EAAE,EAC9B,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC;CAoC/B"}
1
+ {"version":3,"file":"deleteRules.d.ts","sourceRoot":"","sources":["../../../src/api/delete/deleteRules.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAExB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EACN,KAAK,sBAAsB,EAG3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,KAAK,iBAAiB,GAAG,OAAO,CAC/B,sBAAsB,EAAE,EACxB,CAAC,CAAC,UAAU,EACZ,OAAO,CACP,CAAC;AAEF,qBAAa,mBAAoB,YAAW,WAAW,CAAC,iBAAiB,CAAC;IAExE,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM;IAGzB,oBAAoB,IAAI,iBAAiB;IAI1C,cAAc,CACnB,IAAI,EAAE,sBAAsB,EAAE,EAC9B,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC;CAoC/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"deleteRules.js","sourceRoot":"","sources":["../../../src/api/delete/deleteRules.ts"],"names":[],"mappings":"AAcA,OAAO,EAGN,yBAAyB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAEN,sBAAsB,EACtB,6BAA6B,GAC7B,MAAM,gCAAgC,CAAC;AAKxC,MAAM,OAAO,mBAAmB;IAC/B,YACkB,kBAAsC,EACtC,MAAc;QADd,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,WAAM,GAAN,MAAM,CAAQ;IAC7B,CAAC;IAEG,oBAAoB;QAC1B,OAAO,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,cAAc,CACnB,IAA8B,EAC9B,MAAe;QAEf,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QAClC,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,KAAK,MAAM,gBAAgB,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,WAAW,GAAG,6BAA6B,CAAC,gBAAgB,CAAC,CAAC;YAEpE,MAAM,0BAA0B,CAAC,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;gBAClE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;gBAGtE,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAE5C,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;oBAEzD,YAAY,IAAI,aAAa,CAAC,MAAM,CAAC;oBAErC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;wBACf,GAAG,EAAE,wBAAwB;wBAC7B,IAAI,EAAE;4BACL,gBAAgB;4BAChB,aAAa;yBACb;qBACD,CAAC,CAAC,CAAC;gBACL,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC;QAED,OAAO;YACN,MAAM,EAAE,yBAAyB,CAAC,OAAO;YACzC,IAAI,EAAE;gBACL,aAAa,EAAE,YAAY;aAC3B;SACD,CAAC;IACH,CAAC;CACD"}
1
+ {"version":3,"file":"deleteRules.js","sourceRoot":"","sources":["../../../src/api/delete/deleteRules.ts"],"names":[],"mappings":"AAcA,OAAO,EAGN,yBAAyB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAEN,sBAAsB,EACtB,6BAA6B,GAC7B,MAAM,gCAAgC,CAAC;AAWxC,MAAM,OAAO,mBAAmB;IAC/B,YACkB,kBAAsC,EACtC,MAAc;QADd,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,WAAM,GAAN,MAAM,CAAQ;IAC7B,CAAC;IAEG,oBAAoB;QAC1B,OAAO,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,cAAc,CACnB,IAA8B,EAC9B,MAAe;QAEf,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QAClC,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,KAAK,MAAM,gBAAgB,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,WAAW,GAAG,6BAA6B,CAAC,gBAAgB,CAAC,CAAC;YAEpE,MAAM,0BAA0B,CAAC,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;gBAClE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;gBAGtE,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAE5C,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;oBAEzD,YAAY,IAAI,aAAa,CAAC,MAAM,CAAC;oBAErC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;wBACf,GAAG,EAAE,wBAAwB;wBAC7B,IAAI,EAAE;4BACL,gBAAgB;4BAChB,aAAa;yBACb;qBACD,CAAC,CAAC,CAAC;gBACL,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC;QAED,OAAO;YACN,MAAM,EAAE,yBAAyB,CAAC,OAAO;YACzC,IAAI,EAAE;gBACL,aAAa,EAAE,YAAY;aAC3B;SACD,CAAC;IACH,CAAC;CACD"}
@@ -3,7 +3,7 @@ import type { Logger } from "@prosopo/logger";
3
3
  import { type ZodType, z } from "zod";
4
4
  import { type AccessRulesFilterInput } from "#policy/ruleInput/ruleInput.js";
5
5
  import type { AccessRulesStorage } from "#policy/rulesStorage.js";
6
- type FindRulesSchema = ZodType<AccessRulesFilterInput[]>;
6
+ type FindRulesSchema = ZodType<AccessRulesFilterInput[], z.ZodTypeDef, unknown>;
7
7
  export type RuleIdsResponse = {
8
8
  ruleIds: string[];
9
9
  };
@@ -1 +1 @@
1
- {"version":3,"file":"findRuleIds.d.ts","sourceRoot":"","sources":["../../../src/api/read/findRuleIds.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAExB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EACN,KAAK,sBAAsB,EAG3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,KAAK,eAAe,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;EAE4C,CAAC;AAEzE,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC3D,IAAI,CAAC,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,qBAAa,mBAAoB,YAAW,WAAW,CAAC,eAAe,CAAC;IAEtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM;IAGzB,oBAAoB,IAAI,eAAe;IAIxC,cAAc,CACnB,IAAI,EAAE,sBAAsB,EAAE,EAC9B,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,uBAAuB,CAAC;CAsCnC"}
1
+ {"version":3,"file":"findRuleIds.d.ts","sourceRoot":"","sources":["../../../src/api/read/findRuleIds.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAExB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EACN,KAAK,sBAAsB,EAG3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,KAAK,eAAe,GAAG,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;EAE4C,CAAC;AAEzE,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC3D,IAAI,CAAC,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,qBAAa,mBAAoB,YAAW,WAAW,CAAC,eAAe,CAAC;IAEtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM;IAGzB,oBAAoB,IAAI,eAAe;IAIxC,cAAc,CACnB,IAAI,EAAE,sBAAsB,EAAE,EAC9B,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,uBAAuB,CAAC;CAsCnC"}
@@ -1 +1 @@
1
- {"version":3,"file":"findRuleIds.js","sourceRoot":"","sources":["../../../src/api/read/findRuleIds.ts"],"names":[],"mappings":"AAcA,OAAO,EAGN,yBAAyB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAgB,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAE3E,OAAO,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAEN,sBAAsB,EACtB,6BAA6B,GAC7B,MAAM,gCAAgC,CAAC;AASxC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;CACQ,CAAoC,CAAC;AAMzE,MAAM,OAAO,mBAAmB;IAC/B,YACkB,kBAAsC,EACtC,MAAc;QADd,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,WAAM,GAAN,MAAM,CAAQ;IAC7B,CAAC;IAEG,oBAAoB;QAC1B,OAAO,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,cAAc,CACnB,IAA8B,EAC9B,MAAe;QAEf,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QAClC,MAAM,aAAa,GAAG,MAAM,0BAA0B,CACrD,IAAI,EACJ,KAAK,EAAE,gBAAgB,EAAE,EAAE;YAC1B,MAAM,WAAW,GAAG,6BAA6B,CAAC,gBAAgB,CAAC,CAAC;YAEpE,MAAM,OAAO,GAAG,MAAM,0BAA0B,CAC/C,WAAW,EACX,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,UAAU,CAAC,CAC/D,CAAC;YAEF,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC,CACD,CAAC;QAEF,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QAGrC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAE5C,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACf,GAAG,EAAE,sBAAsB;YAC3B,IAAI,EAAE;gBACL,eAAe,EAAE,OAAO,CAAC,MAAM;gBAC/B,gBAAgB,EAAE,aAAa,CAAC,MAAM;gBACtC,aAAa,EAAE,IAAI;gBACnB,QAAQ,EAAE,aAAa;aACvB;SACD,CAAC,CAAC,CAAC;QAEJ,OAAO;YACN,MAAM,EAAE,yBAAyB,CAAC,OAAO;YACzC,IAAI,EAAE;gBACL,OAAO,EAAE,aAAa;aACtB;SACD,CAAC;IACH,CAAC;CACD"}
1
+ {"version":3,"file":"findRuleIds.js","sourceRoot":"","sources":["../../../src/api/read/findRuleIds.ts"],"names":[],"mappings":"AAcA,OAAO,EAGN,yBAAyB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAgB,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAE3E,OAAO,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAEN,sBAAsB,EACtB,6BAA6B,GAC7B,MAAM,gCAAgC,CAAC;AAWxC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;CACQ,CAAoC,CAAC;AAMzE,MAAM,OAAO,mBAAmB;IAC/B,YACkB,kBAAsC,EACtC,MAAc;QADd,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,WAAM,GAAN,MAAM,CAAQ;IAC7B,CAAC;IAEG,oBAAoB;QAC1B,OAAO,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,cAAc,CACnB,IAA8B,EAC9B,MAAe;QAEf,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QAClC,MAAM,aAAa,GAAG,MAAM,0BAA0B,CACrD,IAAI,EACJ,KAAK,EAAE,gBAAgB,EAAE,EAAE;YAC1B,MAAM,WAAW,GAAG,6BAA6B,CAAC,gBAAgB,CAAC,CAAC;YAEpE,MAAM,OAAO,GAAG,MAAM,0BAA0B,CAC/C,WAAW,EACX,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,UAAU,CAAC,CAC/D,CAAC;YAEF,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC,CACD,CAAC;QAEF,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QAGrC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAE5C,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACf,GAAG,EAAE,sBAAsB;YAC3B,IAAI,EAAE;gBACL,eAAe,EAAE,OAAO,CAAC,MAAM;gBAC/B,gBAAgB,EAAE,aAAa,CAAC,MAAM;gBACtC,aAAa,EAAE,IAAI;gBACnB,QAAQ,EAAE,aAAa;aACvB;SACD,CAAC,CAAC,CAAC;QAEJ,OAAO;YACN,MAAM,EAAE,yBAAyB,CAAC,OAAO;YACzC,IAAI,EAAE;gBACL,OAAO,EAAE,aAAa;aACtB;SACD,CAAC;IACH,CAAC;CACD"}
@@ -87,12 +87,13 @@ const getUserScopeFieldQuery = (fieldName, fieldValue, scopeMatch, fullScope) =>
87
87
  const queryValue = FIELDS_REQUIRING_ESCAPE.has(fieldName) ? escapeTagValue(stringValue) : stringValue;
88
88
  return `@${fieldName}:{${queryValue}}`;
89
89
  };
90
+ const GLOBAL_MATCH_CLAUSE_INNER = `@clientId:{${rule.GLOBAL_CLIENT_SCOPE_SENTINEL}} | ismissing(@clientId)`;
90
91
  const getPolicyScopeQuery = (policyScope, scopeMatch) => {
91
92
  const clientId = policyScope?.clientId;
92
93
  if ("string" === typeof clientId) {
93
- return rulesStorage.FilterScopeMatch.Exact === scopeMatch ? `@clientId:{${clientId}}` : `( @clientId:{${clientId}} | ismissing(@clientId) )`;
94
+ return rulesStorage.FilterScopeMatch.Exact === scopeMatch ? `@clientId:{${clientId}}` : `( @clientId:{${clientId}} | ${GLOBAL_MATCH_CLAUSE_INNER} )`;
94
95
  }
95
- return rulesStorage.FilterScopeMatch.Exact === scopeMatch ? "ismissing(@clientId)" : "";
96
+ return rulesStorage.FilterScopeMatch.Exact === scopeMatch ? `( ${GLOBAL_MATCH_CLAUSE_INNER} )` : "";
96
97
  };
97
98
  const getRulesRedisQuery = (filter, matchingFieldsOnly) => {
98
99
  const { policyScope, userScope } = filter;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const util = require("node:util");
4
4
  const common = require("@prosopo/common");
5
5
  const redisRulesQuery = require("./redisRulesQuery.cjs");
6
+ const redisRulesSplitQuery = require("./redisRulesSplitQuery.cjs");
6
7
  const redisClient = require("../redisClient.cjs");
7
8
  const redisRuleIndex = require("../redisRuleIndex.cjs");
8
9
  const rule = require("../../rule.cjs");
@@ -27,6 +28,7 @@ function _interopNamespaceDefault(e) {
27
28
  const util__namespace = /* @__PURE__ */ _interopNamespaceDefault(util);
28
29
  const SERVER_SIDE_RANK_TOP_N = 20;
29
30
  const GREEDY_MAX_CANDIDATES = redisClient.REDIS_BATCH_SIZE * 10;
31
+ const SPLIT_MAX_CANDIDATES_PER_SUB = 500;
30
32
  const SPECIFICITY_EXPR = [
31
33
  "exists(@clientId)",
32
34
  "exists(@userId)",
@@ -94,14 +96,112 @@ class RedisRulesReader {
94
96
  }
95
97
  async findRules(filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) {
96
98
  const query = redisRulesQuery.getRulesRedisQuery(filter, matchingFieldsOnly);
97
- if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
99
+ if (skipEmptyUserScopes && // Sentinel query shape emitted by getPolicyScopeQuery when the
100
+ // caller passed neither a userScope nor a clientId — i.e. an
101
+ // empty filter that would otherwise resolve to "all global
102
+ // rules". Guard so an accidentally-empty filter can't return
103
+ // the full global rule set. The exact literal must stay in
104
+ // sync with GLOBAL_MATCH_CLAUSE_INNER over there.
105
+ query === "( @clientId:{global} | ismissing(@clientId) )") {
98
106
  return [];
99
107
  }
108
+ if (matchingFieldsOnly && filter.blockOnly && filter.userScope !== void 0) {
109
+ return this.findBlockRulesSplit(filter);
110
+ }
100
111
  if (matchingFieldsOnly) {
101
112
  return this.findRulesRanked(filter, query);
102
113
  }
103
114
  return this.findRulesGreedy(filter, query);
104
115
  }
116
+ // Union candidate keys from each split sub-query, de-dupe, HGETALL,
117
+ // parse, hand back to `getPrioritisedAccessRule` for JS-side ranking.
118
+ // Ranking stays in JS because rankCandidateRules already encodes the
119
+ // strict "every populated rule field matches request" semantics —
120
+ // FT can't express that shape cheaply and the previous server-side
121
+ // APPLY / SORTBY imposed the linear-per-rule cost we're eliminating.
122
+ async findBlockRulesSplit(filter) {
123
+ const userScope = filter.userScope;
124
+ if (userScope === void 0) {
125
+ return [];
126
+ }
127
+ const clientId = filter.policyScope?.clientId;
128
+ const subQueries = redisRulesSplitQuery.buildScopedBlockSubQueries(userScope, clientId);
129
+ try {
130
+ const keyLists = await Promise.all(
131
+ subQueries.map(
132
+ (sub) => redisAggregate.aggregateRedisKeys(
133
+ this.client,
134
+ sub.query,
135
+ this.logger,
136
+ void 0,
137
+ // Per-sub-query cap. Each probe hits a discriminating
138
+ // index (e.g. numericIp posting list), so the natural
139
+ // cardinality is tiny — this cap only fires on
140
+ // pathological rule-set shapes and mirrors
141
+ // SPLIT_MAX_CANDIDATES_PER_SUB below.
142
+ SPLIT_MAX_CANDIDATES_PER_SUB
143
+ ).catch((err) => {
144
+ this.logger.error(() => ({
145
+ err,
146
+ data: {
147
+ inspect: util__namespace.inspect(
148
+ { subKind: sub.kind, query: sub.query },
149
+ { depth: null }
150
+ )
151
+ },
152
+ msg: "failed to execute split block sub-query"
153
+ }));
154
+ return [];
155
+ })
156
+ )
157
+ );
158
+ const uniqueKeys = /* @__PURE__ */ new Set();
159
+ for (const list of keyLists) {
160
+ for (const key of list) {
161
+ uniqueKeys.add(key);
162
+ }
163
+ }
164
+ if (uniqueKeys.size === 0) {
165
+ return [];
166
+ }
167
+ const ruleKeys = [...uniqueKeys];
168
+ this.logger.debug(() => ({
169
+ msg: "Executed split block sub-queries",
170
+ data: {
171
+ inspect: util__namespace.inspect(
172
+ {
173
+ subQueryCount: subQueries.length,
174
+ uniqueCandidates: ruleKeys.length,
175
+ perSubQueryCounts: keyLists.map((l, i) => ({
176
+ kind: subQueries[i]?.kind,
177
+ found: l.length
178
+ })),
179
+ filter
180
+ },
181
+ { depth: null }
182
+ )
183
+ }
184
+ }));
185
+ const { records } = await redisClient.fetchRedisHashRecords(
186
+ this.client,
187
+ ruleKeys,
188
+ this.logger
189
+ );
190
+ const nonEmptyRecords = records.filter(
191
+ (record) => Object.keys(record).length > 0
192
+ );
193
+ return redisClient.parseRedisRecords(nonEmptyRecords, ruleInput.accessRuleInput, this.logger);
194
+ } catch (e) {
195
+ this.logger.error(() => ({
196
+ err: e,
197
+ data: {
198
+ inspect: util__namespace.inspect({ filter }, { depth: null })
199
+ },
200
+ msg: "failed to execute split block query set"
201
+ }));
202
+ return [];
203
+ }
204
+ }
105
205
  async findRulesRanked(filter, query) {
106
206
  try {
107
207
  const reply = await this.client.ft.aggregate(
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const rule = require("../../rule.cjs");
4
+ const escapeTagValue = (value) => value.replace(/([,.<>{}\[\]"':;!@#$%^&*()\-+=~|/\\])/g, "\\$1");
5
+ const FIELDS_REQUIRING_ESCAPE = /* @__PURE__ */ new Set([
6
+ "coords"
7
+ ]);
8
+ const NUMERIC_FIELDS = /* @__PURE__ */ new Set(["asn"]);
9
+ const SCALAR_USER_SCOPE_FIELDS = [
10
+ "userId",
11
+ "ja4Hash",
12
+ "headersHash",
13
+ "userAgentHash",
14
+ "headHash",
15
+ "coords",
16
+ "countryCode",
17
+ "asn"
18
+ ];
19
+ const ALL_USER_SCOPE_FIELDS = [
20
+ ...SCALAR_USER_SCOPE_FIELDS,
21
+ "numericIp",
22
+ "numericIpMaskMin",
23
+ "numericIpMaskMax"
24
+ ];
25
+ const GLOBAL_SCOPE_INNER = `@clientId:{${rule.GLOBAL_CLIENT_SCOPE_SENTINEL}} | ismissing(@clientId)`;
26
+ const buildScopeClause = (clientId) => {
27
+ if (clientId === void 0) {
28
+ return `( ${GLOBAL_SCOPE_INNER} )`;
29
+ }
30
+ return `( @clientId:{${clientId}} | ${GLOBAL_SCOPE_INNER} )`;
31
+ };
32
+ const buildFieldClause = (field, value) => {
33
+ if (value === void 0) {
34
+ return null;
35
+ }
36
+ const stringValue = String(value);
37
+ if (NUMERIC_FIELDS.has(field)) {
38
+ return `@${field}:[${stringValue} ${stringValue}]`;
39
+ }
40
+ const queryValue = FIELDS_REQUIRING_ESCAPE.has(field) ? escapeTagValue(stringValue) : stringValue;
41
+ return `@${field}:{${queryValue}}`;
42
+ };
43
+ const buildScopedBlockSubQueries = (userScope, clientId) => {
44
+ const typeClause = `@type:{${rule.AccessPolicyType.Block}}`;
45
+ const scopeClause = buildScopeClause(clientId);
46
+ const prefix = `${typeClause} ${scopeClause}`;
47
+ const subQueries = [];
48
+ for (const field of SCALAR_USER_SCOPE_FIELDS) {
49
+ const clause = buildFieldClause(field, userScope[field]);
50
+ if (clause === null) {
51
+ continue;
52
+ }
53
+ subQueries.push({
54
+ kind: `field:${field}`,
55
+ query: `${prefix} ${clause}`
56
+ });
57
+ }
58
+ const requestIp = userScope.numericIp;
59
+ if (requestIp !== void 0) {
60
+ subQueries.push({
61
+ kind: "ip:exact",
62
+ query: `${prefix} @numericIp:[${requestIp} ${requestIp}]`
63
+ });
64
+ subQueries.push({
65
+ kind: "ip:mask",
66
+ query: `${prefix} @numericIpMaskMin:[-inf ${requestIp}] @numericIpMaskMax:[${requestIp} +inf]`
67
+ });
68
+ }
69
+ const noScopeIsmissing = ALL_USER_SCOPE_FIELDS.map(
70
+ (field) => `ismissing(@${field})`
71
+ ).join(" ");
72
+ subQueries.push({
73
+ kind: "no-user-scope",
74
+ query: `${prefix} ${noScopeIsmissing}`
75
+ });
76
+ return subQueries;
77
+ };
78
+ exports.buildScopedBlockSubQueries = buildScopedBlockSubQueries;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const search = require("@redis/search");
4
4
  const transformRule = require("../transformRule.cjs");
5
+ const rule = require("../rule.cjs");
5
6
  const userIpRedisSchema = {
6
7
  numericIpMaskMin: { type: search.SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
7
8
  numericIpMaskMax: { type: search.SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
@@ -50,7 +51,8 @@ const accessRulesRedisIndex = {
50
51
  PREFIX: [ACCESS_RULE_REDIS_KEY_PREFIX]
51
52
  }
52
53
  };
53
- const getAccessRuleRedisKey = (rule) => ACCESS_RULE_REDIS_KEY_PREFIX + transformRule.makeAccessRuleHash(rule);
54
+ const getAccessRuleRedisKey = (rule2) => ACCESS_RULE_REDIS_KEY_PREFIX + transformRule.makeAccessRuleHash(rule2);
55
+ exports.GLOBAL_CLIENT_SCOPE_SENTINEL = rule.GLOBAL_CLIENT_SCOPE_SENTINEL;
54
56
  exports.ACCESS_RULES_REDIS_INDEX_NAME = ACCESS_RULES_REDIS_INDEX_NAME;
55
57
  exports.ACCESS_RULE_REDIS_KEY_PREFIX = ACCESS_RULE_REDIS_KEY_PREFIX;
56
58
  exports.accessRuleRedisSchema = accessRuleRedisSchema;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const common = require("@prosopo/common");
4
4
  const redisClient = require("./redisClient.cjs");
5
+ const rule = require("../rule.cjs");
5
6
  const redisRuleIndex = require("./redisRuleIndex.cjs");
6
7
  class RedisRulesWriter {
7
8
  constructor(client, logger) {
@@ -51,9 +52,9 @@ class RedisRulesWriter {
51
52
  async insertRuleEntries(ruleEntries) {
52
53
  const queries = this.client.multi();
53
54
  const ruleKeys = ruleEntries.map((ruleEntry) => {
54
- const { rule, expiresUnixTimestamp } = ruleEntry;
55
- const ruleKey = redisRuleIndex.getAccessRuleRedisKey(rule);
56
- const ruleValue = getRedisRuleValue(rule);
55
+ const { rule: rule2, expiresUnixTimestamp } = ruleEntry;
56
+ const ruleKey = redisRuleIndex.getAccessRuleRedisKey(rule2);
57
+ const ruleValue = getRedisRuleValue(rule2);
57
58
  queries.hSet(ruleKey, ruleValue);
58
59
  if (expiresUnixTimestamp) {
59
60
  const MILLISECOND_THRESHOLD = 1e10;
@@ -70,9 +71,19 @@ class RedisRulesWriter {
70
71
  return ruleKeys;
71
72
  }
72
73
  }
73
- const getRedisRuleValue = (rule) => Object.fromEntries(
74
- Object.entries(rule).map(([key, value]) => [key, String(value)])
75
- );
74
+ const getRedisRuleValue = (rule$1) => {
75
+ const record = {};
76
+ for (const [key, value] of Object.entries(rule$1)) {
77
+ if (value === void 0) {
78
+ continue;
79
+ }
80
+ record[key] = String(value);
81
+ }
82
+ if (record.clientId === void 0) {
83
+ record.clientId = rule.GLOBAL_CLIENT_SCOPE_SENTINEL;
84
+ }
85
+ return record;
86
+ };
76
87
  class DummyRedisRulesWriter {
77
88
  constructor(logger) {
78
89
  this.logger = logger;