@via-profit/ability 2.1.0 → 3.1.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 (36) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/CONTRIBUTING.md +14 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1058 -319
  5. package/SECURITY.md +33 -0
  6. package/dist/cache/AbilityCacheAdapter.d.ts +8 -0
  7. package/dist/cache/AbilityInMemoryCache.d.ts +12 -0
  8. package/dist/core/AbilityCondition.d.ts +21 -0
  9. package/dist/core/AbilityExplain.d.ts +27 -0
  10. package/dist/core/AbilityParser.d.ts +61 -0
  11. package/dist/core/AbilityPolicy.d.ts +84 -0
  12. package/dist/core/AbilityResolver.d.ts +35 -0
  13. package/dist/core/AbilityResult.d.ts +27 -0
  14. package/dist/core/AbilityRule.d.ts +77 -0
  15. package/dist/{AbilityRuleSet.d.ts → core/AbilityRuleSet.d.ts} +14 -11
  16. package/dist/index.d.ts +19 -11
  17. package/dist/index.js +2097 -303
  18. package/dist/parsers/dsl/AbilityDSLLexer.d.ts +24 -0
  19. package/dist/parsers/dsl/AbilityDSLParser.d.ts +86 -0
  20. package/dist/parsers/dsl/AbilityDSLSyntaxError.d.ts +13 -0
  21. package/dist/parsers/dsl/AbilityDSLToken.d.ts +55 -0
  22. package/dist/parsers/json/AbilityJSONParser.d.ts +22 -0
  23. package/package.json +15 -4
  24. package/assets/ability-01.drawio.png +0 -0
  25. package/build/playground.js +0 -456
  26. package/build/playground.js.map +0 -1
  27. package/dist/AbilityCondition.d.ts +0 -16
  28. package/dist/AbilityParser.d.ts +0 -18
  29. package/dist/AbilityPolicy.d.ts +0 -65
  30. package/dist/AbilityResolver.d.ts +0 -30
  31. package/dist/AbilityRule.d.ts +0 -70
  32. /package/dist/{AbilityCode.d.ts → core/AbilityCode.d.ts} +0 -0
  33. /package/dist/{AbilityCompare.d.ts → core/AbilityCompare.d.ts} +0 -0
  34. /package/dist/{AbilityError.d.ts → core/AbilityError.d.ts} +0 -0
  35. /package/dist/{AbilityMatch.d.ts → core/AbilityMatch.d.ts} +0 -0
  36. /package/dist/{AbilityPolicyEffect.d.ts → core/AbilityPolicyEffect.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -2,7 +2,99 @@
2
2
  /******/ "use strict";
3
3
  /******/ var __webpack_modules__ = ({
4
4
 
5
- /***/ 19:
5
+ /***/ 6:
6
+ /***/ ((__unused_webpack_module, exports) => {
7
+
8
+
9
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10
+
11
+
12
+ /***/ }),
13
+
14
+ /***/ 829:
15
+ /***/ ((__unused_webpack_module, exports) => {
16
+
17
+
18
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
19
+ exports.AbilityInMemoryCache = void 0;
20
+ class AbilityInMemoryCache {
21
+ store = new Map();
22
+ async get(key) {
23
+ const entry = this.store.get(key);
24
+ if (!entry)
25
+ return undefined;
26
+ if (Date.now() > entry.expires) {
27
+ this.store.delete(key);
28
+ return undefined;
29
+ }
30
+ return entry.value;
31
+ }
32
+ async set(key, value, ttlSeconds = 60) {
33
+ this.store.set(key, {
34
+ value,
35
+ expires: Date.now() + ttlSeconds * 1000,
36
+ });
37
+ }
38
+ serialize(input) {
39
+ return this.fastHash(this.stableStringify(input));
40
+ }
41
+ async delete(key) {
42
+ this.store.delete(key);
43
+ }
44
+ async clear() {
45
+ this.store.clear();
46
+ }
47
+ async deleteByPrefix(prefix) {
48
+ for (const key of this.store.keys()) {
49
+ if (key.startsWith(prefix)) {
50
+ this.store.delete(key);
51
+ }
52
+ }
53
+ }
54
+ fastHash(str) {
55
+ let hash = 5381;
56
+ for (let i = 0; i < str.length; i++) {
57
+ hash = (hash * 33) ^ str.charCodeAt(i);
58
+ }
59
+ return (hash >>> 0).toString(36);
60
+ }
61
+ stableStringify(obj) {
62
+ if (obj === null)
63
+ return 'null';
64
+ const type = typeof obj;
65
+ if (type === 'string')
66
+ return JSON.stringify(obj);
67
+ if (type === 'number' || type === 'boolean')
68
+ return String(obj);
69
+ if (type === 'undefined')
70
+ return 'undefined';
71
+ if (Array.isArray(obj)) {
72
+ let out = '[';
73
+ for (let i = 0; i < obj.length; i++) {
74
+ if (i > 0)
75
+ out += ',';
76
+ out += this.stableStringify(obj[i]);
77
+ }
78
+ return out + ']';
79
+ }
80
+ const keys = Object.keys(obj);
81
+ keys.sort();
82
+ let out = '{';
83
+ for (let i = 0; i < keys.length; i++) {
84
+ const k = keys[i];
85
+ if (i > 0)
86
+ out += ',';
87
+ out += k + ':' + this.stableStringify(obj[k]);
88
+ }
89
+ return out + '}';
90
+ }
91
+ }
92
+ exports.AbilityInMemoryCache = AbilityInMemoryCache;
93
+
94
+
95
+ /***/ }),
96
+
97
+ /***/ 301:
6
98
  /***/ ((__unused_webpack_module, exports) => {
7
99
 
8
100
 
@@ -29,7 +121,7 @@ exports["default"] = AbilityCode;
29
121
 
30
122
  /***/ }),
31
123
 
32
- /***/ 923:
124
+ /***/ 413:
33
125
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
34
126
 
35
127
 
@@ -38,7 +130,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
130
  };
39
131
  Object.defineProperty(exports, "__esModule", ({ value: true }));
40
132
  exports.AbilityCompare = void 0;
41
- const AbilityCode_1 = __importDefault(__webpack_require__(19));
133
+ const AbilityCode_1 = __importDefault(__webpack_require__(301));
42
134
  class AbilityCompare extends AbilityCode_1.default {
43
135
  static and = new AbilityCompare('and');
44
136
  static or = new AbilityCompare('or');
@@ -49,7 +141,7 @@ exports["default"] = AbilityCompare;
49
141
 
50
142
  /***/ }),
51
143
 
52
- /***/ 261:
144
+ /***/ 167:
53
145
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
54
146
 
55
147
 
@@ -58,23 +150,51 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
58
150
  };
59
151
  Object.defineProperty(exports, "__esModule", ({ value: true }));
60
152
  exports.AbilityCondition = void 0;
61
- const AbilityCode_1 = __importDefault(__webpack_require__(19));
62
- const AbilityError_1 = __webpack_require__(122);
153
+ const AbilityCode_1 = __importDefault(__webpack_require__(301));
154
+ const AbilityError_1 = __webpack_require__(216);
63
155
  class AbilityCondition extends AbilityCode_1.default {
64
- static equal = new AbilityCondition('=');
65
- static not_equal = new AbilityCondition('<>');
66
- static more_than = new AbilityCondition('>');
156
+ static equals = new AbilityCondition('=');
157
+ static not_equals = new AbilityCondition('<>');
158
+ static greater_than = new AbilityCondition('>');
67
159
  static less_than = new AbilityCondition('<');
68
160
  static less_or_equal = new AbilityCondition('<=');
69
- static more_or_equal = new AbilityCondition('>=');
161
+ static greater_or_equal = new AbilityCondition('>=');
70
162
  static in = new AbilityCondition('in');
71
163
  static not_in = new AbilityCondition('not in');
164
+ static contains = new AbilityCondition('contains');
165
+ static not_contains = new AbilityCondition('not contains');
166
+ static length_greater_than = new AbilityCondition('length greater than');
167
+ static length_less_than = new AbilityCondition('length less than');
168
+ static length_equals = new AbilityCondition('length equals');
72
169
  static fromLiteral(literal) {
73
- const code = AbilityCondition[literal]?.code || null;
74
- if (code === null) {
75
- throw new AbilityError_1.AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
170
+ switch (literal) {
171
+ case 'equals':
172
+ return this.equals;
173
+ case 'not_equals':
174
+ return this.not_equals;
175
+ case 'greater_than':
176
+ return this.greater_than;
177
+ case 'less_than':
178
+ return this.less_than;
179
+ case 'less_or_equal':
180
+ return this.less_or_equal;
181
+ case 'greater_or_equal':
182
+ return this.greater_or_equal;
183
+ case 'contains':
184
+ return this.contains;
185
+ case 'no_contains':
186
+ return this.not_contains;
187
+ case 'in':
188
+ return this.in;
189
+ case 'not_in':
190
+ return this.not_in;
191
+ case 'length_greater_than':
192
+ return this.length_greater_than;
193
+ case 'length_equals':
194
+ return this.length_equals;
195
+ default:
196
+ throw new AbilityError_1.AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
76
197
  }
77
- return new AbilityCondition(code);
78
198
  }
79
199
  get literal() {
80
200
  const literal = Object.keys(AbilityCondition).find(member => {
@@ -93,7 +213,7 @@ exports["default"] = AbilityCondition;
93
213
 
94
214
  /***/ }),
95
215
 
96
- /***/ 122:
216
+ /***/ 216:
97
217
  /***/ ((__unused_webpack_module, exports) => {
98
218
 
99
219
 
@@ -115,7 +235,75 @@ exports.AbilityParserError = AbilityParserError;
115
235
 
116
236
  /***/ }),
117
237
 
118
- /***/ 909:
238
+ /***/ 221:
239
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
240
+
241
+
242
+ var __importDefault = (this && this.__importDefault) || function (mod) {
243
+ return (mod && mod.__esModule) ? mod : { "default": mod };
244
+ };
245
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
246
+ exports.AbilityExplainPolicy = exports.AbilityExplainRuleSet = exports.AbilityExplainRule = exports.AbilityExplain = void 0;
247
+ const AbilityMatch_1 = __importDefault(__webpack_require__(247));
248
+ class AbilityExplain {
249
+ type;
250
+ children;
251
+ name;
252
+ match;
253
+ constructor(config, children = []) {
254
+ this.type = config.type;
255
+ this.children = children;
256
+ this.name = config.name;
257
+ this.match = config.match;
258
+ }
259
+ toString(indent = 0) {
260
+ const pad = ' '.repeat(indent);
261
+ const mark = this.match.code === AbilityMatch_1.default.match.code ? '✓' : '✗';
262
+ let out = `${pad}${mark} ${this.type} «${this.name}» is ${this.match.code}`;
263
+ this.children.forEach(child => {
264
+ out += '\n' + child.toString(indent + 1);
265
+ });
266
+ return out;
267
+ }
268
+ }
269
+ exports.AbilityExplain = AbilityExplain;
270
+ class AbilityExplainRule extends AbilityExplain {
271
+ constructor(rule) {
272
+ super({
273
+ type: 'rule',
274
+ match: rule.state,
275
+ name: rule.name,
276
+ });
277
+ }
278
+ }
279
+ exports.AbilityExplainRule = AbilityExplainRule;
280
+ class AbilityExplainRuleSet extends AbilityExplain {
281
+ constructor(ruleSet) {
282
+ const children = ruleSet.rules.map(rule => new AbilityExplainRule(rule));
283
+ super({
284
+ type: 'ruleSet',
285
+ match: ruleSet.state,
286
+ name: ruleSet.name,
287
+ }, children);
288
+ }
289
+ }
290
+ exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
291
+ class AbilityExplainPolicy extends AbilityExplain {
292
+ constructor(policy) {
293
+ const children = policy.ruleSet.map(ruleSet => new AbilityExplainRuleSet(ruleSet));
294
+ super({
295
+ type: 'policy',
296
+ name: policy.name,
297
+ match: policy.matchState,
298
+ }, children);
299
+ }
300
+ }
301
+ exports.AbilityExplainPolicy = AbilityExplainPolicy;
302
+
303
+
304
+ /***/ }),
305
+
306
+ /***/ 247:
119
307
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
120
308
 
121
309
 
@@ -124,7 +312,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
124
312
  };
125
313
  Object.defineProperty(exports, "__esModule", ({ value: true }));
126
314
  exports.AbilityMatch = void 0;
127
- const AbilityCode_1 = __importDefault(__webpack_require__(19));
315
+ const AbilityCode_1 = __importDefault(__webpack_require__(301));
128
316
  class AbilityMatch extends AbilityCode_1.default {
129
317
  static pending = new AbilityMatch('pending');
130
318
  static match = new AbilityMatch('match');
@@ -136,7 +324,7 @@ exports["default"] = AbilityMatch;
136
324
 
137
325
  /***/ }),
138
326
 
139
- /***/ 189:
327
+ /***/ 147:
140
328
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
141
329
 
142
330
 
@@ -145,23 +333,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
145
333
  };
146
334
  Object.defineProperty(exports, "__esModule", ({ value: true }));
147
335
  exports.AbilityParser = void 0;
