@via-profit/ability 3.4.0 → 3.5.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.
package/dist/index.js CHANGED
@@ -22,75 +22,116 @@ class AbilityCompare extends AbilityCode {
22
22
  }
23
23
 
24
24
  class AbilityError extends Error {
25
- constructor(message) {
26
- super(message);
25
+ constructor(message, options) {
26
+ super(message, options);
27
+ this.name = 'AbilityError';
28
+ if (Error.captureStackTrace) {
29
+ Error.captureStackTrace(this, this.constructor);
30
+ }
27
31
  }
28
32
  }
29
33
  class AbilityParserError extends Error {
30
- constructor(message) {
31
- super(message);
34
+ constructor(message, options) {
35
+ super(message, options);
36
+ this.name = 'AbilityParserError';
37
+ if (Error.captureStackTrace) {
38
+ Error.captureStackTrace(this, this.constructor);
39
+ }
32
40
  }
33
41
  }
34
42
 
35
43
  class AbilityCondition extends AbilityCode {
36
- static equals = new AbilityCondition('=');
37
- static not_equals = new AbilityCondition('<>');
38
- static greater_than = new AbilityCondition('>');
39
- static less_than = new AbilityCondition('<');
40
- static less_or_equal = new AbilityCondition('<=');
41
- static greater_or_equal = new AbilityCondition('>=');
42
- static in = new AbilityCondition('in');
43
- static not_in = new AbilityCondition('not in');
44
- static contains = new AbilityCondition('contains');
45
- static not_contains = new AbilityCondition('not contains');
46
- static length_greater_than = new AbilityCondition('length greater than');
47
- static length_less_than = new AbilityCondition('length less than');
48
- static length_equals = new AbilityCondition('length equals');
49
- static always = new AbilityCondition('always');
50
- static never = new AbilityCondition('never');
44
+ static equals;
45
+ static not_equals;
46
+ static greater_than;
47
+ static less_than;
48
+ static less_or_equal;
49
+ static greater_or_equal;
50
+ static in;
51
+ static not_in;
52
+ static contains;
53
+ static not_contains;
54
+ static length_greater_than;
55
+ static length_less_than;
56
+ static length_equals;
57
+ static always;
58
+ static never;
59
+ static {
60
+ this.equals = new AbilityCondition('=');
61
+ this.not_equals = new AbilityCondition('<>');
62
+ this.greater_than = new AbilityCondition('>');
63
+ this.less_than = new AbilityCondition('<');
64
+ this.less_or_equal = new AbilityCondition('<=');
65
+ this.greater_or_equal = new AbilityCondition('>=');
66
+ this.in = new AbilityCondition('in');
67
+ this.not_in = new AbilityCondition('not in');
68
+ this.contains = new AbilityCondition('contains');
69
+ this.not_contains = new AbilityCondition('not contains');
70
+ this.length_greater_than = new AbilityCondition('length greater than');
71
+ this.length_less_than = new AbilityCondition('length less than');
72
+ this.length_equals = new AbilityCondition('length equals');
73
+ this.always = new AbilityCondition('always');
74
+ this.never = new AbilityCondition('never');
75
+ }
51
76
  static fromLiteral(literal) {
52
- switch (literal) {
53
- case 'equals':
54
- return this.equals;
55
- case 'not_equals':
56
- return this.not_equals;
57
- case 'greater_than':
58
- return this.greater_than;
59
- case 'less_than':
60
- return this.less_than;
61
- case 'less_or_equal':
62
- return this.less_or_equal;
63
- case 'greater_or_equal':
64
- return this.greater_or_equal;
65
- case 'contains':
66
- return this.contains;
67
- case 'no_contains':
68
- return this.not_contains;
77
+ const map = {
78
+ equals: this.equals,
79
+ not_equals: this.not_equals,
80
+ greater_than: this.greater_than,
81
+ less_than: this.less_than,
82
+ less_or_equal: this.less_or_equal,
83
+ greater_or_equal: this.greater_or_equal,
84
+ in: this.in,
85
+ not_in: this.not_in,
86
+ contains: this.contains,
87
+ not_contains: this.not_contains,
88
+ length_greater_than: this.length_greater_than,
89
+ length_equals: this.length_equals,
90
+ always: this.always,
91
+ never: this.never,
92
+ length_less_than: this.length_less_than,
93
+ };
94
+ const condition = map[literal];
95
+ if (!condition) {
96
+ throw new AbilityParserError(`Literal "${literal}" does not found in AbilityCondition class`);
97
+ }
98
+ return condition;
99
+ }
100
+ get literal() {
101
+ switch (this.code) {
102
+ case '=':
103
+ return 'equals';
104
+ case '<>':
105
+ return 'not_equals';
106
+ case '>':
107
+ return 'greater_than';
108
+ case '<':
109
+ return 'less_than';
110
+ case '>=':
111
+ return 'greater_or_equal';
112
+ case '<=':
113
+ return 'less_or_equal';
69
114
  case 'in':
70
- return this.in;
71
- case 'not_in':
72
- return this.not_in;
73
- case 'length_greater_than':
74
- return this.length_greater_than;
75
- case 'length_equals':
76
- return this.length_equals;
115
+ return 'in';
116
+ case 'not in':
117
+ return 'not_in';
118
+ case 'contains':
119
+ return 'contains';
120
+ case 'not contains':
121
+ return 'not_contains';
122
+ case 'length greater than':
123
+ return 'length_greater_than';
124
+ case 'length less than':
125
+ return 'length_less_than';
126
+ case 'length equals':
127
+ return 'length_equals';
77
128
  case 'always':
78
- return this.always;
129
+ return 'always';
79
130
  case 'never':
80
- return this.never;
131
+ return 'never';
81
132
  default:
82
- throw new AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
83
- }
84
- }
85
- get literal() {
86
- const literal = Object.keys(AbilityCondition).find(member => {
87
- const val = AbilityCondition[member];
88
- return val.code === this.code;
89
- });
90
- if (typeof literal === 'undefined') {
91
- throw new Error(`Literal value does not found in class AbilityCondition`);
133
+ throw new Error(`Unknown condition code: ${String(this.code)}`);
92
134
  }
93
- return literal;
94
135
  }
95
136
  }
96
137
 
@@ -126,8 +167,11 @@ class AbilityTypeGenerator {
126
167
  const subjectPath = rule.subject;
127
168
  const existingType = typeStructure[action][subjectPath];
128
169
  const ruleType = this.determineTypeFromRule(rule);
129
- // If a type already exists for this path, create a union
170
+ if (!ruleType) {
171
+ return;
172
+ }
130
173
  if (existingType && existingType !== ruleType) {
174
+ // If a type already exists for this path, create a union
131
175
  typeStructure[action][subjectPath] = `${existingType} | ${ruleType}`;
132
176
  }
133
177
  else {
@@ -146,6 +190,10 @@ class AbilityTypeGenerator {
146
190
  * @returns TypeScript type as string
147
191
  */
148
192
  determineTypeFromRule(rule) {
193
+ if (rule.condition.isEqual(AbilityCondition.never) ||
194
+ rule.condition.isEqual(AbilityCondition.always)) {
195
+ return null;
196
+ }
149
197
  // Numeric comparisons - always number
150
198
  if (rule.condition.isEqual(AbilityCondition.greater_than) ||
151
199
  rule.condition.isEqual(AbilityCondition.less_than) ||
@@ -172,8 +220,9 @@ class AbilityTypeGenerator {
172
220
  */
173
221
  getArrayType(resource) {
174
222
  if (Array.isArray(resource)) {
175
- if (resource.length === 0)
223
+ if (resource.length === 0) {
176
224
  return 'any[]';
225
+ }
177
226
  // Determine types of array elements
178
227
  const elementTypes = new Set(resource.map(item => this.getPrimitiveType(item)));
179
228
  const elementType = elementTypes.size === 1
@@ -191,10 +240,12 @@ class AbilityTypeGenerator {
191
240
  * @returns TypeScript primitive type as string
192
241
  */
193
242
  getPrimitiveType(value) {
194
- if (value === null)
243
+ if (value === null) {
195
244
  return 'null';
196
- if (value === undefined)
245
+ }
246
+ if (value === undefined) {
197
247
  return 'undefined';
248
+ }
198
249
  switch (typeof value) {
199
250
  case 'string':
200
251
  return 'string';
@@ -253,12 +304,20 @@ class AbilityTypeGenerator {
253
304
  let output = '// Automatically generated by via-profit/ability\n';
254
305
  output += '// Do not edit manually\n';
255
306
  output += 'export type Resources = {\n';
256
- // Sort actions for stable output
257
307
  const sortedActions = Object.keys(structure).sort();
258
308
  sortedActions.forEach(action => {
259
- output += ` ['${action}']: {\n`;
260
- output += this.formatNestedObject(structure[action], 4);
261
- output += ' };\n';
309
+ const actionObj = structure[action];
310
+ const isEmpty = Object.keys(actionObj).length === 0;
311
+ if (isEmpty) {
312
+ // Пустой объект → undefined
313
+ output += ` ['${action}']: undefined;\n`;
314
+ }
315
+ else {
316
+ // Непустой объект → как раньше
317
+ output += ` ['${action}']: {\n`;
318
+ output += this.formatNestedObject(actionObj, 4);
319
+ output += ' };\n';
320
+ }
262
321
  });
263
322
  output += '}\n';
264
323
  return output;
@@ -403,14 +462,14 @@ class AbilityPolicy {
403
462
  * @param resource - The resource to check
404
463
  * @param environment - The user environment object
405
464
  */
406
- async check(resource, environment) {
465
+ check(resource, environment) {
407
466
  this.matchState = AbilityMatch.mismatch;
408
467
  if (!this.ruleSet.length) {
409
468
  return this.matchState;
410
469
  }
411
470
  const rulesetCheckStates = [];
412
471
  for (const ruleSet of this.ruleSet) {
413
- const state = await ruleSet.check(resource, environment);
472
+ const state = ruleSet.check(resource, environment);
414
473
  rulesetCheckStates.push(state);
415
474
  if (AbilityCompare.and.isEqual(this.compareMethod) && AbilityMatch.mismatch.isEqual(state)) {
416
475
  return this.matchState; // mismatch
@@ -512,14 +571,11 @@ class AbilityResult {
512
571
 
513
572
  class AbilityResolver {
514
573
  policies;
515
- cache;
516
574
  constructor(
517
575
  /**
518
576
  * `Important!` The incorrect Resources type was intentionally passed to AbilityPolicy so that TypeScript could suggest the name of the permission and the structure of its resource in the parse method.
519
577
  */
520
- policyOrListOfPolicies, options) {
521
- const { cache } = options || {};
522
- this.cache = cache;
578
+ policyOrListOfPolicies) {
523
579
  this.policies = Array.isArray(policyOrListOfPolicies)
524
580
  ? policyOrListOfPolicies
525
581
  : [policyOrListOfPolicies];
@@ -531,30 +587,18 @@ class AbilityResolver {
531
587
  * @param resource - Resource
532
588
  * @param environment
533
589
  */
534
- async resolve(permission, resource, environment) {
590
+ resolve(permission, resource, environment) {
535
591
  const filteredPolicies = this.policies.filter(policy => AbilityResolver.isInPermissionContain(policy.permission, String(permission).replace(/^permission\./, '')));
536
592
  for (const policy of filteredPolicies) {
537
- const cacheKey = this.cache ? this.makeCacheKey(policy.id, resource, environment) : '';
538
- // cache
539
- if (this.cache) {
540
- const cached = await this.cache.get(cacheKey);
541
- if (cached !== undefined) {
542
- policy.matchState = cached;
543
- continue;
544
- }
545
- }
546
- const policyMatchState = await policy.check(resource, environment);
593
+ const policyMatchState = policy.check(resource, environment);
547
594
  if (policyMatchState === AbilityMatch.pending) {
548
595
  throw new AbilityError(`The policy "${policy.name}" is still in a pending state. Make sure to call "check" to evaluate the policy before resolving permissions.`);
549
596
  }
550
- if (this.cache) {
551
- await this.cache.set(cacheKey, policyMatchState);
552
- }
553
597
  }
554
598
  return new AbilityResult(filteredPolicies);
555
599
  }
556
- async enforce(permission, resource, environment) {
557
- const result = await this.resolve(permission, resource, environment);
600
+ enforce(permission, resource, environment) {
601
+ const result = this.resolve(permission, resource, environment);
558
602
  if (result.isDenied()) {
559
603
  const lastPolicy = result.getLastMatchedPolicy();
560
604
  if (lastPolicy) {
@@ -577,18 +621,6 @@ class AbilityResolver {
577
621
  return chunk === '*' || longer[i] === '*' || chunk === longer[i];
578
622
  });
579
623
  }
580
- makeCacheKey(policyId, resource, environment) {
581
- if (!this.cache) {
582
- return '';
583
- }
584
- return `policy:${policyId}:res:${this.cache.serialize(resource)}:env:${this.cache.serialize(environment)}`;
585
- }
586
- async invalidatePolicy(policyId) {
587
- await this.cache?.deleteByPrefix(policyId);
588
- }
589
- async invalidateCache() {
590
- await this.cache?.clear();
591
- }
592
624
  }
593
625
 
594
626
  /**
@@ -624,149 +656,122 @@ class AbilityRule {
624
656
  this.resource = resource;
625
657
  this.condition = condition;
626
658
  }
627
- /**
628
- * Check if the rule is matched
629
- * @param resource - The resource to check
630
- * @param environment
631
- */
632
- async check(resource, environment) {
633
- let is = false;
634
- const [subjectValue, resourceValue] = this.extractValues(resource, environment);
635
- const isValue = (v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
636
- // always
637
- if (AbilityCondition.always.isEqual(this.condition)) {
638
- is = true;
639
- }
640
- // equals
641
- if (AbilityCondition.equals.isEqual(this.condition)) {
642
- is = subjectValue === resourceValue;
643
- }
644
- // not equals
645
- if (AbilityCondition.not_equals.isEqual(this.condition)) {
646
- is = subjectValue !== resourceValue;
647
- }
648
- // less than
649
- if (AbilityCondition.less_than.isEqual(this.condition)) {
650
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
651
- is = subjectValue < resourceValue;
652
- }
653
- }
654
- // less or equal
655
- if (AbilityCondition.less_or_equal.isEqual(this.condition)) {
656
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
657
- is = subjectValue <= resourceValue;
658
- }
659
- }
660
- // more than
661
- if (AbilityCondition.greater_than.isEqual(this.condition)) {
662
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
663
- is = subjectValue > resourceValue;
664
- }
665
- }
666
- // more or equal
667
- if (AbilityCondition.greater_or_equal.isEqual(this.condition)) {
668
- if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
669
- is = subjectValue >= resourceValue;
670
- }
671
- }
672
- // in
673
- if (AbilityCondition.in.isEqual(this.condition)) {
674
- // value in array
675
- if (isValue(subjectValue) && Array.isArray(resourceValue)) {
676
- is = resourceValue.includes(subjectValue);
677
- }
678
- // array intersects array
679
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
680
- is = subjectValue.some(v => resourceValue.includes(v));
681
- }
682
- }
683
- // not in
684
- if (AbilityCondition.not_in.isEqual(this.condition)) {
685
- if (isValue(subjectValue) && Array.isArray(resourceValue)) {
686
- is = !resourceValue.includes(subjectValue);
687
- }
688
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
689
- is = !subjectValue.some(v => resourceValue.includes(v));
690
- }
691
- }
692
- // contains
693
- if (AbilityCondition.contains.isEqual(this.condition)) {
694
- // array contains value
695
- if (Array.isArray(subjectValue) && isValue(resourceValue)) {
696
- is = subjectValue.includes(resourceValue);
697
- }
698
- // array intersects array
699
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
700
- is = subjectValue.some(v => resourceValue.includes(v));
659
+ isPrimitive(v) {
660
+ return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
661
+ }
662
+ isNumber(v) {
663
+ return typeof v === 'number';
664
+ }
665
+ isString(v) {
666
+ return typeof v === 'string';
667
+ }
668
+ valueLen = (v) => this.isString(v) || Array.isArray(v) ? v.length : null;
669
+ operatorHandlers = {
670
+ [AbilityCondition.always.literal]: () => true,
671
+ [AbilityCondition.never.literal]: () => false,
672
+ [AbilityCondition.equals.literal]: (a, b) => a === b,
673
+ [AbilityCondition.not_equals.literal]: (a, b) => a !== b,
674
+ [AbilityCondition.contains.literal]: (a, b) => {
675
+ if (Array.isArray(a) && this.isPrimitive(b)) {
676
+ return a.includes(b);
701
677
  }
702
- }
703
- // not contains
704
- if (AbilityCondition.not_contains.isEqual(this.condition)) {
705
- if (Array.isArray(subjectValue) && isValue(resourceValue)) {
706
- is = !subjectValue.includes(resourceValue);
678
+ if (Array.isArray(a) && Array.isArray(b)) {
679
+ return a.some(v => b.includes(v));
707
680
  }
708
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
709
- is = !subjectValue.some(v => resourceValue.includes(v));
681
+ return false;
682
+ },
683
+ [AbilityCondition.not_contains.literal]: (a, b) => {
684
+ if (Array.isArray(a) && this.isPrimitive(b)) {
685
+ return !a.includes(b);
710
686
  }
711
- }
712
- // length equals
713
- if (AbilityCondition.length_equals.isEqual(this.condition)) {
714
- // foo.bar == n
715
- if (isValue(subjectValue) && typeof resourceValue === 'number') {
716
- is = String(subjectValue).length === resourceValue;
687
+ if (Array.isArray(a) && Array.isArray(b)) {
688
+ return !a.some(v => b.includes(v));
717
689
  }
718
- // ['foo', 'bar'] = n
719
- else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
720
- is = subjectValue.length === resourceValue;
690
+ return false;
691
+ },
692
+ [AbilityCondition.in.literal]: (a, b) => {
693
+ if (this.isPrimitive(a) && Array.isArray(b)) {
694
+ return b.includes(a);
721
695
  }
722
- // ['foo', 'bar'] = ['baz', 'taz']
723
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
724
- is = subjectValue.length === resourceValue.length;
696
+ if (Array.isArray(a) && Array.isArray(b)) {
697
+ return a.some(v => b.includes(v));
725
698
  }
726
- // 'foo' = 'bar'
727
- else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
728
- is = subjectValue.length === resourceValue.length;
699
+ return false;
700
+ },
701
+ [AbilityCondition.not_in.literal]: (a, b) => {
702
+ if (this.isPrimitive(a) && Array.isArray(b)) {
703
+ return !b.includes(a);
729
704
  }
730
- }
731
- // length greater than
732
- if (AbilityCondition.length_greater_than.isEqual(this.condition)) {
733
- // foo.bar > n
734
- if (isValue(subjectValue) && typeof resourceValue === 'number') {
735
- is = String(subjectValue).length > resourceValue;
705
+ if (Array.isArray(a) && Array.isArray(b)) {
706
+ return !a.some(v => b.includes(v));
736
707
  }
737
- // ['foo', 'bar'] > n
738
- else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
739
- is = subjectValue.length > resourceValue;
708
+ return false;
709
+ },
710
+ [AbilityCondition.greater_than.literal]: (a, b) => {
711
+ return this.isNumber(a) && this.isNumber(b) ? a > b : false;
712
+ },
713
+ [AbilityCondition.less_than.literal]: (a, b) => {
714
+ return this.isNumber(a) && this.isNumber(b) ? a < b : false;
715
+ },
716
+ [AbilityCondition.greater_or_equal.literal]: (a, b) => {
717
+ return this.isNumber(a) && this.isNumber(b) ? a >= b : false;
718
+ },
719
+ [AbilityCondition.less_or_equal.literal]: (a, b) => {
720
+ return this.isNumber(a) && this.isNumber(b) ? a <= b : false;
721
+ },
722
+ [AbilityCondition.length_greater_than.literal]: (a, b) => {
723
+ const alen = this.valueLen(a);
724
+ if (alen === null) {
725
+ return false;
726
+ }
727
+ if (this.isNumber(b)) {
728
+ return alen > b;
729
+ }
730
+ const bLen = this.valueLen(b);
731
+ if (bLen !== null) {
732
+ return alen > bLen;
740
733
  }
741
- // ['foo', 'bar'] > ['baz', 'taz']
742
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
743
- is = subjectValue.length > resourceValue.length;
734
+ return false;
735
+ },
736
+ [AbilityCondition.length_less_than.literal]: (a, b) => {
737
+ const alen = this.valueLen(a);
738
+ if (alen === null) {
739
+ return false;
744
740
  }
745
- // 'foo' > 'bar'
746
- else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
747
- is = subjectValue.length > resourceValue.length;
741
+ if (this.isNumber(b)) {
742
+ return alen < b;
748
743
  }
749
- }
750
- // length greater than
751
- if (AbilityCondition.length_less_than.isEqual(this.condition)) {
752
- // foo.bar < n
753
- if (isValue(subjectValue) && typeof resourceValue === 'number') {
754
- is = String(subjectValue).length < resourceValue;
744
+ const bLen = this.valueLen(b);
745
+ if (bLen !== null) {
746
+ return alen < bLen;
755
747
  }
756
- // ['foo', 'bar'] < n
757
- else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
758
- is = subjectValue.length < resourceValue;
748
+ return false;
749
+ },
750
+ [AbilityCondition.length_equals.literal]: (a, b) => {
751
+ const alen = this.valueLen(a);
752
+ if (alen === null) {
753
+ return false;
759
754
  }
760
- // ['foo', 'bar'] < ['baz', 'taz']
761
- else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
762
- is = subjectValue.length < resourceValue.length;
755
+ if (this.isNumber(b)) {
756
+ return alen === b;
763
757
  }
764
- // 'foo' < 'bar'
765
- else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
766
- is = subjectValue.length < resourceValue.length;
758
+ const bLen = this.valueLen(b);
759
+ if (bLen !== null) {
760
+ return alen === bLen;
767
761
  }
768
- }
769
- this.state = is ? AbilityMatch.match : AbilityMatch.mismatch;
762
+ return false;
763
+ },
764
+ };
765
+ /**
766
+ * Check if the rule is matched
767
+ * @param resource - The resource to check
768
+ * @param environment
769
+ */
770
+ check(resource, environment) {
771
+ const [subjectValue, resourceValue] = this.extractValues(resource, environment);
772
+ const handler = this.operatorHandlers[this.condition.literal];
773
+ const result = handler(subjectValue, resourceValue);
774
+ this.state = result ? AbilityMatch.match : AbilityMatch.mismatch;
770
775
  return this.state;
771
776
  }
772
777
  /**
@@ -965,14 +970,14 @@ class AbilityRuleSet {
965
970
  rules.forEach(rule => this.addRule(rule));
966
971
  return this;
967
972
  }
968
- async check(resources, environment) {
973
+ check(resources, environment) {
969
974
  this.state = AbilityMatch.mismatch;
970
975
  if (!this.rules.length) {
971
976
  return this.state;
972
977
  }
973
978
  const ruleCheckStates = [];
974
979
  for (const rule of this.rules) {
975
- const state = await rule.check(resources, environment);
980
+ const state = rule.check(resources, environment);
976
981
  ruleCheckStates.push(state);
977
982
  if (AbilityCompare.and.isEqual(this.compareMethod) && AbilityMatch.mismatch.isEqual(state)) {
978
983
  return this.state; // mismatch
@@ -1021,79 +1026,6 @@ class AbilityRuleSet {
1021
1026
  }
1022
1027
  }
1023
1028
 
1024
- class AbilityInMemoryCache {
1025
- store = new Map();
1026
- async get(key) {
1027
- const entry = this.store.get(key);
1028
- if (!entry)
1029
- return undefined;
1030
- if (Date.now() > entry.expires) {
1031
- this.store.delete(key);
1032
- return undefined;
1033
- }
1034
- return entry.value;
1035
- }
1036
- async set(key, value, ttlSeconds = 60) {
1037
- this.store.set(key, {
1038
- value,
1039
- expires: Date.now() + ttlSeconds * 1000,
1040
- });
1041
- }
1042
- serialize(input) {
1043
- return this.fastHash(this.stableStringify(input));
1044
- }
1045
- async delete(key) {
1046
- this.store.delete(key);
1047
- }
1048
- async clear() {
1049
- this.store.clear();
1050
- }
1051
- async deleteByPrefix(prefix) {
1052
- for (const key of this.store.keys()) {
1053
- if (key.startsWith(prefix)) {
1054
- this.store.delete(key);
1055
- }
1056
- }
1057
- }
1058
- fastHash(str) {
1059
- let hash = 5381;
1060
- for (let i = 0; i < str.length; i++) {
1061
- hash = (hash * 33) ^ str.charCodeAt(i);
1062
- }
1063
- return (hash >>> 0).toString(36);
1064
- }
1065
- stableStringify(obj) {
1066
- if (obj === null)
1067
- return 'null';
1068
- const type = typeof obj;
1069
- if (type === 'string')
1070
- return JSON.stringify(obj);
1071
- if (type === 'number' || type === 'boolean')
1072
- return String(obj);
1073
- if (type === 'undefined')
1074
- return 'undefined';
1075
- if (Array.isArray(obj)) {
1076
- let out = '[';
1077
- for (let i = 0; i < obj.length; i++) {
1078
- if (i > 0)
1079
- out += ',';
1080
- out += this.stableStringify(obj[i]);
1081
- }
1082
- return out + ']';
1083
- }
1084
- const keys = Object.keys(obj);
1085
- keys.sort();
1086
- let out = '{';
1087
- for (let i = 0; i < keys.length; i++) {
1088
- const k = keys[i];
1089
- if (i > 0)
1090
- out += ',';
1091
- out += k + ':' + this.stableStringify(obj[k]);
1092
- }
1093
- return out + '}';
1094
- }
1095
- }
1096
-
1097
1029
  class AbilityJSONParser {
1098
1030
  /**
1099
1031
  * Parses an array of policy configurations into an array of AbilityPolicy instances.
@@ -2171,9 +2103,7 @@ class AbilityDSLParser {
2171
2103
  return best;
2172
2104
  }
2173
2105
  levenshteinDistance(a, b) {
2174
- const matrix = Array(b.length + 1)
2175
- .fill(null)
2176
- .map(() => Array(a.length + 1).fill(null));
2106
+ const matrix = Array.from({ length: b.length + 1 }, () => Array.from({ length: a.length + 1 }, () => 0));
2177
2107
  for (let i = 0; i <= a.length; i++)
2178
2108
  matrix[0][i] = i;
2179
2109
  for (let j = 0; j <= b.length; j++)
@@ -2248,7 +2178,6 @@ exports.AbilityExplain = AbilityExplain;
2248
2178
  exports.AbilityExplainPolicy = AbilityExplainPolicy;
2249
2179
  exports.AbilityExplainRule = AbilityExplainRule;
2250
2180
  exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
2251
- exports.AbilityInMemoryCache = AbilityInMemoryCache;
2252
2181
  exports.AbilityJSONParser = AbilityJSONParser;
2253
2182
  exports.AbilityMatch = AbilityMatch;
2254
2183
  exports.AbilityParserError = AbilityParserError;