@nmnmcc/ability 0.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.
- package/LICENSE +201 -0
- package/README.md +251 -0
- package/dist/Ability.d.ts +516 -0
- package/dist/Ability.js +211 -0
- package/dist/AbilityExtra.d.ts +79 -0
- package/dist/AbilityExtra.js +195 -0
- package/dist/AbilityRef.d.ts +110 -0
- package/dist/AbilityRef.js +160 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/internal/ability.d.ts +114 -0
- package/dist/internal/ability.js +624 -0
- package/dist/internal/serializable.js +22 -0
- package/package.json +75 -0
package/dist/Ability.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { AliasError as AliasError$1, AuthorizationError as AuthorizationError$1, ConditionError as ConditionError$1, PredicateEvaluationError as PredicateEvaluationError$1, QueryGenerationError as QueryGenerationError$1, RawRuleError as RawRuleError$1, RuleTypeId as RuleTypeId$1, SerializationError as SerializationError$1, SubjectDetectionError as SubjectDetectionError$1, SubjectTypeId as SubjectTypeId$1, TypeId as TypeId$1, actionsFor as actionsFor$1, check as check$1, createAliasResolver as createAliasResolver$1, define as define$1, detectSubjectType as detectSubjectType$1, fromRawRules as fromRawRules$1, isAbility as isAbility$1, isRule as isRule$1, isSubject as isSubject$1, make as make$1, permittedFields as permittedFields$1, possibleRulesFor as possibleRulesFor$1, relevantRuleFor as relevantRuleFor$1, rulesFor as rulesFor$1, subject as subject$1, toRawRules as toRawRules$1, unwrapSubject as unwrapSubject$1 } from "./internal/ability.js";
|
|
3
|
+
//#region src/Ability.ts
|
|
4
|
+
var Ability_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
AliasError: () => AliasError,
|
|
6
|
+
AuthorizationError: () => AuthorizationError,
|
|
7
|
+
ConditionError: () => ConditionError,
|
|
8
|
+
PredicateEvaluationError: () => PredicateEvaluationError,
|
|
9
|
+
QueryGenerationError: () => QueryGenerationError,
|
|
10
|
+
RawRuleError: () => RawRuleError,
|
|
11
|
+
RuleTypeId: () => RuleTypeId,
|
|
12
|
+
SerializationError: () => SerializationError,
|
|
13
|
+
SubjectDetectionError: () => SubjectDetectionError,
|
|
14
|
+
SubjectTypeId: () => SubjectTypeId,
|
|
15
|
+
TypeId: () => TypeId,
|
|
16
|
+
actionsFor: () => actionsFor,
|
|
17
|
+
check: () => check,
|
|
18
|
+
createAliasResolver: () => createAliasResolver,
|
|
19
|
+
define: () => define,
|
|
20
|
+
detectSubjectType: () => detectSubjectType,
|
|
21
|
+
fromRawRules: () => fromRawRules,
|
|
22
|
+
isAbility: () => isAbility,
|
|
23
|
+
isRule: () => isRule,
|
|
24
|
+
isSubject: () => isSubject,
|
|
25
|
+
make: () => make,
|
|
26
|
+
permittedFields: () => permittedFields,
|
|
27
|
+
possibleRulesFor: () => possibleRulesFor,
|
|
28
|
+
relevantRuleFor: () => relevantRuleFor,
|
|
29
|
+
rulesFor: () => rulesFor,
|
|
30
|
+
subject: () => subject,
|
|
31
|
+
toRawRules: () => toRawRules,
|
|
32
|
+
unwrapSubject: () => unwrapSubject
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* @since 0.1.0
|
|
36
|
+
* @category Type Ids
|
|
37
|
+
*/
|
|
38
|
+
const TypeId = TypeId$1;
|
|
39
|
+
/**
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
* @category Type Ids
|
|
42
|
+
*/
|
|
43
|
+
const SubjectTypeId = SubjectTypeId$1;
|
|
44
|
+
/**
|
|
45
|
+
* Unique identifier for `Rule` values.
|
|
46
|
+
*
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
* @category Type Ids
|
|
49
|
+
*/
|
|
50
|
+
const RuleTypeId = RuleTypeId$1;
|
|
51
|
+
/**
|
|
52
|
+
* @since 0.1.0
|
|
53
|
+
* @category Errors
|
|
54
|
+
*/
|
|
55
|
+
const AuthorizationError = AuthorizationError$1;
|
|
56
|
+
/**
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
* @category Errors
|
|
59
|
+
*/
|
|
60
|
+
const ConditionError = ConditionError$1;
|
|
61
|
+
/**
|
|
62
|
+
* @since 0.1.0
|
|
63
|
+
* @category Errors
|
|
64
|
+
*/
|
|
65
|
+
const PredicateEvaluationError = PredicateEvaluationError$1;
|
|
66
|
+
/**
|
|
67
|
+
* @since 0.1.0
|
|
68
|
+
* @category Errors
|
|
69
|
+
*/
|
|
70
|
+
const SerializationError = SerializationError$1;
|
|
71
|
+
/**
|
|
72
|
+
* @since 0.1.0
|
|
73
|
+
* @category Errors
|
|
74
|
+
*/
|
|
75
|
+
const RawRuleError = RawRuleError$1;
|
|
76
|
+
/**
|
|
77
|
+
* @since 0.1.0
|
|
78
|
+
* @category Errors
|
|
79
|
+
*/
|
|
80
|
+
const SubjectDetectionError = SubjectDetectionError$1;
|
|
81
|
+
/**
|
|
82
|
+
* @since 0.1.0
|
|
83
|
+
* @category Errors
|
|
84
|
+
*/
|
|
85
|
+
const AliasError = AliasError$1;
|
|
86
|
+
/**
|
|
87
|
+
* @since 0.1.0
|
|
88
|
+
* @category Errors
|
|
89
|
+
*/
|
|
90
|
+
const QueryGenerationError = QueryGenerationError$1;
|
|
91
|
+
/**
|
|
92
|
+
* Creates an action resolver for an alias map.
|
|
93
|
+
*
|
|
94
|
+
* @since 0.1.0
|
|
95
|
+
* @category Constructors
|
|
96
|
+
*/
|
|
97
|
+
const createAliasResolver = createAliasResolver$1;
|
|
98
|
+
/**
|
|
99
|
+
* Defines an immutable Ability from a generator-based rule program.
|
|
100
|
+
*
|
|
101
|
+
* @since 0.1.0
|
|
102
|
+
* @category Constructors
|
|
103
|
+
*/
|
|
104
|
+
const define = define$1;
|
|
105
|
+
/**
|
|
106
|
+
* Constructs an Ability directly from rules.
|
|
107
|
+
*
|
|
108
|
+
* @since 0.1.0
|
|
109
|
+
* @category Constructors
|
|
110
|
+
*/
|
|
111
|
+
const make = make$1;
|
|
112
|
+
/**
|
|
113
|
+
* Constructs an Ability from JSON-safe rules.
|
|
114
|
+
*
|
|
115
|
+
* @since 0.1.0
|
|
116
|
+
* @category Constructors
|
|
117
|
+
*/
|
|
118
|
+
const fromRawRules = fromRawRules$1;
|
|
119
|
+
/**
|
|
120
|
+
* Creates a pure typed subject wrapper without mutating the wrapped value.
|
|
121
|
+
*
|
|
122
|
+
* @since 0.1.0
|
|
123
|
+
* @category Constructors
|
|
124
|
+
*/
|
|
125
|
+
const subject = subject$1;
|
|
126
|
+
/**
|
|
127
|
+
* Returns whether a value is an Ability.
|
|
128
|
+
*
|
|
129
|
+
* @since 0.1.0
|
|
130
|
+
* @category Guards
|
|
131
|
+
*/
|
|
132
|
+
const isAbility = isAbility$1;
|
|
133
|
+
/**
|
|
134
|
+
* Returns whether a value is a rule.
|
|
135
|
+
*
|
|
136
|
+
* @since 0.1.0
|
|
137
|
+
* @category Guards
|
|
138
|
+
*/
|
|
139
|
+
const isRule = isRule$1;
|
|
140
|
+
/**
|
|
141
|
+
* Returns whether a value is a typed subject wrapper.
|
|
142
|
+
*
|
|
143
|
+
* @since 0.1.0
|
|
144
|
+
* @category Guards
|
|
145
|
+
*/
|
|
146
|
+
const isSubject = isSubject$1;
|
|
147
|
+
/**
|
|
148
|
+
* Unwraps a typed subject wrapper.
|
|
149
|
+
*
|
|
150
|
+
* @since 0.1.0
|
|
151
|
+
* @category Accessors
|
|
152
|
+
*/
|
|
153
|
+
const unwrapSubject = unwrapSubject$1;
|
|
154
|
+
/**
|
|
155
|
+
* Detects a subject name for a value using an Ability's detection strategy.
|
|
156
|
+
*
|
|
157
|
+
* @since 0.1.0
|
|
158
|
+
* @category Accessors
|
|
159
|
+
*/
|
|
160
|
+
const detectSubjectType = detectSubjectType$1;
|
|
161
|
+
/**
|
|
162
|
+
* Converts an Ability to JSON-safe raw rules.
|
|
163
|
+
*
|
|
164
|
+
* @since 0.1.0
|
|
165
|
+
* @category Conversions
|
|
166
|
+
*/
|
|
167
|
+
const toRawRules = toRawRules$1;
|
|
168
|
+
/**
|
|
169
|
+
* Returns rules that may apply for a request before field, condition and predicate checks.
|
|
170
|
+
*
|
|
171
|
+
* @since 0.1.0
|
|
172
|
+
* @category Accessors
|
|
173
|
+
*/
|
|
174
|
+
const possibleRulesFor = possibleRulesFor$1;
|
|
175
|
+
/**
|
|
176
|
+
* Returns rules that may apply for a request after field checks.
|
|
177
|
+
*
|
|
178
|
+
* @since 0.1.0
|
|
179
|
+
* @category Accessors
|
|
180
|
+
*/
|
|
181
|
+
const rulesFor = rulesFor$1;
|
|
182
|
+
/**
|
|
183
|
+
* Returns the first matching rule for a request, if any.
|
|
184
|
+
*
|
|
185
|
+
* @since 0.1.0
|
|
186
|
+
* @category Accessors
|
|
187
|
+
*/
|
|
188
|
+
const relevantRuleFor = relevantRuleFor$1;
|
|
189
|
+
/**
|
|
190
|
+
* Returns actions that have rules for a subject.
|
|
191
|
+
*
|
|
192
|
+
* @since 0.1.0
|
|
193
|
+
* @category Accessors
|
|
194
|
+
*/
|
|
195
|
+
const actionsFor = actionsFor$1;
|
|
196
|
+
/**
|
|
197
|
+
* Checks a request and fails with `AuthorizationError` when the request is not authorized.
|
|
198
|
+
*
|
|
199
|
+
* @since 0.1.0
|
|
200
|
+
* @category Combinators
|
|
201
|
+
*/
|
|
202
|
+
const check = check$1;
|
|
203
|
+
/**
|
|
204
|
+
* Computes fields permitted for a request.
|
|
205
|
+
*
|
|
206
|
+
* @since 0.1.0
|
|
207
|
+
* @category Combinators
|
|
208
|
+
*/
|
|
209
|
+
const permittedFields = permittedFields$1;
|
|
210
|
+
//#endregion
|
|
211
|
+
export { Ability_exports, AliasError, AuthorizationError, ConditionError, PredicateEvaluationError, QueryGenerationError, RawRuleError, RuleTypeId, SerializationError, SubjectDetectionError, SubjectTypeId, TypeId, actionsFor, check, createAliasResolver, define, detectSubjectType, fromRawRules, isAbility, isRule, isSubject, make, permittedFields, possibleRulesFor, relevantRuleFor, rulesFor, subject, toRawRules, unwrapSubject };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Ability, ActionAliases, AnyRule, QueryGenerationError, RawRule, RuleQueryRequest, SubjectDetectionError, SubjectMap } from "./Ability.js";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
|
|
4
|
+
//#region src/AbilityExtra.d.ts
|
|
5
|
+
declare namespace AbilityExtra_d_exports {
|
|
6
|
+
export { LogicalQuery, PackRule, RulesToConditionHooks, packRules, rulesToCondition, rulesToFields, rulesToQuery, unpackRules };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
* @category Models
|
|
11
|
+
*/
|
|
12
|
+
type PackRule<Rule extends RawRule = RawRule> = readonly [action: Rule["action"], subject: Rule["subject"], conditions?: Rule["conditions"] | 0, inverted?: 1 | 0, fields?: Rule["fields"] | 0, reason?: string];
|
|
13
|
+
/**
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
* @category Models
|
|
16
|
+
*/
|
|
17
|
+
interface RulesToConditionHooks<Query> {
|
|
18
|
+
readonly and: (conditions: ReadonlyArray<Query>) => Query;
|
|
19
|
+
readonly or: (conditions: ReadonlyArray<Query>) => Query;
|
|
20
|
+
readonly not: (condition: Query) => Query;
|
|
21
|
+
readonly empty: () => Query;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
* @category Models
|
|
26
|
+
*/
|
|
27
|
+
type LogicalQuery<Query> = Query | {
|
|
28
|
+
readonly $and: ReadonlyArray<LogicalQuery<Query>>;
|
|
29
|
+
} | {
|
|
30
|
+
readonly $or: ReadonlyArray<LogicalQuery<Query>>;
|
|
31
|
+
} | {
|
|
32
|
+
readonly $not: LogicalQuery<Query>;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Packs JSON-safe raw rules into a compact tuple format.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.1.0
|
|
38
|
+
* @category Conversions
|
|
39
|
+
*/
|
|
40
|
+
declare const packRules: <Rule extends RawRule>(rules: Iterable<Rule>) => ReadonlyArray<PackRule<Rule>>;
|
|
41
|
+
/**
|
|
42
|
+
* Unpacks compact raw rules produced by `packRules`.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.1.0
|
|
45
|
+
* @category Conversions
|
|
46
|
+
*/
|
|
47
|
+
declare const unpackRules: <Rule extends RawRule = RawRule>(rules: Iterable<PackRule<Rule>>) => ReadonlyArray<Rule>;
|
|
48
|
+
/**
|
|
49
|
+
* Extracts scalar condition values as default fields.
|
|
50
|
+
*
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
* @category Conversions
|
|
53
|
+
*/
|
|
54
|
+
declare const rulesToFields: {
|
|
55
|
+
<Subjects extends SubjectMap, Rules extends AnyRule, Aliases extends ActionAliases, const Request extends RuleQueryRequest<Subjects, Rules, Aliases>>(request: Request): (self: Ability<Subjects, Rules, Aliases>) => Effect.Effect<Readonly<Record<string, unknown>>, QueryGenerationError | SubjectDetectionError>;
|
|
56
|
+
<Subjects extends SubjectMap, Rules extends AnyRule, Aliases extends ActionAliases, const Request extends RuleQueryRequest<Subjects, Rules, Aliases>>(self: Ability<Subjects, Rules, Aliases>, request: Request): Effect.Effect<Readonly<Record<string, unknown>>, QueryGenerationError | SubjectDetectionError>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Converts rules into a caller-defined logical condition.
|
|
60
|
+
*
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
* @category Conversions
|
|
63
|
+
*/
|
|
64
|
+
declare const rulesToCondition: {
|
|
65
|
+
<Subjects extends SubjectMap, Rules extends AnyRule, Aliases extends ActionAliases, const Request extends RuleQueryRequest<Subjects, Rules, Aliases>, Query, E = never, R = never>(request: Request, convert: (rule: Rules) => Query | Effect.Effect<Query, E, R>, hooks: RulesToConditionHooks<Query>): (self: Ability<Subjects, Rules, Aliases>) => Effect.Effect<Query | null, QueryGenerationError | SubjectDetectionError | E, R>;
|
|
66
|
+
<Subjects extends SubjectMap, Rules extends AnyRule, Aliases extends ActionAliases, const Request extends RuleQueryRequest<Subjects, Rules, Aliases>, Query, E = never, R = never>(self: Ability<Subjects, Rules, Aliases>, request: Request, convert: (rule: Rules) => Query | Effect.Effect<Query, E, R>, hooks: RulesToConditionHooks<Query>): Effect.Effect<Query | null, QueryGenerationError | SubjectDetectionError | E, R>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Converts rules into a small generic logical query object.
|
|
70
|
+
*
|
|
71
|
+
* @since 0.1.0
|
|
72
|
+
* @category Conversions
|
|
73
|
+
*/
|
|
74
|
+
declare const rulesToQuery: {
|
|
75
|
+
<Subjects extends SubjectMap, Rules extends AnyRule, Aliases extends ActionAliases, const Request extends RuleQueryRequest<Subjects, Rules, Aliases>, Query, E = never, R = never>(request: Request, convert: (rule: Rules) => Query | Effect.Effect<Query, E, R>): (self: Ability<Subjects, Rules, Aliases>) => Effect.Effect<LogicalQuery<Query> | null, QueryGenerationError | SubjectDetectionError | E, R>;
|
|
76
|
+
<Subjects extends SubjectMap, Rules extends AnyRule, Aliases extends ActionAliases, const Request extends RuleQueryRequest<Subjects, Rules, Aliases>, Query, E = never, R = never>(self: Ability<Subjects, Rules, Aliases>, request: Request, convert: (rule: Rules) => Query | Effect.Effect<Query, E, R>): Effect.Effect<LogicalQuery<Query> | null, QueryGenerationError | SubjectDetectionError | E, R>;
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { AbilityExtra_d_exports, LogicalQuery, PackRule, RulesToConditionHooks, packRules, rulesToCondition, rulesToFields, rulesToQuery, unpackRules };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { cloneSerializable, isPlainObject } from "./internal/serializable.js";
|
|
3
|
+
import { QueryGenerationError, rulesFor } from "./Ability.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { dual } from "effect/Function";
|
|
6
|
+
//#region src/AbilityExtra.ts
|
|
7
|
+
/**
|
|
8
|
+
* Extra helpers for serializing and transforming Ability rules.
|
|
9
|
+
*
|
|
10
|
+
* Mental model:
|
|
11
|
+
* - `AbilityExtra` works with already-defined abilities and JSON-safe rules.
|
|
12
|
+
* - Packing preserves rule structure while reducing wire size.
|
|
13
|
+
* - Query helpers transform rule lists into caller-owned condition formats.
|
|
14
|
+
*
|
|
15
|
+
* Common tasks:
|
|
16
|
+
* - Compact raw rules with {@link packRules} and {@link unpackRules}.
|
|
17
|
+
* - Derive default fields from rule conditions with {@link rulesToFields}.
|
|
18
|
+
* - Convert matching rules to logical conditions with {@link rulesToCondition}
|
|
19
|
+
* or {@link rulesToQuery}.
|
|
20
|
+
*
|
|
21
|
+
* Gotchas:
|
|
22
|
+
* - Query generation rejects function predicates because they cannot be
|
|
23
|
+
* represented as static conditions.
|
|
24
|
+
* - Conversion callbacks may be synchronous or Effectful; synchronous throws are
|
|
25
|
+
* converted to `QueryGenerationError`.
|
|
26
|
+
*
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
var AbilityExtra_exports = /* @__PURE__ */ __exportAll({
|
|
31
|
+
packRules: () => packRules,
|
|
32
|
+
rulesToCondition: () => rulesToCondition,
|
|
33
|
+
rulesToFields: () => rulesToFields,
|
|
34
|
+
rulesToQuery: () => rulesToQuery,
|
|
35
|
+
unpackRules: () => unpackRules
|
|
36
|
+
});
|
|
37
|
+
const forbiddenPathKeys = new Set([
|
|
38
|
+
"__proto__",
|
|
39
|
+
"constructor",
|
|
40
|
+
"prototype"
|
|
41
|
+
]);
|
|
42
|
+
const setByPath = (object, path, value) => {
|
|
43
|
+
const keys = path.split(".");
|
|
44
|
+
let cursor = object;
|
|
45
|
+
let index = 0;
|
|
46
|
+
while (index < keys.length - 1) {
|
|
47
|
+
const key = keys[index];
|
|
48
|
+
if (key === void 0 || forbiddenPathKeys.has(key)) return;
|
|
49
|
+
const current = cursor[key];
|
|
50
|
+
if (!isPlainObject(current)) {
|
|
51
|
+
const next = {};
|
|
52
|
+
cursor[key] = next;
|
|
53
|
+
cursor = next;
|
|
54
|
+
} else cursor = current;
|
|
55
|
+
index = index + 1;
|
|
56
|
+
}
|
|
57
|
+
const lastKey = keys[keys.length - 1];
|
|
58
|
+
if (lastKey !== void 0 && !forbiddenPathKeys.has(lastKey)) cursor[lastKey] = value;
|
|
59
|
+
};
|
|
60
|
+
const packRule = (rule) => {
|
|
61
|
+
const packed = [
|
|
62
|
+
cloneSerializable(rule.action),
|
|
63
|
+
cloneSerializable(rule.subject),
|
|
64
|
+
rule.conditions === void 0 ? 0 : cloneSerializable(rule.conditions),
|
|
65
|
+
rule.inverted === true ? 1 : 0,
|
|
66
|
+
rule.fields === void 0 ? 0 : cloneSerializable(rule.fields),
|
|
67
|
+
rule.reason ?? ""
|
|
68
|
+
];
|
|
69
|
+
while (packed.length > 2 && (packed[packed.length - 1] === 0 || packed[packed.length - 1] === "" || packed[packed.length - 1] === void 0)) packed.pop();
|
|
70
|
+
return Object.freeze(packed);
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Packs JSON-safe raw rules into a compact tuple format.
|
|
74
|
+
*
|
|
75
|
+
* @since 0.1.0
|
|
76
|
+
* @category Conversions
|
|
77
|
+
*/
|
|
78
|
+
const packRules = (rules) => Object.freeze(Array.from(rules, packRule));
|
|
79
|
+
const unpackRule = (packed) => {
|
|
80
|
+
const [action, subject, conditions, inverted, fields, reason] = packed;
|
|
81
|
+
const rule = {
|
|
82
|
+
action: cloneSerializable(action),
|
|
83
|
+
subject: cloneSerializable(subject)
|
|
84
|
+
};
|
|
85
|
+
if (conditions !== void 0 && conditions !== 0) rule.conditions = cloneSerializable(conditions);
|
|
86
|
+
if (inverted === 1) rule.inverted = true;
|
|
87
|
+
if (fields !== void 0 && fields !== 0) rule.fields = cloneSerializable(fields);
|
|
88
|
+
if (reason !== void 0 && reason !== "") rule.reason = reason;
|
|
89
|
+
return Object.freeze(rule);
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Unpacks compact raw rules produced by `packRules`.
|
|
93
|
+
*
|
|
94
|
+
* @since 0.1.0
|
|
95
|
+
* @category Conversions
|
|
96
|
+
*/
|
|
97
|
+
const unpackRules = (rules) => Object.freeze(Array.from(rules, unpackRule));
|
|
98
|
+
const queryGenerationError = (reason, ruleIndex, cause) => {
|
|
99
|
+
const error = { reason };
|
|
100
|
+
if (ruleIndex !== void 0) error.ruleIndex = ruleIndex;
|
|
101
|
+
if (cause !== void 0) error.cause = cause;
|
|
102
|
+
return new QueryGenerationError(error);
|
|
103
|
+
};
|
|
104
|
+
const assertSerializableRule = (rule, ruleIndex) => {
|
|
105
|
+
if (rule.when !== void 0) return Effect.fail(queryGenerationError("Cannot transform rules with function predicates", ruleIndex));
|
|
106
|
+
return Effect.void;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Extracts scalar condition values as default fields.
|
|
110
|
+
*
|
|
111
|
+
* @since 0.1.0
|
|
112
|
+
* @category Conversions
|
|
113
|
+
*/
|
|
114
|
+
const rulesToFields = dual(2, Effect.fnUntraced(function* (self, request) {
|
|
115
|
+
const rules = yield* rulesFor(self, request);
|
|
116
|
+
const fields = {};
|
|
117
|
+
let index = 0;
|
|
118
|
+
for (const rule of rules) {
|
|
119
|
+
yield* assertSerializableRule(rule, index);
|
|
120
|
+
if (rule._tag === "Allow" && rule.conditions !== void 0) for (const key of Object.keys(rule.conditions)) {
|
|
121
|
+
const value = rule.conditions[key];
|
|
122
|
+
if (!isPlainObject(value)) setByPath(fields, key, value);
|
|
123
|
+
}
|
|
124
|
+
index = index + 1;
|
|
125
|
+
}
|
|
126
|
+
return Object.freeze(fields);
|
|
127
|
+
}));
|
|
128
|
+
const convertRule = (rule, convert, ruleIndex) => Effect.flatMap(Effect.try({
|
|
129
|
+
try: () => convert(rule),
|
|
130
|
+
catch: (cause) => queryGenerationError("Rule conversion failed", ruleIndex, cause)
|
|
131
|
+
}), (result) => Effect.isEffect(result) ? result : Effect.succeed(result));
|
|
132
|
+
/**
|
|
133
|
+
* Converts rules into a caller-defined logical condition.
|
|
134
|
+
*
|
|
135
|
+
* @since 0.1.0
|
|
136
|
+
* @category Conversions
|
|
137
|
+
*/
|
|
138
|
+
const rulesToConditionEffect = Effect.fnUntraced(function* (self, request, convert, hooks) {
|
|
139
|
+
const rules = yield* rulesFor(self, request);
|
|
140
|
+
const higherDenyBounds = [];
|
|
141
|
+
const orConditions = [];
|
|
142
|
+
let hasUnconditionalAllow = false;
|
|
143
|
+
let index = 0;
|
|
144
|
+
while (index < rules.length) {
|
|
145
|
+
const rule = rules[index];
|
|
146
|
+
yield* assertSerializableRule(rule, index);
|
|
147
|
+
if (rule._tag === "Deny") {
|
|
148
|
+
if (rule.conditions === void 0) break;
|
|
149
|
+
const condition = yield* convertRule(rule, convert, index);
|
|
150
|
+
higherDenyBounds.push(hooks.not(condition));
|
|
151
|
+
} else {
|
|
152
|
+
if (rule.conditions === void 0) {
|
|
153
|
+
hasUnconditionalAllow = true;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
const condition = yield* convertRule(rule, convert, index);
|
|
157
|
+
orConditions.push(higherDenyBounds.length === 0 ? condition : hooks.and([condition, ...higherDenyBounds]));
|
|
158
|
+
}
|
|
159
|
+
index = index + 1;
|
|
160
|
+
}
|
|
161
|
+
if (hasUnconditionalAllow) {
|
|
162
|
+
if (higherDenyBounds.length === 0) return hooks.empty();
|
|
163
|
+
if (orConditions.length === 0) return hooks.and(higherDenyBounds);
|
|
164
|
+
orConditions.push(hooks.and(higherDenyBounds));
|
|
165
|
+
}
|
|
166
|
+
if (orConditions.length === 0) return null;
|
|
167
|
+
return hooks.or(orConditions);
|
|
168
|
+
});
|
|
169
|
+
/**
|
|
170
|
+
* Converts rules into a caller-defined logical condition.
|
|
171
|
+
*
|
|
172
|
+
* @since 0.1.0
|
|
173
|
+
* @category Conversions
|
|
174
|
+
*/
|
|
175
|
+
const rulesToCondition = dual(4, rulesToConditionEffect);
|
|
176
|
+
/**
|
|
177
|
+
* Converts rules into a small generic logical query object.
|
|
178
|
+
*
|
|
179
|
+
* @since 0.1.0
|
|
180
|
+
* @category Conversions
|
|
181
|
+
*/
|
|
182
|
+
const rulesToQuery = dual(3, Effect.fnUntraced(function* (self, request, convert) {
|
|
183
|
+
const logicalConvert = (rule) => {
|
|
184
|
+
const result = convert(rule);
|
|
185
|
+
return Effect.isEffect(result) ? result : result;
|
|
186
|
+
};
|
|
187
|
+
return yield* rulesToConditionEffect(self, request, logicalConvert, {
|
|
188
|
+
and: (conditions) => ({ $and: conditions }),
|
|
189
|
+
or: (conditions) => ({ $or: conditions }),
|
|
190
|
+
not: (condition) => ({ $not: condition }),
|
|
191
|
+
empty: () => ({})
|
|
192
|
+
});
|
|
193
|
+
}));
|
|
194
|
+
//#endregion
|
|
195
|
+
export { AbilityExtra_exports, packRules, rulesToCondition, rulesToFields, rulesToQuery, unpackRules };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Ability, AliasError, AnyRule, MongoCondition, RawRule, RawRuleError, RuleSubject, SubjectMap, SubjectUnion } from "./Ability.js";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Equal from "effect/Equal";
|
|
4
|
+
import * as Inspectable from "effect/Inspectable";
|
|
5
|
+
import * as Pipeable from "effect/Pipeable";
|
|
6
|
+
|
|
7
|
+
//#region src/AbilityRef.d.ts
|
|
8
|
+
declare namespace AbilityRef_d_exports {
|
|
9
|
+
export { AbilityRef, Event, EventHandler, EventName, TypeId, Unsubscribe, UpdateEvent, UpdatedEvent, get, isAbilityRef, make, on, set, update };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
* @category Type Ids
|
|
14
|
+
*/
|
|
15
|
+
declare const TypeId: unique symbol;
|
|
16
|
+
/**
|
|
17
|
+
* @since 0.1.0
|
|
18
|
+
* @category Type Ids
|
|
19
|
+
*/
|
|
20
|
+
type TypeId = typeof TypeId;
|
|
21
|
+
/**
|
|
22
|
+
* @since 0.1.0
|
|
23
|
+
* @category Models
|
|
24
|
+
*/
|
|
25
|
+
type EventName = "update" | "updated";
|
|
26
|
+
/**
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
* @category Models
|
|
29
|
+
*/
|
|
30
|
+
interface UpdateEvent<Subjects extends SubjectMap> {
|
|
31
|
+
readonly previous: Ability<Subjects, AnyRule>;
|
|
32
|
+
readonly next: Ability<Subjects, AnyRule>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @since 0.1.0
|
|
36
|
+
* @category Models
|
|
37
|
+
*/
|
|
38
|
+
interface UpdatedEvent<Subjects extends SubjectMap> {
|
|
39
|
+
readonly previous: Ability<Subjects, AnyRule>;
|
|
40
|
+
readonly current: Ability<Subjects, AnyRule>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
* @category Models
|
|
45
|
+
*/
|
|
46
|
+
type Event<Subjects extends SubjectMap, Name extends EventName> = Name extends "update" ? UpdateEvent<Subjects> : UpdatedEvent<Subjects>;
|
|
47
|
+
/**
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
* @category Models
|
|
50
|
+
*/
|
|
51
|
+
type EventHandler<Subjects extends SubjectMap, Name extends EventName> = (event: Event<Subjects, Name>) => void | Effect.Effect<void>;
|
|
52
|
+
/**
|
|
53
|
+
* @since 0.1.0
|
|
54
|
+
* @category Models
|
|
55
|
+
*/
|
|
56
|
+
type Unsubscribe = () => void;
|
|
57
|
+
/**
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
* @category Models
|
|
60
|
+
*/
|
|
61
|
+
interface AbilityRef<Subjects extends SubjectMap> extends Equal.Equal, Inspectable.Inspectable, Pipeable.Pipeable {
|
|
62
|
+
readonly [TypeId]: TypeId;
|
|
63
|
+
readonly current: Ability<Subjects, AnyRule>;
|
|
64
|
+
/** @internal */
|
|
65
|
+
readonly handlers: {
|
|
66
|
+
readonly update: Array<EventHandler<Subjects, "update">>;
|
|
67
|
+
readonly updated: Array<EventHandler<Subjects, "updated">>;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @since 0.1.0
|
|
72
|
+
* @category Constructors
|
|
73
|
+
*/
|
|
74
|
+
declare const make: <Subjects extends SubjectMap, Rules extends AnyRule>(ability: Ability<Subjects, Rules>) => AbilityRef<Subjects>;
|
|
75
|
+
/**
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @category Guards
|
|
78
|
+
*/
|
|
79
|
+
declare const isAbilityRef: (value: unknown) => value is AbilityRef<SubjectMap>;
|
|
80
|
+
/**
|
|
81
|
+
* @since 0.1.0
|
|
82
|
+
* @category Accessors
|
|
83
|
+
*/
|
|
84
|
+
declare const get: <Subjects extends SubjectMap>(self: AbilityRef<Subjects>) => Ability<Subjects, AnyRule>;
|
|
85
|
+
/**
|
|
86
|
+
* @since 0.1.0
|
|
87
|
+
* @category Combinators
|
|
88
|
+
*/
|
|
89
|
+
declare const set: {
|
|
90
|
+
<Subjects extends SubjectMap, Rules extends AnyRule>(ability: Ability<Subjects, Rules>): (self: AbilityRef<Subjects>) => Effect.Effect<void>;
|
|
91
|
+
<Subjects extends SubjectMap, Rules extends AnyRule>(self: AbilityRef<Subjects>, ability: Ability<Subjects, Rules>): Effect.Effect<void>;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* @since 0.1.0
|
|
95
|
+
* @category Combinators
|
|
96
|
+
*/
|
|
97
|
+
declare const update: {
|
|
98
|
+
<Subjects extends SubjectMap, Rules extends AnyRule>(next: Ability<Subjects, Rules> | Iterable<RawRule<string, RuleSubject<Subjects>, string, MongoCondition<SubjectUnion<Subjects>>>>): (self: AbilityRef<Subjects>) => Effect.Effect<void, AliasError | RawRuleError>;
|
|
99
|
+
<Subjects extends SubjectMap, Rules extends AnyRule>(self: AbilityRef<Subjects>, next: Ability<Subjects, Rules> | Iterable<RawRule<string, RuleSubject<Subjects>, string, MongoCondition<SubjectUnion<Subjects>>>>): Effect.Effect<void, AliasError | RawRuleError>;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* @since 0.1.0
|
|
103
|
+
* @category Combinators
|
|
104
|
+
*/
|
|
105
|
+
declare const on: {
|
|
106
|
+
<Subjects extends SubjectMap, Name extends EventName>(name: Name, handler: EventHandler<Subjects, Name>): (self: AbilityRef<Subjects>) => Effect.Effect<Unsubscribe>;
|
|
107
|
+
<Subjects extends SubjectMap, Name extends EventName>(self: AbilityRef<Subjects>, name: Name, handler: EventHandler<Subjects, Name>): Effect.Effect<Unsubscribe>;
|
|
108
|
+
};
|
|
109
|
+
//#endregion
|
|
110
|
+
export { AbilityRef, AbilityRef_d_exports, Event, EventHandler, EventName, TypeId, Unsubscribe, UpdateEvent, UpdatedEvent, get, isAbilityRef, make, on, set, update };
|