148
- const AbilityError_1 = __webpack_require__(122);
149
- const AbilityCondition_1 = __importDefault(__webpack_require__(261));
336
+ const AbilityError_1 = __webpack_require__(216);
337
+ const AbilityCondition_1 = __importDefault(__webpack_require__(167));
150
338
  class AbilityParser {
151
- /*
152
- *
153
- * readonly ['order.update']: {
154
- * readonly user: {
155
- * readonly roles: readonly string[];
156
- * readonly department: string;
157
- * };
158
- * readonly order: {
159
- * readonly estimatedArrivalAt: number;
160
- * readonly status: string;
161
- * }
162
- * }
163
- *
164
- * */
165
339
  /**
166
340
  * Sets a value in a nested object structure based on a dot/bracket notation path.
167
341
  * @param object - The target object to modify.
@@ -169,52 +343,223 @@ class AbilityParser {
169
343
  * @param value - The value to set at the specified path.
170
344
  */
171
345
  static setValueDotValue(object, path, value) {
172
- const way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.');
346
+ if (!path || path.trim().length === 0) {
347
+ throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
348
+ }
349
+ const way = path.replace(/\[/g, '.').replace(/]/g, '').split('.');
173
350
  const last = way.pop();
174
351
  if (!last) {
175
352
  throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
176
353
  }
177
- way.reduce((o, k, i, kk) => {
178
- if (!o[k]) {
179
- o[k] = isFinite(Number(kk[i + 1])) ? [] : {};
354
+ const lastObj = way.reduce((acc, key, index, array) => {
355
+ const currentValue = acc[key];
356
+ const nextKey = array[index + 1];
357
+ const shouldBeArray = nextKey !== undefined && isFinite(Number(nextKey));
358
+ if (currentValue === undefined || currentValue === null) {
359
+ // Create missing property
360
+ const newValue = shouldBeArray ? [] : {};
361
+ acc[key] = newValue;
362
+ return newValue;
363
+ }
364
+ if (typeof currentValue !== 'object') {
365
+ throw new AbilityError_1.AbilityParserError(`Cannot set property '${key}' on non-object value at path: ${path}`);
180
366
  }
181
- return o[k];
182
- }, object)[last] = value;
367
+ return currentValue;
368
+ }, object);
369
+ const existingValue = lastObj[last];
370
+ if (existingValue !== undefined &&
371
+ typeof existingValue === 'object' &&
372
+ existingValue !== null &&
373
+ !Array.isArray(existingValue)) {
374
+ throw new AbilityError_1.AbilityParserError(`Cannot set primitive value on existing object at path: ${path}`);
375
+ }
376
+ lastObj[last] = value;
183
377
  }
184
378
  /**
185
379
  * Generates TypeScript type definitions based on the provided policies.
186
380
  * @param policies - An array of AbilityPolicy instances.
187
- * @param outPath - The output path for the generated type definitions.
188
- * @returns A record containing the generated type definitions.
381
+ * @returns A generated type definitions.
189
382
  */
190
- static generateTypeDefs(policies, outPath) {
191
- const record = {};
383
+ static generateTypeDefs(policies) {
384
+ // Structure to store types: { [action]: { [subjectPath]: type } }
385
+ const typeStructure = {};
386
+ // Iterate through all policies
192
387
  policies.forEach(policy => {
388
+ const action = policy.permission;
389
+ // Initialize object for action if it doesn't exist
390
+ if (!typeStructure[action]) {
391
+ typeStructure[action] = {};
392
+ }
393
+ // Iterate through all ruleSets in the policy
193
394
  policy.ruleSet.forEach(ruleSet => {
395
+ // Iterate through all rules in the ruleSet
194
396
  ruleSet.rules.forEach(rule => {
195
- let value = 'any';
196
- switch (true) {
197
- case rule.condition.isEqual(AbilityCondition_1.default.not_equal):
198
- case rule.condition.isEqual(AbilityCondition_1.default.equal):
199
- value = typeof rule.resource;
200
- break;
201
- case rule.condition.isEqual(AbilityCondition_1.default.in):
202
- case rule.condition.isEqual(AbilityCondition_1.default.not_in):
203
- value = `${typeof rule.resource}[]`;
204
- break;
205
- case rule.condition.isEqual(AbilityCondition_1.default.more_or_equal):
206
- case rule.condition.isEqual(AbilityCondition_1.default.more_than):
207
- case rule.condition.isEqual(AbilityCondition_1.default.less_or_equal):
208
- case rule.condition.isEqual(AbilityCondition_1.default.less_than):
209
- value = 'number';
210
- break;
397
+ const subjectPath = rule.subject;
398
+ const existingType = typeStructure[action][subjectPath];
399
+ const ruleType = this.determineTypeFromRule(rule);
400
+ // If a type already exists for this path, create a union
401
+ if (existingType && existingType !== ruleType) {
402
+ typeStructure[action][subjectPath] = `${existingType} | ${ruleType}`;
403
+ }
404
+ else {
405
+ typeStructure[action][subjectPath] = ruleType;
211
406
  }
212
- AbilityParser.setValueDotValue(record, rule.subject, value);
213
407
  });
214
408
  });
215
409
  });
216
- console.log(JSON.stringify(record));
217
- return record;
410
+ // Transform flat structure into nested structure for easier use
411
+ const nestedStructure = this.buildNestedStructure(typeStructure);
412
+ return this.formatTypeDefinitions(nestedStructure);
413
+ }
414
+ /**
415
+ * Determines TypeScript type based on the rule
416
+ * @param rule - The rule to analyze
417
+ * @returns TypeScript type as string
418
+ */
419
+ static determineTypeFromRule(rule) {
420
+ // Numeric comparisons - always number
421
+ if (rule.condition.isEqual(AbilityCondition_1.default.greater_than) ||
422
+ rule.condition.isEqual(AbilityCondition_1.default.less_than) ||
423
+ rule.condition.isEqual(AbilityCondition_1.default.greater_or_equal) ||
424
+ rule.condition.isEqual(AbilityCondition_1.default.less_or_equal)) {
425
+ return 'number';
426
+ }
427
+ // Array operations
428
+ if (rule.condition.isEqual(AbilityCondition_1.default.in) ||
429
+ rule.condition.isEqual(AbilityCondition_1.default.not_in)) {
430
+ return this.getArrayType(rule.resource);
431
+ }
432
+ // Equality/Inequality operations
433
+ if (rule.condition.isEqual(AbilityCondition_1.default.equals) ||
434
+ rule.condition.isEqual(AbilityCondition_1.default.not_equals)) {
435
+ return this.getPrimitiveType(rule.resource);
436
+ }
437
+ return 'any';
438
+ }
439
+ /**
440
+ * Gets TypeScript type for array values
441
+ * @param resource - The resource value to analyze
442
+ * @returns TypeScript array type as string
443
+ */
444
+ static getArrayType(resource) {
445
+ if (Array.isArray(resource)) {
446
+ if (resource.length === 0)
447
+ return 'any[]';
448
+ // Determine types of array elements
449
+ const elementTypes = new Set(resource.map(item => this.getPrimitiveType(item)));
450
+ const elementType = elementTypes.size === 1
451
+ ? Array.from(elementTypes)[0]
452
+ : `(${Array.from(elementTypes).join(' | ')})`;
453
+ return `${elementType}[]`;
454
+ }
455
+ // If resource is not an array but condition is in/not_in,
456
+ // it expects an array of such elements
457
+ return `${this.getPrimitiveType(resource)}[]`;
458
+ }
459
+ /**
460
+ * Gets primitive TypeScript type for a value
461
+ * @param value - The value to analyze
462
+ * @returns TypeScript primitive type as string
463
+ */
464
+ static getPrimitiveType(value) {
465
+ if (value === null)
466
+ return 'null';
467
+ if (value === undefined)
468
+ return 'undefined';
469
+ switch (typeof value) {
470
+ case 'string':
471
+ return 'string';
472
+ case 'number':
473
+ return 'number';
474
+ case 'boolean':
475
+ return 'boolean';
476
+ case 'object':
477
+ if (Array.isArray(value)) {
478
+ return 'array'; // special marker, handled separately
479
+ }
480
+ return 'object';
481
+ default:
482
+ return 'any';
483
+ }
484
+ }
485
+ /**
486
+ * Builds nested structure from flat paths
487
+ * Example: 'user.profile.name' -> { user: { profile: { name: 'string' } } }
488
+ * @param flatStructure - Flat structure with dot notation paths
489
+ * @returns Nested object structure
490
+ */
491
+ static buildNestedStructure(flatStructure) {
492
+ const result = {};
493
+ Object.entries(flatStructure).forEach(([action, paths]) => {
494
+ result[action] = {};
495
+ Object.entries(paths).forEach(([path, type]) => {
496
+ const parts = path.split('.');
497
+ let current = result[action];
498
+ // Iterate through all parts except the last one
499
+ for (let i = 0; i < parts.length - 1; i++) {
500
+ const part = parts[i];
501
+ const currentValue = current[part];
502
+ if (!currentValue || typeof currentValue !== 'object') {
503
+ const newObj = {};
504
+ current[part] = newObj;
505
+ current = newObj;
506
+ }
507
+ else {
508
+ current = currentValue;
509
+ }
510
+ }
511
+ // Set type for the last part
512
+ const lastPart = parts[parts.length - 1];
513
+ current[lastPart] = type;
514
+ });
515
+ });
516
+ return result;
517
+ }
518
+ /**
519
+ * Formats type structure into a string
520
+ * @param structure - Nested type structure
521
+ * @returns Formatted TypeScript type definition string
522
+ */
523
+ static formatTypeDefinitions(structure) {
524
+ let output = '// Automatically generated by via-profit/ability\n';
525
+ output += '// Do not edit manually\n';
526
+ output += '\n/* eslint-disable */\n\n';
527
+ output += 'export type Resources = {\n';
528
+ // Sort actions for stable output
529
+ const sortedActions = Object.keys(structure).sort();
530
+ sortedActions.forEach(action => {
531
+ output += ` ['${action}']: {\n`;
532
+ output += this.formatNestedObject(structure[action], 4);
533
+ output += ' };\n';
534
+ });
535
+ output += '}\n';
536
+ return output;
537
+ }
538
+ /**
539
+ * Recursively formats nested object
540
+ * @param obj - Object to format
541
+ * @param indent - Current indentation level
542
+ * @returns Formatted string
543
+ */
544
+ static formatNestedObject(obj, indent) {
545
+ const spaces = ' '.repeat(indent);
546
+ let output = '';
547
+ // Sort keys for stable output
548
+ const sortedKeys = Object.keys(obj).sort();
549
+ sortedKeys.forEach(key => {
550
+ const value = obj[key];
551
+ if (typeof value === 'object' && value !== null) {
552
+ // Nested object
553
+ output += `${spaces}readonly ${key}: {\n`;
554
+ output += this.formatNestedObject(value, indent + 2);
555
+ output += `${spaces}};\n`;
556
+ }
557
+ else {
558
+ // Primitive type
559
+ output += `${spaces}readonly ${key}: ${value};\n`;
560
+ }
561
+ });
562
+ return output;
218
563
  }
219
564
  }
220
565
  exports.AbilityParser = AbilityParser;
@@ -223,7 +568,7 @@ exports["default"] = AbilityParser;
223
568
 
224
569
  /***/ }),
225
570
 
226
- /***/ 844:
571
+ /***/ 278:
227
572
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
228
573
 
229
574
 
@@ -232,10 +577,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
232
577
  };
233
578
  Object.defineProperty(exports, "__esModule", ({ value: true }));
234
579
  exports.AbilityPolicy = void 0;
235
- const AbilityRuleSet_1 = __importDefault(__webpack_require__(402));
236
- const AbilityMatch_1 = __importDefault(__webpack_require__(909));
237
- const AbilityCompare_1 = __importDefault(__webpack_require__(923));
238
- const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(277));
580
+ const AbilityMatch_1 = __importDefault(__webpack_require__(247));
581
+ const AbilityCompare_1 = __importDefault(__webpack_require__(413));
582
+ const AbilityExplain_1 = __webpack_require__(221);
583
+ const AbilityError_1 = __webpack_require__(216);
584
+ const AbilityJSONParser_1 = __webpack_require__(909);
585
+ const AbilityDSLParser_1 = __webpack_require__(577);
239
586
  class AbilityPolicy {
240
587
  matchState = AbilityMatch_1.default.pending;
241
588
  /**
@@ -262,15 +609,17 @@ class AbilityPolicy {
262
609
  */
263
610
  id;
264
611
  /**
265
- * Soon
612
+ * Running the `enforce` or `resolve` method
613
+ * will select only those from all passed policies that fall under the specified permission key.
266
614
  */
267
- action;
615
+ permission;
268
616
  constructor(params) {
269
- const { name, id, action, effect } = params;
617
+ const { name, id, permission, effect, compareMethod = AbilityCompare_1.default.and } = params;
270
618
  this.name = name;
271
619
  this.id = id;
272
- this.action = action;
620
+ this.permission = permission;
273
621
  this.effect = effect;
622
+ this.compareMethod = compareMethod;
274
623
  }
275
624
  /**
276
625
  * Add rule set to the policy
@@ -280,57 +629,78 @@ class AbilityPolicy {
280
629
  this.ruleSet.push(ruleSet);
281
630
  return this;
282
631
  }
632
+ /**
633
+ * Add rule set to the policy
634
+ * @param ruleSets - The array of rule set to add
635
+ */
636
+ addRuleSets(ruleSets) {
637
+ for (const ruleSet of ruleSets) {
638
+ this.ruleSet.push(ruleSet);
639
+ }
640
+ return this;
641
+ }
283
642
  /**
284
643
  * Check if the policy is matched
285
- * @param resources - The resource to check
644
+ * @param resource - The resource to check
645
+ * @param environment - The user environment object
286
646
  */
287
- check(resources) {
647
+ async check(resource, environment) {
288
648
  this.matchState = AbilityMatch_1.default.mismatch;
289
649
  if (!this.ruleSet.length) {
290
650
  return this.matchState;
291
651
  }
292
- const rulesetCheckStates = this.ruleSet.reduce((collect, ruleSet) => {
293
- return collect.concat(ruleSet.check(resources));
294
- }, []);
652
+ const rulesetCheckStates = [];
653
+ for (const ruleSet of this.ruleSet) {
654
+ const state = await ruleSet.check(resource, environment);
655
+ rulesetCheckStates.push(state);
656
+ if (AbilityCompare_1.default.and.isEqual(this.compareMethod) && AbilityMatch_1.default.mismatch.isEqual(state)) {
657
+ return this.matchState; // mismatch
658
+ }
659
+ if (AbilityCompare_1.default.or.isEqual(this.compareMethod) && AbilityMatch_1.default.match.isEqual(state)) {
660
+ this.matchState = AbilityMatch_1.default.match;
661
+ return this.matchState;
662
+ }
663
+ }
295
664
  if (AbilityCompare_1.default.and.isEqual(this.compareMethod)) {
296
- if (rulesetCheckStates.every(ruleState => AbilityMatch_1.default.match.isEqual(ruleState))) {
665
+ if (rulesetCheckStates.every(s => AbilityMatch_1.default.match.isEqual(s))) {
297
666
  this.matchState = AbilityMatch_1.default.match;
298
667
  }
299
668
  }
300
669
  if (AbilityCompare_1.default.or.isEqual(this.compareMethod)) {
301
- if (rulesetCheckStates.some(ruleState => AbilityMatch_1.default.match.isEqual(ruleState))) {
670
+ if (rulesetCheckStates.some(s => AbilityMatch_1.default.match.isEqual(s))) {
302
671
  this.matchState = AbilityMatch_1.default.match;
303
672
  }
304
673
  }
305
674
  return this.matchState;
306
675
  }
676
+ explain() {
677
+ if (this.matchState === AbilityMatch_1.default.pending) {
678
+ throw new AbilityError_1.AbilityError('First, run the check method, then explain');
679
+ }
680
+ return new AbilityExplain_1.AbilityExplainPolicy(this);
681
+ }
682
+ /**
683
+ * Parses an array of policy configurations into an array of AbilityPolicy instances.
684
+ * @param configs - Array of policy configurations
685
+ * @returns Array of AbilityPolicy instances
686
+ */
687
+ static fromJSONAll(configs) {
688
+ return AbilityJSONParser_1.AbilityJSONParser.parse(configs);
689
+ }
307
690
  /**
308
691
  * Parse the config JSON format to Policy class instance
309
692
  */
310
- static parse(config) {
311
- const { id, name, ruleSet, compareMethod, action, effect } = config;
312
- // Create the empty policy
313
- const policy = new AbilityPolicy({
314
- name,
315
- id,
316
- action,
317
- effect: new AbilityPolicyEffect_1.default(effect),
318
- });
319
- policy.compareMethod = new AbilityCompare_1.default(compareMethod);
320
- ruleSet.forEach(ruleSetConfig => {
321
- policy.addRuleSet(AbilityRuleSet_1.default.parse(ruleSetConfig));
322
- });
323
- return policy;
693
+ static fromJSON(config) {
694
+ return AbilityJSONParser_1.AbilityJSONParser.parsePolicy(config);
324
695
  }
325
- export() {
326
- return {
327
- id: this.id.toString(),
328
- name: this.name.toString(),
329
- compareMethod: this.compareMethod.code.toString(),
330
- ruleSet: this.ruleSet.map(rule => rule.export()),
331
- action: this.action,
332
- effect: this.effect.code,
333
- };
696
+ static fromDSL(dsl) {
697
+ return new AbilityDSLParser_1.AbilityDSLParser(dsl).parse()[0];
698
+ }
699
+ toJSON() {
700
+ return AbilityJSONParser_1.AbilityJSONParser.toJSON([this])[0];
701
+ }
702
+ toString() {
703
+ return JSON.stringify(this.toJSON());
334
704
  }
335
705
  }
336
706
  exports.AbilityPolicy = AbilityPolicy;
@@ -339,7 +709,7 @@ exports["default"] = AbilityPolicy;
339
709
 
340
710
  /***/ }),
341
711
 
342
- /***/ 277:
712
+ /***/ 179:
343
713
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
344
714
 
345
715
 
@@ -348,7 +718,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
348
718
  };
349
719
  Object.defineProperty(exports, "__esModule", ({ value: true }));
350
720
  exports.AbilityPolicyEffect = void 0;
351
- const AbilityCode_1 = __importDefault(__webpack_require__(19));
721
+ const AbilityCode_1 = __importDefault(__webpack_require__(301));
352
722
  class AbilityPolicyEffect extends AbilityCode_1.default {
353
723
  static deny = new AbilityPolicyEffect('deny');
354
724
  static permit = new AbilityPolicyEffect('permit');
@@ -359,7 +729,7 @@ exports["default"] = AbilityPolicyEffect;
359
729
 
360
730
  /***/ }),
361
731
 
362
- /***/ 668:
732
+ /***/ 634:
363
733
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
364
734
 
365
735
 
@@ -368,90 +738,159 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
368
738
  };
