@adaas/a-utils 0.1.7 → 0.1.9

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 (35) hide show
  1. package/README.md +393 -6
  2. package/dist/index.d.ts +7 -2
  3. package/dist/index.js +38 -7
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/lib/A-Channel/A-Channel.component.d.ts +19 -0
  6. package/dist/src/lib/A-Channel/A-Channel.component.js +76 -0
  7. package/dist/src/lib/A-Channel/A-Channel.component.js.map +1 -1
  8. package/dist/src/lib/A-Channel/A-Channel.error.d.ts +1 -0
  9. package/dist/src/lib/A-Channel/A-Channel.error.js +1 -0
  10. package/dist/src/lib/A-Channel/A-Channel.error.js.map +1 -1
  11. package/dist/src/lib/A-Config/A-Config.container.js +1 -1
  12. package/dist/src/lib/A-Config/A-Config.container.js.map +1 -1
  13. package/dist/src/lib/A-Manifest/A-Manifest.context.d.ts +52 -0
  14. package/dist/src/lib/A-Manifest/A-Manifest.context.js +154 -0
  15. package/dist/src/lib/A-Manifest/A-Manifest.context.js.map +1 -0
  16. package/dist/src/lib/A-Manifest/A-Manifest.error.d.ts +4 -0
  17. package/dist/src/lib/A-Manifest/A-Manifest.error.js +9 -0
  18. package/dist/src/lib/A-Manifest/A-Manifest.error.js.map +1 -0
  19. package/dist/src/lib/A-Manifest/A-Manifest.types.d.ts +43 -0
  20. package/dist/src/lib/A-Manifest/A-Manifest.types.js +3 -0
  21. package/dist/src/lib/A-Manifest/A-Manifest.types.js.map +1 -0
  22. package/dist/src/lib/A-Manifest/classes/A-ManifestChecker.class.d.ts +13 -0
  23. package/dist/src/lib/A-Manifest/classes/A-ManifestChecker.class.js +24 -0
  24. package/dist/src/lib/A-Manifest/classes/A-ManifestChecker.class.js.map +1 -0
  25. package/index.ts +42 -10
  26. package/package.json +2 -2
  27. package/src/lib/A-Channel/A-Channel.component.ts +70 -2
  28. package/src/lib/A-Channel/A-Channel.error.ts +2 -0
  29. package/src/lib/A-Config/A-Config.container.ts +1 -1
  30. package/src/lib/A-Manifest/A-Manifest.context.ts +198 -0
  31. package/src/lib/A-Manifest/A-Manifest.error.ts +7 -0
  32. package/src/lib/A-Manifest/A-Manifest.types.ts +62 -0
  33. package/src/lib/A-Manifest/README.md +201 -0
  34. package/src/lib/A-Manifest/classes/A-ManifestChecker.class.ts +24 -0
  35. package/tests/A-Manifest.test.ts +290 -0
