@nmnmcc/ability 1.0.1 → 2.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/README.md +112 -69
- package/dist/Authorizer.d.ts +43 -47
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +5 -290
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +164 -332
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +75 -890
- package/dist/Condition.js.map +1 -1
- package/dist/Decision.d.ts +13 -13
- package/dist/Decision.d.ts.map +1 -1
- package/dist/Decision.js.map +1 -1
- package/dist/Domain.d.ts +10 -21
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +4 -62
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +31 -7
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +25 -8
- package/dist/HttpApi.js.map +1 -1
- package/dist/Limits.d.ts +114 -0
- package/dist/Limits.d.ts.map +1 -0
- package/dist/Limits.js +132 -0
- package/dist/Limits.js.map +1 -0
- package/dist/Policy.d.ts +276 -269
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +73 -1177
- package/dist/Policy.js.map +1 -1
- package/dist/Resource.d.ts +4 -177
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +4 -803
- package/dist/Resource.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +4 -5
- package/dist/internal/algebra.js +267 -0
- package/dist/internal/algebra.js.map +1 -0
- package/dist/internal/analysis.js +245 -0
- package/dist/internal/analysis.js.map +1 -0
- package/dist/internal/authorizer.d.ts +30 -0
- package/dist/internal/authorizer.d.ts.map +1 -0
- package/dist/internal/authorizer.js +221 -0
- package/dist/internal/authorizer.js.map +1 -0
- package/dist/internal/bind.js +311 -0
- package/dist/internal/bind.js.map +1 -0
- package/dist/internal/catalog.d.ts +56 -0
- package/dist/internal/catalog.d.ts.map +1 -0
- package/dist/internal/catalog.js +292 -0
- package/dist/internal/catalog.js.map +1 -0
- package/dist/internal/decide.js +166 -0
- package/dist/internal/decide.js.map +1 -0
- package/dist/internal/domain.js +32 -0
- package/dist/internal/domain.js.map +1 -0
- package/dist/internal/json.js +134 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/limits.d.ts +14 -0
- package/dist/internal/limits.d.ts.map +1 -0
- package/dist/internal/limits.js +43 -0
- package/dist/internal/limits.js.map +1 -0
- package/dist/internal/nodes.d.ts +121 -0
- package/dist/internal/nodes.d.ts.map +1 -0
- package/dist/internal/nodes.js +277 -0
- package/dist/internal/nodes.js.map +1 -0
- package/dist/internal/patterns.js +110 -0
- package/dist/internal/patterns.js.map +1 -0
- package/dist/internal/policy-json.js +265 -0
- package/dist/internal/policy-json.js.map +1 -0
- package/dist/internal/policy-nodes.d.ts +61 -0
- package/dist/internal/policy-nodes.d.ts.map +1 -0
- package/dist/internal/policy-nodes.js +146 -0
- package/dist/internal/policy-nodes.js.map +1 -0
- package/dist/internal/policy.d.ts +42 -0
- package/dist/internal/policy.d.ts.map +1 -0
- package/dist/internal/policy.js +124 -0
- package/dist/internal/policy.js.map +1 -0
- package/dist/internal/read.js +39 -0
- package/dist/internal/read.js.map +1 -0
- package/dist/internal/resource.js +115 -0
- package/dist/internal/resource.js.map +1 -0
- package/dist/internal/rule.d.ts +26 -0
- package/dist/internal/rule.d.ts.map +1 -0
- package/dist/internal/rule.js +103 -0
- package/dist/internal/rule.js.map +1 -0
- package/dist/internal/search.d.ts +22 -0
- package/dist/internal/search.d.ts.map +1 -0
- package/dist/internal/search.js +274 -0
- package/dist/internal/search.js.map +1 -0
- package/dist/internal/suggest.js +46 -0
- package/dist/internal/suggest.js.map +1 -0
- package/package.json +3 -4
- package/src/Authorizer.ts +67 -583
- package/src/Condition.ts +339 -2095
- package/src/Decision.ts +6 -5
- package/src/Domain.ts +12 -69
- package/src/HttpApi.ts +72 -10
- package/src/Limits.ts +132 -0
- package/src/Policy.ts +391 -2772
- package/src/Resource.ts +7 -1548
- package/src/index.ts +3 -9
- package/src/internal/algebra.ts +482 -0
- package/src/internal/analysis.ts +478 -0
- package/src/internal/authorizer.ts +418 -0
- package/src/internal/bind.ts +648 -0
- package/src/internal/catalog.ts +534 -0
- package/src/internal/decide.ts +402 -0
- package/src/internal/domain.ts +66 -0
- package/src/internal/json.ts +260 -0
- package/src/internal/limits.ts +78 -0
- package/src/internal/nodes.ts +404 -0
- package/src/internal/patterns.ts +211 -0
- package/src/internal/policy-json.ts +487 -0
- package/src/internal/policy-nodes.ts +207 -0
- package/src/internal/policy.ts +264 -0
- package/src/internal/read.ts +55 -0
- package/src/internal/resource.ts +180 -0
- package/src/internal/rule.ts +196 -0
- package/src/internal/search.ts +541 -0
- package/src/internal/suggest.ts +104 -0
- package/dist/Principal.d.ts +0 -35
- package/dist/Principal.d.ts.map +0 -1
- package/dist/Principal.js +0 -37
- package/dist/Principal.js.map +0 -1
- package/dist/Rule.d.ts +0 -275
- package/dist/Rule.d.ts.map +0 -1
- package/dist/Rule.js +0 -257
- package/dist/Rule.js.map +0 -1
- package/src/Principal.ts +0 -41
- package/src/Rule.ts +0 -683
package/dist/Condition.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { And, Compare, Exists, In, Not, Or, Predicate, Under, always as always$1, isNode, literal, never as never$1, principalRef, toNode } from "./internal/nodes.js";
|
|
3
|
+
import { evaluate as evaluate$1, evaluateSync as evaluateSync$1, fields as fields$1, partial as partial$1, provide, simplify as simplify$1 } from "./internal/algebra.js";
|
|
4
|
+
import { catalogOf } from "./internal/catalog.js";
|
|
5
|
+
import { decode, toJson } from "./internal/json.js";
|
|
6
|
+
import { kindsIn, satisfiable as satisfiable$1 } from "./internal/search.js";
|
|
4
7
|
import * as Effect from "effect/Effect";
|
|
5
|
-
import * as
|
|
6
|
-
import * as Option from "effect/Option";
|
|
8
|
+
import * as Result from "effect/Result";
|
|
7
9
|
import * as Schema from "effect/Schema";
|
|
8
|
-
import * as
|
|
10
|
+
import * as Arr from "effect/Array";
|
|
9
11
|
import { dual } from "effect/Function";
|
|
10
|
-
import
|
|
11
|
-
import * as HashSet from "effect/HashSet";
|
|
12
|
-
import { hasProperty, isObject } from "effect/Predicate";
|
|
12
|
+
import { hasProperty } from "effect/Predicate";
|
|
13
13
|
import * as SchemaIssue from "effect/SchemaIssue";
|
|
14
14
|
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
15
15
|
// src/Condition.ts
|
|
@@ -46,8 +46,6 @@ var Condition_exports = /* @__PURE__ */ __exportAll({
|
|
|
46
46
|
evaluateSync: () => evaluateSync,
|
|
47
47
|
fields: () => fields,
|
|
48
48
|
fold: () => fold,
|
|
49
|
-
hasPredicate: () => hasPredicate,
|
|
50
|
-
isAst: () => isAst,
|
|
51
49
|
never: () => never,
|
|
52
50
|
not: () => not,
|
|
53
51
|
or: () => or,
|
|
@@ -56,29 +54,18 @@ var Condition_exports = /* @__PURE__ */ __exportAll({
|
|
|
56
54
|
provideContext: () => provideContext,
|
|
57
55
|
satisfiable: () => satisfiable,
|
|
58
56
|
schema: () => schema,
|
|
59
|
-
|
|
60
|
-
simplify: () => simplify,
|
|
61
|
-
toJsonValue: () => toJsonValue
|
|
57
|
+
simplify: () => simplify
|
|
62
58
|
});
|
|
63
|
-
const nodes = Data.taggedEnum();
|
|
64
|
-
const operands = Data.taggedEnum();
|
|
65
|
-
const literal = (value) => operands.Literal({ value });
|
|
66
59
|
const isOperand = (u) => hasProperty(u, "_tag") && (u._tag === "Literal" || u._tag === "Principal");
|
|
67
60
|
const toOperand = (value) => isOperand(value) ? value : literal(value);
|
|
68
|
-
const compare = (op) => (field, value) =>
|
|
61
|
+
const compare = (op) => (field, value) => new Compare({
|
|
69
62
|
op,
|
|
70
63
|
field,
|
|
71
64
|
value: toOperand(value)
|
|
72
65
|
});
|
|
73
|
-
const andOf = (conditions) =>
|
|
74
|
-
const orOf = (conditions) =>
|
|
75
|
-
const
|
|
76
|
-
const makeOr = (...conditions) => orOf(conditions);
|
|
77
|
-
const makeNot = (condition) => nodes.Not({ condition });
|
|
78
|
-
const makePredicate = (name, run) => nodes.Predicate({
|
|
79
|
-
name,
|
|
80
|
-
run
|
|
81
|
-
});
|
|
66
|
+
const andOf = (conditions) => new And(conditions);
|
|
67
|
+
const orOf = (conditions) => new Or(conditions);
|
|
68
|
+
const makeNot = (condition) => new Not(condition);
|
|
82
69
|
const sharedBuilder = {
|
|
83
70
|
eq: compare("eq"),
|
|
84
71
|
ne: compare("ne"),
|
|
@@ -86,20 +73,23 @@ const sharedBuilder = {
|
|
|
86
73
|
lte: compare("lte"),
|
|
87
74
|
gt: compare("gt"),
|
|
88
75
|
gte: compare("gte"),
|
|
89
|
-
in: (field, values) =>
|
|
76
|
+
in: (field, values) => new In({
|
|
90
77
|
field,
|
|
91
|
-
values:
|
|
78
|
+
values: Arr.map(Array.from(values), toOperand)
|
|
92
79
|
}),
|
|
93
|
-
exists: (field) =>
|
|
94
|
-
under: (...prefix) =>
|
|
95
|
-
principal: (path) =>
|
|
96
|
-
and:
|
|
97
|
-
or:
|
|
80
|
+
exists: (field) => new Exists({ field }),
|
|
81
|
+
under: (...prefix) => new Under({ prefix: Arr.map(prefix, toOperand) }),
|
|
82
|
+
principal: (path) => principalRef(path),
|
|
83
|
+
and: (...conditions) => andOf(conditions),
|
|
84
|
+
or: (...conditions) => orOf(conditions),
|
|
98
85
|
not: makeNot,
|
|
99
|
-
predicate: (name, test) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
86
|
+
predicate: (name, test) => new Predicate({
|
|
87
|
+
name,
|
|
88
|
+
run: (value, principal) => Effect.suspend(() => {
|
|
89
|
+
const result = test(value, principal);
|
|
90
|
+
return Effect.isEffect(result) ? result : Effect.succeed(result);
|
|
91
|
+
})
|
|
92
|
+
})
|
|
103
93
|
};
|
|
104
94
|
/**
|
|
105
95
|
* Makes a builder. Rules give you a builder with the right types, so you do not
|
|
@@ -117,7 +107,7 @@ const builder = () => sharedBuilder;
|
|
|
117
107
|
* @category constructors
|
|
118
108
|
* @since 0.3.0
|
|
119
109
|
*/
|
|
120
|
-
const always =
|
|
110
|
+
const always = always$1;
|
|
121
111
|
/**
|
|
122
112
|
* Always false.
|
|
123
113
|
*
|
|
@@ -125,7 +115,7 @@ const always = nodes.True();
|
|
|
125
115
|
* @category constructors
|
|
126
116
|
* @since 0.3.0
|
|
127
117
|
*/
|
|
128
|
-
const never =
|
|
118
|
+
const never = never$1;
|
|
129
119
|
/**
|
|
130
120
|
* True when all the conditions are true. With no conditions, it is true.
|
|
131
121
|
*
|
|
@@ -133,7 +123,7 @@ const never = nodes.False();
|
|
|
133
123
|
* @category combinators
|
|
134
124
|
* @since 0.3.0
|
|
135
125
|
*/
|
|
136
|
-
const and =
|
|
126
|
+
const and = ((...conditions) => andOf(conditions));
|
|
137
127
|
/**
|
|
138
128
|
* The same as {@link and}, for a list of conditions of one type. Use it for a
|
|
139
129
|
* long list: a function call may take only so many arguments.
|
|
@@ -159,118 +149,15 @@ const orAll = orOf;
|
|
|
159
149
|
* @category combinators
|
|
160
150
|
* @since 0.3.0
|
|
161
151
|
*/
|
|
162
|
-
const or =
|
|
152
|
+
const or = ((...conditions) => orOf(conditions));
|
|
163
153
|
/**
|
|
164
154
|
* @stability stable
|
|
165
155
|
* @category combinators
|
|
166
156
|
* @since 0.3.0
|
|
167
157
|
*/
|
|
168
158
|
const not = makeNot;
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
* under a value that is not an object, gives `null`.
|
|
172
|
-
*/
|
|
173
|
-
/**
|
|
174
|
-
* Tests if an object has a field. A field may be the object's own, or come
|
|
175
|
-
* from its class, as a getter does: `Schema.is` reads such a field too. A
|
|
176
|
-
* name that every object has from `Object.prototype`, such as `constructor`
|
|
177
|
-
* or `toString`, is a field only when the object gives it another value, as
|
|
178
|
-
* its own or from its class.
|
|
179
|
-
*/
|
|
180
|
-
const hasField = (value, key) => {
|
|
181
|
-
if (Object.hasOwn(value, key)) return true;
|
|
182
|
-
if (key === "__proto__" || !(key in value)) return false;
|
|
183
|
-
return !(key in Object.prototype) || value[key] !== Object.prototype[key];
|
|
184
|
-
};
|
|
185
|
-
const getField = (value, path) => Option.match(Arr.reduce(path.split("."), Option.some(value), (cursor, key) => Option.flatMap(cursor, (current) => isObject(current) && hasField(current, key) ? Option.some(current[key]) : Option.none())), {
|
|
186
|
-
onNone: () => null,
|
|
187
|
-
onSome: (field) => field === void 0 ? null : field
|
|
188
|
-
});
|
|
189
|
-
const isScalar = (value) => typeof value === "string" || typeof value === "boolean" || typeof value === "number";
|
|
190
|
-
/**
|
|
191
|
-
* Gives the value of an operand. A principal field that is `null`, missing, or
|
|
192
|
-
* not a {@link Scalar} gives `undefined`. No field of a resource is
|
|
193
|
-
* `undefined`, so a test with it is never true. This keeps the check closed: a
|
|
194
|
-
* principal with no id never matches a resource with no owner. It also keeps
|
|
195
|
-
* other values, such as a `bigint`, out of the residual condition.
|
|
196
|
-
*/
|
|
197
|
-
const resolve = (operand, principal) => {
|
|
198
|
-
if (operand._tag === "Literal") return operand.value;
|
|
199
|
-
const value = getField(principal, operand.path);
|
|
200
|
-
return isScalar(value) ? value : void 0;
|
|
201
|
-
};
|
|
202
|
-
const testCompare = (op, left, right) => {
|
|
203
|
-
if (right === void 0) return false;
|
|
204
|
-
switch (op) {
|
|
205
|
-
case "eq": return left === right;
|
|
206
|
-
case "ne": return left !== right;
|
|
207
|
-
default: {
|
|
208
|
-
if (!(typeof left === "number" && typeof right === "number" || typeof left === "string" && typeof right === "string")) return false;
|
|
209
|
-
const a = left;
|
|
210
|
-
const b = right;
|
|
211
|
-
switch (op) {
|
|
212
|
-
case "lt": return a < b;
|
|
213
|
-
case "lte": return a <= b;
|
|
214
|
-
case "gt": return a > b;
|
|
215
|
-
case "gte": return a >= b;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
/**
|
|
221
|
-
* Gives the values of a resource path. It gives `undefined` when the resource
|
|
222
|
-
* has no path.
|
|
223
|
-
*/
|
|
224
|
-
const pathValues = (value, path) => path?.map((segment) => typeof segment === "string" ? segment : getField(value, segment.field));
|
|
225
|
-
const testUnder = (prefix, input) => {
|
|
226
|
-
const values = pathValues(input.value, input.path);
|
|
227
|
-
if (values === void 0 || prefix.length > values.length) return false;
|
|
228
|
-
return prefix.every((operand, index) => {
|
|
229
|
-
const value = resolve(operand, input.principal);
|
|
230
|
-
return value !== void 0 && value === values[index];
|
|
231
|
-
});
|
|
232
|
-
};
|
|
233
|
-
/**
|
|
234
|
-
* True when a condition has a {@link Predicate}.
|
|
235
|
-
*
|
|
236
|
-
* @stability stable
|
|
237
|
-
* @category predicates
|
|
238
|
-
* @since 1.0.0
|
|
239
|
-
*/
|
|
240
|
-
const hasPredicate = (condition) => hasPredicateIn(condition, /* @__PURE__ */ new Map());
|
|
241
|
-
/**
|
|
242
|
-
* Gives the answer for a key from the memo, or works it out and keeps it.
|
|
243
|
-
*
|
|
244
|
-
* A memo keyed by a node is a native `Map`, because it must find a node by
|
|
245
|
-
* the node itself. An Effect `HashMap` finds a key by `Equal`, and the nodes
|
|
246
|
-
* are Effect data, so it would hash the whole structure of a node, and a
|
|
247
|
-
* shared child again for each path to it.
|
|
248
|
-
*/
|
|
249
|
-
const remember = (memo, key, work) => {
|
|
250
|
-
if (memo.has(key)) return memo.get(key);
|
|
251
|
-
const result = work();
|
|
252
|
-
memo.set(key, result);
|
|
253
|
-
return result;
|
|
254
|
-
};
|
|
255
|
-
const hasPredicateIn = (ast, memo) => remember(memo, ast, () => {
|
|
256
|
-
switch (ast._tag) {
|
|
257
|
-
case "Predicate": return true;
|
|
258
|
-
case "And":
|
|
259
|
-
case "Or": return Arr.some(ast.conditions, (child) => hasPredicateIn(child, memo));
|
|
260
|
-
case "Not": return hasPredicateIn(ast.condition, memo);
|
|
261
|
-
default: return false;
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
const provideAst = (ast, context, done = /* @__PURE__ */ new Map(), predicates = /* @__PURE__ */ new Map()) => !hasPredicateIn(ast, predicates) ? ast : remember(done, ast, () => {
|
|
265
|
-
const provide = (child) => provideAst(child, context, done, predicates);
|
|
266
|
-
switch (ast._tag) {
|
|
267
|
-
case "Predicate": return makePredicate(ast.name, (value, principal) => Effect.provideContext(ast.run(value, principal), context));
|
|
268
|
-
case "And": return andOf(Arr.map(ast.conditions, provide));
|
|
269
|
-
case "Or": return orOf(Arr.map(ast.conditions, provide));
|
|
270
|
-
case "Not": return makeNot(provide(ast.condition));
|
|
271
|
-
default: return ast;
|
|
272
|
-
}
|
|
273
|
-
});
|
|
159
|
+
/** Gives a condition as a node. A value that no constructor made is a defect. */
|
|
160
|
+
const nodeOf = (condition) => toNode(condition);
|
|
274
161
|
/**
|
|
275
162
|
* Gives the services in `context` to every predicate of a condition, so the
|
|
276
163
|
* condition needs no services when it runs.
|
|
@@ -279,37 +166,7 @@ const provideAst = (ast, context, done = /* @__PURE__ */ new Map(), predicates =
|
|
|
279
166
|
* @category services
|
|
280
167
|
* @since 1.0.0
|
|
281
168
|
*/
|
|
282
|
-
const provideContext = dual(2,
|
|
283
|
-
const evaluateSyncAst = (ast, input) => evaluateSyncNode(ast, input, /* @__PURE__ */ new Map());
|
|
284
|
-
const evaluateSyncNode = (ast, input, done) => remember(done, ast, () => evaluateSyncOnce(ast, input, done));
|
|
285
|
-
const evaluateSyncOnce = (ast, input, done) => {
|
|
286
|
-
switch (ast._tag) {
|
|
287
|
-
case "True": return true;
|
|
288
|
-
case "False": return false;
|
|
289
|
-
case "Compare": return testCompare(ast.op, getField(input.value, ast.field), resolve(ast.value, input.principal));
|
|
290
|
-
case "In": {
|
|
291
|
-
const field = getField(input.value, ast.field);
|
|
292
|
-
return ast.values.some((operand) => resolve(operand, input.principal) === field);
|
|
293
|
-
}
|
|
294
|
-
case "Exists": return getField(input.value, ast.field) !== null;
|
|
295
|
-
case "Under": return testUnder(ast.prefix, input);
|
|
296
|
-
case "And": return Arr.every(ast.conditions, (child) => evaluateSyncNode(child, input, done));
|
|
297
|
-
case "Or": return Arr.some(ast.conditions, (child) => evaluateSyncNode(child, input, done));
|
|
298
|
-
case "Not": return !evaluateSyncNode(ast.condition, input, done);
|
|
299
|
-
case "Predicate": throw new globalThis.Error(`The predicate "${ast.name}" needs an Effect. Use Condition.evaluate.`);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
const evaluateAst = (ast, input, predicates = /* @__PURE__ */ new Map()) => {
|
|
303
|
-
if (!hasPredicateIn(ast, predicates)) return Effect.sync(() => evaluateSyncAst(ast, input));
|
|
304
|
-
const evaluate = (child) => evaluateAst(child, input, predicates);
|
|
305
|
-
switch (ast._tag) {
|
|
306
|
-
case "Predicate": return ast.run(input.value, input.principal);
|
|
307
|
-
case "Not": return Effect.map(evaluate(ast.condition), (value) => !value);
|
|
308
|
-
case "And": return Effect.map(Effect.findFirst(ast.conditions, (child) => Effect.map(evaluate(child), (value) => !value)), Option.isNone);
|
|
309
|
-
case "Or": return Effect.map(Effect.findFirst(ast.conditions, evaluate), Option.isSome);
|
|
310
|
-
default: return Effect.sync(() => evaluateSyncAst(ast, input));
|
|
311
|
-
}
|
|
312
|
-
};
|
|
169
|
+
const provideContext = dual(2, (condition, context) => provide(nodeOf(condition), context));
|
|
313
170
|
/**
|
|
314
171
|
* Tests a condition on a value. A predicate may fail with its error. When the
|
|
315
172
|
* condition has a predicate, the input must have the value and the principal
|
|
@@ -319,7 +176,7 @@ const evaluateAst = (ast, input, predicates = /* @__PURE__ */ new Map()) => {
|
|
|
319
176
|
* @category evaluation
|
|
320
177
|
* @since 0.3.0
|
|
321
178
|
*/
|
|
322
|
-
const evaluate =
|
|
179
|
+
const evaluate = ((condition, input) => Effect.suspend(() => evaluate$1(nodeOf(condition), input)));
|
|
323
180
|
/**
|
|
324
181
|
* Tests a condition with no predicate on a value, at once.
|
|
325
182
|
*
|
|
@@ -327,203 +184,17 @@ const evaluate = dual(2, evaluateAst);
|
|
|
327
184
|
* @category evaluation
|
|
328
185
|
* @since 0.3.0
|
|
329
186
|
*/
|
|
330
|
-
const evaluateSync =
|
|
331
|
-
const makeMemo = () => ({
|
|
332
|
-
simple: /* @__PURE__ */ new Map(),
|
|
333
|
-
predicates: /* @__PURE__ */ new Map(),
|
|
334
|
-
ids: /* @__PURE__ */ new Map(),
|
|
335
|
-
structures: MutableHashMap.empty()
|
|
336
|
-
});
|
|
337
|
-
const literalText = (value) => typeof value === "number" ? `n${Object.is(value, -0) ? "-0" : String(value)}` : `${typeof value}:${JSON.stringify(value)}`;
|
|
338
|
-
const operandText = (operand) => operand._tag === "Literal" ? `L${literalText(operand.value)}` : `R${JSON.stringify(operand.path)}`;
|
|
339
|
-
const structureText = (ast, memo) => {
|
|
340
|
-
const id = (child) => structureId(child, memo);
|
|
341
|
-
switch (ast._tag) {
|
|
342
|
-
case "True":
|
|
343
|
-
case "False": return ast._tag;
|
|
344
|
-
case "Compare": return `C${ast.op}:${JSON.stringify(ast.field)}:${operandText(ast.value)}`;
|
|
345
|
-
case "In": return `I${JSON.stringify(ast.field)}:${Arr.map(ast.values, operandText).join(",")}`;
|
|
346
|
-
case "Exists": return `E${JSON.stringify(ast.field)}`;
|
|
347
|
-
case "Under": return `U${Arr.map(ast.prefix, operandText).join(",")}`;
|
|
348
|
-
case "And":
|
|
349
|
-
case "Or": return `${ast._tag}:${Arr.map(ast.conditions, id).join(",")}`;
|
|
350
|
-
case "Not": return `N${String(id(ast.condition))}`;
|
|
351
|
-
case "Predicate": return;
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
/**
|
|
355
|
-
* Gives the number of the structure of a node. Two nodes with the same
|
|
356
|
-
* structure get the same number, so a set of numbers finds an equal node at
|
|
357
|
-
* once. The hash of Effect data would walk a shared child again for each path
|
|
358
|
-
* to it. A new number is the count of the numbered nodes, so it is new.
|
|
359
|
-
*/
|
|
360
|
-
const structureId = (ast, memo) => remember(memo.ids, ast, () => {
|
|
361
|
-
const text = structureText(ast, memo);
|
|
362
|
-
const fresh = memo.ids.size;
|
|
363
|
-
if (text === void 0) return fresh;
|
|
364
|
-
return Option.getOrElse(MutableHashMap.get(memo.structures, text), () => {
|
|
365
|
-
MutableHashMap.set(memo.structures, text, fresh);
|
|
366
|
-
return fresh;
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
const dedupe = (conditions, memo) => {
|
|
370
|
-
const ids = Arr.map(conditions, (condition) => structureId(condition, memo));
|
|
371
|
-
const first = HashMap.fromIterable(Arr.reverse(Arr.map(ids, (id, index) => [id, index])));
|
|
372
|
-
return Arr.filter(conditions, (_, index) => Option.contains(HashMap.get(first, ids[index]), index));
|
|
373
|
-
};
|
|
374
|
-
/**
|
|
375
|
-
* Removes the children that the other children make useless. In an `Or`,
|
|
376
|
-
* `a OR (a AND b)` is `a`. In an `And`, `a AND (a OR b)` is `a`. The
|
|
377
|
-
* children are already without copies.
|
|
378
|
-
*/
|
|
379
|
-
const absorb = (inner, conditions, memo) => {
|
|
380
|
-
const all = HashSet.fromIterable(Arr.map(conditions, (condition) => structureId(condition, memo)));
|
|
381
|
-
return Arr.filter(conditions, (condition) => condition._tag !== inner || !Arr.some(condition.conditions, (part) => HashSet.has(all, structureId(part, memo))));
|
|
382
|
-
};
|
|
383
|
-
/**
|
|
384
|
-
* Removes children that do not change the result. A predicate may fail, and it
|
|
385
|
-
* may give another answer when it runs again. So when a child has a predicate,
|
|
386
|
-
* every child stays, and each one runs in its place.
|
|
387
|
-
*/
|
|
388
|
-
const reduce = (inner, conditions, memo) => Arr.some(conditions, (condition) => hasPredicateIn(condition, memo.predicates)) ? conditions : absorb(inner, dedupe(conditions, memo), memo);
|
|
389
|
-
/**
|
|
390
|
-
* Puts the children of a child of the same kind into the node. A child with a
|
|
391
|
-
* predicate stays one child: its children all stay anyway, and one shared
|
|
392
|
-
* child would come again for each path to it, so the node could grow without
|
|
393
|
-
* end.
|
|
394
|
-
*/
|
|
395
|
-
const flatten = (tag, conditions, memo) => Arr.flatMap(conditions, (child) => {
|
|
396
|
-
const simple = simplifyIn(child, memo);
|
|
397
|
-
return simple._tag === tag && !hasPredicateIn(simple, memo.predicates) ? simple.conditions : [simple];
|
|
398
|
-
});
|
|
399
|
-
/**
|
|
400
|
-
* Gives the result of an `And` with a `False` child, or of an `Or` with a
|
|
401
|
-
* `True` child. The children are tested in order, so a predicate before the
|
|
402
|
-
* constant still runs, and it may fail. When there is such a predicate, the
|
|
403
|
-
* children before the constant stay, so the failure is not lost. The other
|
|
404
|
-
* children stay too, because they decide if the predicate runs.
|
|
405
|
-
*/
|
|
406
|
-
const shortCircuit = (tag, children, index, memo) => {
|
|
407
|
-
const constant = tag === "And" ? never : always;
|
|
408
|
-
const neutral = tag === "And" ? "True" : "False";
|
|
409
|
-
const before = Arr.filter(Arr.take(children, index), (child) => child._tag !== neutral);
|
|
410
|
-
if (!Arr.some(before, (child) => hasPredicateIn(child, memo.predicates))) return constant;
|
|
411
|
-
return tag === "And" ? andOf([...before, constant]) : orOf([...before, constant]);
|
|
412
|
-
};
|
|
413
|
-
const simplifyIn = (ast, memo) => remember(memo.simple, ast, () => {
|
|
414
|
-
const result = simplifyNode(ast, memo);
|
|
415
|
-
memo.simple.set(result, result);
|
|
416
|
-
return result;
|
|
417
|
-
});
|
|
418
|
-
const simplifyJunction = (tag, conditions, memo) => {
|
|
419
|
-
const constant = tag === "And" ? "False" : "True";
|
|
420
|
-
const neutral = tag === "And" ? "True" : "False";
|
|
421
|
-
const flat = flatten(tag, conditions, memo);
|
|
422
|
-
return Option.match(Arr.findFirstIndex(flat, (child) => child._tag === constant), {
|
|
423
|
-
onSome: (stop) => shortCircuit(tag, flat, stop, memo),
|
|
424
|
-
onNone: () => {
|
|
425
|
-
const unique = reduce(tag === "And" ? "Or" : "And", Arr.filter(flat, (child) => child._tag !== neutral), memo);
|
|
426
|
-
return unique.length === 0 ? tag === "And" ? always : never : unique.length === 1 ? unique[0] : tag === "And" ? andOf(unique) : orOf(unique);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
};
|
|
430
|
-
const simplifyNode = (ast, memo) => {
|
|
431
|
-
switch (ast._tag) {
|
|
432
|
-
case "And":
|
|
433
|
-
case "Or": return simplifyJunction(ast._tag, ast.conditions, memo);
|
|
434
|
-
case "Not": {
|
|
435
|
-
const child = simplifyIn(ast.condition, memo);
|
|
436
|
-
switch (child._tag) {
|
|
437
|
-
case "True": return never;
|
|
438
|
-
case "False": return always;
|
|
439
|
-
case "Not": return child.condition;
|
|
440
|
-
case "Compare":
|
|
441
|
-
if (child.value._tag === "Literal" && (child.op === "eq" || child.op === "ne")) return nodes.Compare({
|
|
442
|
-
op: child.op === "eq" ? "ne" : "eq",
|
|
443
|
-
field: child.field,
|
|
444
|
-
value: child.value
|
|
445
|
-
});
|
|
446
|
-
return makeNot(child);
|
|
447
|
-
default: return makeNot(child);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
case "In": return ast.values.length === 0 ? never : ast;
|
|
451
|
-
default: return ast;
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
const simplifyAst = (ast) => simplifyIn(ast, makeMemo());
|
|
187
|
+
const evaluateSync = (condition, input) => evaluateSync$1(nodeOf(condition), input);
|
|
455
188
|
/**
|
|
456
189
|
* Makes a condition smaller without a change to its meaning. It removes `True`
|
|
457
190
|
* and `False` from `And` and `Or`, joins nested `And` and `Or` nodes, and
|
|
458
|
-
* removes double `Not` nodes.
|
|
191
|
+
* removes double `Not` nodes and copies of a child.
|
|
459
192
|
*
|
|
460
193
|
* @stability stable
|
|
461
194
|
* @category evaluation
|
|
462
195
|
* @since 0.3.0
|
|
463
196
|
*/
|
|
464
|
-
const simplify =
|
|
465
|
-
/**
|
|
466
|
-
* Makes a {@link simplify} function that keeps its work for the parts it has
|
|
467
|
-
* seen. Use one for many calls on conditions that share parts, such as the
|
|
468
|
-
* steps of one residual condition. Do not keep it for long: it keeps every
|
|
469
|
-
* part that it has seen.
|
|
470
|
-
*
|
|
471
|
-
* @stability stable
|
|
472
|
-
* @category evaluation
|
|
473
|
-
* @since 1.0.0
|
|
474
|
-
*/
|
|
475
|
-
const simplifier = () => {
|
|
476
|
-
const memo = makeMemo();
|
|
477
|
-
return ((ast) => simplifyIn(ast, memo));
|
|
478
|
-
};
|
|
479
|
-
/**
|
|
480
|
-
* Puts the value of the principal into an operand. It gives `undefined` for a
|
|
481
|
-
* principal field with no value. See {@link resolve}.
|
|
482
|
-
*/
|
|
483
|
-
const bindOperand = (operand, principal) => {
|
|
484
|
-
if (operand._tag === "Literal") return operand;
|
|
485
|
-
const value = resolve(operand, principal);
|
|
486
|
-
return value === void 0 ? void 0 : literal(value);
|
|
487
|
-
};
|
|
488
|
-
const expandUnder = (prefix, principal, path) => {
|
|
489
|
-
if (path === void 0 || prefix.length > path.length) return never;
|
|
490
|
-
const parts = Arr.map(prefix, (operand, index) => {
|
|
491
|
-
const value = resolve(operand, principal);
|
|
492
|
-
const segment = path[index];
|
|
493
|
-
return value === void 0 ? never : typeof segment === "string" ? segment === value ? always : never : nodes.Compare({
|
|
494
|
-
op: "eq",
|
|
495
|
-
field: segment.field,
|
|
496
|
-
value: literal(value)
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
return andOf(parts);
|
|
500
|
-
};
|
|
501
|
-
const bind = (ast, principal, path, done = /* @__PURE__ */ new Map()) => remember(done, ast, () => bindOnce(ast, principal, path, done));
|
|
502
|
-
const bindOnce = (ast, principal, path, done) => {
|
|
503
|
-
switch (ast._tag) {
|
|
504
|
-
case "Compare": {
|
|
505
|
-
const value = bindOperand(ast.value, principal);
|
|
506
|
-
return value === void 0 ? never : nodes.Compare({
|
|
507
|
-
op: ast.op,
|
|
508
|
-
field: ast.field,
|
|
509
|
-
value
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
case "In": return nodes.In({
|
|
513
|
-
field: ast.field,
|
|
514
|
-
values: ast.values.flatMap((operand) => {
|
|
515
|
-
const value = bindOperand(operand, principal);
|
|
516
|
-
return value === void 0 ? [] : [value];
|
|
517
|
-
})
|
|
518
|
-
});
|
|
519
|
-
case "Under": return expandUnder(ast.prefix, principal, path);
|
|
520
|
-
case "And": return andOf(ast.conditions.map((child) => bind(child, principal, path, done)));
|
|
521
|
-
case "Or": return orOf(ast.conditions.map((child) => bind(child, principal, path, done)));
|
|
522
|
-
case "Not": return makeNot(bind(ast.condition, principal, path, done));
|
|
523
|
-
case "Predicate": return makePredicate(ast.name, (value) => ast.run(value, principal));
|
|
524
|
-
default: return ast;
|
|
525
|
-
}
|
|
526
|
-
};
|
|
197
|
+
const simplify = ((condition) => condition.pipe(nodeOf, simplify$1));
|
|
527
198
|
/**
|
|
528
199
|
* Puts the principal into a condition and expands `Under` with the path of the
|
|
529
200
|
* resource. The result is simplified. It has no principal fields and no
|
|
@@ -534,7 +205,7 @@ const bindOnce = (ast, principal, path, done) => {
|
|
|
534
205
|
* @category evaluation
|
|
535
206
|
* @since 0.3.0
|
|
536
207
|
*/
|
|
537
|
-
const partial =
|
|
208
|
+
const partial = ((condition, input) => partial$1(nodeOf(condition), input));
|
|
538
209
|
/**
|
|
539
210
|
* The error when `fold` can not change a node.
|
|
540
211
|
*
|
|
@@ -547,25 +218,21 @@ const literalValue = (operand, field) => {
|
|
|
547
218
|
if (operand._tag !== "Literal") throw Untranslatable.make({ reason: `The field "${field}" uses a principal field. Call Condition.partial first.` });
|
|
548
219
|
return operand.value;
|
|
549
220
|
};
|
|
550
|
-
/**
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
*/
|
|
554
|
-
const foldAst = (ast, interpreter, done = /* @__PURE__ */ new Map()) => remember(done, ast, () => foldOnce(ast, interpreter, done));
|
|
555
|
-
const foldOnce = (ast, interpreter, done) => {
|
|
556
|
-
switch (ast._tag) {
|
|
221
|
+
/** Folds a condition. A shared child is folded again for each place. */
|
|
222
|
+
const foldNode = (node, interpreter) => {
|
|
223
|
+
switch (node._tag) {
|
|
557
224
|
case "True": return interpreter.true();
|
|
558
225
|
case "False": return interpreter.false();
|
|
559
|
-
case "Compare": return interpreter.compare(
|
|
560
|
-
case "In": return interpreter.in(
|
|
561
|
-
case "Exists": return interpreter.exists(
|
|
226
|
+
case "Compare": return interpreter.compare(node.op, node.field, literalValue(node.value, node.field));
|
|
227
|
+
case "In": return interpreter.in(node.field, Arr.map(node.values, (operand) => literalValue(operand, node.field)));
|
|
228
|
+
case "Exists": return interpreter.exists(node.field);
|
|
562
229
|
case "Under": throw Untranslatable.make({ reason: "An Under node needs the resource path. Call Condition.partial first." });
|
|
563
|
-
case "And": return interpreter.and(Arr.map(
|
|
564
|
-
case "Or": return interpreter.or(Arr.map(
|
|
565
|
-
case "Not": return interpreter.not(
|
|
230
|
+
case "And": return interpreter.and(Arr.map(node.conditions, (child) => foldNode(child, interpreter)));
|
|
231
|
+
case "Or": return interpreter.or(Arr.map(node.conditions, (child) => foldNode(child, interpreter)));
|
|
232
|
+
case "Not": return interpreter.not(foldNode(node.condition, interpreter));
|
|
566
233
|
case "Predicate":
|
|
567
|
-
if (interpreter.predicate === void 0) throw Untranslatable.make({ reason: `The predicate "${
|
|
568
|
-
return interpreter.predicate(
|
|
234
|
+
if (interpreter.predicate === void 0) throw Untranslatable.make({ reason: `The predicate "${node.name}" can not become a query` });
|
|
235
|
+
return interpreter.predicate(node.name);
|
|
569
236
|
}
|
|
570
237
|
};
|
|
571
238
|
/**
|
|
@@ -577,547 +244,65 @@ const foldOnce = (ast, interpreter, done) => {
|
|
|
577
244
|
* @category converting
|
|
578
245
|
* @since 0.3.0
|
|
579
246
|
*/
|
|
580
|
-
const fold = dual(2, (
|
|
581
|
-
try: () =>
|
|
247
|
+
const fold = dual(2, (condition, interpreter) => Effect.try({
|
|
248
|
+
try: () => foldNode(nodeOf(condition), interpreter),
|
|
582
249
|
catch: (cause) => {
|
|
583
250
|
if (cause instanceof Untranslatable) return cause;
|
|
584
251
|
throw cause;
|
|
585
252
|
}
|
|
586
253
|
}));
|
|
587
254
|
/**
|
|
588
|
-
* The fields that a condition uses.
|
|
255
|
+
* The fields that a condition uses, in the order that they first come.
|
|
589
256
|
*
|
|
590
257
|
* @stability stable
|
|
591
258
|
* @category getters
|
|
592
259
|
* @since 0.3.0
|
|
593
260
|
*/
|
|
594
|
-
const fields = (condition) =>
|
|
595
|
-
/**
|
|
596
|
-
* The fields of a node, in the order that they first come. A `HashSet` would
|
|
597
|
-
* not keep that order, so it only says which fields came before.
|
|
598
|
-
*/
|
|
599
|
-
/** Leaves out each string that came before, in linear time. */
|
|
600
|
-
const dedupeStrings = (values) => Arr.flatten(Arr.mapAccum(values, HashSet.empty(), (seen, value) => [HashSet.add(seen, value), HashSet.has(seen, value) ? [] : [value]])[1]);
|
|
601
|
-
const fieldsIn = (ast, memo) => remember(memo, ast, () => {
|
|
602
|
-
switch (ast._tag) {
|
|
603
|
-
case "Compare":
|
|
604
|
-
case "In":
|
|
605
|
-
case "Exists": return [ast.field];
|
|
606
|
-
case "And":
|
|
607
|
-
case "Or": return dedupeStrings(Arr.flatMap(ast.conditions, (child) => fieldsIn(child, memo)));
|
|
608
|
-
case "Not": return fieldsIn(ast.condition, memo);
|
|
609
|
-
default: return [];
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
const satisfiability = Data.taggedEnum();
|
|
613
|
-
/** Stands for a value with parts, such as a struct or a date. */
|
|
614
|
-
const opaque = Object.freeze({});
|
|
615
|
-
/** Marks a field that a step of the search did not fix. */
|
|
616
|
-
const free = Symbol("free");
|
|
617
|
-
/** The least number that is greater than `value`. */
|
|
618
|
-
const nextUp = (value) => {
|
|
619
|
-
if (Number.isNaN(value) || value === Infinity) return value;
|
|
620
|
-
if (value === 0) return Number.MIN_VALUE;
|
|
621
|
-
const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
|
|
622
|
-
view.setFloat64(0, value);
|
|
623
|
-
const bits = view.getBigUint64(0);
|
|
624
|
-
view.setBigUint64(0, value > 0 ? bits + 1n : bits - 1n);
|
|
625
|
-
return view.getFloat64(0);
|
|
626
|
-
};
|
|
627
|
-
/**
|
|
628
|
-
* Gives one number from each part of the number line that the constants
|
|
629
|
-
* make: each constant, and one number between each two neighbours when there
|
|
630
|
-
* is one. The ends of the domain are in the list too.
|
|
631
|
-
*/
|
|
632
|
-
const numberCells = (kind, constants) => {
|
|
633
|
-
const integer = kind === "integer";
|
|
634
|
-
const low = integer ? Number.MIN_SAFE_INTEGER : kind === "finite" ? -Number.MAX_VALUE : -Infinity;
|
|
635
|
-
const high = integer ? Number.MAX_SAFE_INTEGER : kind === "finite" ? Number.MAX_VALUE : Infinity;
|
|
636
|
-
const known = Arr.filter(constants, (value) => !Number.isNaN(value));
|
|
637
|
-
const ordered = [
|
|
638
|
-
low,
|
|
639
|
-
high,
|
|
640
|
-
...Arr.filter(Arr.flatMap(known, (value) => integer && Number.isFinite(value) ? [Math.floor(value), Math.ceil(value)] : [value]), (value) => value >= low && value <= high)
|
|
641
|
-
].sort((a, b) => a - b);
|
|
642
|
-
const sorted = Arr.dedupeAdjacentWith(ordered, (a, b) => a === b);
|
|
643
|
-
const cells = Arr.flatMap(sorted, (value, index) => {
|
|
644
|
-
const after = sorted[index + 1];
|
|
645
|
-
const between = integer ? value + 1 : nextUp(value);
|
|
646
|
-
return after !== void 0 && between < after ? [value, between] : [value];
|
|
647
|
-
});
|
|
648
|
-
return kind === "any" ? Arr.append(cells, NaN) : cells;
|
|
649
|
-
};
|
|
650
|
-
/**
|
|
651
|
-
* Gives one string from each part that the constants make. Strings are in the
|
|
652
|
-
* order of their UTF-16 code units, as `<` sees them. The least string after
|
|
653
|
-
* `a` is `a + "\0"`, so there is a string between `a` and `b` exactly when
|
|
654
|
-
* `b` is not `a + "\0"`.
|
|
655
|
-
*/
|
|
656
|
-
const stringCells = (constants) => {
|
|
657
|
-
const ordered = [...constants].sort();
|
|
658
|
-
const sorted = Arr.dedupeAdjacentWith(ordered, (a, b) => a === b);
|
|
659
|
-
if (sorted.length === 0) return [""];
|
|
660
|
-
return [...sorted[0] === "" ? [] : [""], ...Arr.flatMap(sorted, (value, index) => {
|
|
661
|
-
const next = `${value}\0`;
|
|
662
|
-
return next === sorted[index + 1] ? [value] : [value, next];
|
|
663
|
-
})];
|
|
664
|
-
};
|
|
665
|
-
/**
|
|
666
|
-
* Gives one value from each cell of a field. Each test of a field compares it
|
|
667
|
-
* with constants, so the constants cut the domain into a few cells, and every
|
|
668
|
-
* test gives the same answer for all the values of one cell.
|
|
669
|
-
*/
|
|
670
|
-
const cellsOf = (domain, constants) => dedupeCells([
|
|
671
|
-
...domain.null ? [null] : [],
|
|
672
|
-
...domain.object ? [opaque] : [],
|
|
673
|
-
...domain.boolean ? [true, false] : [],
|
|
674
|
-
...domain.string ? stringCells(Arr.filter(constants, (value) => typeof value === "string")) : [],
|
|
675
|
-
...domain.number === "none" ? [] : numberCells(domain.number, Arr.filter(constants, (value) => typeof value === "number")),
|
|
676
|
-
...domain.literals
|
|
677
|
-
]);
|
|
678
|
-
/**
|
|
679
|
-
* A text for a cell. Two cells get one text when every test gives the same
|
|
680
|
-
* answer for them: `0` and `-0` are one, and `NaN` is one.
|
|
681
|
-
*/
|
|
682
|
-
const cellText = (cell) => cell === opaque ? "opaque" : typeof cell === "number" ? `number:${Object.is(cell, -0) ? "0" : String(cell)}` : `${typeof cell}:${String(cell)}`;
|
|
683
|
-
/**
|
|
684
|
-
* Keeps the first cell of each text, in order. A map from each text to its
|
|
685
|
-
* first place does it in one pass. A test of each pair would take a very long
|
|
686
|
-
* time for many constants.
|
|
687
|
-
*/
|
|
688
|
-
const dedupeCells = (cells) => {
|
|
689
|
-
const texts = Arr.map(cells, cellText);
|
|
690
|
-
const first = HashMap.fromIterable(Arr.reverse(Arr.map(texts, (text, index) => [text, index])));
|
|
691
|
-
return Arr.filter(cells, (_, index) => Option.contains(HashMap.get(first, texts[index]), index));
|
|
692
|
-
};
|
|
693
|
-
var OutOfBudget = class {};
|
|
694
|
-
const literalOf = (operand) => {
|
|
695
|
-
if (operand._tag !== "Literal") throw new Error("Condition.satisfiable needs a condition with no principal fields. Call Condition.partial first.");
|
|
696
|
-
return operand.value;
|
|
697
|
-
};
|
|
698
|
-
/**
|
|
699
|
-
* The value of the field `name` after `field` gets `value`. A field below
|
|
700
|
-
* `field` is `null` when `value` is not a value with parts.
|
|
701
|
-
*/
|
|
702
|
-
const fixedValue = (name, field, value) => name === field ? value : name.startsWith(`${field}.`) ? value === opaque ? free : null : free;
|
|
703
|
-
const testFixed = (ast, value, search) => {
|
|
704
|
-
switch (ast._tag) {
|
|
705
|
-
case "Compare": return testCompare(ast.op, value, literalOf(ast.value));
|
|
706
|
-
case "In": return !Number.isNaN(value) && HashSet.has(remember(search.members, ast, () => HashSet.fromIterable(Arr.map(Arr.filter(Arr.map(ast.values, literalOf), (literal) => !Number.isNaN(literal)), cellText))), cellText(value));
|
|
707
|
-
case "Exists": return value !== null;
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
/**
|
|
711
|
-
* Puts one value into a field, and into the fields below it. The memo has a
|
|
712
|
-
* node for each node that the step visits, so its size is the work of the
|
|
713
|
-
* step.
|
|
714
|
-
*/
|
|
715
|
-
const fix = (ast, field, value, search, done) => remember(done, ast, () => {
|
|
716
|
-
const fixChild = (child) => fix(child, field, value, search, done);
|
|
717
|
-
switch (ast._tag) {
|
|
718
|
-
case "Compare":
|
|
719
|
-
case "In":
|
|
720
|
-
case "Exists": {
|
|
721
|
-
const known = fixedValue(ast.field, field, value);
|
|
722
|
-
return known === free ? ast : testFixed(ast, known, search) ? always : never;
|
|
723
|
-
}
|
|
724
|
-
case "And": return andOf(Arr.map(ast.conditions, fixChild));
|
|
725
|
-
case "Or": return orOf(Arr.map(ast.conditions, fixChild));
|
|
726
|
-
case "Not": return makeNot(fixChild(ast.condition));
|
|
727
|
-
default: return ast;
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
/**
|
|
731
|
-
* Counts the tests of each field. Each node is visited one time, so the work
|
|
732
|
-
* is the number of nodes, and the walk gives that work back for the budget.
|
|
733
|
-
* The counts and the seen nodes are a memo of this walk only.
|
|
734
|
-
*/
|
|
735
|
-
const countFields = (ast) => {
|
|
736
|
-
const counts = MutableHashMap.empty();
|
|
737
|
-
const seen = /* @__PURE__ */ new Set();
|
|
738
|
-
const visit = (node) => {
|
|
739
|
-
if (seen.has(node)) return;
|
|
740
|
-
seen.add(node);
|
|
741
|
-
switch (node._tag) {
|
|
742
|
-
case "Compare":
|
|
743
|
-
case "In":
|
|
744
|
-
case "Exists":
|
|
745
|
-
MutableHashMap.set(counts, node.field, Option.getOrElse(MutableHashMap.get(counts, node.field), () => 0) + 1);
|
|
746
|
-
return;
|
|
747
|
-
case "And":
|
|
748
|
-
case "Or": return Arr.forEach(node.conditions, visit);
|
|
749
|
-
case "Not": return visit(node.condition);
|
|
750
|
-
default: return;
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
visit(ast);
|
|
754
|
-
return {
|
|
755
|
-
counts,
|
|
756
|
-
work: seen.size
|
|
757
|
-
};
|
|
758
|
-
};
|
|
759
|
-
/**
|
|
760
|
-
* Picks the next field to split on. A field comes before the fields below it,
|
|
761
|
-
* so a value of a field can fix them. Then a field in more tests comes first,
|
|
762
|
-
* and then the first name in order, so the choice is the same each time.
|
|
763
|
-
*/
|
|
764
|
-
const nextField = (counts) => {
|
|
765
|
-
const depth = (field) => field.split(".").length;
|
|
766
|
-
const [first, ...rest] = Array.from(counts);
|
|
767
|
-
return Arr.reduce(rest, first, (best, next) => depth(next[0]) < depth(best[0]) || depth(next[0]) === depth(best[0]) && (next[1] > best[1] || next[1] === best[1] && next[0] < best[0]) ? next : best)[0];
|
|
768
|
-
};
|
|
769
|
-
/**
|
|
770
|
-
* Finds values of the fields that make the condition true. It splits on one
|
|
771
|
-
* field at a time, and tries one value from each cell of the field.
|
|
772
|
-
*/
|
|
773
|
-
const searchAst = (ast, search, state, assignment) => {
|
|
774
|
-
const current = simplifyIn(ast, search.memo);
|
|
775
|
-
if (current._tag === "True") return {
|
|
776
|
-
assignment: Option.some(assignment),
|
|
777
|
-
state
|
|
778
|
-
};
|
|
779
|
-
if (current._tag === "False") return {
|
|
780
|
-
assignment: Option.none(),
|
|
781
|
-
state
|
|
782
|
-
};
|
|
783
|
-
const id = structureId(current, search.memo);
|
|
784
|
-
if (HashSet.has(state.failed, id)) return {
|
|
785
|
-
assignment: Option.none(),
|
|
786
|
-
state
|
|
787
|
-
};
|
|
788
|
-
const counted = countFields(current);
|
|
789
|
-
if (state.steps + counted.work > search.budget) throw new OutOfBudget();
|
|
790
|
-
const field = nextField(counted.counts);
|
|
791
|
-
const cells = search.cells(field);
|
|
792
|
-
const result = Arr.reduce(cells, {
|
|
793
|
-
assignment: Option.none(),
|
|
794
|
-
state
|
|
795
|
-
}, (before, cell) => {
|
|
796
|
-
if (Option.isSome(before.assignment)) return before;
|
|
797
|
-
const done = /* @__PURE__ */ new Map();
|
|
798
|
-
const fixed = fix(current, field, cell, search, done);
|
|
799
|
-
const steps = before.state.steps + done.size;
|
|
800
|
-
if (steps > search.budget) throw new OutOfBudget();
|
|
801
|
-
return searchAst(fixed, search, {
|
|
802
|
-
...before.state,
|
|
803
|
-
steps
|
|
804
|
-
}, [...assignment, [field, cell]]);
|
|
805
|
-
});
|
|
806
|
-
return Option.isSome(result.assignment) ? result : {
|
|
807
|
-
assignment: Option.none(),
|
|
808
|
-
state: {
|
|
809
|
-
...result.state,
|
|
810
|
-
failed: HashSet.add(result.state.failed, id)
|
|
811
|
-
}
|
|
812
|
-
};
|
|
813
|
-
};
|
|
814
|
-
/**
|
|
815
|
-
* Puts a value at a path in an object, and gives a new object. A value with
|
|
816
|
-
* parts (`opaque`) becomes an empty object, and it keeps an object that is
|
|
817
|
-
* there.
|
|
818
|
-
*/
|
|
819
|
-
const setPath = (root, names, value) => {
|
|
820
|
-
const [name, ...rest] = names;
|
|
821
|
-
if (name === void 0) return root;
|
|
822
|
-
const inner = Object.hasOwn(root, name) ? root[name] : void 0;
|
|
823
|
-
if (rest.length === 0) return value === opaque && isObject(inner) ? root : {
|
|
824
|
-
...root,
|
|
825
|
-
[name]: value === opaque ? {} : value
|
|
826
|
-
};
|
|
827
|
-
const base = isObject(inner) && inner !== opaque ? inner : {};
|
|
828
|
-
return {
|
|
829
|
-
...root,
|
|
830
|
-
[name]: setPath(base, rest, value)
|
|
831
|
-
};
|
|
832
|
-
};
|
|
833
|
-
const exampleOf = (assignment) => Arr.reduce(assignment, {}, (root, [field, value]) => setPath(root, field.split("."), value));
|
|
834
|
-
const addConstants = (left, right) => HashMap.reduce(right, left, (all, values, field) => HashMap.set(all, field, HashSet.union(Option.getOrElse(HashMap.get(all, field), HashSet.empty), values)));
|
|
835
|
-
const constantsOf = (ast, memo = /* @__PURE__ */ new Map()) => remember(memo, ast, () => {
|
|
836
|
-
switch (ast._tag) {
|
|
837
|
-
case "Compare": return HashMap.make([ast.field, HashSet.make(literalOf(ast.value))]);
|
|
838
|
-
case "In": return HashMap.make([ast.field, HashSet.fromIterable(Arr.map(ast.values, literalOf))]);
|
|
839
|
-
case "Exists": return HashMap.make([ast.field, HashSet.empty()]);
|
|
840
|
-
case "Under": throw new Error("Condition.satisfiable needs a condition with no Under nodes. Call Condition.partial first.");
|
|
841
|
-
case "And":
|
|
842
|
-
case "Or": return Arr.reduce(ast.conditions, HashMap.empty(), (all, child) => addConstants(all, constantsOf(child, memo)));
|
|
843
|
-
case "Not": return constantsOf(ast.condition, memo);
|
|
844
|
-
default: return HashMap.empty();
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
/**
|
|
848
|
-
* The most fields that the search of {@link satisfiable} takes. Past it, the
|
|
849
|
-
* answer is `Unknown` with the reason `"budget"`.
|
|
850
|
-
*/
|
|
851
|
-
const maxSearchFields = 256;
|
|
852
|
-
/**
|
|
853
|
-
* The most parts of a field path that the search of {@link satisfiable}
|
|
854
|
-
* takes. Past it, the answer is `Unknown` with the reason `"budget"`.
|
|
855
|
-
*/
|
|
856
|
-
const maxSearchParts = 256;
|
|
857
|
-
const satisfiableAst = (ast, options = {}) => {
|
|
858
|
-
if (hasPredicate(ast)) return satisfiability.Unknown({ reason: "predicate" });
|
|
859
|
-
const schema = options.schema;
|
|
860
|
-
const tested = fields(ast);
|
|
861
|
-
if (schema !== void 0 && hasNoValue(schema)) return satisfiability.Unsatisfiable();
|
|
862
|
-
const complete = schema === void 0 || hasValue(schema);
|
|
863
|
-
if (schema !== void 0 && !Arr.every(tested, (field) => isPlainField(schema, field))) return satisfiability.Unknown({ reason: "schema" });
|
|
864
|
-
if (tested.length > maxSearchFields || Arr.some(tested, (field) => field.split(".", 257).length > maxSearchParts)) return satisfiability.Unknown({ reason: "budget" });
|
|
865
|
-
const constants = constantsOf(ast);
|
|
866
|
-
const cells = MutableHashMap.empty();
|
|
867
|
-
const search = {
|
|
868
|
-
memo: makeMemo(),
|
|
869
|
-
members: /* @__PURE__ */ new Map(),
|
|
870
|
-
cells: (field) => Option.getOrElse(MutableHashMap.get(cells, field), () => {
|
|
871
|
-
const known = HashMap.get(constants, field);
|
|
872
|
-
if (Option.isSome(known) && HashSet.size(known.value) > (options.budget ?? 1e6)) throw new OutOfBudget();
|
|
873
|
-
const found = cellsOf(schema === void 0 ? anyDomain : domainOf(schema, field), Array.from(Option.getOrElse(HashMap.get(constants, field), HashSet.empty)));
|
|
874
|
-
MutableHashMap.set(cells, field, found);
|
|
875
|
-
return found;
|
|
876
|
-
}),
|
|
877
|
-
budget: options.budget ?? 1e6
|
|
878
|
-
};
|
|
879
|
-
try {
|
|
880
|
-
const found = searchAst(ast, search, {
|
|
881
|
-
steps: 0,
|
|
882
|
-
failed: HashSet.empty()
|
|
883
|
-
}, []);
|
|
884
|
-
return Option.match(found.assignment, {
|
|
885
|
-
onNone: () => satisfiability.Unsatisfiable(),
|
|
886
|
-
onSome: (assignment) => complete ? satisfiability.Satisfiable({ example: exampleOf(assignment) }) : satisfiability.Unknown({ reason: "schema" })
|
|
887
|
-
});
|
|
888
|
-
} catch (error) {
|
|
889
|
-
if (error instanceof OutOfBudget) return satisfiability.Unknown({ reason: "budget" });
|
|
890
|
-
throw error;
|
|
891
|
-
}
|
|
892
|
-
};
|
|
261
|
+
const fields = (condition) => condition.pipe(nodeOf, fields$1);
|
|
893
262
|
/**
|
|
894
263
|
* Tests if some value makes a condition true, and gives such a value.
|
|
895
264
|
*
|
|
896
265
|
* Call {@link partial} first: the condition must have no principal fields and
|
|
897
266
|
* no `Under` nodes. A condition with a predicate gives `Unknown`.
|
|
898
267
|
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
*
|
|
268
|
+
* Each test of a condition compares one field with constants, so the
|
|
269
|
+
* constants cut the values of each field into a few cells. The search tries
|
|
270
|
+
* one value from each cell, one field at a time. The values of a field come
|
|
271
|
+
* from the shape of the Schema in `options.schema`: its literals, `null` for
|
|
272
|
+
* a nullable or optional field, and finite or integer numbers.
|
|
904
273
|
*
|
|
905
|
-
* The
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
* of a field come from the Schema in `options.schema`: its literals, `null`
|
|
909
|
-
* for a nullable or optional field, and finite or integer numbers. A field
|
|
910
|
-
* with another Schema check is not a plain field, so the search never tries a
|
|
911
|
-
* value that such a check stops.
|
|
274
|
+
* The search reads the shape of the Schema, not its other checks, such as a
|
|
275
|
+
* minimum length. So `Unsatisfiable` holds for every value of the Schema. A
|
|
276
|
+
* `Satisfiable` example fits the shape, but a check may still stop it.
|
|
912
277
|
*
|
|
913
278
|
* The problem is as hard as boolean satisfiability, so the search has a
|
|
914
279
|
* budget. Past it, the answer is `Unknown`. A condition with more than 256
|
|
915
|
-
* fields
|
|
280
|
+
* fields gives `Unknown` too.
|
|
916
281
|
*
|
|
917
282
|
* @stability stable
|
|
918
283
|
* @category evaluation
|
|
919
284
|
* @since 1.0.0
|
|
920
285
|
*/
|
|
921
|
-
const satisfiable =
|
|
922
|
-
const operandJson = (operand) => operand._tag === "Literal" && typeof operand.value === "number" && !Number.isFinite(operand.value) ? {
|
|
923
|
-
...operand,
|
|
924
|
-
value: { nonFinite: String(operand.value) }
|
|
925
|
-
} : operand;
|
|
926
|
-
const jsonIn = (ast, done) => remember(done, ast, () => {
|
|
927
|
-
switch (ast._tag) {
|
|
928
|
-
case "Compare": return {
|
|
929
|
-
...ast,
|
|
930
|
-
value: operandJson(ast.value)
|
|
931
|
-
};
|
|
932
|
-
case "In": return {
|
|
933
|
-
...ast,
|
|
934
|
-
values: Arr.map(ast.values, operandJson)
|
|
935
|
-
};
|
|
936
|
-
case "Under": return {
|
|
937
|
-
...ast,
|
|
938
|
-
prefix: Arr.map(ast.prefix, operandJson)
|
|
939
|
-
};
|
|
940
|
-
case "And":
|
|
941
|
-
case "Or": return {
|
|
942
|
-
...ast,
|
|
943
|
-
conditions: Arr.map(ast.conditions, (child) => jsonIn(child, done))
|
|
944
|
-
};
|
|
945
|
-
case "Not": return {
|
|
946
|
-
...ast,
|
|
947
|
-
condition: jsonIn(ast.condition, done)
|
|
948
|
-
};
|
|
949
|
-
default: return ast;
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
/**
|
|
953
|
-
* Gives a condition as a value that `JSON.stringify` keeps with no loss. JSON
|
|
954
|
-
* has no `Infinity`, `-Infinity` or `NaN`: it writes `null` in their place,
|
|
955
|
-
* and a test with `null` has another meaning. Such a number becomes an object
|
|
956
|
-
* that {@link schema} does not decode, so a policy with it fails to load and
|
|
957
|
-
* never gives a wrong answer.
|
|
958
|
-
*
|
|
959
|
-
* @stability stable
|
|
960
|
-
* @category conversions
|
|
961
|
-
* @since 1.0.0
|
|
962
|
-
*/
|
|
963
|
-
const toJsonValue = (condition) => jsonIn(condition, /* @__PURE__ */ new Map());
|
|
964
|
-
const ScalarSchema = Schema.Union([
|
|
965
|
-
Schema.String,
|
|
966
|
-
Schema.Finite,
|
|
967
|
-
Schema.Boolean,
|
|
968
|
-
Schema.Null
|
|
969
|
-
]);
|
|
970
|
-
const OperandSchema = Schema.Union([Schema.TaggedStruct("Literal", { value: ScalarSchema }), Schema.TaggedStruct("Principal", { path: Schema.String })]);
|
|
971
|
-
const Recursive = Schema.suspend(() => shape);
|
|
972
|
-
/**
|
|
973
|
-
* The most levels of objects and arrays in the JSON form of a condition. The
|
|
974
|
-
* Schema of a condition calls itself for each level, so a much deeper input
|
|
975
|
-
* could fill the stack before it fails.
|
|
976
|
-
*/
|
|
977
|
-
const maxJsonDepth = 1e3;
|
|
978
|
-
/**
|
|
979
|
-
* The most objects and arrays in the JSON form of an input, with a shared one
|
|
980
|
-
* counted for each place. The decoder writes out a shared one each time, so
|
|
981
|
-
* an input with few objects may give a very large tree.
|
|
982
|
-
*/
|
|
983
|
-
const maxInputNodes = 1e6;
|
|
984
|
-
/**
|
|
985
|
-
* Measures a value, or gives `undefined` when it is higher than `levels`. A
|
|
986
|
-
* shared object is measured one time. The walk stops at that height, so it
|
|
987
|
-
* does not fill the stack itself. The memo is a native `Map`, because it
|
|
988
|
-
* must find an object by the object itself.
|
|
989
|
-
*/
|
|
990
|
-
const measure = (input, levels, memo) => {
|
|
991
|
-
if (typeof input !== "object" || input === null) return {
|
|
992
|
-
size: 0,
|
|
993
|
-
height: 0
|
|
994
|
-
};
|
|
995
|
-
const cached = memo.get(input);
|
|
996
|
-
if (cached !== void 0 && ("failed" in cached ? levels <= cached.failed : true)) return "failed" in cached || cached.height > levels ? void 0 : cached;
|
|
997
|
-
const result = levels === 0 || !isPlainData(input) ? void 0 : Arr.reduce(Object.values(input), {
|
|
998
|
-
size: 1,
|
|
999
|
-
height: 1
|
|
1000
|
-
}, (total, child) => {
|
|
1001
|
-
const measured = total === void 0 ? void 0 : measure(child, levels - 1, memo);
|
|
1002
|
-
return total === void 0 || measured === void 0 ? void 0 : {
|
|
1003
|
-
size: total.size + measured.size,
|
|
1004
|
-
height: Math.max(total.height, 1 + measured.height)
|
|
1005
|
-
};
|
|
1006
|
-
});
|
|
1007
|
-
memo.set(input, result ?? { failed: levels });
|
|
1008
|
-
return result;
|
|
1009
|
-
};
|
|
1010
|
-
/**
|
|
1011
|
-
* Tests if each own key of an object is a plain data key that a loop over the
|
|
1012
|
-
* keys sees. The `length` of an array is the one key that is not seen. The
|
|
1013
|
-
* decoder reads keys of the prototypes too, so a prototype other than the
|
|
1014
|
-
* one of a plain object or an array may have only methods, as the prototype
|
|
1015
|
-
* of a class has.
|
|
1016
|
-
*/
|
|
1017
|
-
const isPlainData = (input) => Arr.every(Reflect.ownKeys(input), (key) => {
|
|
1018
|
-
const property = Object.getOwnPropertyDescriptor(input, key);
|
|
1019
|
-
return property !== void 0 && "value" in property && (property.enumerable === true || Array.isArray(input) && key === "length");
|
|
1020
|
-
}) && hasOnlyMethods(Object.getPrototypeOf(input));
|
|
1021
|
-
/** Tests if the prototypes from `prototype` up have only methods. */
|
|
1022
|
-
const hasOnlyMethods = (prototype) => prototype === null || prototype === Object.prototype || prototype === Array.prototype || typeof prototype === "object" && Arr.every(Reflect.ownKeys(prototype), (key) => {
|
|
1023
|
-
const property = Object.getOwnPropertyDescriptor(prototype, key);
|
|
1024
|
-
return typeof key === "symbol" || key === "constructor" || property !== void 0 && "value" in property && typeof property.value === "function";
|
|
1025
|
-
}) && hasOnlyMethods(Object.getPrototypeOf(prototype));
|
|
1026
|
-
/** Tests if a value is low and small enough to decode. */
|
|
1027
|
-
const fitsLimits = (input) => {
|
|
1028
|
-
const measured = measure(input, maxJsonDepth, /* @__PURE__ */ new Map());
|
|
1029
|
-
return measured === void 0 ? `A condition may have at most ${String(maxJsonDepth)} levels of JSON` : measured.size > maxInputNodes ? `A condition may have at most ${String(maxInputNodes)} objects and arrays in JSON` : true;
|
|
1030
|
-
};
|
|
1031
|
-
/** The shape of a condition, with no check of its depth. */
|
|
1032
|
-
const shape = Schema.Union([
|
|
1033
|
-
Schema.TaggedStruct("True", {}),
|
|
1034
|
-
Schema.TaggedStruct("False", {}),
|
|
1035
|
-
Schema.TaggedStruct("Compare", {
|
|
1036
|
-
op: Schema.Literals([
|
|
1037
|
-
"eq",
|
|
1038
|
-
"ne",
|
|
1039
|
-
"lt",
|
|
1040
|
-
"lte",
|
|
1041
|
-
"gt",
|
|
1042
|
-
"gte"
|
|
1043
|
-
]),
|
|
1044
|
-
field: Schema.String,
|
|
1045
|
-
value: OperandSchema
|
|
1046
|
-
}),
|
|
1047
|
-
Schema.TaggedStruct("In", {
|
|
1048
|
-
field: Schema.String,
|
|
1049
|
-
values: Schema.Array(OperandSchema)
|
|
1050
|
-
}),
|
|
1051
|
-
Schema.TaggedStruct("Exists", { field: Schema.String }),
|
|
1052
|
-
Schema.TaggedStruct("Under", { prefix: Schema.Array(OperandSchema) }),
|
|
1053
|
-
Schema.TaggedStruct("And", { conditions: Schema.Array(Recursive) }),
|
|
1054
|
-
Schema.TaggedStruct("Or", { conditions: Schema.Array(Recursive) }),
|
|
1055
|
-
Schema.TaggedStruct("Not", { condition: Recursive })
|
|
1056
|
-
]);
|
|
286
|
+
const satisfiable = (condition, options = {}) => satisfiable$1(nodeOf(condition), kindsIn(options.schema === void 0 ? void 0 : catalogOf(options.schema.ast)), options.budget);
|
|
1057
287
|
/**
|
|
1058
288
|
* The Schema of a condition with no predicate. It checks the shape only. Use
|
|
1059
289
|
* `Policy.schema` to check the field names against a domain too.
|
|
1060
290
|
*
|
|
291
|
+
* The decoder takes at most 500 levels of conditions, and at most 1000000
|
|
292
|
+
* objects and array items, with a shared object counted for each place. It
|
|
293
|
+
* reads only the own data fields of the input.
|
|
294
|
+
*
|
|
1061
295
|
* @stability stable
|
|
1062
296
|
* @category schemas
|
|
1063
297
|
* @since 0.3.0
|
|
1064
298
|
*/
|
|
1065
|
-
const schema = Schema.
|
|
1066
|
-
decode: (
|
|
1067
|
-
|
|
299
|
+
const schema = Schema.Unknown.pipe(Schema.decodeTo(Schema.declare((input) => isNode(input) && !input.opaque), SchemaTransformation.transformEffect({
|
|
300
|
+
decode: (input, options) => Result.match(decode(input), {
|
|
301
|
+
onSuccess: (node) => Effect.succeed(node),
|
|
302
|
+
onFailure: (problem) => Effect.fail(new SchemaIssue.Pointer(problem.path, new SchemaIssue.InvalidValue({ message: problem.message }, input, options)))
|
|
303
|
+
}),
|
|
304
|
+
encode: (condition) => Effect.sync(() => toJson(condition))
|
|
1068
305
|
})));
|
|
1069
|
-
|
|
1070
|
-
/**
|
|
1071
|
-
* Tests if a value is an operand. With `json`, its number must be finite, as
|
|
1072
|
-
* the JSON form holds only such numbers.
|
|
1073
|
-
*/
|
|
1074
|
-
const isOperandValue = (operand, json) => hasProperty(operand, "_tag") && (operand._tag === "Principal" ? hasProperty(operand, "path") && typeof operand.path === "string" : operand._tag === "Literal" && hasProperty(operand, "value") && (operand.value === null || typeof operand.value === "string" || typeof operand.value === "boolean" || typeof operand.value === "number" && (!json || Number.isFinite(operand.value))));
|
|
1075
|
-
const isOperandList = (values, json) => Array.isArray(values) && Arr.every(values, (operand) => isOperandValue(operand, json));
|
|
1076
|
-
/**
|
|
1077
|
-
* Tests if a value is a whole condition with no predicate, as the JSON form
|
|
1078
|
-
* holds it. A shared node is tested one time. A node deeper than
|
|
1079
|
-
* `maxJsonDepth` is not taken, so the walk does not fill the stack.
|
|
1080
|
-
*/
|
|
1081
|
-
const isPlainNode = (input, memo, depth, predicates = false) => depth <= maxJsonDepth && hasProperty(input, "_tag") && remember(memo, input, () => {
|
|
1082
|
-
const child = (node) => isPlainNode(node, memo, depth + 1, predicates);
|
|
1083
|
-
switch (input._tag) {
|
|
1084
|
-
case "Predicate": return predicates && hasProperty(input, "name") && typeof input.name === "string" && hasProperty(input, "run") && typeof input.run === "function";
|
|
1085
|
-
case "True":
|
|
1086
|
-
case "False": return true;
|
|
1087
|
-
case "Compare": return hasProperty(input, "op") && typeof input.op === "string" && HashSet.has(compareOperators, input.op) && hasProperty(input, "field") && typeof input.field === "string" && hasProperty(input, "value") && isOperandValue(input.value, !predicates);
|
|
1088
|
-
case "In": return hasProperty(input, "field") && typeof input.field === "string" && hasProperty(input, "values") && isOperandList(input.values, !predicates);
|
|
1089
|
-
case "Exists": return hasProperty(input, "field") && typeof input.field === "string";
|
|
1090
|
-
case "Under": return hasProperty(input, "prefix") && isOperandList(input.prefix, !predicates);
|
|
1091
|
-
case "And":
|
|
1092
|
-
case "Or": return hasProperty(input, "conditions") && Array.isArray(input.conditions) && Arr.every(input.conditions, child);
|
|
1093
|
-
case "Not": return hasProperty(input, "condition") && child(input.condition);
|
|
1094
|
-
default: return false;
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
/**
|
|
1098
|
-
* Tests if a value is a whole condition tree: each node has a known tag and
|
|
1099
|
-
* the fields of that tag. A predicate node needs a name and a test. A tree
|
|
1100
|
-
* deeper than 1000 levels is not taken.
|
|
1101
|
-
*
|
|
1102
|
-
* @stability stable
|
|
1103
|
-
* @category guards
|
|
1104
|
-
* @since 1.0.0
|
|
1105
|
-
*/
|
|
1106
|
-
const isAst = (u) => isPlainNode(u, /* @__PURE__ */ new Map(), 0, true);
|
|
1107
|
-
/** The most nodes of the JSON form of a condition. */
|
|
1108
|
-
const maxJsonNodes = 1e5;
|
|
1109
|
-
/**
|
|
1110
|
-
* The number of nodes of the JSON form of a condition. A shared child counts
|
|
1111
|
-
* one time for each place.
|
|
1112
|
-
*/
|
|
1113
|
-
const jsonSize = (ast, memo) => remember(memo, ast, () => {
|
|
1114
|
-
switch (ast._tag) {
|
|
1115
|
-
case "And":
|
|
1116
|
-
case "Or": return Arr.reduce(ast.conditions, 1, (size, child) => size + jsonSize(child, memo));
|
|
1117
|
-
case "Not": return 1 + jsonSize(ast.condition, memo);
|
|
1118
|
-
default: return 1;
|
|
1119
|
-
}
|
|
1120
|
-
});
|
|
1121
|
-
export { Condition_exports, Untranslatable, always, and, andAll, builder, evaluate, evaluateSync, fields, fold, hasPredicate, isAst, never, not, or, orAll, partial, provideContext, satisfiable, schema, simplifier, simplify, toJsonValue };
|
|
306
|
+
export { Condition_exports, Untranslatable, always, and, andAll, builder, evaluate, evaluateSync, fields, fold, never, not, or, orAll, partial, provideContext, satisfiable, schema, simplify };
|
|
1122
307
|
|
|
1123
308
|
//# sourceMappingURL=Condition.js.map
|