369
739
  Object.defineProperty(exports, "__esModule", ({ value: true }));
370
740
  exports.AbilityResolver = void 0;
371
- const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(277));
372
- const AbilityMatch_1 = __importDefault(__webpack_require__(909));
373
- const AbilityError_1 = __webpack_require__(122);
741
+ const AbilityError_1 = __webpack_require__(216);
742
+ const AbilityResult_1 = __webpack_require__(941);
743
+ const AbilityMatch_1 = __importDefault(__webpack_require__(247));
374
744
  class AbilityResolver {
375
745
  policies;
376
- constructor(policyOrListOfPolicies) {
746
+ cache;
747
+ constructor(
748
+ /**
749
+ * `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.
750
+ */
751
+ policyOrListOfPolicies, options) {
752
+ const { cache } = options || {};
753
+ this.cache = cache;
377
754
  this.policies = Array.isArray(policyOrListOfPolicies)
378
755
  ? policyOrListOfPolicies
379
756
  : [policyOrListOfPolicies];
380
757
  }
381
758
  /**
382
- * Resolve policy for the resource and action
759
+ * Resolve policy for the resource and permission key
383
760
  *
384
- @param action - Action
761
+ * @param permission - Permission key
385
762
  * @param resource - Resource
763
+ * @param environment
764
+ */
765
+ async resolve(permission, resource, environment) {
766
+ const filteredPolicies = this.policies.filter(policy => AbilityResolver.isInPermissionContain(policy.permission, String(permission).replace(/^permission\./, '')));
767
+ for (const policy of filteredPolicies) {
768
+ const cacheKey = this.cache ? this.makeCacheKey(policy.id, resource, environment) : '';
769
+ // cache
770
+ if (this.cache) {
771
+ const cached = await this.cache.get(cacheKey);
772
+ if (cached !== undefined) {
773
+ policy.matchState = cached;
774
+ continue;
775
+ }
776
+ }
777
+ const policyMatchState = await policy.check(resource, environment);
778
+ if (policyMatchState === AbilityMatch_1.default.pending) {
779
+ throw new AbilityError_1.AbilityError(`The policy "${policy.name}" is still in a pending state. Make sure to call "check" to evaluate the policy before resolving permissions.`);
780
+ }
781
+ if (this.cache) {
782
+ await this.cache.set(cacheKey, policyMatchState);
783
+ }
784
+ }
785
+ return new AbilityResult_1.AbilityResult(filteredPolicies);
786
+ }
787
+ async enforce(permission, resource, environment) {
788
+ const result = await this.resolve(permission, resource, environment);
789
+ if (result.isDenied()) {
790
+ const policyName = result.getLastMatchedPolicy()?.name?.toString() || 'unknown';
791
+ throw new AbilityError_1.AbilityError(`Permission denied by policy "${policyName}"`);
792
+ }
793
+ }
794
+ /**
795
+ * Check if the permission key is contained in another permission key
796
+ * @param permissionA - The first permission to check
797
+ * @param permissionB - The second permission to check
386
798
  */
387
- resolve(action, resource) {
388
- const filteredPolicies = this.policies.filter(policy => {
389
- return AbilityResolver.isInActionContain(policy.action, String(action));
799
+ static isInPermissionContain(permissionA, permissionB) {
800
+ const A = permissionA.split('.');
801
+ const B = permissionB.split('.');
802
+ const [longer, shorter] = A.length >= B.length ? [A, B] : [B, A];
803
+ return shorter.every((chunk, i) => {
804
+ return chunk === '*' || longer[i] === '*' || chunk === longer[i];
390
805
  });
391
- filteredPolicies.map(policy => policy.check(resource));
392
- this.policies = filteredPolicies;
393
- return this;
394
806
  }
395
- enforce(action, resource) {
396
- const resolver = this.resolve(action, resource);
397
- if (resolver.isDeny()) {
398
- throw new AbilityError_1.AbilityError(resolver.getMatchedPolicy()?.name?.toString() || 'Unknown permission error');
807
+ makeCacheKey(policyId, resource, environment) {
808
+ if (!this.cache) {
809
+ return '';
399
810
  }
811
+ return `policy:${policyId}:res:${this.cache.serialize(resource)}:env:${this.cache.serialize(environment)}`;
812
+ }
813
+ async invalidatePolicy(policyId) {
814
+ await this.cache?.deleteByPrefix(policyId);
815
+ }
816
+ async invalidateCache() {
817
+ await this.cache?.clear();
400
818
  }
819
+ }
820
+ exports.AbilityResolver = AbilityResolver;
821
+ exports["default"] = AbilityResolver;
822
+
823
+
824
+ /***/ }),
825
+
826
+ /***/ 941:
827
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
828
+
829
+
830
+ var __importDefault = (this && this.__importDefault) || function (mod) {
831
+ return (mod && mod.__esModule) ? mod : { "default": mod };
832
+ };
833
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
834
+ exports.AbilityResult = void 0;
835
+ const AbilityExplain_1 = __webpack_require__(221);
836
+ const AbilityMatch_1 = __importDefault(__webpack_require__(247));
837
+ const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(179));
838
+ class AbilityResult {
401
839
  /**
402
- * Get the last effect of the policy
840
+ * Already checked policies (after call the policy.check())
841
+ */
842
+ policies;
843
+ constructor(policies) {
844
+ this.policies = policies;
845
+ }
846
+ /**
847
+ * Returns a list of explanations for each policy involved in the ability evaluation.
848
+ * Each item describes how a specific policy contributed to the final permission result.
403
849
  *
404
- * @returns {AbilityPolicyEffect | null}
850
+ * Useful for debugging, logging, or building UI tools that visualize permission logic.
405
851
  */
406
- getEffect() {
407
- const effects = this.policies.reduce((collect, policy, _index) => {
408
- if (policy.matchState.isEqual(AbilityMatch_1.default.match)) {
409
- return collect.concat(policy.effect);
852
+ explain() {
853
+ return this.policies.map(policy => {
854
+ return new AbilityExplain_1.AbilityExplainPolicy(policy);
855
+ });
856
+ }
857
+ getLastMatchedPolicy() {
858
+ for (let i = this.policies.length - 1; i >= 0; i--) {
859
+ if (this.policies[i].matchState.isEqual(AbilityMatch_1.default.match)) {
860
+ return this.policies[i];
410
861
  }
411
- return collect;
412
- }, []);
413
- if (effects.length) {
414
- return effects[effects.length - 1];
415
862
  }
416
863
  return null;
417
864
  }
418
- isPermit() {
419
- const effect = this.getEffect();
420
- return effect !== null && effect.isEqual(AbilityPolicyEffect_1.default.permit);
865
+ isAllowed() {
866
+ const effect = this.getLastEffectOfMatchedPolicy();
867
+ return effect?.isEqual(AbilityPolicyEffect_1.default.permit) ?? false;
421
868
  }
422
- isDeny() {
423
- const effect = this.getEffect();
424
- return effect !== null && effect.isEqual(AbilityPolicyEffect_1.default.deny);
425
- }
426
- getMatchedPolicy() {
427
- const matchedPolicies = this.policies.filter(policy => policy.matchState.isEqual(AbilityMatch_1.default.match));
428
- const lastPolicy = matchedPolicies.length ? matchedPolicies[matchedPolicies.length - 1] : null;
429
- return lastPolicy || null;
869
+ isDenied() {
870
+ const effect = this.getLastEffectOfMatchedPolicy();
871
+ return effect?.isEqual(AbilityPolicyEffect_1.default.deny) ?? true;
430
872
  }
431
873
  /**
432
- * Check if the action is contained in another action
433
- * @param actionA - The first action to check
434
- * @param actionB - The second action to check
874
+ * Get the last effect of the policy
875
+ *
876
+ * @returns {AbilityPolicyEffect | null}
435
877
  */
436
- static isInActionContain(actionA, actionB) {
437
- const actionAArray = String(actionA).split('.');
438
- const actionBArray = String(actionB).split('.');
439
- const a = actionAArray.length >= actionBArray.length ? actionAArray : actionBArray;
440
- const b = actionBArray.length <= actionAArray.length ? actionBArray : actionAArray;
441
- const c = a.reduce((acc, chunk, index) => {
442
- const iterationRes = chunk === b[index] || b[index] === '*' || chunk === '*';
443
- return acc.concat(iterationRes);
444
- }, []);
445
- return c.every(Boolean);
878
+ getLastEffectOfMatchedPolicy() {
879
+ for (let i = this.policies.length - 1; i >= 0; i--) {
880
+ const p = this.policies[i];
881
+ if (p.matchState.isEqual(AbilityMatch_1.default.match)) {
882
+ return p.effect;
883
+ }
884
+ }
885
+ return null;
446
886
  }
447
887
  }
448
- exports.AbilityResolver = AbilityResolver;
449
- exports["default"] = AbilityResolver;
888
+ exports.AbilityResult = AbilityResult;
450
889
 
451
890
 
452
891
  /***/ }),
453
892
 
454
- /***/ 476:
893
+ /***/ 306:
455
894
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
456
895
 
457
896
 
@@ -460,8 +899,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
460
899
  };
461
900
  Object.defineProperty(exports, "__esModule", ({ value: true }));
462
901
  exports.AbilityRule = void 0;
463
- const AbilityMatch_1 = __importDefault(__webpack_require__(909));
464
- const AbilityCondition_1 = __importDefault(__webpack_require__(261));
902
+ const AbilityMatch_1 = __importDefault(__webpack_require__(247));
903
+ const AbilityCondition_1 = __importDefault(__webpack_require__(167));
904
+ const AbilityJSONParser_1 = __webpack_require__(909);
465
905
  /**
466
906
  * Represents a rule that defines a condition to be checked against a subject and resource.
467
907
  */
@@ -480,17 +920,17 @@ class AbilityRule {
480
920
  state = AbilityMatch_1.default.pending;
481
921
  /**
482
922
  * Creates an instance of AbilityRule.
483
- * @param {string} options.id - The unique identifier of the rule.
484
- * @param {string} options.name - The name of the rule.
485
- * @param {AbilityCondition} options.condition - The condition to evaluate.
486
- * @param {string} options.subject - The subject of the rule.
487
- * @param {string} options.resource - The resource to compare against.
923
+ * @param {string} params.id - The unique identifier of the rule.
924
+ * @param {string} params.name - The name of the rule.
925
+ * @param {AbilityCondition} params.condition - The condition to evaluate.
926
+ * @param {string} params.subject - The subject of the rule.
927
+ * @param {string} params.resource - The resource to compare against.
488
928
  * @param params
489
929
  */
490
930
  constructor(params) {
491
931
  const { id, name, subject, resource, condition } = params;
492
- this.id = id;
493
- this.name = name;
932
+ this.name = name || `${JSON.stringify(subject)} ${condition.code} ${JSON.stringify(resource)}`;
933
+ this.id = id || this.name;
494
934
  this.subject = subject;
495
935
  this.resource = resource;
496
936
  this.condition = condition;
@@ -498,54 +938,139 @@ class AbilityRule {
498
938
  /**
499
939
  * Check if the rule is matched
500
940
  * @param resource - The resource to check
941
+ * @param environment
501
942
  */
502
- check(resource) {
943
+ async check(resource, environment) {
503
944
  let is = false;
504
- const [valueS, valueO] = this.extractValues(resource);
945
+ const [subjectValue, resourceValue] = this.extractValues(resource, environment);
946
+ const isValue = (v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
947
+ // equals
948
+ if (AbilityCondition_1.default.equals.isEqual(this.condition)) {
949
+ is = subjectValue === resourceValue;
950
+ }
951
+ // not equals
952
+ if (AbilityCondition_1.default.not_equals.isEqual(this.condition)) {
953
+ is = subjectValue !== resourceValue;
954
+ }
955
+ // less than
505
956
  if (AbilityCondition_1.default.less_than.isEqual(this.condition)) {
506
- is = Number(valueS) < Number(valueO);
957
+ if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
958
+ is = subjectValue < resourceValue;
959
+ }
507
960
  }
961
+ // less or equal
508
962
  if (AbilityCondition_1.default.less_or_equal.isEqual(this.condition)) {
509
- is = Number(valueS) <= Number(valueO);
963
+ if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
964
+ is = subjectValue <= resourceValue;
965
+ }
510
966
  }
511
- if (AbilityCondition_1.default.more_than.isEqual(this.condition)) {
512
- is = Number(valueS) > Number(valueO);
967
+ // more than
968
+ if (AbilityCondition_1.default.greater_than.isEqual(this.condition)) {
969
+ if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
970
+ is = subjectValue > resourceValue;
971
+ }
513
972
  }
514
- if (AbilityCondition_1.default.more_or_equal.isEqual(this.condition)) {
515
- is = Number(valueS) >= Number(valueO);
973
+ // more or equal
974
+ if (AbilityCondition_1.default.greater_or_equal.isEqual(this.condition)) {
975
+ if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
976
+ is = subjectValue >= resourceValue;
977
+ }
516
978
  }
517
- if (AbilityCondition_1.default.equal.isEqual(this.condition)) {
518
- is = valueS === valueO;
979
+ // in
980
+ if (AbilityCondition_1.default.in.isEqual(this.condition)) {
981
+ // value in array
982
+ if (isValue(subjectValue) && Array.isArray(resourceValue)) {
983
+ is = resourceValue.includes(subjectValue);
984
+ }
985
+ // array intersects array
986
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
987
+ is = subjectValue.some(v => resourceValue.includes(v));
988
+ }
519
989
  }
520
- if (AbilityCondition_1.default.not_equal.isEqual(this.condition)) {
521
- is = valueS !== valueO;
990
+ // not in
991
+ if (AbilityCondition_1.default.not_in.isEqual(this.condition)) {
992
+ if (isValue(subjectValue) && Array.isArray(resourceValue)) {
993
+ is = !resourceValue.includes(subjectValue);
994
+ }
995
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
996
+ is = !subjectValue.some(v => resourceValue.includes(v));
997
+ }
522
998
  }
523
- if (AbilityCondition_1.default.in.isEqual(this.condition)) {
524
- // [<some>] and [<some>]
525
- if (Array.isArray(valueS) && Array.isArray(valueO)) {
526
- is = valueS.some(v => valueO.find(v1 => v1 === v));
999
+ // contains
1000
+ if (AbilityCondition_1.default.contains.isEqual(this.condition)) {
1001
+ // array contains value
1002
+ if (Array.isArray(subjectValue) && isValue(resourceValue)) {
1003
+ is = subjectValue.includes(resourceValue);
1004
+ }
1005
+ // array intersects array
1006
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
1007
+ is = subjectValue.some(v => resourceValue.includes(v));
1008
+ }
1009
+ }
1010
+ // not contains
1011
+ if (AbilityCondition_1.default.not_contains.isEqual(this.condition)) {
1012
+ if (Array.isArray(subjectValue) && isValue(resourceValue)) {
1013
+ is = !subjectValue.includes(resourceValue);
1014
+ }
1015
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
1016
+ is = !subjectValue.some(v => resourceValue.includes(v));
1017
+ }
1018
+ }
1019
+ // length equals
1020
+ if (AbilityCondition_1.default.length_equals.isEqual(this.condition)) {
1021
+ // foo.bar == n
1022
+ if (isValue(subjectValue) && typeof resourceValue === 'number') {
1023
+ is = String(subjectValue).length === resourceValue;
527
1024
  }
528
- // <some> and [<some>]
529
- if ((typeof valueS === 'string' || typeof valueS === 'number') && Array.isArray(valueO)) {
530
- is = valueO.includes(valueS);
1025
+ // ['foo', 'bar'] = n
1026
+ else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
1027
+ is = subjectValue.length === resourceValue;
531
1028
  }
532
- // [<some>] and <some>
533
- if ((typeof valueO === 'string' || typeof valueO === 'number') && Array.isArray(valueS)) {
534
- is = valueS.includes(valueO);
1029
+ // ['foo', 'bar'] = ['baz', 'taz']
1030
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
1031
+ is = subjectValue.length === resourceValue.length;
1032
+ }
1033
+ // 'foo' = 'bar'
1034
+ else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
1035
+ is = subjectValue.length === resourceValue.length;
535
1036
  }
536
1037
  }
537
- if (AbilityCondition_1.default.not_in.isEqual(this.condition)) {
538
- // [<some>] and [<some>]
539
- if (Array.isArray(valueS) && Array.isArray(valueO)) {
540
- is = !valueS.some(v => valueO.find(v1 => v1 === v));
1038
+ // length greater than
1039
+ if (AbilityCondition_1.default.length_greater_than.isEqual(this.condition)) {
1040
+ // foo.bar > n
1041
+ if (isValue(subjectValue) && typeof resourceValue === 'number') {
1042
+ is = String(subjectValue).length > resourceValue;
1043
+ }
1044
+ // ['foo', 'bar'] > n
1045
+ else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
1046
+ is = subjectValue.length > resourceValue;
1047
+ }
1048
+ // ['foo', 'bar'] > ['baz', 'taz']
1049
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
1050
+ is = subjectValue.length > resourceValue.length;
541
1051
  }
542
- // <some> and [<some>]
543
- if ((typeof valueS === 'string' || typeof valueS === 'number') && Array.isArray(valueO)) {
544
- is = !valueO.includes(valueS);
1052
+ // 'foo' > 'bar'
1053
+ else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
1054
+ is = subjectValue.length > resourceValue.length;
545
1055
  }
546
- // [<some>] and <some>
547
- if ((typeof valueO === 'string' || typeof valueO === 'number') && Array.isArray(valueS)) {
548
- is = !valueS.includes(valueO);
1056
+ }
1057
+ // length greater than
1058
+ if (AbilityCondition_1.default.length_less_than.isEqual(this.condition)) {
1059
+ // foo.bar < n
1060
+ if (isValue(subjectValue) && typeof resourceValue === 'number') {
1061
+ is = String(subjectValue).length < resourceValue;
1062
+ }
1063
+ // ['foo', 'bar'] < n
1064
+ else if (Array.isArray(subjectValue) && typeof resourceValue === 'number') {
1065
+ is = subjectValue.length < resourceValue;
1066
+ }
1067
+ // ['foo', 'bar'] < ['baz', 'taz']
1068
+ else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
1069
+ is = subjectValue.length < resourceValue.length;
1070
+ }
1071
+ // 'foo' < 'bar'
1072
+ else if (typeof subjectValue === 'string' && typeof resourceValue === 'string') {
1073
+ is = subjectValue.length < resourceValue.length;
549
1074
  }
550
1075
  }
551
1076
  this.state = is ? AbilityMatch_1.default.match : AbilityMatch_1.default.mismatch;
@@ -554,29 +1079,44 @@ class AbilityRule {
554
1079
  /**
555
1080
  * Extract values from the resourceData
556
1081
  * @param resourceData - The resourceData to extract values from
1082
+ * @param environment - Environment data
557
1083
  */
558
- extractValues(resourceData) {
559
- let leftSideValue;
560
- let rightSideValue;
561
- if (resourceData === null || typeof resourceData === 'undefined') {
1084
+ extractValues(resourceData, environment) {
1085
+ let subjectValue;
1086
+ let resourceValue;
1087
+ if ((resourceData === null || typeof resourceData === 'undefined') &&
1088
+ (environment === null || typeof environment === 'undefined')) {
562
1089
  return [NaN, NaN];
563
1090
  }
564
- const isPath = (str) => {
565
- return typeof str === 'string' && str.match(/\./g) !== null;
566
- };
567
- if (isPath(this.subject)) {
568
- leftSideValue = this.getDotNotationValue(resourceData, this.subject);
1091
+ // left side resolve
1092
+ if (this.subject.includes('.')) {
1093
+ // if is environment
1094
+ if (this.subject.startsWith('env.') && typeof environment !== 'undefined') {
1095
+ subjectValue = this.getDotNotationValue(environment, this.subject.replace(/^env\./, ''));
1096
+ // if is resource
1097
+ }
1098
+ else {
1099
+ subjectValue = this.getDotNotationValue(resourceData, this.subject);
1100
+ }
569
1101
  }
570
1102
  else {
571
- leftSideValue = this.subject;
1103
+ subjectValue = this.subject;
572
1104
  }
573
- if (isPath(this.resource)) {
574
- rightSideValue = this.getDotNotationValue(resourceData, this.resource);
1105
+ // right side resolve
1106
+ if (typeof this.resource === 'string' && this.resource.includes('.')) {
1107
+ // if is environment
1108
+ if (this.resource.startsWith('env.') && typeof environment !== 'undefined') {
1109
+ resourceValue = this.getDotNotationValue(environment, this.resource.replace(/^env\./, ''));
1110
+ }
1111
+ else {
1112
+ // if is resource
1113
+ resourceValue = this.getDotNotationValue(resourceData, this.resource);
1114
+ }
575
1115
  }
576
1116
  else {
577
- rightSideValue = this.resource;
1117
+ resourceValue = this.resource;
578
1118
  }
579
- return [leftSideValue, rightSideValue];
1119
+ return [subjectValue, resourceValue];
580
1120
  }
581
1121
  /**
582
1122
  * Get the value of the object by dot notation
@@ -587,7 +1127,7 @@ class AbilityRule {
587
1127
  const arr = desc.split('.');
588
1128
  while (arr.length && resource) {
589
1129
  const comp = arr.shift() || '';
590
- const match = new RegExp('(.+)\\[([0-9]*)\\]').exec(comp);
1130
+ const match = new RegExp('(.+)\\[([0-9]*)]').exec(comp);
591
1131
  if (match !== null && match.length == 3) {
592
1132
  const arrayData = {
593
1133
  arrName: match[1],
@@ -606,63 +1146,124 @@ class AbilityRule {
606
1146
  }
607
1147
  return resource;
608
1148
  }
609
- static parse(config) {
610
- const { id, name, subject, resource, condition } = config;
1149
+ toString() {
1150
+ return `AbilityRule: ${this.name} condition: ${this.condition.code} subject: "${this.subject?.toString()}" resource: "${this.resource?.toString()}"`;
1151
+ }
1152
+ static fromJSON(config) {
1153
+ return AbilityJSONParser_1.AbilityJSONParser.parseRule(config);
1154
+ }
1155
+ static equals(subject, resource) {
611
1156
  return new AbilityRule({
612
- id,
613
- name,
1157
+ condition: AbilityCondition_1.default.equals,
614
1158
  subject,
615
1159
  resource,
616
- condition: new AbilityCondition_1.default(condition),
617
1160
  });
618
1161
  }
619
- /**
620
- * Export the rule to config object
621
- */
622
- export() {
623
- return {
624
- id: this.id,
625
- name: this.name,
626
- subject: this.subject,
627
- resource: this.resource,
628
- condition: this.condition.code,
629
- };
1162
+ static notEquals(subject, resource) {
1163
+ return new AbilityRule({
1164
+ condition: AbilityCondition_1.default.not_equals,
1165
+ subject,
1166
+ resource,
1167
+ });
630
1168
  }
631
- }
632
- exports.AbilityRule = AbilityRule;
633
- exports["default"] = AbilityRule;
634
-
635
-
636
- /***/ }),
637
-
638
- /***/ 402:
639
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
640
-
641
-
642
- var __importDefault = (this && this.__importDefault) || function (mod) {
643
- return (mod && mod.__esModule) ? mod : { "default": mod };
644
- };
645
- Object.defineProperty(exports, "__esModule", ({ value: true }));
646
- exports.AbilityRuleSet = void 0;
647
- const AbilityRule_1 = __importDefault(__webpack_require__(476));
648
- const AbilityCompare_1 = __importDefault(__webpack_require__(923));
649
- const AbilityMatch_1 = __importDefault(__webpack_require__(909));
650
- class AbilityRuleSet {
651
- state = AbilityMatch_1.default.pending;
652
- /**
653
- * List of rules
654
- */
655
- rules = [];
656
- /**
657
- * Rules compare method.\
658
- * For the «and» method the rule will be permitted if all\
659
- * rules will be returns «permit» status and for the «or» - if\
660
- * one of the rules returns as «permit»
661
- */
662
- compareMethod = AbilityCompare_1.default.and;
663
- /**
664
- * Group name
665
- */
1169
+ static contains(subject, resource) {
1170
+ return new AbilityRule({
1171
+ condition: AbilityCondition_1.default.contains,
1172
+ subject,
1173
+ resource,
1174
+ });
1175
+ }
1176
+ static notContains(subject, resource) {
1177
+ return new AbilityRule({
1178
+ condition: AbilityCondition_1.default.not_contains,
1179
+ subject,
1180
+ resource,
1181
+ });
1182
+ }
1183
+ static notIn(subject, resource) {
1184
+ return new AbilityRule({
1185
+ condition: AbilityCondition_1.default.not_in,
1186
+ subject,
1187
+ resource,
1188
+ });
1189
+ }
1190
+ static in(subject, resource) {
1191
+ return new AbilityRule({
1192
+ condition: AbilityCondition_1.default.in,
1193
+ subject,
1194
+ resource,
1195
+ });
1196
+ }
1197
+ static notEqual(subject, resource) {
1198
+ return new AbilityRule({
1199
+ condition: AbilityCondition_1.default.not_equals,
1200
+ subject,
1201
+ resource,
1202
+ });
1203
+ }
1204
+ static lessThan(subject, resource) {
1205
+ return new AbilityRule({
1206
+ condition: AbilityCondition_1.default.less_than,
1207
+ subject,
1208
+ resource,
1209
+ });
1210
+ }
1211
+ static lessOrEqual(subject, resource) {
1212
+ return new AbilityRule({
1213
+ condition: AbilityCondition_1.default.less_or_equal,
1214
+ subject,
1215
+ resource,
1216
+ });
1217
+ }
1218
+ static moreThan(subject, resource) {
1219
+ return new AbilityRule({
1220
+ condition: AbilityCondition_1.default.greater_than,
1221
+ subject,
1222
+ resource,
1223
+ });
1224
+ }
1225
+ static moreOrEqual(subject, resource) {
1226
+ return new AbilityRule({
1227
+ condition: AbilityCondition_1.default.greater_or_equal,
1228
+ subject,
1229
+ resource,
1230
+ });
1231
+ }
1232
+ }
1233
+ exports.AbilityRule = AbilityRule;
1234
+ exports["default"] = AbilityRule;
1235
+
1236
+
1237
+ /***/ }),
1238
+
1239
+ /***/ 56:
1240
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1241
+
1242
+
1243
+ var __importDefault = (this && this.__importDefault) || function (mod) {
1244
+ return (mod && mod.__esModule) ? mod : { "default": mod };
1245
+ };
1246
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1247
+ exports.AbilityRuleSet = void 0;
1248
+ const AbilityCompare_1 = __importDefault(__webpack_require__(413));
1249
+ const AbilityMatch_1 = __importDefault(__webpack_require__(247));
1250
+ const AbilityJSONParser_1 = __webpack_require__(909);
1251
+ class AbilityRuleSet {
1252
+ state = AbilityMatch_1.default.pending;
1253
+ /**
1254
+ * List of rules
1255
+ */
1256
+ rules = [];
1257
+ /**
1258
+ * Rules compare method.\
1259
+ * For the «and» method the rule will be permitted if all\
1260
+ * rules will be returns «permit» status and for the «or» - if\
1261
+ * one of the rules returns as «permit»
1262
+ */
1263
+ compareMethod = AbilityCompare_1.default.and;
1264
+ /**
1265
+ * Group name
1266
+ */
666
1267
  name;
667
1268
  /**
668
1269
  * Group ID
@@ -670,8 +1271,8 @@ class AbilityRuleSet {
670
1271
  id;
671
1272
  constructor(params) {
672
1273
  const { name, id, compareMethod } = params;
673
- this.name = name;
674
- this.id = id;
1274
+ this.name = name || '';
1275
+ this.id = id || this.name;
675
1276
  this.compareMethod = compareMethod;
676
1277
  }
677
1278
  addRule(rule) {
@@ -682,50 +1283,53 @@ class AbilityRuleSet {
682
1283
  rules.forEach(rule => this.addRule(rule));
683
1284
  return this;
684
1285
  }
685
- check(resources) {
1286
+ async check(resources, environment) {
686
1287
  this.state = AbilityMatch_1.default.mismatch;
687
1288
  if (!this.rules.length) {
688
1289
  return this.state;
689
1290
  }
690
- const ruleCheckStates = this.rules.reduce((collect, rule) => {
691
- return collect.concat(rule.check(resources));
692
- }, []);
1291
+ const ruleCheckStates = [];
1292
+ for (const rule of this.rules) {
1293
+ const state = await rule.check(resources, environment);
1294
+ ruleCheckStates.push(state);
1295
+ if (AbilityCompare_1.default.and.isEqual(this.compareMethod) && AbilityMatch_1.default.mismatch.isEqual(state)) {
1296
+ return this.state; // mismatch
1297
+ }
1298
+ if (AbilityCompare_1.default.or.isEqual(this.compareMethod) && AbilityMatch_1.default.match.isEqual(state)) {
1299
+ this.state = AbilityMatch_1.default.match;
1300
+ return this.state;
1301
+ }
1302
+ }
693
1303
  if (AbilityCompare_1.default.and.isEqual(this.compareMethod)) {
694
- if (ruleCheckStates.every(ruleState => AbilityMatch_1.default.match.isEqual(ruleState))) {
1304
+ if (ruleCheckStates.every(s => AbilityMatch_1.default.match.isEqual(s))) {
695
1305
  this.state = AbilityMatch_1.default.match;
696
1306
  }
697
1307
  }
698
1308
  if (AbilityCompare_1.default.or.isEqual(this.compareMethod)) {
699
- if (ruleCheckStates.some(ruleState => AbilityMatch_1.default.match.isEqual(ruleState))) {
1309
+ if (ruleCheckStates.some(s => AbilityMatch_1.default.match.isEqual(s))) {
700
1310
  this.state = AbilityMatch_1.default.match;
701
1311
  }
702
1312
  }
703
1313
  return this.state;
704
1314
  }
1315
+ toString() {
1316
+ return `AbilityRuleSet: ${this.name} compareMethod: ${this.compareMethod.code}, rules: ${this.rules.map(rule => rule.toString()).join('\n')}`;
1317
+ }
705
1318
  /**
706
1319
  * Parse the config JSON format to Group class instance
707
1320
  */
708
- static parse(config) {
709
- const { id, name, rules, compareMethod } = config;
710
- const ruleSet = new AbilityRuleSet({
711
- compareMethod: new AbilityCompare_1.default(compareMethod),
712
- name,
713
- id,
714
- });
715
- // Adding rules if exists
716
- if (rules && rules.length > 0) {
717
- const abilityRules = rules.map(ruleConfig => AbilityRule_1.default.parse(ruleConfig));
718
- ruleSet.addRules(abilityRules);
719
- }
720
- return ruleSet;
1321
+ static fromJSON(config) {
1322
+ return AbilityJSONParser_1.AbilityJSONParser.parseRuleSet(config);
721
1323
  }
722
- export() {
723
- return {
724
- id: this.id.toString(),
725
- name: this.name.toString(),
726
- compareMethod: this.compareMethod.code.toString(),
727
- rules: this.rules.map(rule => rule.export()),
728
- };
1324
+ static and(rules) {
1325
+ return new AbilityRuleSet({
1326
+ compareMethod: AbilityCompare_1.default.and,
1327
+ }).addRules(rules);
1328
+ }
1329
+ static or(rules) {
1330
+ return new AbilityRuleSet({
1331
+ compareMethod: AbilityCompare_1.default.or,
1332
+ }).addRules(rules);
729
1333
  }
730
1334
  }
731
1335
  exports.AbilityRuleSet = AbilityRuleSet;
@@ -753,17 +1357,1207 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
753
1357
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
754
1358
  };
755
1359
  Object.defineProperty(exports, "__esModule", ({ value: true }));
756
- __exportStar(__webpack_require__(19), exports);
757
- __exportStar(__webpack_require__(923), exports);
758
- __exportStar(__webpack_require__(261), exports);
759
- __exportStar(__webpack_require__(122), exports);
1360
+ __exportStar(__webpack_require__(301), exports);
1361
+ __exportStar(__webpack_require__(413), exports);
1362
+ __exportStar(__webpack_require__(167), exports);
1363
+ __exportStar(__webpack_require__(216), exports);
1364
+ __exportStar(__webpack_require__(247), exports);
1365
+ __exportStar(__webpack_require__(147), exports);
1366
+ __exportStar(__webpack_require__(278), exports);
1367
+ __exportStar(__webpack_require__(179), exports);
1368
+ __exportStar(__webpack_require__(634), exports);
1369
+ __exportStar(__webpack_require__(306), exports);
1370
+ __exportStar(__webpack_require__(56), exports);
1371
+ __exportStar(__webpack_require__(221), exports);
1372
+ __exportStar(__webpack_require__(941), exports);
1373
+ __exportStar(__webpack_require__(6), exports);
1374
+ __exportStar(__webpack_require__(829), exports);
760
1375
  __exportStar(__webpack_require__(909), exports);
761
- __exportStar(__webpack_require__(189), exports);
762
- __exportStar(__webpack_require__(844), exports);
763
- __exportStar(__webpack_require__(277), exports);
764
- __exportStar(__webpack_require__(668), exports);
765
- __exportStar(__webpack_require__(476), exports);
766
- __exportStar(__webpack_require__(402), exports);
1376
+ __exportStar(__webpack_require__(577), exports);
1377
+ __exportStar(__webpack_require__(10), exports);
1378
+ __exportStar(__webpack_require__(325), exports);
1379
+
1380
+
1381
+ /***/ }),
1382
+
1383
+ /***/ 10:
1384
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1385
+
1386
+
1387
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1388
+ exports.AbilityDSLLexer = void 0;
1389
+ const AbilityDSLToken_1 = __webpack_require__(325);
1390
+ class AbilityDSLLexer {
1391
+ input;
1392
+ pos = 0;
1393
+ tokens = [];
1394
+ line = 1;
1395
+ column = 1;
1396
+ // Список ключевых слов
1397
+ keywords = new Set([
1398
+ 'if',
1399
+ 'all',
1400
+ 'any',
1401
+ 'of',
1402
+ 'permit',
1403
+ 'allow',
1404
+ 'deny',
1405
+ 'forbidden',
1406
+ 'true',
1407
+ 'false',
1408
+ 'null',
1409
+ 'contains',
1410
+ 'includes',
1411
+ 'length',
1412
+ 'has',
1413
+ 'in',
1414
+ 'gt',
1415
+ 'lt',
1416
+ 'gte',
1417
+ 'lte',
1418
+ 'equals',
1419
+ 'greater',
1420
+ 'less',
1421
+ 'not',
1422
+ 'is',
1423
+ 'or',
1424
+ 'than',
1425
+ ]);
1426
+ constructor(input) {
1427
+ this.input = input;
1428
+ }
1429
+ tokenize() {
1430
+ while (!this.isAtEnd()) {
1431
+ this.skipWhitespace();
1432
+ if (this.isAtEnd())
1433
+ break;
1434
+ const char = this.peek();
1435
+ if (char === '#') {
1436
+ this.tokens.push(this.readComment());
1437
+ continue;
1438
+ }
1439
+ if (char === '"' || char === "'") {
1440
+ this.tokens.push(this.readString());
1441
+ continue;
1442
+ }
1443
+ if (this.isDigit(char)) {
1444
+ this.tokens.push(this.readNumber());
1445
+ continue;
1446
+ }
1447
+ if (this.isSymbol(char)) {
1448
+ this.tokens.push(this.readSymbol());
1449
+ continue;
1450
+ }
1451
+ if (this.isAlpha(char)) {
1452
+ this.tokens.push(this.readWord());
1453
+ continue;
1454
+ }
1455
+ throw new Error(`Unexpected character '${char}' at ${this.line}:${this.column}`);
1456
+ }
1457
+ this.tokens.push(new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.EOF, '', this.line, this.column));
1458
+ return this.tokens;
1459
+ }
1460
+ readComment() {
1461
+ const startLine = this.line;
1462
+ const startColumn = this.column;
1463
+ this.advance(); // skip '#'
1464
+ let value = '';
1465
+ while (!this.isAtEnd() && !this.isNewline()) {
1466
+ value += this.advance();
1467
+ }
1468
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.COMMENT, value.trim(), startLine, startColumn);
1469
+ }
1470
+ readString() {
1471
+ const startLine = this.line;
1472
+ const startColumn = this.column;
1473
+ const quote = this.advance();
1474
+ let value = '';
1475
+ let escaped = false;
1476
+ while (!this.isAtEnd()) {
1477
+ const char = this.advance();
1478
+ if (escaped) {
1479
+ value += char;
1480
+ escaped = false;
1481
+ continue;
1482
+ }
1483
+ if (char === '\\') {
1484
+ escaped = true;
1485
+ continue;
1486
+ }
1487
+ if (char === quote) {
1488
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.STRING, value, startLine, startColumn);
1489
+ }
1490
+ value += char;
1491
+ }
1492
+ throw new Error(`Unterminated string at ${startLine}:${startColumn}`);
1493
+ }
1494
+ readNumber() {
1495
+ const startLine = this.line;
1496
+ const startColumn = this.column;
1497
+ const start = this.pos;
1498
+ while (!this.isAtEnd() && this.isDigit(this.peek())) {
1499
+ this.advance();
1500
+ }
1501
+ const value = this.input.slice(start, this.pos);
1502
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.NUMBER, value, startLine, startColumn);
1503
+ }
1504
+ readSymbol() {
1505
+ const startLine = this.line;
1506
+ const startColumn = this.column;
1507
+ const char = this.advance();
1508
+ switch (char) {
1509
+ case '.':
1510
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.DOT, char, startLine, startColumn);
1511
+ case ':':
1512
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.COLON, char, startLine, startColumn);
1513
+ case ',':
1514
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.COMMA, char, startLine, startColumn);
1515
+ case '[':
1516
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.LBRACKET, char, startLine, startColumn);
1517
+ case ']':
1518
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.RBRACKET, char, startLine, startColumn);
1519
+ case '>':
1520
+ if (this.peek() === '=') {
1521
+ this.advance();
1522
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '>=', startLine, startColumn);
1523
+ }
1524
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '>', startLine, startColumn);
1525
+ case '<':
1526
+ if (this.peek() === '=') {
1527
+ this.advance();
1528
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '<=', startLine, startColumn);
1529
+ }
1530
+ if (this.peek() === '>') {
1531
+ this.advance();
1532
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '<>', startLine, startColumn);
1533
+ }
1534
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '<', startLine, startColumn);
1535
+ case '=':
1536
+ if (this.peek() === '=') {
1537
+ this.advance();
1538
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '==', startLine, startColumn);
1539
+ }
1540
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '=', startLine, startColumn);
1541
+ case '!':
1542
+ if (this.peek() === '=') {
1543
+ this.advance();
1544
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.SYMBOL, '!=', startLine, startColumn);
1545
+ }
1546
+ throw new Error(`Unexpected symbol '!' at ${this.line}:${this.column}`);
1547
+ default:
1548
+ throw new Error(`Unknown symbol '${char}' at ${this.line}:${this.column}`);
1549
+ }
1550
+ }
1551
+ readWord() {
1552
+ const startLine = this.line;
1553
+ const startColumn = this.column;
1554
+ const start = this.pos;
1555
+ // Первый сегмент
1556
+ while (!this.isAtEnd() && /[a-zA-Z0-9_*]/.test(this.peek())) {
1557
+ this.advance();
1558
+ }
1559
+ // Сегменты через точку
1560
+ while (!this.isAtEnd() && this.peek() === '.') {
1561
+ this.advance(); // dot
1562
+ if (!/[a-zA-Z_*]/.test(this.peek())) {
1563
+ break;
1564
+ }
1565
+ while (!this.isAtEnd() && /[a-zA-Z0-9_*]/.test(this.peek())) {
1566
+ this.advance();
1567
+ }
1568
+ }
1569
+ const word = this.input.slice(start, this.pos);
1570
+ // Если есть точка — это путь (identifier или permission)
1571
+ if (word.includes('.')) {
1572
+ const last = this.tokens[this.tokens.length - 1];
1573
+ if (last?.code === AbilityDSLToken_1.AbilityDSLToken.EFFECT) {
1574
+ if (word.startsWith('permission.')) {
1575
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.PERMISSION, word, startLine, startColumn);
1576
+ }
1577
+ }
1578
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER, word, startLine, startColumn);
1579
+ }
1580
+ // Ключевые слова
1581
+ if (this.keywords.has(word)) {
1582
+ // Эффекты
1583
+ if (word === 'permit' || word === 'allow') {
1584
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.EFFECT, 'permit', startLine, startColumn);
1585
+ }
1586
+ if (word === 'deny' || word === 'forbidden') {
1587
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.EFFECT, 'deny', startLine, startColumn);
1588
+ }
1589
+ // Групповые ключевые слова
1590
+ if (word === 'all') {
1591
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.ALL, word, startLine, startColumn);
1592
+ }
1593
+ if (word === 'any') {
1594
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.ANY, word, startLine, startColumn);
1595
+ }
1596
+ if (word === 'of') {
1597
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.OF, word, startLine, startColumn);
1598
+ }
1599
+ if (word === 'if') {
1600
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.IF, word, startLine, startColumn);
1601
+ }
1602
+ // Булевы и null
1603
+ if (word === 'true' || word === 'false') {
1604
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.BOOLEAN, word, startLine, startColumn);
1605
+ }
1606
+ if (word === 'null') {
1607
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.NULL, word, startLine, startColumn);
1608
+ }
1609
+ // Остальные ключевые слова (contains, in, equals, greater, less, not, is, or, than, equal)
1610
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.KEYWORD, word, startLine, startColumn);
1611
+ }
1612
+ // Если после EFFECT и нет точки — действие (например, "create")
1613
+ const lastToken = this.tokens[this.tokens.length - 1];
1614
+ if (lastToken?.code === AbilityDSLToken_1.AbilityDSLToken.EFFECT) {
1615
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.PERMISSION, word, startLine, startColumn);
1616
+ }
1617
+ // Обычный идентификатор
1618
+ return new AbilityDSLToken_1.AbilityDSLToken(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER, word, startLine, startColumn);
1619
+ }
1620
+ skipWhitespace() {
1621
+ while (!this.isAtEnd() && /\s/.test(this.peek())) {
1622
+ this.advance();
1623
+ }
1624
+ }
1625
+ isDigit(char) {
1626
+ return char >= '0' && char <= '9';
1627
+ }
1628
+ isAlpha(char) {
1629
+ return /[a-zA-Z_]/.test(char);
1630
+ }
1631
+ isSymbol(char) {
1632
+ return ['.', ':', ',', '[', ']', '>', '<', '=', '!'].includes(char);
1633
+ }
1634
+ isNewline() {
1635
+ return this.peek() === '\n';
1636
+ }
1637
+ peek() {
1638
+ return this.input[this.pos];
1639
+ }
1640
+ advance() {
1641
+ const ch = this.input[this.pos++];
1642
+ if (ch === '\n') {
1643
+ this.line++;
1644
+ this.column = 1;
1645
+ }
1646
+ else {
1647
+ this.column++;
1648
+ }
1649
+ return ch;
1650
+ }
1651
+ isAtEnd() {
1652
+ return this.pos >= this.input.length;
1653
+ }
1654
+ }
1655
+ exports.AbilityDSLLexer = AbilityDSLLexer;
1656
+
1657
+
1658
+ /***/ }),
1659
+
1660
+ /***/ 577:
1661
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1662
+
1663
+
1664
+ var __importDefault = (this && this.__importDefault) || function (mod) {
1665
+ return (mod && mod.__esModule) ? mod : { "default": mod };
1666
+ };
1667
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1668
+ exports.AbilityDSLParser = void 0;
1669
+ const AbilityCompare_1 = __importDefault(__webpack_require__(413));
1670
+ const AbilityCondition_1 = __importDefault(__webpack_require__(167));
1671
+ const AbilityPolicy_1 = __importDefault(__webpack_require__(278));
1672
+ const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(179));
1673
+ const AbilityRule_1 = __importDefault(__webpack_require__(306));
1674
+ const AbilityRuleSet_1 = __importDefault(__webpack_require__(56));
1675
+ const AbilityDSLLexer_1 = __webpack_require__(10);
1676
+ const AbilityDSLToken_1 = __webpack_require__(325);
1677
+ const AbilityDSLSyntaxError_1 = __webpack_require__(883);
1678
+ /**
1679
+ * Parser for the Ability DSL.
1680
+ *
1681
+ * Converts a DSL string into one or more AbilityPolicy instances.
1682
+ * The grammar follows the structure:
1683
+ *
1684
+ * <effect> <permission> if <group> [ <group> ... ]
1685
+ *
1686
+ * where <group> is either "all of:" or "any of:", followed by a colon,
1687
+ * and then a list of rules (one per line).
1688
+ *
1689
+ * Each rule is: <identifier> <operator> <value>
1690
+ *
1691
+ * Operators can be simple (equals, contains, in) or
1692
+ * composed (is null, is not null, greater than, less than or equal, etc.).
1693
+ */
1694
+ class AbilityDSLParser {
1695
+ dsl;
1696
+ tokens = [];
1697
+ pos = 0;
1698
+ annotationBuffer = {
1699
+ name: null,
1700
+ description: null,
1701
+ };
1702
+ constructor(dsl) {
1703
+ this.dsl = dsl;
1704
+ }
1705
+ /**
1706
+ * Main entry point: tokenize the input and parse all policies.
1707
+ * @returns Array of AbilityPolicy instances.
1708
+ */
1709
+ parse() {
1710
+ // Tokenize the entire DSL string.
1711
+ this.tokens = new AbilityDSLLexer_1.AbilityDSLLexer(this.dsl).tokenize();
1712
+ this.pos = 0;
1713
+ const policies = [];
1714
+ // Keep parsing until we've consumed all tokens.
1715
+ while (!this.isAtEnd()) {
1716
+ this.consumeLeadingComments();
1717
+ // Every policy must start with an EFFECT token.
1718
+ if (!this.isStartOfPolicy()) {
1719
+ const token = this.peek();
1720
+ this.syntaxError(`Expected policy, got ${token.code}.`, token, ['EFFECT']);
1721
+ }
1722
+ policies.push(this.parsePolicy());
1723
+ }
1724
+ return policies;
1725
+ }
1726
+ // -------------------------------------------------------------------------
1727
+ // #region Policy parsing
1728
+ // -------------------------------------------------------------------------
1729
+ /**
1730
+ * Parses a single policy from the current token position.
1731
+ *
1732
+ * Grammar:
1733
+ * policy = EFFECT PERMISSION IF (ALL | ANY) COLON ruleSets
1734
+ */
1735
+ parsePolicy() {
1736
+ this.consumeLeadingComments();
1737
+ const meta = this.takeAnnotations();
1738
+ // Effect: "permit" or "deny"
1739
+ const effectToken = this.consume(AbilityDSLToken_1.AbilityDSLToken.EFFECT, 'Expected effect');
1740
+ const effect = effectToken.value;
1741
+ // Permission: e.g. "order.update"
1742
+ const permissionToken = this.consume(AbilityDSLToken_1.AbilityDSLToken.PERMISSION, 'Expected permission');
1743
+ const permission = permissionToken.value;
1744
+ if (!permission.startsWith('permission.')) {
1745
+ return this.syntaxError(`Unexpected token. The permission key, must be starts with prefix \`permission.\`, but got \`${permission}\`.\nDid you mean \`permission.${permission}\`?`, permissionToken);
1746
+ }
1747
+ // "if" keyword
1748
+ this.consume(AbilityDSLToken_1.AbilityDSLToken.IF, 'Expected "if"');
1749
+ // Group selector: "all" or "any" – determines how the top‑level rule sets are combined.
1750
+ const compareToken = this.consumeOneOf([AbilityDSLToken_1.AbilityDSLToken.ALL, AbilityDSLToken_1.AbilityDSLToken.ANY], 'Expected "all" or "any"');
1751
+ const compareMethod = compareToken.code === AbilityDSLToken_1.AbilityDSLToken.ALL ? AbilityCompare_1.default.and : AbilityCompare_1.default.or;
1752
+ // Colon after the group keyword
1753
+ this.consume(AbilityDSLToken_1.AbilityDSLToken.COLON, 'Expected ":"');
1754
+ // Parse the list of rule sets (each "all of:" or "any of:" block)
1755
+ const ruleSets = this.parseRuleSets(compareMethod);
1756
+ // Construct the policy instance.
1757
+ return new AbilityPolicy_1.default({
1758
+ id: `${effect}:${permission}:${Math.random()}`,
1759
+ name: meta.name ?? `${effect} ${permission}`,
1760
+ permission: permission.replace(/^permission\./, ''),
1761
+ effect: effect === 'permit' ? AbilityPolicyEffect_1.default.permit : AbilityPolicyEffect_1.default.deny,
1762
+ compareMethod,
1763
+ }).addRuleSets(ruleSets);
1764
+ }
1765
+ // -------------------------------------------------------------------------
1766
+ // #region Rule set parsing (groups of rules)
1767
+ // -------------------------------------------------------------------------
1768
+ /**
1769
+ * Parses a sequence of rule sets (groups) until a new policy starts or EOF.
1770
+ */
1771
+ parseRuleSets(policyCompareMethod) {
1772
+ const sets = [];
1773
+ while (!this.isAtEnd() && !this.isStartOfPolicy()) {
1774
+ this.consumeLeadingComments();
1775
+ // Если начинается новая группа — парсим её
1776
+ if (this.isStartOfGroup()) {
1777
+ sets.push(this.parseGroup());
1778
+ continue;
1779
+ }
1780
+ // Иначе — implicit group (all-of по умолчанию)
1781
+ const meta = this.takeAnnotations();
1782
+ const group = new AbilityRuleSet_1.default({
1783
+ compareMethod: policyCompareMethod,
1784
+ name: meta.name,
1785
+ });
1786
+ // Читаем правила implicit-группы
1787
+ while (!this.isAtEnd()) {
1788
+ this.consumeLeadingComments();
1789
+ if (this.isStartOfGroup() || this.isStartOfPolicy()) {
1790
+ break;
1791
+ }
1792
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER)) {
1793
+ group.addRule(this.parseRule());
1794
+ }
1795
+ else {
1796
+ this.syntaxError(`Unexpected token in implicit group: ${this.peek().code}`, this.peek());
1797
+ }
1798
+ }
1799
+ sets.push(group);
1800
+ }
1801
+ return sets;
1802
+ }
1803
+ /**
1804
+ * Parses a single group, e.g. "all of:" or "any of:", and returns a RuleSet.
1805
+ */
1806
+ parseGroup() {
1807
+ this.consumeLeadingComments();
1808
+ const meta = this.takeAnnotations();
1809
+ const compareToken = this.consumeOneOf([AbilityDSLToken_1.AbilityDSLToken.ALL, AbilityDSLToken_1.AbilityDSLToken.ANY], 'Expected "all" or "any"');
1810
+ const compareMethod = compareToken.code === AbilityDSLToken_1.AbilityDSLToken.ALL ? AbilityCompare_1.default.and : AbilityCompare_1.default.or;
1811
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.OF)) {
1812
+ this.advance();
1813
+ }
1814
+ this.consume(AbilityDSLToken_1.AbilityDSLToken.COLON, 'Expected ":"');
1815
+ const group = new AbilityRuleSet_1.default({ compareMethod, name: meta.name });
1816
+ while (!this.isAtEnd()) {
1817
+ this.consumeLeadingComments();
1818
+ if (this.isStartOfGroup() || this.isStartOfPolicy()) {
1819
+ break;
1820
+ }
1821
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER)) {
1822
+ group.addRule(this.parseRule());
1823
+ }
1824
+ else {
1825
+ this.syntaxError(`Unexpected token in group: ${this.peek().code}`, this.peek());
1826
+ }
1827
+ }
1828
+ return group;
1829
+ }
1830
+ // -------------------------------------------------------------------------
1831
+ // #region Rule parsing
1832
+ // -------------------------------------------------------------------------
1833
+ /**
1834
+ * Parses a single rule: subject operator value
1835
+ */
1836
+ parseRule() {
1837
+ this.consumeLeadingComments();
1838
+ const meta = this.takeAnnotations();
1839
+ if (!this.check(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER)) {
1840
+ this.syntaxError(`Expected identifier, got ${this.peek().code}`, this.peek());
1841
+ }
1842
+ // Subject (e.g., "user.roles")
1843
+ const subject = this.consume(AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER, 'Expected field').value;
1844
+ // Operator (e.g., "contains", "equals", "is not null")
1845
+ const { condition, operator } = this.parseConditionOperator();
1846
+ let resource;
1847
+ let beforePos = this.pos;
1848
+ // Special operators that don't consume a value token.
1849
+ if (operator === AbilityDSLToken_1.AbilityDSLToken.EQ_NULL ||
1850
+ operator === AbilityDSLToken_1.AbilityDSLToken.NOT_EQ_NULL ||
1851
+ operator === AbilityDSLToken_1.AbilityDSLToken.NULL) {
1852
+ resource = null;
1853
+ }
1854
+ else {
1855
+ beforePos = this.pos;
1856
+ resource = this.parseValue();
1857
+ }
1858
+ // Checking that there are no extra tokens after the value
1859
+ // (skip comments)
1860
+ this.consumeLeadingComments();
1861
+ const resourceToken = this.tokens[beforePos];
1862
+ if (typeof resource === 'string' &&
1863
+ resourceToken.code === AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER &&
1864
+ !resourceToken.value.includes('.')) {
1865
+ this.syntaxError(`Expected comparison operator or value, got \`${resource}\``, this.tokens[beforePos], [AbilityDSLToken_1.AbilityDSLToken.KEYWORD]);
1866
+ }
1867
+ return new AbilityRule_1.default({
1868
+ subject,
1869
+ resource,
1870
+ condition,
1871
+ name: meta.name,
1872
+ });
1873
+ }
1874
+ // -------------------------------------------------------------------------
1875
+ // #region Operator parsing
1876
+ // -------------------------------------------------------------------------
1877
+ /**
1878
+ * Parses the comparison operator part of a rule.
1879
+ * Returns both the resulting AbilityCondition and the token type that was consumed.
1880
+ */
1881
+ parseConditionOperator() {
1882
+ const savedPos = this.pos;
1883
+ // "length equals"
1884
+ if (this.matchWord('length') && this.matchWord('equals')) {
1885
+ return { condition: AbilityCondition_1.default.length_equals, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_EQ };
1886
+ }
1887
+ this.pos = savedPos;
1888
+ // "length ="
1889
+ if (this.matchWord('length') && this.matchSymbol('=')) {
1890
+ return { condition: AbilityCondition_1.default.length_equals, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_EQ };
1891
+ }
1892
+ this.pos = savedPos;
1893
+ // "length greater than"
1894
+ if (this.matchWord('length') && this.matchWord('greater') && this.matchWord('than')) {
1895
+ return { condition: AbilityCondition_1.default.length_greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_GT };
1896
+ }
1897
+ this.pos = savedPos;
1898
+ // "length >"
1899
+ if (this.matchWord('length') && this.matchSymbol('>')) {
1900
+ return { condition: AbilityCondition_1.default.length_greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_GT };
1901
+ }
1902
+ this.pos = savedPos;
1903
+ // "length less than"
1904
+ if (this.matchWord('length') && this.matchWord('less') && this.matchWord('than')) {
1905
+ return { condition: AbilityCondition_1.default.length_less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_LT };
1906
+ }
1907
+ this.pos = savedPos;
1908
+ // "length <"
1909
+ if (this.matchWord('length') && this.matchSymbol('<')) {
1910
+ return { condition: AbilityCondition_1.default.length_less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LEN_LT };
1911
+ }
1912
+ this.pos = savedPos;
1913
+ // "greater than or equal"
1914
+ if (this.matchWord('greater') &&
1915
+ this.matchWord('than') &&
1916
+ this.matchWord('or') &&
1917
+ this.matchWord('equal')) {
1918
+ return { condition: AbilityCondition_1.default.greater_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.GTE };
1919
+ }
1920
+ this.pos = savedPos;
1921
+ // greater than
1922
+ if (this.matchWord('greater') && this.matchWord('than')) {
1923
+ return { condition: AbilityCondition_1.default.greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.GT };
1924
+ }
1925
+ this.pos = savedPos;
1926
+ // less than or equal
1927
+ if (this.matchWord('less') &&
1928
+ this.matchWord('than') &&
1929
+ this.matchWord('or') &&
1930
+ this.matchWord('equal')) {
1931
+ return { condition: AbilityCondition_1.default.less_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.LTE };
1932
+ }
1933
+ this.pos = savedPos;
1934
+ // less than
1935
+ if (this.matchWord('less') && this.matchWord('than')) {
1936
+ return { condition: AbilityCondition_1.default.less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LT };
1937
+ }
1938
+ this.pos = savedPos;
1939
+ // "not contains"
1940
+ if (this.matchWord('not') && this.matchWord('contains')) {
1941
+ return {
1942
+ condition: AbilityCondition_1.default.not_contains,
1943
+ operator: AbilityDSLToken_1.AbilityDSLToken.NOT_CONTAINS,
1944
+ };
1945
+ }
1946
+ this.pos = savedPos;
1947
+ // "not includes"
1948
+ if (this.matchWord('not') && this.matchWord('includes')) {
1949
+ return {
1950
+ condition: AbilityCondition_1.default.not_contains,
1951
+ operator: AbilityDSLToken_1.AbilityDSLToken.NOT_CONTAINS,
1952
+ };
1953
+ }
1954
+ this.pos = savedPos;
1955
+ // "not includes"
1956
+ if (this.matchWord('not') && this.matchWord('has')) {
1957
+ return {
1958
+ condition: AbilityCondition_1.default.not_contains,
1959
+ operator: AbilityDSLToken_1.AbilityDSLToken.NOT_CONTAINS,
1960
+ };
1961
+ }
1962
+ this.pos = savedPos;
1963
+ // "is equals"
1964
+ if (this.matchWord('is') && this.matchWord('equals')) {
1965
+ return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
1966
+ }
1967
+ this.pos = savedPos;
1968
+ // not equal
1969
+ if (this.matchWord('not') && this.matchWord('equals')) {
1970
+ return { condition: AbilityCondition_1.default.not_equals, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ };
1971
+ }
1972
+ this.pos = savedPos;
1973
+ // is not equals
1974
+ if (this.matchWord('is') && this.matchWord('not') && this.matchWord('equals')) {
1975
+ return { condition: AbilityCondition_1.default.not_equals, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ };
1976
+ }
1977
+ this.pos = savedPos;
1978
+ // is in
1979
+ if (this.matchWord('is') && this.matchWord('in')) {
1980
+ return { condition: AbilityCondition_1.default.in, operator: AbilityDSLToken_1.AbilityDSLToken.IN };
1981
+ }
1982
+ this.pos = savedPos;
1983
+ // not in
1984
+ if (this.matchWord('not') && this.matchWord('in')) {
1985
+ return { condition: AbilityCondition_1.default.not_in, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_IN };
1986
+ }
1987
+ this.pos = savedPos;
1988
+ // is not null
1989
+ if (this.matchWord('is') && this.matchWord('not')) {
1990
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.NULL)) {
1991
+ this.advance();
1992
+ return {
1993
+ condition: AbilityCondition_1.default.not_equals,
1994
+ operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ_NULL,
1995
+ };
1996
+ }
1997
+ }
1998
+ this.pos = savedPos;
1999
+ // is null
2000
+ if (this.matchWord('is') && this.matchWord('null')) {
2001
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.NULL)) {
2002
+ this.advance();
2003
+ return {
2004
+ condition: AbilityCondition_1.default.equals,
2005
+ operator: AbilityDSLToken_1.AbilityDSLToken.EQ_NULL,
2006
+ };
2007
+ }
2008
+ }
2009
+ this.pos = savedPos;
2010
+ // Single token (symbol or keyword)
2011
+ const token = this.peek();
2012
+ if (token.code !== AbilityDSLToken_1.AbilityDSLToken.SYMBOL &&
2013
+ token.code !== AbilityDSLToken_1.AbilityDSLToken.KEYWORD &&
2014
+ token.code !== AbilityDSLToken_1.AbilityDSLToken.NULL) {
2015
+ this.syntaxError(`Expected comparison operator, got \`${token.value}\``, token, [
2016
+ AbilityDSLToken_1.AbilityDSLToken.SYMBOL,
2017
+ AbilityDSLToken_1.AbilityDSLToken.KEYWORD,
2018
+ AbilityDSLToken_1.AbilityDSLToken.NULL,
2019
+ ]);
2020
+ }
2021
+ this.advance();
2022
+ switch (token.code) {
2023
+ case AbilityDSLToken_1.AbilityDSLToken.SYMBOL:
2024
+ if (token.value === '=' || token.value === '==')
2025
+ return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
2026
+ if (token.value === '!=' || token.value === '<>')
2027
+ return { condition: AbilityCondition_1.default.not_equals, operator: AbilityDSLToken_1.AbilityDSLToken.NOT_EQ };
2028
+ if (token.value === '>')
2029
+ return { condition: AbilityCondition_1.default.greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.GT };
2030
+ if (token.value === '<')
2031
+ return { condition: AbilityCondition_1.default.less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LT };
2032
+ if (token.value === '>=')
2033
+ return { condition: AbilityCondition_1.default.greater_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.GTE };
2034
+ if (token.value === '<=')
2035
+ return { condition: AbilityCondition_1.default.less_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.LTE };
2036
+ break;
2037
+ case AbilityDSLToken_1.AbilityDSLToken.KEYWORD:
2038
+ if (token.value === 'contains' || token.value === 'includes' || token.value === 'has')
2039
+ return { condition: AbilityCondition_1.default.contains, operator: AbilityDSLToken_1.AbilityDSLToken.CONTAINS };
2040
+ if (token.value === 'in')
2041
+ return { condition: AbilityCondition_1.default.in, operator: AbilityDSLToken_1.AbilityDSLToken.IN };
2042
+ if (token.value === 'equals')
2043
+ return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
2044
+ if (token.value === 'gte') {
2045
+ return { condition: AbilityCondition_1.default.greater_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.GTE };
2046
+ }
2047
+ if (token.value === 'greater' || token.value === 'gt') {
2048
+ // If we come here, it means "greater" without "than" – treat as '>'
2049
+ return { condition: AbilityCondition_1.default.greater_than, operator: AbilityDSLToken_1.AbilityDSLToken.GT };
2050
+ }
2051
+ if (token.value === 'less' || token.value === 'lt') {
2052
+ return { condition: AbilityCondition_1.default.less_than, operator: AbilityDSLToken_1.AbilityDSLToken.LT };
2053
+ }
2054
+ if (token.value === 'lte') {
2055
+ return { condition: AbilityCondition_1.default.less_or_equal, operator: AbilityDSLToken_1.AbilityDSLToken.LTE };
2056
+ }
2057
+ if (token.value === 'is') {
2058
+ // "is" alone -> equals
2059
+ return { condition: AbilityCondition_1.default.equals, operator: AbilityDSLToken_1.AbilityDSLToken.EQ };
2060
+ }
2061
+ break;
2062
+ default:
2063
+ break;
2064
+ }
2065
+ return this.syntaxError(`Unexpected operator token \`${token.value}\``, token, [
2066
+ AbilityDSLToken_1.AbilityDSLToken.SYMBOL,
2067
+ AbilityDSLToken_1.AbilityDSLToken.KEYWORD,
2068
+ ]);
2069
+ }
2070
+ /**
2071
+ * Helper to match and consume a specific word token (KEYWORD or IDENTIFIER).
2072
+ * @param word The exact string to look for.
2073
+ * @returns True if the next token has that value.
2074
+ */
2075
+ matchWord(word) {
2076
+ if (this.isAtEnd()) {
2077
+ return false;
2078
+ }
2079
+ const token = this.peek();
2080
+ if ((token.code === AbilityDSLToken_1.AbilityDSLToken.KEYWORD || token.code === AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER) &&
2081
+ token.value === word) {
2082
+ this.advance();
2083
+ return true;
2084
+ }
2085
+ return false;
2086
+ }
2087
+ matchSymbol(symbol) {
2088
+ if (this.isAtEnd())
2089
+ return false;
2090
+ const token = this.peek();
2091
+ if (token.code === AbilityDSLToken_1.AbilityDSLToken.SYMBOL && token.value === symbol) {
2092
+ this.advance();
2093
+ return true;
2094
+ }
2095
+ return false;
2096
+ }
2097
+ // -------------------------------------------------------------------------
2098
+ // #region Value parsing (literals, arrays, identifiers)
2099
+ // -------------------------------------------------------------------------
2100
+ /**
2101
+ * Parses a resource value. Can be a string literal, number, boolean,
2102
+ * null, a path (identifier), or an array.
2103
+ */
2104
+ parseValue() {
2105
+ // Arrays start with a left bracket
2106
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.LBRACKET)) {
2107
+ this.advance();
2108
+ return this.parseArray();
2109
+ }
2110
+ // Ensure we are not about to read a structural token as a value.
2111
+ const token = this.peek();
2112
+ if (token.code === AbilityDSLToken_1.AbilityDSLToken.ALL ||
2113
+ token.code === AbilityDSLToken_1.AbilityDSLToken.ANY ||
2114
+ token.code === AbilityDSLToken_1.AbilityDSLToken.EFFECT) {
2115
+ this.syntaxError(`Unexpected ${token.code} in value position`, token);
2116
+ }
2117
+ this.advance();
2118
+ // CHECK THIS SWITCH COMPARE
2119
+ switch (token.code) {
2120
+ case AbilityDSLToken_1.AbilityDSLToken.STRING:
2121
+ return token.value;
2122
+ case AbilityDSLToken_1.AbilityDSLToken.NUMBER:
2123
+ return Number(token.value);
2124
+ case AbilityDSLToken_1.AbilityDSLToken.BOOLEAN:
2125
+ return token.value === 'true';
2126
+ case AbilityDSLToken_1.AbilityDSLToken.NULL:
2127
+ return null;
2128
+ case AbilityDSLToken_1.AbilityDSLToken.IDENTIFIER:
2129
+ return token.value;
2130
+ default: {
2131
+ this.syntaxError(`Unexpected value token "${token.value}"`, token ?? this.tokens[this.tokens.length - 1], [AbilityDSLToken_1.AbilityDSLToken.KEYWORD]);
2132
+ }
2133
+ }
2134
+ }
2135
+ /**
2136
+ * Parses an array literal: [ <value>, <value>, ... ]
2137
+ * The opening bracket has already been consumed.
2138
+ */
2139
+ parseArray() {
2140
+ const arr = [];
2141
+ // Handle empty array
2142
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.RBRACKET)) {
2143
+ this.advance();
2144
+ return arr;
2145
+ }
2146
+ while (!this.isAtEnd() && !this.check(AbilityDSLToken_1.AbilityDSLToken.RBRACKET)) {
2147
+ const value = this.parseValue();
2148
+ // Flatten nested arrays if they appear (though grammar doesn't currently allow nesting).
2149
+ if (Array.isArray(value)) {
2150
+ arr.push(...value);
2151
+ }
2152
+ else if (typeof value === 'string' ||
2153
+ typeof value === 'number' ||
2154
+ typeof value === 'boolean') {
2155
+ arr.push(value);
2156
+ }
2157
+ else if (value === null) {
2158
+ // Null is allowed in arrays? Currently, we throw.
2159
+ this.syntaxError('Unexpected null in array', this.peek());
2160
+ }
2161
+ // Optional comma between elements
2162
+ if (this.check(AbilityDSLToken_1.AbilityDSLToken.COMMA)) {
2163
+ this.advance();
2164
+ }
2165
+ }
2166
+ this.consume(AbilityDSLToken_1.AbilityDSLToken.RBRACKET, 'Expected "]"');
2167
+ return arr;
2168
+ }
2169
+ // -------------------------------------------------------------------------
2170
+ // #region Annotations and comments
2171
+ // -------------------------------------------------------------------------
2172
+ consumeLeadingComments() {
2173
+ while (this.check(AbilityDSLToken_1.AbilityDSLToken.COMMENT)) {
2174
+ const token = this.advance();
2175
+ this.processCommentToken(token);
2176
+ }
2177
+ }
2178
+ processCommentToken(token) {
2179
+ const text = token.value.trim();
2180
+ if (text.startsWith('@name ')) {
2181
+ this.annotationBuffer.name = text.slice(6).trim();
2182
+ }
2183
+ if (text.startsWith('@description ')) {
2184
+ this.annotationBuffer.description = text.slice(13).trim();
2185
+ }
2186
+ }
2187
+ takeAnnotations() {
2188
+ const meta = { ...this.annotationBuffer };
2189
+ this.annotationBuffer = {
2190
+ name: null,
2191
+ description: null,
2192
+ };
2193
+ return meta;
2194
+ }
2195
+ // -------------------------------------------------------------------------
2196
+ // #region Errors
2197
+ // -------------------------------------------------------------------------
2198
+ syntaxError(details, token, expected) {
2199
+ const lines = this.dsl.split(/\r?\n/);
2200
+ const lineIdx = token.line - 1;
2201
+ const lineBefore = lineIdx > 0 ? lines[lineIdx - 1] : '';
2202
+ const current = lines[lineIdx];
2203
+ const linesAfter = lineIdx + 1 < lines.length ? lines[lineIdx + 1] : '';
2204
+ const wave = ' '.repeat(Math.max(0, token.column - 1)) + '~'.repeat(token.value.length);
2205
+ const lineNumWidth = String(token.line + 1).length;
2206
+ const num = (n) => String(n).padStart(lineNumWidth, ' ');
2207
+ let context = '';
2208
+ if (lineBefore.trim() !== '') {
2209
+ context += `${num(token.line - 1)} | ${lineBefore}\n`;
2210
+ }
2211
+ context += `${num(token.line)} | ${current}\n`;
2212
+ context += `${' '.repeat(lineNumWidth)} | ${wave}\n`;
2213
+ if (linesAfter.trim() !== '') {
2214
+ context += `${num(token.line + 1)} | ${linesAfter}`;
2215
+ }
2216
+ let finalDetails = details;
2217
+ if (expected && expected?.length > 0) {
2218
+ const actual = token.value;
2219
+ const suggestion = this.suggest(actual, expected);
2220
+ const detailsMsg = `${details}\nDetails: Unexpected value token \`${actual}\``;
2221
+ finalDetails = suggestion ? `${detailsMsg} Did you mean \`${suggestion}\`?` : detailsMsg;
2222
+ }
2223
+ throw new AbilityDSLSyntaxError_1.AbilityDSLSyntaxError(token.line, token.column, context + '\n', finalDetails);
2224
+ }
2225
+ getLine(lineNumber) {
2226
+ return this.dsl.split(/\r?\n/)[lineNumber - 1] ?? '';
2227
+ }
2228
+ suggest(actual, expectedTypes) {
2229
+ const candidates = [];
2230
+ for (const type of expectedTypes) {
2231
+ candidates.push(type);
2232
+ }
2233
+ const uniqueCandidates = [...new Set(candidates)];
2234
+ let best = null;
2235
+ let bestDist = 3;
2236
+ for (const candidate of uniqueCandidates) {
2237
+ const d = this.levenshteinDistance(actual.toLowerCase(), candidate.toLowerCase());
2238
+ if (d < bestDist) {
2239
+ bestDist = d;
2240
+ best = candidate;
2241
+ }
2242
+ }
2243
+ return best;
2244
+ }
2245
+ levenshteinDistance(a, b) {
2246
+ const matrix = Array(b.length + 1)
2247
+ .fill(null)
2248
+ .map(() => Array(a.length + 1).fill(null));
2249
+ for (let i = 0; i <= a.length; i++)
2250
+ matrix[0][i] = i;
2251
+ for (let j = 0; j <= b.length; j++)
2252
+ matrix[j][0] = j;
2253
+ for (let j = 1; j <= b.length; j++) {
2254
+ for (let i = 1; i <= a.length; i++) {
2255
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
2256
+ matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + cost);
2257
+ }
2258
+ }
2259
+ return matrix[b.length][a.length];
2260
+ }
2261
+ // -------------------------------------------------------------------------
2262
+ // #region Helper / lookahead methods
2263
+ // -------------------------------------------------------------------------
2264
+ consumeOneOf(types, message) {
2265
+ const token = this.peek();
2266
+ for (const t of types) {
2267
+ if (token && token.code === t) {
2268
+ return this.advance();
2269
+ }
2270
+ }
2271
+ const expected = types.map(t => t).join(', ');
2272
+ const actual = token ? token.value : AbilityDSLToken_1.AbilityDSLToken.EOF;
2273
+ const suggestion = this.suggest(actual, types);
2274
+ const details = `${message}\nDetails: Unexpected token \`${actual}\`, expected one of: ${expected}.`;
2275
+ const finalMsg = suggestion ? `${details} Did you mean \`${suggestion}\`?` : details;
2276
+ this.syntaxError(finalMsg, token ?? this.tokens[this.tokens.length - 1]);
2277
+ }
2278
+ consume(type, message) {
2279
+ const token = this.peek();
2280
+ if (token && token.code === type) {
2281
+ return this.advance();
2282
+ }
2283
+ const expected = type;
2284
+ const actual = token ? token.value : AbilityDSLToken_1.AbilityDSLToken.EOF;
2285
+ const suggestion = this.suggest(actual, [type]);
2286
+ const details = `${message}\nDetails: Unexpected token \`${actual}\`, expected "${expected}".`;
2287
+ const finalMsg = suggestion ? `${details} Did you mean \`${suggestion}\`?` : details;
2288
+ this.syntaxError(finalMsg, token ?? this.tokens[this.tokens.length - 1]);
2289
+ }
2290
+ check(type) {
2291
+ if (this.isAtEnd())
2292
+ return false;
2293
+ return this.peek().code === type;
2294
+ }
2295
+ isStartOfPolicy() {
2296
+ return this.check(AbilityDSLToken_1.AbilityDSLToken.EFFECT);
2297
+ }
2298
+ isStartOfGroup() {
2299
+ return this.check(AbilityDSLToken_1.AbilityDSLToken.ALL) || this.check(AbilityDSLToken_1.AbilityDSLToken.ANY);
2300
+ }
2301
+ advance() {
2302
+ return this.tokens[this.pos++];
2303
+ }
2304
+ peek() {
2305
+ return this.tokens[this.pos];
2306
+ }
2307
+ isAtEnd() {
2308
+ return this.peek().code === AbilityDSLToken_1.AbilityDSLToken.EOF;
2309
+ }
2310
+ }
2311
+ exports.AbilityDSLParser = AbilityDSLParser;
2312
+
2313
+
2314
+ /***/ }),
2315
+
2316
+ /***/ 883:
2317
+ /***/ ((__unused_webpack_module, exports) => {
2318
+
2319
+
2320
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2321
+ exports.AbilityDSLSyntaxError = void 0;
2322
+ class AbilityDSLSyntaxError extends Error {
2323
+ line;
2324
+ column;
2325
+ context;
2326
+ details;
2327
+ _formattedMessage;
2328
+ _originalStack;
2329
+ constructor(line, column, context, // строка DSL + ^ + соседние строки
2330
+ details) {
2331
+ super(details.split('\n')[0]); // message = только первая строка
2332
+ this.line = line;
2333
+ this.column = column;
2334
+ this.context = context;
2335
+ this.details = details;
2336
+ this.name = 'AbilityDSLSyntaxError';
2337
+ if (Error.captureStackTrace) {
2338
+ Error.captureStackTrace(this, AbilityDSLSyntaxError);
2339
+ }
2340
+ this._originalStack = this.stack;
2341
+ this._formattedMessage = this.formatMessage();
2342
+ Object.defineProperty(this, 'stack', {
2343
+ get: () => this._formattedMessage,
2344
+ configurable: true,
2345
+ });
2346
+ }
2347
+ static supportsColor() {
2348
+ return typeof process !== 'undefined' && process.stdout?.isTTY;
2349
+ }
2350
+ formatMessage() {
2351
+ const useColor = AbilityDSLSyntaxError.supportsColor();
2352
+ const BOLD = useColor ? '\x1b[1m' : '';
2353
+ const RED = useColor ? '\x1b[31m' : '';
2354
+ const ORANGE = useColor ? '\x1b[33;1m' : '';
2355
+ const GRAY = useColor ? '\x1b[90m' : '';
2356
+ const RESET = useColor ? '\x1b[0m' : '';
2357
+ const lines = this.context.split('\n');
2358
+ // Find line with ^
2359
+ const pointerIndex = lines.findIndex(l => l.includes('^') || l.includes('~'));
2360
+ const commentIndex = lines.findIndex(l => l.trim().includes('#'));
2361
+ const formattedLines = lines.map((line, idx) => {
2362
+ if (idx === pointerIndex - 1) {
2363
+ // Error line
2364
+ return `${BOLD}${ORANGE}${line}${RESET}`;
2365
+ }
2366
+ if (idx === pointerIndex) {
2367
+ // Error with ~~~~~
2368
+ return `${RED}${line}${RESET}`;
2369
+ }
2370
+ // Comments # ...
2371
+ if (idx === commentIndex) {
2372
+ return `${GRAY}${line}${RESET}`;
2373
+ }
2374
+ return line;
2375
+ });
2376
+ const contextBlock = formattedLines.join('\n');
2377
+ return `${BOLD}${RED}${this.name}: ${this.details}${RESET}\n\n` + contextBlock;
2378
+ }
2379
+ toString() {
2380
+ return this._formattedMessage;
2381
+ }
2382
+ }
2383
+ exports.AbilityDSLSyntaxError = AbilityDSLSyntaxError;
2384
+
2385
+
2386
+ /***/ }),
2387
+
2388
+ /***/ 325:
2389
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
2390
+
2391
+
2392
+ var __importDefault = (this && this.__importDefault) || function (mod) {
2393
+ return (mod && mod.__esModule) ? mod : { "default": mod };
2394
+ };
2395
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2396
+ exports.AbilityDSLToken = void 0;
2397
+ const AbilityCode_1 = __importDefault(__webpack_require__(301));
2398
+ /**
2399
+ * Represents a single token produced by the Ability DSL lexer.
2400
+ * Each token carries a type (e.g., EFFECT, IDENTIFIER, STRING) and its raw string value.
2401
+ */
2402
+ class AbilityDSLToken extends AbilityCode_1.default {
2403
+ /** The literal text of the token as it appeared in the input (e.g., "permit", "user.roles", "admin"). */
2404
+ value = '';
2405
+ /** The line number in DSL */
2406
+ line = 1;
2407
+ /** The column in dsl */
2408
+ column = 1;
2409
+ constructor(type, value, line, column) {
2410
+ super(type);
2411
+ this.value = value;
2412
+ this.line = line;
2413
+ this.column = column;
2414
+ }
2415
+ /**
2416
+ * Returns a human-readable representation of the token, useful for debugging.
2417
+ * Example output: "AbilityDSLToken([EFFECT] permit"
2418
+ */
2419
+ toString() {
2420
+ return `AbilityDSLToken([${this.code}] "${this.value}" at ${this.line}:${this.column})`;
2421
+ }
2422
+ static EFFECT = 'EFFECT';
2423
+ static IF = 'IF';
2424
+ static PERMISSION = 'PERMISSION';
2425
+ static IDENTIFIER = 'IDENTIFIER';
2426
+ static COLON = 'COLON';
2427
+ static COMMA = 'COMMA';
2428
+ static DOT = 'DOT';
2429
+ static LBRACKET = 'LBRACKET';
2430
+ static RBRACKET = 'RBRACKET';
2431
+ static ALL = 'ALL';
2432
+ static ANY = 'ANY';
2433
+ static OF = 'OF';
2434
+ static EOF = 'EOF';
2435
+ static COMMENT = 'COMMENT';
2436
+ static EQ = 'EQ';
2437
+ static CONTAINS = 'CONTAINS';
2438
+ static IN = 'IN';
2439
+ static NOT_IN = 'NOT_IN';
2440
+ static NOT_CONTAINS = 'NOT_CONTAINS';
2441
+ static GT = 'GT';
2442
+ static GTE = 'GTE';
2443
+ static LT = 'LT';
2444
+ static LTE = 'LTE';
2445
+ static NULL = 'NULL';
2446
+ static EQ_NULL = 'EQ_NULL';
2447
+ static NOT_EQ_NULL = 'NOT_EQ_NULL';
2448
+ static LEN_GT = 'LEN_GT';
2449
+ static LEN_LT = 'LEN_LT';
2450
+ static LEN_EQ = 'LEN_EQ';
2451
+ static NOT_EQ = 'NOT_EQ';
2452
+ static STRING = 'STRING';
2453
+ static NUMBER = 'NUMBER';
2454
+ static BOOLEAN = 'BOOLEAN';
2455
+ static SYMBOL = 'SYMBOL';
2456
+ static KEYWORD = 'KEYWORD';
2457
+ }
2458
+ exports.AbilityDSLToken = AbilityDSLToken;
2459
+
2460
+
2461
+ /***/ }),
2462
+
2463
+ /***/ 909:
2464
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
2465
+
2466
+
2467
+ var __importDefault = (this && this.__importDefault) || function (mod) {
2468
+ return (mod && mod.__esModule) ? mod : { "default": mod };
2469
+ };
2470
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2471
+ exports.AbilityJSONParser = void 0;
2472
+ const AbilityCondition_1 = __importDefault(__webpack_require__(167));
2473
+ const AbilityRule_1 = __webpack_require__(306);
2474
+ const AbilityRuleSet_1 = __webpack_require__(56);
2475
+ const AbilityCompare_1 = __importDefault(__webpack_require__(413));
2476
+ const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(179));
2477
+ const AbilityPolicy_1 = __webpack_require__(278);
2478
+ class AbilityJSONParser {
2479
+ /**
2480
+ * Parses an array of policy configurations into an array of AbilityPolicy instances.
2481
+ * @param configs - Array of policy configurations
2482
+ * @returns Array of AbilityPolicy instances
2483
+ */
2484
+ static parse(configs) {
2485
+ return configs.map(config => AbilityJSONParser.parsePolicy(config));
2486
+ }
2487
+ static parsePolicy(config) {
2488
+ const { id, name, ruleSet, compareMethod, permission, effect } = config;
2489
+ // Create the empty policy
2490
+ const policy = new AbilityPolicy_1.AbilityPolicy({
2491
+ name,
2492
+ id,
2493
+ permission: permission,
2494
+ effect: new AbilityPolicyEffect_1.default(effect),
2495
+ });
2496
+ policy.compareMethod = new AbilityCompare_1.default(compareMethod);
2497
+ ruleSet.forEach(ruleSetConfig => {
2498
+ policy.addRuleSet(AbilityRuleSet_1.AbilityRuleSet.fromJSON(ruleSetConfig));
2499
+ });
2500
+ return policy;
2501
+ }
2502
+ static parseRule(config) {
2503
+ const { id, name, subject, resource, condition } = config;
2504
+ return new AbilityRule_1.AbilityRule({
2505
+ id,
2506
+ name,
2507
+ subject,
2508
+ resource,
2509
+ condition: new AbilityCondition_1.default(condition),
2510
+ });
2511
+ }
2512
+ /**
2513
+ * Parse the config JSON format to Group class instance
2514
+ */
2515
+ static parseRuleSet(config) {
2516
+ const { id, name, rules, compareMethod } = config;
2517
+ const ruleSet = new AbilityRuleSet_1.AbilityRuleSet({
2518
+ compareMethod: new AbilityCompare_1.default(compareMethod),
2519
+ name,
2520
+ id,
2521
+ });
2522
+ // Adding rules if exists
2523
+ if (rules && rules.length > 0) {
2524
+ const abilityRules = rules.map(ruleConfig => AbilityRule_1.AbilityRule.fromJSON(ruleConfig));
2525
+ ruleSet.addRules(abilityRules);
2526
+ }
2527
+ return ruleSet;
2528
+ }
2529
+ static ruleToJSON(rule) {
2530
+ return {
2531
+ id: rule.id,
2532
+ name: rule.name,
2533
+ subject: rule.subject,
2534
+ resource: rule.resource,
2535
+ condition: rule.condition.code,
2536
+ };
2537
+ }
2538
+ static ruleSetToJSON(ruleSet) {
2539
+ return {
2540
+ id: ruleSet.id.toString(),
2541
+ name: ruleSet.name.toString(),
2542
+ compareMethod: ruleSet.compareMethod.code.toString(),
2543
+ rules: ruleSet.rules.map(rule => AbilityJSONParser.ruleToJSON(rule)),
2544
+ };
2545
+ }
2546
+ static policyToJSON(policy) {
2547
+ return {
2548
+ id: policy.id.toString(),
2549
+ name: policy.name.toString(),
2550
+ compareMethod: policy.compareMethod.code.toString(),
2551
+ ruleSet: policy.ruleSet.map(ruleSet => AbilityJSONParser.ruleSetToJSON(ruleSet)),
2552
+ permission: policy.permission,
2553
+ effect: policy.effect.code,
2554
+ };
2555
+ }
2556
+ static toJSON(policies) {
2557
+ return policies.map(policy => AbilityJSONParser.policyToJSON(policy));
2558
+ }
2559
+ }
2560
+ exports.AbilityJSONParser = AbilityJSONParser;
767
2561
 
768
2562
 
769
2563
  /***/ })