@via-profit/ability 2.1.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/CONTRIBUTING.md +14 -0
- package/LICENSE +21 -0
- package/README.md +809 -66
- package/SECURITY.md +33 -0
- package/dist/AbilityExplain.d.ts +27 -0
- package/dist/AbilityParser.d.ts +45 -4
- package/dist/AbilityPolicy.d.ts +11 -1
- package/dist/AbilityResolver.d.ts +2 -0
- package/dist/AbilityRule.d.ts +10 -2
- package/dist/AbilityRuleSet.d.ts +6 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +383 -53
- package/package.json +11 -2
- package/assets/ability-01.drawio.png +0 -0
- package/build/playground.js +0 -456
- package/build/playground.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -70,11 +70,26 @@ class AbilityCondition extends AbilityCode_1.default {
|
|
|
70
70
|
static in = new AbilityCondition('in');
|
|
71
71
|
static not_in = new AbilityCondition('not in');
|
|
72
72
|
static fromLiteral(literal) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
switch (literal) {
|
|
74
|
+
case 'equal':
|
|
75
|
+
return this.equal;
|
|
76
|
+
case 'not_equal':
|
|
77
|
+
return this.not_equal;
|
|
78
|
+
case 'more_than':
|
|
79
|
+
return this.more_than;
|
|
80
|
+
case 'less_than':
|
|
81
|
+
return this.less_than;
|
|
82
|
+
case 'less_or_equal':
|
|
83
|
+
return this.less_or_equal;
|
|
84
|
+
case 'more_or_equal':
|
|
85
|
+
return this.more_or_equal;
|
|
86
|
+
case 'in':
|
|
87
|
+
return this.in;
|
|
88
|
+
case 'not_in':
|
|
89
|
+
return this.not_in;
|
|
90
|
+
default:
|
|
91
|
+
throw new AbilityError_1.AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
|
|
76
92
|
}
|
|
77
|
-
return new AbilityCondition(code);
|
|
78
93
|
}
|
|
79
94
|
get literal() {
|
|
80
95
|
const literal = Object.keys(AbilityCondition).find(member => {
|
|
@@ -113,6 +128,74 @@ class AbilityParserError extends Error {
|
|
|
113
128
|
exports.AbilityParserError = AbilityParserError;
|
|
114
129
|
|
|
115
130
|
|
|
131
|
+
/***/ }),
|
|
132
|
+
|
|
133
|
+
/***/ 363:
|
|
134
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
138
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
139
|
+
};
|
|
140
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
141
|
+
exports.AbilityExplainPolicy = exports.AbilityExplainRuleSet = exports.AbilityExplainRule = exports.AbilityExplain = void 0;
|
|
142
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
143
|
+
class AbilityExplain {
|
|
144
|
+
type;
|
|
145
|
+
children;
|
|
146
|
+
name;
|
|
147
|
+
match;
|
|
148
|
+
constructor(config, children = []) {
|
|
149
|
+
this.type = config.type;
|
|
150
|
+
this.children = children;
|
|
151
|
+
this.name = config.name;
|
|
152
|
+
this.match = config.match;
|
|
153
|
+
}
|
|
154
|
+
toString(indent = 0) {
|
|
155
|
+
const pad = ' '.repeat(indent);
|
|
156
|
+
const mark = this.match.code === AbilityMatch_1.default.match.code ? '✓' : '✗';
|
|
157
|
+
let out = `${pad}${mark} ${this.type} «${this.name}» is ${this.match.code}`;
|
|
158
|
+
this.children.forEach(child => {
|
|
159
|
+
out += '\n' + child.toString(indent + 1);
|
|
160
|
+
});
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.AbilityExplain = AbilityExplain;
|
|
165
|
+
class AbilityExplainRule extends AbilityExplain {
|
|
166
|
+
constructor(rule) {
|
|
167
|
+
super({
|
|
168
|
+
type: 'rule',
|
|
169
|
+
match: rule.state,
|
|
170
|
+
name: rule.name,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.AbilityExplainRule = AbilityExplainRule;
|
|
175
|
+
class AbilityExplainRuleSet extends AbilityExplain {
|
|
176
|
+
constructor(ruleSet) {
|
|
177
|
+
const children = ruleSet.rules.map(rule => new AbilityExplainRule(rule));
|
|
178
|
+
super({
|
|
179
|
+
type: 'ruleSet',
|
|
180
|
+
match: ruleSet.state,
|
|
181
|
+
name: ruleSet.name,
|
|
182
|
+
}, children);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
|
|
186
|
+
class AbilityExplainPolicy extends AbilityExplain {
|
|
187
|
+
constructor(policy) {
|
|
188
|
+
const children = policy.ruleSet.map(ruleSet => new AbilityExplainRuleSet(ruleSet));
|
|
189
|
+
super({
|
|
190
|
+
type: 'policy',
|
|
191
|
+
name: policy.name,
|
|
192
|
+
match: policy.matchState,
|
|
193
|
+
}, children);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
197
|
+
|
|
198
|
+
|
|
116
199
|
/***/ }),
|
|
117
200
|
|
|
118
201
|
/***/ 909:
|
|
@@ -148,20 +231,6 @@ exports.AbilityParser = void 0;
|
|
|
148
231
|
const AbilityError_1 = __webpack_require__(122);
|
|
149
232
|
const AbilityCondition_1 = __importDefault(__webpack_require__(261));
|
|
150
233
|
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
234
|
/**
|
|
166
235
|
* Sets a value in a nested object structure based on a dot/bracket notation path.
|
|
167
236
|
* @param object - The target object to modify.
|
|
@@ -169,52 +238,222 @@ class AbilityParser {
|
|
|
169
238
|
* @param value - The value to set at the specified path.
|
|
170
239
|
*/
|
|
171
240
|
static setValueDotValue(object, path, value) {
|
|
172
|
-
|
|
241
|
+
if (!path || path.trim().length === 0) {
|
|
242
|
+
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
243
|
+
}
|
|
244
|
+
const way = path.replace(/\[/g, '.').replace(/]/g, '').split('.');
|
|
173
245
|
const last = way.pop();
|
|
174
246
|
if (!last) {
|
|
175
247
|
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
176
248
|
}
|
|
177
|
-
way.reduce((
|
|
178
|
-
|
|
179
|
-
|
|
249
|
+
const lastObj = way.reduce((acc, key, index, array) => {
|
|
250
|
+
const currentValue = acc[key];
|
|
251
|
+
const nextKey = array[index + 1];
|
|
252
|
+
const shouldBeArray = nextKey !== undefined && isFinite(Number(nextKey));
|
|
253
|
+
if (currentValue === undefined || currentValue === null) {
|
|
254
|
+
// Create missing property
|
|
255
|
+
const newValue = shouldBeArray ? [] : {};
|
|
256
|
+
acc[key] = newValue;
|
|
257
|
+
return newValue;
|
|
180
258
|
}
|
|
181
|
-
|
|
182
|
-
|
|
259
|
+
if (typeof currentValue !== 'object') {
|
|
260
|
+
throw new AbilityError_1.AbilityParserError(`Cannot set property '${key}' on non-object value at path: ${path}`);
|
|
261
|
+
}
|
|
262
|
+
return currentValue;
|
|
263
|
+
}, object);
|
|
264
|
+
const existingValue = lastObj[last];
|
|
265
|
+
if (existingValue !== undefined &&
|
|
266
|
+
typeof existingValue === 'object' &&
|
|
267
|
+
existingValue !== null &&
|
|
268
|
+
!Array.isArray(existingValue)) {
|
|
269
|
+
throw new AbilityError_1.AbilityParserError(`Cannot set primitive value on existing object at path: ${path}`);
|
|
270
|
+
}
|
|
271
|
+
lastObj[last] = value;
|
|
183
272
|
}
|
|
184
273
|
/**
|
|
185
274
|
* Generates TypeScript type definitions based on the provided policies.
|
|
186
275
|
* @param policies - An array of AbilityPolicy instances.
|
|
187
|
-
* @
|
|
188
|
-
* @returns A record containing the generated type definitions.
|
|
276
|
+
* @returns A generated type definitions.
|
|
189
277
|
*/
|
|
190
|
-
static generateTypeDefs(policies
|
|
191
|
-
|
|
278
|
+
static generateTypeDefs(policies) {
|
|
279
|
+
// Structure to store types: { [action]: { [subjectPath]: type } }
|
|
280
|
+
const typeStructure = {};
|
|
281
|
+
// Iterate through all policies
|
|
192
282
|
policies.forEach(policy => {
|
|
283
|
+
const action = policy.action;
|
|
284
|
+
// Initialize object for action if it doesn't exist
|
|
285
|
+
if (!typeStructure[action]) {
|
|
286
|
+
typeStructure[action] = {};
|
|
287
|
+
}
|
|
288
|
+
// Iterate through all ruleSets in the policy
|
|
193
289
|
policy.ruleSet.forEach(ruleSet => {
|
|
290
|
+
// Iterate through all rules in the ruleSet
|
|
194
291
|
ruleSet.rules.forEach(rule => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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;
|
|
292
|
+
const subjectPath = rule.subject;
|
|
293
|
+
const existingType = typeStructure[action][subjectPath];
|
|
294
|
+
const ruleType = this.determineTypeFromRule(rule);
|
|
295
|
+
// If a type already exists for this path, create a union
|
|
296
|
+
if (existingType && existingType !== ruleType) {
|
|
297
|
+
typeStructure[action][subjectPath] = `${existingType} | ${ruleType}`;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
typeStructure[action][subjectPath] = ruleType;
|
|
211
301
|
}
|
|
212
|
-
AbilityParser.setValueDotValue(record, rule.subject, value);
|
|
213
302
|
});
|
|
214
303
|
});
|
|
215
304
|
});
|
|
216
|
-
|
|
217
|
-
|
|
305
|
+
// Transform flat structure into nested structure for easier use
|
|
306
|
+
const nestedStructure = this.buildNestedStructure(typeStructure);
|
|
307
|
+
return this.formatTypeDefinitions(nestedStructure);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Determines TypeScript type based on the rule
|
|
311
|
+
* @param rule - The rule to analyze
|
|
312
|
+
* @returns TypeScript type as string
|
|
313
|
+
*/
|
|
314
|
+
static determineTypeFromRule(rule) {
|
|
315
|
+
// Numeric comparisons - always number
|
|
316
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.more_than) ||
|
|
317
|
+
rule.condition.isEqual(AbilityCondition_1.default.less_than) ||
|
|
318
|
+
rule.condition.isEqual(AbilityCondition_1.default.more_or_equal) ||
|
|
319
|
+
rule.condition.isEqual(AbilityCondition_1.default.less_or_equal)) {
|
|
320
|
+
return 'number';
|
|
321
|
+
}
|
|
322
|
+
// Array operations
|
|
323
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.in) ||
|
|
324
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_in)) {
|
|
325
|
+
return this.getArrayType(rule.resource);
|
|
326
|
+
}
|
|
327
|
+
// Equality/Inequality operations
|
|
328
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.equal) ||
|
|
329
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_equal)) {
|
|
330
|
+
return this.getPrimitiveType(rule.resource);
|
|
331
|
+
}
|
|
332
|
+
return 'any';
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Gets TypeScript type for array values
|
|
336
|
+
* @param resource - The resource value to analyze
|
|
337
|
+
* @returns TypeScript array type as string
|
|
338
|
+
*/
|
|
339
|
+
static getArrayType(resource) {
|
|
340
|
+
if (Array.isArray(resource)) {
|
|
341
|
+
if (resource.length === 0)
|
|
342
|
+
return 'any[]';
|
|
343
|
+
// Determine types of array elements
|
|
344
|
+
const elementTypes = new Set(resource.map(item => this.getPrimitiveType(item)));
|
|
345
|
+
const elementType = elementTypes.size === 1
|
|
346
|
+
? Array.from(elementTypes)[0]
|
|
347
|
+
: `(${Array.from(elementTypes).join(' | ')})`;
|
|
348
|
+
return `${elementType}[]`;
|
|
349
|
+
}
|
|
350
|
+
// If resource is not an array but condition is in/not_in,
|
|
351
|
+
// it expects an array of such elements
|
|
352
|
+
return `${this.getPrimitiveType(resource)}[]`;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Gets primitive TypeScript type for a value
|
|
356
|
+
* @param value - The value to analyze
|
|
357
|
+
* @returns TypeScript primitive type as string
|
|
358
|
+
*/
|
|
359
|
+
static getPrimitiveType(value) {
|
|
360
|
+
if (value === null)
|
|
361
|
+
return 'null';
|
|
362
|
+
if (value === undefined)
|
|
363
|
+
return 'undefined';
|
|
364
|
+
switch (typeof value) {
|
|
365
|
+
case 'string':
|
|
366
|
+
return 'string';
|
|
367
|
+
case 'number':
|
|
368
|
+
return 'number';
|
|
369
|
+
case 'boolean':
|
|
370
|
+
return 'boolean';
|
|
371
|
+
case 'object':
|
|
372
|
+
if (Array.isArray(value)) {
|
|
373
|
+
return 'array'; // special marker, handled separately
|
|
374
|
+
}
|
|
375
|
+
return 'object';
|
|
376
|
+
default:
|
|
377
|
+
return 'any';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Builds nested structure from flat paths
|
|
382
|
+
* Example: 'user.profile.name' -> { user: { profile: { name: 'string' } } }
|
|
383
|
+
* @param flatStructure - Flat structure with dot notation paths
|
|
384
|
+
* @returns Nested object structure
|
|
385
|
+
*/
|
|
386
|
+
static buildNestedStructure(flatStructure) {
|
|
387
|
+
const result = {};
|
|
388
|
+
Object.entries(flatStructure).forEach(([action, paths]) => {
|
|
389
|
+
result[action] = {};
|
|
390
|
+
Object.entries(paths).forEach(([path, type]) => {
|
|
391
|
+
const parts = path.split('.');
|
|
392
|
+
let current = result[action];
|
|
393
|
+
// Iterate through all parts except the last one
|
|
394
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
395
|
+
const part = parts[i];
|
|
396
|
+
const currentValue = current[part];
|
|
397
|
+
if (!currentValue || typeof currentValue !== 'object') {
|
|
398
|
+
const newObj = {};
|
|
399
|
+
current[part] = newObj;
|
|
400
|
+
current = newObj;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
current = currentValue;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// Set type for the last part
|
|
407
|
+
const lastPart = parts[parts.length - 1];
|
|
408
|
+
current[lastPart] = type;
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Formats type structure into a string
|
|
415
|
+
* @param structure - Nested type structure
|
|
416
|
+
* @returns Formatted TypeScript type definition string
|
|
417
|
+
*/
|
|
418
|
+
static formatTypeDefinitions(structure) {
|
|
419
|
+
let output = '// Automatically generated by via-profit/ability\n';
|
|
420
|
+
output += '// Do not edit manually\n\n';
|
|
421
|
+
output += 'export type Resources = {\n';
|
|
422
|
+
// Sort actions for stable output
|
|
423
|
+
const sortedActions = Object.keys(structure).sort();
|
|
424
|
+
sortedActions.forEach(action => {
|
|
425
|
+
output += ` ['${action}']: {\n`;
|
|
426
|
+
output += this.formatNestedObject(structure[action], 4);
|
|
427
|
+
output += ' };\n';
|
|
428
|
+
});
|
|
429
|
+
output += '}\n';
|
|
430
|
+
return output;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Recursively formats nested object
|
|
434
|
+
* @param obj - Object to format
|
|
435
|
+
* @param indent - Current indentation level
|
|
436
|
+
* @returns Formatted string
|
|
437
|
+
*/
|
|
438
|
+
static formatNestedObject(obj, indent) {
|
|
439
|
+
const spaces = ' '.repeat(indent);
|
|
440
|
+
let output = '';
|
|
441
|
+
// Sort keys for stable output
|
|
442
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
443
|
+
sortedKeys.forEach(key => {
|
|
444
|
+
const value = obj[key];
|
|
445
|
+
if (typeof value === 'object' && value !== null) {
|
|
446
|
+
// Nested object
|
|
447
|
+
output += `${spaces}readonly ${key}: {\n`;
|
|
448
|
+
output += this.formatNestedObject(value, indent + 2);
|
|
449
|
+
output += `${spaces}};\n`;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Primitive type
|
|
453
|
+
output += `${spaces}readonly ${key}: ${value};\n`;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return output;
|
|
218
457
|
}
|
|
219
458
|
}
|
|
220
459
|
exports.AbilityParser = AbilityParser;
|
|
@@ -236,6 +475,8 @@ const AbilityRuleSet_1 = __importDefault(__webpack_require__(402));
|
|
|
236
475
|
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
237
476
|
const AbilityCompare_1 = __importDefault(__webpack_require__(923));
|
|
238
477
|
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(277));
|
|
478
|
+
const AbilityExplain_1 = __webpack_require__(363);
|
|
479
|
+
const AbilityError_1 = __webpack_require__(122);
|
|
239
480
|
class AbilityPolicy {
|
|
240
481
|
matchState = AbilityMatch_1.default.pending;
|
|
241
482
|
/**
|
|
@@ -262,15 +503,17 @@ class AbilityPolicy {
|
|
|
262
503
|
*/
|
|
263
504
|
id;
|
|
264
505
|
/**
|
|
265
|
-
*
|
|
506
|
+
* Running the `enforce` or `resolve` method
|
|
507
|
+
* will select only those from all passed policies that fall under the specified action.
|
|
266
508
|
*/
|
|
267
509
|
action;
|
|
268
510
|
constructor(params) {
|
|
269
|
-
const { name, id, action, effect } = params;
|
|
511
|
+
const { name, id, action, effect, compareMethod = AbilityCompare_1.default.and } = params;
|
|
270
512
|
this.name = name;
|
|
271
513
|
this.id = id;
|
|
272
514
|
this.action = action;
|
|
273
515
|
this.effect = effect;
|
|
516
|
+
this.compareMethod = compareMethod;
|
|
274
517
|
}
|
|
275
518
|
/**
|
|
276
519
|
* Add rule set to the policy
|
|
@@ -304,6 +547,20 @@ class AbilityPolicy {
|
|
|
304
547
|
}
|
|
305
548
|
return this.matchState;
|
|
306
549
|
}
|
|
550
|
+
explain() {
|
|
551
|
+
if (this.matchState === AbilityMatch_1.default.pending) {
|
|
552
|
+
throw new AbilityError_1.AbilityError('First, run the check method, then explain');
|
|
553
|
+
}
|
|
554
|
+
return new AbilityExplain_1.AbilityExplainPolicy(this);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Parses an array of policy configurations into an array of AbilityPolicy instances.
|
|
558
|
+
* @param configs - Array of policy configurations
|
|
559
|
+
* @returns Array of AbilityPolicy instances
|
|
560
|
+
*/
|
|
561
|
+
static parseAll(configs) {
|
|
562
|
+
return configs.map(config => this.parse(config));
|
|
563
|
+
}
|
|
307
564
|
/**
|
|
308
565
|
* Parse the config JSON format to Policy class instance
|
|
309
566
|
*/
|
|
@@ -371,6 +628,7 @@ exports.AbilityResolver = void 0;
|
|
|
371
628
|
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(277));
|
|
372
629
|
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
373
630
|
const AbilityError_1 = __webpack_require__(122);
|
|
631
|
+
const AbilityExplain_1 = __webpack_require__(363);
|
|
374
632
|
class AbilityResolver {
|
|
375
633
|
policies;
|
|
376
634
|
constructor(policyOrListOfPolicies) {
|
|
@@ -392,6 +650,11 @@ class AbilityResolver {
|
|
|
392
650
|
this.policies = filteredPolicies;
|
|
393
651
|
return this;
|
|
394
652
|
}
|
|
653
|
+
resolveWithExplain(action, resource) {
|
|
654
|
+
return this.resolve(action, resource).policies.map(policy => {
|
|
655
|
+
return new AbilityExplain_1.AbilityExplainPolicy(policy);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
395
658
|
enforce(action, resource) {
|
|
396
659
|
const resolver = this.resolve(action, resource);
|
|
397
660
|
if (resolver.isDeny()) {
|
|
@@ -489,8 +752,8 @@ class AbilityRule {
|
|
|
489
752
|
*/
|
|
490
753
|
constructor(params) {
|
|
491
754
|
const { id, name, subject, resource, condition } = params;
|
|
492
|
-
this.
|
|
493
|
-
this.
|
|
755
|
+
this.name = name || `${JSON.stringify(subject)} ${condition.code} ${JSON.stringify(resource)}`;
|
|
756
|
+
this.id = id || this.name;
|
|
494
757
|
this.subject = subject;
|
|
495
758
|
this.resource = resource;
|
|
496
759
|
this.condition = condition;
|
|
@@ -628,6 +891,62 @@ class AbilityRule {
|
|
|
628
891
|
condition: this.condition.code,
|
|
629
892
|
};
|
|
630
893
|
}
|
|
894
|
+
static equal(subject, resource) {
|
|
895
|
+
return new AbilityRule({
|
|
896
|
+
condition: AbilityCondition_1.default.equal,
|
|
897
|
+
subject,
|
|
898
|
+
resource,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
static notIn(subject, resource) {
|
|
902
|
+
return new AbilityRule({
|
|
903
|
+
condition: AbilityCondition_1.default.not_in,
|
|
904
|
+
subject,
|
|
905
|
+
resource,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
static in(subject, resource) {
|
|
909
|
+
return new AbilityRule({
|
|
910
|
+
condition: AbilityCondition_1.default.in,
|
|
911
|
+
subject,
|
|
912
|
+
resource,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
static notEqual(subject, resource) {
|
|
916
|
+
return new AbilityRule({
|
|
917
|
+
condition: AbilityCondition_1.default.not_equal,
|
|
918
|
+
subject,
|
|
919
|
+
resource,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
static lessThan(subject, resource) {
|
|
923
|
+
return new AbilityRule({
|
|
924
|
+
condition: AbilityCondition_1.default.less_than,
|
|
925
|
+
subject,
|
|
926
|
+
resource,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
static lessOrEqual(subject, resource) {
|
|
930
|
+
return new AbilityRule({
|
|
931
|
+
condition: AbilityCondition_1.default.less_or_equal,
|
|
932
|
+
subject,
|
|
933
|
+
resource,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
static moreThan(subject, resource) {
|
|
937
|
+
return new AbilityRule({
|
|
938
|
+
condition: AbilityCondition_1.default.more_than,
|
|
939
|
+
subject,
|
|
940
|
+
resource,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
static moreOrEqual(subject, resource) {
|
|
944
|
+
return new AbilityRule({
|
|
945
|
+
condition: AbilityCondition_1.default.more_or_equal,
|
|
946
|
+
subject,
|
|
947
|
+
resource,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
631
950
|
}
|
|
632
951
|
exports.AbilityRule = AbilityRule;
|
|
633
952
|
exports["default"] = AbilityRule;
|
|
@@ -670,8 +989,8 @@ class AbilityRuleSet {
|
|
|
670
989
|
id;
|
|
671
990
|
constructor(params) {
|
|
672
991
|
const { name, id, compareMethod } = params;
|
|
673
|
-
this.name = name;
|
|
674
|
-
this.id = id;
|
|
992
|
+
this.name = name || 'No name';
|
|
993
|
+
this.id = id || this.name;
|
|
675
994
|
this.compareMethod = compareMethod;
|
|
676
995
|
}
|
|
677
996
|
addRule(rule) {
|
|
@@ -727,6 +1046,16 @@ class AbilityRuleSet {
|
|
|
727
1046
|
rules: this.rules.map(rule => rule.export()),
|
|
728
1047
|
};
|
|
729
1048
|
}
|
|
1049
|
+
static and(rules) {
|
|
1050
|
+
return new AbilityRuleSet({
|
|
1051
|
+
compareMethod: AbilityCompare_1.default.and,
|
|
1052
|
+
}).addRules(rules);
|
|
1053
|
+
}
|
|
1054
|
+
static or(rules) {
|
|
1055
|
+
return new AbilityRuleSet({
|
|
1056
|
+
compareMethod: AbilityCompare_1.default.or,
|
|
1057
|
+
}).addRules(rules);
|
|
1058
|
+
}
|
|
730
1059
|
}
|
|
731
1060
|
exports.AbilityRuleSet = AbilityRuleSet;
|
|
732
1061
|
exports["default"] = AbilityRuleSet;
|
|
@@ -764,6 +1093,7 @@ __exportStar(__webpack_require__(277), exports);
|
|
|
764
1093
|
__exportStar(__webpack_require__(668), exports);
|
|
765
1094
|
__exportStar(__webpack_require__(476), exports);
|
|
766
1095
|
__exportStar(__webpack_require__(402), exports);
|
|
1096
|
+
__exportStar(__webpack_require__(363), exports);
|
|
767
1097
|
|
|
768
1098
|
|
|
769
1099
|
/***/ })
|
package/package.json
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@via-profit/ability",
|
|
3
3
|
"support": "https://via-profit.ru",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.1",
|
|
5
5
|
"description": "Via-Profit Ability service",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ability",
|
|
8
8
|
"access",
|
|
9
9
|
"via-profit"
|
|
10
|
-
],
|
|
10
|
+
],
|
|
11
11
|
"main": "./dist/index.js",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">= 17.0.0",
|
|
14
14
|
"npm": ">= 8.19.3"
|
|
15
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"CONTRIBUTING.md",
|
|
22
|
+
"SECURITY.md"
|
|
23
|
+
],
|
|
16
24
|
"scripts": {
|
|
17
25
|
"lint": "tsc --noEmit && eslint --fix .",
|
|
18
26
|
"pretty": "prettier --write ./src",
|
|
27
|
+
"build": "npm run build:dist",
|
|
19
28
|
"build:dev": "cross-env NODE_ENV=development webpack --config ./webpack/webpack-config.ts --color --progress",
|
|
20
29
|
"build:dist": "cross-env NODE_ENV=production webpack --config ./webpack/webpack-config.ts --color --progress",
|
|
21
30
|
"build:playground": "cross-env NODE_ENV=development webpack --config ./webpack/webpack-config-playground.ts --color --progress",
|
|
Binary file
|