@@ -0,0 +1,198 @@
1
+ import { A_Component, A_Fragment, A_TypeGuards, A_TYPES__Component_Constructor } from "@adaas/a-concept";
2
+ import { A_UTILS_TYPES__Manifest_Init, A_UTILS_TYPES__Manifest_ComponentLevelConfig, A_UTILS_TYPES__Manifest_AllowedComponents, A_UTILS_TYPES__ManifestRule, A_UTILS_TYPES__ManifestQuery } from "./A-Manifest.types";
3
+ import { A_ManifestError } from "./A-Manifest.error";
4
+ import { A_ManifestChecker } from "./classes/A-ManifestChecker.class";
5
+
6
+
7
+
8
+
9
+
10
+ export class A_Manifest extends A_Fragment {
11
+
12
+ private rules: A_UTILS_TYPES__ManifestRule[] = [];
13
+
14
+ /**
15
+ * A-Manifest is a configuration set that allows to include or exclude component application for the particular methods.
16
+ *
17
+ * For example, if A-Scope provides polymorphic A-Component that applies for All A-Entities in it but you have another component that should be used for only One particular Entity, you can use A-Manifest to specify this behavior.
18
+ *
19
+ *
20
+ * By default if Component is provided in the scope - it applies for all entities in it. However, if you want to exclude some entities or include only some entities for the particular component - you can use A-Manifest to define this behavior.
21
+ *
22
+ * @param config - Array of component configurations
23
+ */
24
+ constructor(config: A_UTILS_TYPES__Manifest_Init = []) {
25
+ super({
26
+ name: 'A-Manifest',
27
+ });
28
+
29
+ this.prepare(config);
30
+ }
31
+
32
+
33
+ /**
34
+ * Should convert received configuration into internal Regexp applicable for internal storage
35
+ */
36
+ protected prepare(config: A_UTILS_TYPES__Manifest_Init) {
37
+ if (!A_TypeGuards.isArray(config))
38
+ throw new A_ManifestError(
39
+ A_ManifestError.ManifestInitializationError,
40
+ `A-Manifest configuration should be an array of configurations`
41
+ );
42
+
43
+ for (const item of config) {
44
+ this.processConfigItem(item);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Process a single configuration item and convert it to internal rules
50
+ */
51
+ private processConfigItem(item: A_UTILS_TYPES__Manifest_ComponentLevelConfig) {
52
+ if (!A_TypeGuards.isComponentConstructor(item.component))
53
+ throw new A_ManifestError(
54
+ A_ManifestError.ManifestInitializationError,
55
+ `A-Manifest configuration item should be a A-Component constructor`
56
+ );
57
+
58
+ const componentRegex = this.constructorToRegex(item.component);
59
+
60
+ // Always add component-level rule first (applies to all methods)
61
+ if (item.apply || item.exclude) {
62
+ const methodRegex = /.*/; // Match all methods
63
+
64
+ this.rules.push({
65
+ componentRegex,
66
+ methodRegex,
67
+ applyRegex: item.apply ? this.allowedComponentsToRegex(item.apply) : undefined,
68
+ excludeRegex: item.exclude ? this.allowedComponentsToRegex(item.exclude) : undefined,
69
+ });
70
+ }
71
+
72
+ // Then add method-level configurations (these will override component-level)
73
+ if (item.methods && item.methods.length > 0) {
74
+ for (const methodConfig of item.methods) {
75
+ const methodRegex = this.methodToRegex(methodConfig.method);
76
+
77
+ this.rules.push({
78
+ componentRegex,
79
+ methodRegex,
80
+ applyRegex: methodConfig.apply ? this.allowedComponentsToRegex(methodConfig.apply) : undefined,
81
+ excludeRegex: methodConfig.exclude ? this.allowedComponentsToRegex(methodConfig.exclude) : undefined,
82
+ });
83
+ }
84
+ }
85
+ }
86
+
87
+
88
+
89
+ /**
90
+ * Convert a constructor to a regex pattern
91
+ */
92
+ private constructorToRegex(ctor: A_TYPES__Component_Constructor): RegExp {
93
+ return new RegExp(`^${this.escapeRegex(ctor.name)}$`);
94
+ }
95
+
96
+ /**
97
+ * Convert a method name or regex to a regex pattern
98
+ */
99
+ private methodToRegex(method: string | RegExp): RegExp {
100
+ if (method instanceof RegExp) {
101
+ return method;
102
+ }
103
+ return new RegExp(`^${this.escapeRegex(method as string)}$`);
104
+ }
105
+
106
+ /**
107
+ * Convert allowed components array or regex to a single regex
108
+ */
109
+ private allowedComponentsToRegex(components: A_UTILS_TYPES__Manifest_AllowedComponents[] | RegExp): RegExp {
110
+ if (components instanceof RegExp) {
111
+ return components;
112
+ }
113
+
114
+ const patterns = components.map(ctor => this.escapeRegex(ctor.name));
115
+ return new RegExp(`^(${patterns.join('|')})$`);
116
+ }
117
+
118
+ /**
119
+ * Escape special regex characters in a string
120
+ */
121
+ private escapeRegex(str: string): string {
122
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
123
+ }
124
+
125
+ protected configItemToRegexp(item: A_TYPES__Component_Constructor): RegExp {
126
+ return this.constructorToRegex(item);
127
+ }
128
+
129
+
130
+ protected ID(component: A_TYPES__Component_Constructor, method: string) {
131
+ return `${component.name}.${method}`;
132
+ }
133
+
134
+ /**
135
+ * Check if a component and method combination is allowed for a target
136
+ */
137
+ isAllowed<T extends A_Component>(
138
+ ctor: T | A_TYPES__Component_Constructor<T>,
139
+ method: string
140
+ ): A_ManifestChecker {
141
+ const componentCtor = typeof ctor === 'function' ? ctor : ctor.constructor as A_TYPES__Component_Constructor;
142
+ return new A_ManifestChecker(this, componentCtor, method);
143
+ }
144
+
145
+ /**
146
+ * Internal method to check if access is allowed
147
+ */
148
+ internal_checkAccess(query: A_UTILS_TYPES__ManifestQuery): boolean {
149
+ const componentName = query.component.name;
150
+ const methodName = query.method;
151
+ const targetName = query.target.name;
152
+
153
+ // Find matching rules, sorted by specificity (method-specific rules first)
154
+ const matchingRules = this.rules
155
+ .filter(rule =>
156
+ rule.componentRegex.test(componentName) &&
157
+ rule.methodRegex.test(methodName)
158
+ )
159
+ .sort((a, b) => {
160
+ // Method-specific rules (not .* pattern) should come before general rules
161
+ const aIsGeneral = a.methodRegex.source === '.*';
162
+ const bIsGeneral = b.methodRegex.source === '.*';
163
+
164
+ if (aIsGeneral && !bIsGeneral) return 1; // b comes first
165
+ if (!aIsGeneral && bIsGeneral) return -1; // a comes first
166
+ return 0; // same priority
167
+ });
168
+
169
+ // If no rules match, allow by default
170
+ if (matchingRules.length === 0) {
171
+ return true;
172
+ }
173
+
174
+ // Process rules in order of specificity (most specific first)
175
+ for (const rule of matchingRules) {
176
+ // If this rule has an exclusion that matches, deny access
177
+ if (rule.excludeRegex && rule.excludeRegex.test(targetName)) {
178
+ return false;
179
+ }
180
+
181
+ // If this rule has an apply list, check if target is in it
182
+ if (rule.applyRegex) {
183
+ return rule.applyRegex.test(targetName);
184
+ }
185
+ }
186
+
187
+ // If we have rules but no specific apply/exclude, allow by default
188
+ return true;
189
+ }
190
+
191
+ isExcluded<T extends A_Component>(
192
+ ctor: T | A_TYPES__Component_Constructor<T>,
193
+ method: string
194
+ ): A_ManifestChecker {
195
+ const componentCtor = typeof ctor === 'function' ? ctor : ctor.constructor as A_TYPES__Component_Constructor;
196
+ return new A_ManifestChecker(this, componentCtor, method, true);
197
+ }
198
+ }
@@ -0,0 +1,7 @@
1
+ import { A_Error } from "@adaas/a-concept";
2
+
3
+ export class A_ManifestError extends A_Error {
4
+
5
+ static readonly ManifestInitializationError = 'A-Manifest Initialization Error';
6
+
7
+ }
@@ -0,0 +1,62 @@
1
+ import { A_Component, A_Container, A_TYPES__Component_Constructor, A_TYPES__Entity_Constructor, A_TYPES__Fragment_Constructor } from "@adaas/a-concept"
2
+ import { A_TYPES__Container_Constructor } from "@adaas/a-concept/dist/src/global/A-Container/A-Container.types"
3
+
4
+
5
+ export type A_UTILS_TYPES__Manifest_Init = Array<A_UTILS_TYPES__Manifest_ComponentLevelConfig>
6
+
7
+
8
+
9
+ export type A_UTILS_TYPES__Manifest_ComponentLevelConfig<T extends A_Component = A_Component> = {
10
+ /**
11
+ * Component constructor
12
+ */
13
+ component: A_TYPES__Component_Constructor<T>,
14
+ /**
15
+ * Method level configurations for the component
16
+ */
17
+ methods?: Array<A_UTILS_TYPES__Manifest_MethodLevelConfig<T>>
18
+ } & Partial<A_UTILS_TYPES__Manifest_Rules>
19
+
20
+
21
+
22
+ export type A_UTILS_TYPES__Manifest_MethodLevelConfig<T extends A_Component = A_Component> = {
23
+ /**
24
+ * Method name from the component provided
25
+ */
26
+ method: string | RegExp
27
+ } & Partial<A_UTILS_TYPES__Manifest_Rules>
28
+
29
+
30
+
31
+ export type A_UTILS_TYPES__Manifest_Rules = {
32
+ /**
33
+ * A list of entities to which a component is applied
34
+ *
35
+ * By default is for all
36
+ */
37
+ apply: Array<A_UTILS_TYPES__Manifest_AllowedComponents> | RegExp
38
+ /**
39
+ * A list of entities to which a component is excluded
40
+ */
41
+ exclude: Array<A_UTILS_TYPES__Manifest_AllowedComponents> | RegExp
42
+ }
43
+
44
+ export type A_UTILS_TYPES__Manifest_AllowedComponents = A_TYPES__Component_Constructor
45
+ | A_TYPES__Entity_Constructor
46
+ | A_TYPES__Fragment_Constructor
47
+ | A_TYPES__Container_Constructor
48
+
49
+
50
+
51
+ export interface A_UTILS_TYPES__ManifestRule {
52
+ componentRegex: RegExp;
53
+ methodRegex: RegExp;
54
+ applyRegex?: RegExp;
55
+ excludeRegex?: RegExp;
56
+ }
57
+
58
+ export interface A_UTILS_TYPES__ManifestQuery {
59
+ component: A_TYPES__Component_Constructor;
60
+ method: string;
61
+ target: A_TYPES__Component_Constructor;
62
+ }
@@ -0,0 +1,201 @@
1
+ # A-Manifest Usage Guide
2
+
3
+ The `A_Manifest` class provides a flexible configuration system for controlling component access and method permissions using regex patterns. It allows you to include or exclude components for particular methods based on sophisticated rule-based configurations.
4
+
5
+ ## Core Concepts
6
+
7
+ ### 1. **Component-Level Rules**
8
+ Apply rules to all methods of a component:
9
+
10
+ ```typescript
11
+ const manifest = new A_Manifest([
12
+ {
13
+ component: UserController,
14
+ exclude: [GuestUser] // Guests cannot access any UserController methods
15
+ }
16
+ ]);
17
+ ```
18
+
19
+ ### 2. **Method-Level Rules**
20
+ Apply specific rules to individual methods:
21
+
22
+ ```typescript
23
+ const manifest = new A_Manifest([
24
+ {
25
+ component: UserController,
26
+ methods: [
27
+ {
28
+ method: 'delete',
29
+ apply: [AdminUser, SuperAdmin] // Only admins can delete
30
+ },
31
+ {
32
+ method: 'post',
33
+ exclude: [GuestUser] // Guests cannot create
34
+ }
35
+ ]
36
+ }
37
+ ]);
38
+ ```
39
+
40
+ ### 3. **Regex Support**
41
+ Use regex patterns for flexible matching:
42
+
43
+ ```typescript
44
+ const manifest = new A_Manifest([
45
+ {
46
+ component: UserController,
47
+ methods: [
48
+ {
49
+ method: /^(post|put|delete)$/, // Match mutating operations
50
+ exclude: [GuestUser]
51
+ }
52
+ ]
53
+ }
54
+ ]);
55
+ ```
56
+
57
+ ## API Reference
58
+
59
+ ### Constructor
60
+ ```typescript
61
+ new A_Manifest(config: A_UTILS_TYPES__Manifest_Init)
62
+ ```
63
+
64
+ Creates a new manifest with the provided configuration.
65
+
66
+ ### isAllowed()
67
+ ```typescript
68
+ isAllowed<T extends A_Component>(
69
+ ctor: T | A_TYPES__Component_Constructor<T>,
70
+ method: string
71
+ ): A_ManifestChecker
72
+ ```
73
+
74
+ Returns a fluent checker to verify if access is allowed.
75
+
76
+ **Usage:**
77
+ ```typescript
78
+ const allowed = manifest.isAllowed(UserController, 'post').for(GuestUser);
79
+ ```
80
+
81
+ ### isExcluded()
82
+ ```typescript
83
+ isExcluded<T extends A_Component>(
84
+ ctor: T | A_TYPES__Component_Constructor<T>,
85
+ method: string
86
+ ): A_ManifestChecker
87
+ ```
88
+
89
+ Returns a fluent checker to verify if access is explicitly excluded.
90
+
91
+ **Usage:**
92
+ ```typescript
93
+ const excluded = manifest.isExcluded(UserController, 'post').for(GuestUser);
94
+ ```
95
+
96
+ ## Configuration Types
97
+
98
+ ### A_UTILS_TYPES__Manifest_ComponentLevelConfig
99
+ ```typescript
100
+ {
101
+ component: A_TYPES__Component_Constructor<T>,
102
+ methods?: Array<A_UTILS_TYPES__Manifest_MethodLevelConfig<T>>,
103
+ apply?: Array<A_UTILS_TYPES__Manifest_AllowedComponents> | RegExp,
104
+ exclude?: Array<A_UTILS_TYPES__Manifest_AllowedComponents> | RegExp
105
+ }
106
+ ```
107
+
108
+ ### A_UTILS_TYPES__Manifest_MethodLevelConfig
109
+ ```typescript
110
+ {
111
+ method: string | RegExp,
112
+ apply?: Array<A_UTILS_TYPES__Manifest_AllowedComponents> | RegExp,
113
+ exclude?: Array<A_UTILS_TYPES__Manifest_AllowedComponents> | RegExp
114
+ }
115
+ ```
116
+
117
+ ## Rule Precedence
118
+
119
+ 1. **Method-level rules** override component-level rules
120
+ 2. **Exclude rules** take precedence over apply rules
121
+ 3. **No rules** = allow by default
122
+
123
+ ## Advanced Examples
124
+
125
+ ### Example 1: Hierarchical Permissions
126
+ ```typescript
127
+ const manifest = new A_Manifest([
128
+ {
129
+ component: UserController,
130
+ exclude: [GuestUser], // Base rule: no access for guests
131
+ methods: [
132
+ {
133
+ method: 'get',
134
+ apply: [GuestUser, RegisteredUser, AdminUser] // Override: guests can read
135
+ },
136
+ {
137
+ method: 'delete',
138
+ apply: [AdminUser] // Only admins can delete
139
+ }
140
+ ]
141
+ }
142
+ ]);
143
+ ```
144
+
145
+ ### Example 2: Using Regex for Complex Patterns
146
+ ```typescript
147
+ const manifest = new A_Manifest([
148
+ {
149
+ component: AdminController,
150
+ methods: [
151
+ {
152
+ method: /^admin/, // Methods starting with "admin"
153
+ apply: [SuperAdmin] // Only super admins
154
+ },
155
+ {
156
+ method: /^(get|list)/, // Read operations
157
+ apply: [AdminUser, SuperAdmin] // All admins
158
+ }
159
+ ]
160
+ }
161
+ ]);
162
+ ```
163
+
164
+ ### Example 3: Multiple Components
165
+ ```typescript
166
+ const manifest = new A_Manifest([
167
+ {
168
+ component: UserController,
169
+ exclude: [GuestUser]
170
+ },
171
+ {
172
+ component: AdminController,
173
+ apply: [AdminUser, SuperAdmin]
174
+ },
175
+ {
176
+ component: PublicController,
177
+ // No rules = accessible to all
178
+ }
179
+ ]);
180
+ ```
181
+
182
+ ## Best Practices
183
+
184
+ 1. **Start with restrictive rules** and add exceptions rather than the opposite
185
+ 2. **Use component-level rules** for broad access control
186
+ 3. **Use method-level rules** for fine-grained permissions
187
+ 4. **Leverage regex patterns** for dynamic method matching
188
+ 5. **Test your configurations** thoroughly with the provided fluent API
189
+
190
+ ## Error Handling
191
+
192
+ The manifest will throw `A_ManifestError` for:
193
+ - Invalid configuration structure
194
+ - Non-component constructors in configuration
195
+ - Malformed regex patterns
196
+
197
+ ## Performance Considerations
198
+
199
+ - Rules are compiled to regex patterns during initialization
200
+ - Runtime checks are optimized for fast pattern matching
201
+ - Consider the number of rules when designing complex hierarchies
@@ -0,0 +1,24 @@
1
+ import { A_TYPES__Component_Constructor } from "@adaas/a-concept";
2
+ import { A_Manifest } from "../A-Manifest.context";
3
+
4
+ /**
5
+ * Fluent API for checking manifest permissions
6
+ */
7
+ export class A_ManifestChecker {
8
+ constructor(
9
+ private manifest: A_Manifest,
10
+ private component: A_TYPES__Component_Constructor,
11
+ private method: string,
12
+ private checkExclusion: boolean = false
13
+ ) {}
14
+
15
+ for(target: A_TYPES__Component_Constructor): boolean {
16
+ const result = this.manifest.internal_checkAccess({
17
+ component: this.component,
18
+ method: this.method,
19
+ target: target
20
+ });
21
+
22
+ return this.checkExclusion ? !result : result;
23
+ }
24
+ }