@nmnmcc/ability 2.0.5 → 2.0.7
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 +6 -5
- package/dist/Condition.d.ts +6 -6
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +1 -1
- package/dist/Policy.d.ts +2 -2
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js.map +1 -1
- package/dist/internal/algebra.js +128 -79
- package/dist/internal/algebra.js.map +1 -1
- package/dist/internal/analysis.js +1 -1
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +38 -14
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +59 -43
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +6 -0
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +17 -14
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +1 -1
- package/dist/internal/domain.js +1 -1
- package/dist/internal/json.js +2 -10
- package/dist/internal/json.js.map +1 -1
- package/dist/internal/nodes.d.ts +11 -1
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +51 -11
- package/dist/internal/nodes.js.map +1 -1
- package/dist/internal/patterns.js +1 -1
- package/dist/internal/patterns.js.map +1 -1
- package/dist/internal/policy-json.js +2 -8
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.d.ts +4 -1
- package/dist/internal/policy-nodes.d.ts.map +1 -1
- package/dist/internal/policy-nodes.js +14 -5
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/search.js +4 -4
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/trampoline.js +75 -0
- package/dist/internal/trampoline.js.map +1 -0
- package/package.json +1 -1
- package/src/Condition.ts +5 -2
- package/src/Policy.ts +1 -2
- package/src/internal/algebra.ts +262 -143
- package/src/internal/analysis.ts +3 -1
- package/src/internal/authorizer.ts +59 -14
- package/src/internal/bind.ts +149 -71
- package/src/internal/catalog.ts +54 -50
- package/src/internal/json.ts +2 -11
- package/src/internal/nodes.ts +58 -24
- package/src/internal/patterns.ts +7 -3
- package/src/internal/policy-json.ts +2 -9
- package/src/internal/policy-nodes.ts +16 -7
- package/src/internal/search.ts +13 -10
- package/src/internal/trampoline.ts +147 -0
package/src/internal/nodes.ts
CHANGED
|
@@ -21,12 +21,31 @@ import * as Arr from "effect/Array"
|
|
|
21
21
|
import * as Data from "effect/Data"
|
|
22
22
|
import type * as Effect from "effect/Effect"
|
|
23
23
|
import { hasProperty } from "effect/Predicate"
|
|
24
|
+
import * as Schema from "effect/Schema"
|
|
24
25
|
import * as Limits from "./limits.ts"
|
|
25
26
|
|
|
26
27
|
/** A value that a condition can compare. */
|
|
27
28
|
export type Scalar = string | number | boolean | null
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
/** The operators of a `Compare` test. The decoders read this list too. */
|
|
31
|
+
export const CompareOperator = Schema.Literals([
|
|
32
|
+
"eq",
|
|
33
|
+
"ne",
|
|
34
|
+
"lt",
|
|
35
|
+
"lte",
|
|
36
|
+
"gt",
|
|
37
|
+
"gte",
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
export type CompareOperator = typeof CompareOperator.Type
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tests if a value is an operator. It reads the list of the Schema with
|
|
44
|
+
* `includes`: for a short list, this is about twice as fast as
|
|
45
|
+
* `Schema.is`, and a decoder tests each node of a large input.
|
|
46
|
+
*/
|
|
47
|
+
export const isCompareOperator = (u: unknown): u is CompareOperator =>
|
|
48
|
+
(CompareOperator.literals as ReadonlyArray<unknown>).includes(u)
|
|
30
49
|
|
|
31
50
|
/** A fixed value. */
|
|
32
51
|
export interface Literal {
|
|
@@ -49,15 +68,6 @@ export const literal = (value: Scalar): Literal => operands.Literal({ value })
|
|
|
49
68
|
export const principalRef = (path: string): PrincipalRef =>
|
|
50
69
|
operands.Principal({ path })
|
|
51
70
|
|
|
52
|
-
const compareOperators: ReadonlyArray<string> = [
|
|
53
|
-
"eq",
|
|
54
|
-
"ne",
|
|
55
|
-
"lt",
|
|
56
|
-
"lte",
|
|
57
|
-
"gt",
|
|
58
|
-
"gte",
|
|
59
|
-
]
|
|
60
|
-
|
|
61
71
|
/** Tests if a value is a scalar. Any number is one, also `NaN`. */
|
|
62
72
|
export const isScalar = (value: unknown): value is Scalar =>
|
|
63
73
|
value === null ||
|
|
@@ -114,24 +124,39 @@ const checkOperands = (values: unknown): ReadonlyArray<Operand> => {
|
|
|
114
124
|
|
|
115
125
|
interface Facts {
|
|
116
126
|
readonly size: number
|
|
127
|
+
readonly weight: number
|
|
117
128
|
readonly depth: number
|
|
118
129
|
readonly opaque: boolean
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
const leaf: Facts = { size: 1, depth: 1, opaque: false }
|
|
132
|
+
const leaf: Facts = { size: 1, weight: 1, depth: 1, opaque: false }
|
|
122
133
|
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
/**
|
|
135
|
+
* The facts of a test with values, as `In` and `Under`. Each value counts in
|
|
136
|
+
* the weight, since a walk that reads the test reads its values too. It stops
|
|
137
|
+
* a test past the limits.
|
|
138
|
+
*/
|
|
139
|
+
const valuesFacts = (values: ReadonlyArray<Operand>): Facts =>
|
|
140
|
+
checkWeight({ ...leaf, weight: 1 + values.length })
|
|
141
|
+
|
|
142
|
+
const checkWeight = (facts: Facts): Facts => {
|
|
143
|
+
if (facts.weight > Limits.conditionNodes) {
|
|
127
144
|
throw Limits.exceeded(
|
|
128
145
|
"A condition",
|
|
129
146
|
"conditionNodes",
|
|
130
|
-
"nodes",
|
|
131
|
-
|
|
147
|
+
"nodes and values",
|
|
148
|
+
facts.weight,
|
|
132
149
|
Limits.conditionNodes,
|
|
133
150
|
)
|
|
134
151
|
}
|
|
152
|
+
return facts
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The facts of a node with children. It stops a node past the limits. */
|
|
156
|
+
const factsOf = (children: ReadonlyArray<Node>): Facts => {
|
|
157
|
+
const size = Arr.reduce(children, 1, (total, child) => total + child.size)
|
|
158
|
+
const weight = Arr.reduce(children, 1, (total, child) => total + child.weight)
|
|
159
|
+
checkWeight({ ...leaf, weight })
|
|
135
160
|
const depth =
|
|
136
161
|
1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth))
|
|
137
162
|
if (depth > Limits.conditionDepth) {
|
|
@@ -145,6 +170,7 @@ const factsOf = (children: ReadonlyArray<Node>): Facts => {
|
|
|
145
170
|
}
|
|
146
171
|
return {
|
|
147
172
|
size,
|
|
173
|
+
weight,
|
|
148
174
|
depth,
|
|
149
175
|
opaque: Arr.some(children, (child) => child.opaque),
|
|
150
176
|
}
|
|
@@ -173,6 +199,15 @@ abstract class Base extends Data.Class {
|
|
|
173
199
|
return this.#facts.size
|
|
174
200
|
}
|
|
175
201
|
|
|
202
|
+
/**
|
|
203
|
+
* The number of nodes and of the values of their `In` and `Under` tests,
|
|
204
|
+
* with a shared child counted for each place. A walk that reads the tests
|
|
205
|
+
* does this much work, so the limits of the nodes count it.
|
|
206
|
+
*/
|
|
207
|
+
get weight(): number {
|
|
208
|
+
return this.#facts.weight
|
|
209
|
+
}
|
|
210
|
+
|
|
176
211
|
/** The number of levels. */
|
|
177
212
|
get depth(): number {
|
|
178
213
|
return this.#facts.depth
|
|
@@ -211,7 +246,7 @@ export class Compare extends Base {
|
|
|
211
246
|
readonly field: string
|
|
212
247
|
readonly value: Operand
|
|
213
248
|
}) {
|
|
214
|
-
if (!
|
|
249
|
+
if (!isCompareOperator(props.op)) {
|
|
215
250
|
throw new Error(`"${String(props.op)}" is not a compare operator`)
|
|
216
251
|
}
|
|
217
252
|
super(
|
|
@@ -234,10 +269,8 @@ export class In extends Base {
|
|
|
234
269
|
readonly field: string
|
|
235
270
|
readonly values: ReadonlyArray<Operand>
|
|
236
271
|
}) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
leaf,
|
|
240
|
-
)
|
|
272
|
+
const values = checkOperands(props.values)
|
|
273
|
+
super({ field: checkPath(props.field), values }, valuesFacts(values))
|
|
241
274
|
}
|
|
242
275
|
}
|
|
243
276
|
|
|
@@ -255,7 +288,8 @@ export class Under extends Base {
|
|
|
255
288
|
readonly _tag = "Under"
|
|
256
289
|
declare readonly prefix: ReadonlyArray<Operand>
|
|
257
290
|
constructor(props: { readonly prefix: ReadonlyArray<Operand> }) {
|
|
258
|
-
|
|
291
|
+
const prefix = checkOperands(props.prefix)
|
|
292
|
+
super({ prefix }, valuesFacts(prefix))
|
|
259
293
|
}
|
|
260
294
|
}
|
|
261
295
|
|
|
@@ -323,7 +357,7 @@ export class Predicate extends Base {
|
|
|
323
357
|
}
|
|
324
358
|
super(
|
|
325
359
|
{ name: props.name, run: props.run },
|
|
326
|
-
{ size: 1, depth: 1, opaque: true },
|
|
360
|
+
{ size: 1, weight: 1, depth: 1, opaque: true },
|
|
327
361
|
)
|
|
328
362
|
}
|
|
329
363
|
}
|
package/src/internal/patterns.ts
CHANGED
|
@@ -117,9 +117,13 @@ type States = ReadonlyArray<number>
|
|
|
117
117
|
|
|
118
118
|
/** Adds the place after each `**`, since a `**` may match no names. */
|
|
119
119
|
const close = (pattern: Pattern, states: States): States =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
// A native `Set` keeps the first copy of each place, in order. It is
|
|
121
|
+
// faster than `Arr.dedupe`, and this is on the path of each match.
|
|
122
|
+
Array.from(
|
|
123
|
+
new Set(
|
|
124
|
+
Arr.flatMap(states, (state) =>
|
|
125
|
+
pattern[state]?._tag === "Globstar" ? [state, state + 1] : [state],
|
|
126
|
+
),
|
|
123
127
|
),
|
|
124
128
|
)
|
|
125
129
|
|
|
@@ -136,13 +136,6 @@ export const encode = (
|
|
|
136
136
|
return toJson(node, (rule) => forms.get(rule))
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
const algorithms: ReadonlyArray<string> = [
|
|
140
|
-
"denyOverrides",
|
|
141
|
-
"permitOverrides",
|
|
142
|
-
"firstApplicable",
|
|
143
|
-
"lastApplicable",
|
|
144
|
-
]
|
|
145
|
-
|
|
146
139
|
/** Reads a list of strings. */
|
|
147
140
|
const readStrings = (
|
|
148
141
|
input: object,
|
|
@@ -374,7 +367,7 @@ const readTree = (
|
|
|
374
367
|
switch (tag) {
|
|
375
368
|
case "Rules": {
|
|
376
369
|
const algorithm = Json.readString(input, "algorithm", path)
|
|
377
|
-
if (!
|
|
370
|
+
if (!PolicyNodes.isAlgorithm(algorithm)) {
|
|
378
371
|
throw Json.problem(
|
|
379
372
|
[...path, "algorithm"],
|
|
380
373
|
`"${algorithm}" is not a combining algorithm`,
|
|
@@ -392,7 +385,7 @@ const readTree = (
|
|
|
392
385
|
_tag: "Rules",
|
|
393
386
|
place,
|
|
394
387
|
size: 1 + found.length,
|
|
395
|
-
algorithm
|
|
388
|
+
algorithm,
|
|
396
389
|
rules: found,
|
|
397
390
|
})
|
|
398
391
|
}
|
|
@@ -11,19 +11,27 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as Arr from "effect/Array"
|
|
13
13
|
import * as Data from "effect/Data"
|
|
14
|
+
import * as Schema from "effect/Schema"
|
|
14
15
|
import * as Rule from "./rule.ts"
|
|
15
16
|
import * as Limits from "./limits.ts"
|
|
16
17
|
import * as Nodes from "./nodes.ts"
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const algorithms: ReadonlyArray<string> = [
|
|
19
|
+
/** The combining algorithms of a list of rules. The decoder reads this list too. */
|
|
20
|
+
export const Algorithm = Schema.Literals([
|
|
22
21
|
"denyOverrides",
|
|
23
22
|
"permitOverrides",
|
|
24
23
|
"firstApplicable",
|
|
25
24
|
"lastApplicable",
|
|
26
|
-
]
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
export type Algorithm = typeof Algorithm.Type
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if a value is a combining algorithm. It reads the list of the Schema
|
|
31
|
+
* with `includes`, as `Nodes.isCompareOperator` does.
|
|
32
|
+
*/
|
|
33
|
+
export const isAlgorithm = (u: unknown): u is Algorithm =>
|
|
34
|
+
(Algorithm.literals as ReadonlyArray<unknown>).includes(u)
|
|
27
35
|
|
|
28
36
|
interface Facts {
|
|
29
37
|
readonly size: number
|
|
@@ -103,7 +111,7 @@ export class Rules extends Base {
|
|
|
103
111
|
readonly algorithm: Algorithm
|
|
104
112
|
readonly rules: ReadonlyArray<Rule.Rule>
|
|
105
113
|
}) {
|
|
106
|
-
if (!
|
|
114
|
+
if (!isAlgorithm(props.algorithm)) {
|
|
107
115
|
throw new Error(
|
|
108
116
|
`"${String(props.algorithm)}" is not a combining algorithm`,
|
|
109
117
|
)
|
|
@@ -122,7 +130,8 @@ export class Rules extends Base {
|
|
|
122
130
|
size: Arr.reduce(
|
|
123
131
|
conditions,
|
|
124
132
|
1,
|
|
125
|
-
|
|
133
|
+
// A condition counts its nodes and the values of its tests.
|
|
134
|
+
(total, when) => total + 1 + (when?.weight ?? 0),
|
|
126
135
|
),
|
|
127
136
|
depth: 1,
|
|
128
137
|
opaque: Arr.some(conditions, (when) => when?.opaque ?? false),
|
package/src/internal/search.ts
CHANGED
|
@@ -196,12 +196,13 @@ interface Search {
|
|
|
196
196
|
*/
|
|
197
197
|
readonly members: Map<Nodes.In, Set<string>>
|
|
198
198
|
/**
|
|
199
|
-
* The
|
|
200
|
-
*
|
|
201
|
-
* fields, so its answer is the same. The set only grows, so the steps
|
|
202
|
-
* search may share it. It is a native `Set`, as in
|
|
199
|
+
* The keys of the conditions that the search found to be unsatisfiable:
|
|
200
|
+
* see `Algebra.keyOf`. A condition with the same structure has the same
|
|
201
|
+
* free fields, so its answer is the same. The set only grows, so the steps
|
|
202
|
+
* of a search may share it. It is a native `Set`, as in
|
|
203
|
+
* {@link dedupeCells}.
|
|
203
204
|
*/
|
|
204
|
-
readonly failed: Set<string>
|
|
205
|
+
readonly failed: Set<string | Node>
|
|
205
206
|
readonly budget: number
|
|
206
207
|
}
|
|
207
208
|
|
|
@@ -364,10 +365,12 @@ const searchNode = (
|
|
|
364
365
|
if (node._tag === "False") {
|
|
365
366
|
return { assignment: Option.none(), state }
|
|
366
367
|
}
|
|
367
|
-
// The
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
// The key reads at most a small part of the node: a node with very many
|
|
369
|
+
// nodes, or with very many values in its `In` tests, is its own key. A
|
|
370
|
+
// step reads the nodes but not the values, which `membersOf` keeps, so the
|
|
371
|
+
// budget counts the size.
|
|
372
|
+
const key = Algebra.keyOf(node)
|
|
373
|
+
if (search.failed.has(key)) {
|
|
371
374
|
return { assignment: Option.none(), state }
|
|
372
375
|
}
|
|
373
376
|
if (state.steps + node.size > search.budget) {
|
|
@@ -400,7 +403,7 @@ const searchNode = (
|
|
|
400
403
|
},
|
|
401
404
|
)
|
|
402
405
|
if (Option.isNone(result.assignment)) {
|
|
403
|
-
search.failed.add(
|
|
406
|
+
search.failed.add(key)
|
|
404
407
|
}
|
|
405
408
|
return result
|
|
406
409
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walks that do not fill the stack.
|
|
3
|
+
*
|
|
4
|
+
* A walk is written one time, with the steps of a {@link Runner}, and a
|
|
5
|
+
* runner runs it:
|
|
6
|
+
* - {@link direct} runs each step at once, as a plain call does. It is fast,
|
|
7
|
+
* and it is for an input with few levels.
|
|
8
|
+
* - {@link loop} puts each step into the run loop of an Effect. A step gives
|
|
9
|
+
* the next step in place of calling it, and the loop runs one step after
|
|
10
|
+
* the other, so the stack does not grow with the levels of the input. This
|
|
11
|
+
* is a trampoline.
|
|
12
|
+
*
|
|
13
|
+
* {@link run} takes `direct` for an input with at most `directDepth` levels,
|
|
14
|
+
* and `loop` for a deeper one. A walk makes each call for a child through
|
|
15
|
+
* `suspend`, so the loop makes the step of the child only when it runs it.
|
|
16
|
+
*/
|
|
17
|
+
import * as Arr from "effect/Array"
|
|
18
|
+
import * as Cause from "effect/Cause"
|
|
19
|
+
import * as Effect from "effect/Effect"
|
|
20
|
+
import * as Exit from "effect/Exit"
|
|
21
|
+
import * as Option from "effect/Option"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A step of a walk that gives an `A`. A runner of steps knows what a step
|
|
25
|
+
* is: the value itself for `direct`, and an Effect for `loop`. The type is
|
|
26
|
+
* only in the types.
|
|
27
|
+
*/
|
|
28
|
+
export interface Step<A> {
|
|
29
|
+
readonly "~Step": (_: never) => A
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The steps that a walk may take. */
|
|
33
|
+
export interface Runner {
|
|
34
|
+
readonly succeed: <A>(value: A) => Step<A>
|
|
35
|
+
/** The step that `step` makes, when the runner runs it. */
|
|
36
|
+
readonly suspend: <A>(step: () => Step<A>) => Step<A>
|
|
37
|
+
readonly map: <A, B>(step: Step<A>, f: (value: A) => B) => Step<B>
|
|
38
|
+
readonly flatMap: <A, B>(step: Step<A>, f: (value: A) => Step<B>) => Step<B>
|
|
39
|
+
/** Runs a step for each item, in order, and gives their values. */
|
|
40
|
+
readonly forEach: <A, B>(
|
|
41
|
+
items: ReadonlyArray<A>,
|
|
42
|
+
f: (item: A, index: number) => Step<B>,
|
|
43
|
+
) => Step<ReadonlyArray<B>>
|
|
44
|
+
/** Runs a step for each item, in order, up to the first `true`. */
|
|
45
|
+
readonly some: <A>(
|
|
46
|
+
items: ReadonlyArray<A>,
|
|
47
|
+
f: (item: A) => Step<boolean>,
|
|
48
|
+
) => Step<boolean>
|
|
49
|
+
/** Runs a step for each item, in order, up to the first `false`. */
|
|
50
|
+
readonly every: <A>(
|
|
51
|
+
items: ReadonlyArray<A>,
|
|
52
|
+
f: (item: A) => Step<boolean>,
|
|
53
|
+
) => Step<boolean>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A step of `direct` is its value. The casts below only change the type.
|
|
57
|
+
const value = <A>(step: Step<A>): A => step as unknown as A
|
|
58
|
+
const step = <A>(value: A): Step<A> => value as unknown as Step<A>
|
|
59
|
+
|
|
60
|
+
/** Runs each step at once, on the stack. */
|
|
61
|
+
export const direct: Runner = {
|
|
62
|
+
succeed: step,
|
|
63
|
+
suspend: (make) => make(),
|
|
64
|
+
map: (from, f) => step(f(value(from))),
|
|
65
|
+
flatMap: (from, f) => f(value(from)),
|
|
66
|
+
forEach: (items, f) =>
|
|
67
|
+
step(Arr.map(items, (item, index) => value(f(item, index)))),
|
|
68
|
+
some: (items, f) => step(Arr.some(items, (item) => value(f(item)))),
|
|
69
|
+
every: (items, f) => step(Arr.every(items, (item) => value(f(item)))),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// A step of `loop` is an Effect with no error and no services. A failure of a
|
|
73
|
+
// walk is a thrown error, which `run` gives back as it was.
|
|
74
|
+
type Loop<A> = Effect.Effect<A>
|
|
75
|
+
const effect = <A>(step: Step<A>): Loop<A> => step as unknown as Loop<A>
|
|
76
|
+
const fromEffect = <A>(effect: Loop<A>): Step<A> => effect as unknown as Step<A>
|
|
77
|
+
|
|
78
|
+
/** Runs each step in the run loop of an Effect, so a deep walk does not fill the stack. */
|
|
79
|
+
export const loop: Runner = {
|
|
80
|
+
succeed: (value) => fromEffect(Effect.succeed(value)),
|
|
81
|
+
suspend: (make) => fromEffect(Effect.suspend(() => effect(make()))),
|
|
82
|
+
map: (from, f) => fromEffect(Effect.map(effect(from), f)),
|
|
83
|
+
flatMap: (from, f) =>
|
|
84
|
+
fromEffect(Effect.flatMap(effect(from), (value) => effect(f(value)))),
|
|
85
|
+
forEach: (items, f) =>
|
|
86
|
+
fromEffect(Effect.forEach(items, (item, index) => effect(f(item, index)))),
|
|
87
|
+
some: (items, f) =>
|
|
88
|
+
fromEffect(
|
|
89
|
+
Effect.map(
|
|
90
|
+
Effect.findFirst(items, (item) => effect(f(item))),
|
|
91
|
+
Option.isSome,
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
every: (items, f) =>
|
|
95
|
+
fromEffect(
|
|
96
|
+
Effect.map(
|
|
97
|
+
Effect.findFirst(items, (item) =>
|
|
98
|
+
Effect.map(effect(f(item)), (value) => !value),
|
|
99
|
+
),
|
|
100
|
+
Option.isNone,
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The most levels of an input that `run` walks with `direct`. A level takes
|
|
107
|
+
* a few calls of the walk and of the runner on the stack, so this keeps the
|
|
108
|
+
* stack far from full, and most inputs take the fast way.
|
|
109
|
+
*/
|
|
110
|
+
export const directDepth = 200
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gives the runner for an input with `depth` levels. The walks of a condition
|
|
114
|
+
* or a policy tree know the depth of the input from its node. A walk of an
|
|
115
|
+
* input with an unknown depth, as a decoder, takes `loop`.
|
|
116
|
+
*/
|
|
117
|
+
export const runnerFor = (depth: number): Runner =>
|
|
118
|
+
depth <= directDepth ? direct : loop
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Runs a walk at once, with the runner for `depth` levels, and gives its
|
|
122
|
+
* value. An error that a step throws comes out as it was, also from `loop`.
|
|
123
|
+
*/
|
|
124
|
+
export const run = <A>(depth: number, walk: (runner: Runner) => Step<A>): A =>
|
|
125
|
+
runWith(runnerFor(depth), walk)
|
|
126
|
+
|
|
127
|
+
/** Runs a walk at once with a runner, as {@link run} does. */
|
|
128
|
+
export const runWith = <A>(
|
|
129
|
+
runner: Runner,
|
|
130
|
+
walk: (runner: Runner) => Step<A>,
|
|
131
|
+
): A => {
|
|
132
|
+
if (runner === direct) {
|
|
133
|
+
return value(walk(direct))
|
|
134
|
+
}
|
|
135
|
+
return Exit.match(Effect.runSyncExit(effect(walk(loop))), {
|
|
136
|
+
onSuccess: (result) => result,
|
|
137
|
+
onFailure: (cause) => {
|
|
138
|
+
throw Cause.squash(cause)
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Gives the Effect of a step of `loop`, so a walk can go on in an Effect
|
|
145
|
+
* that runs a predicate.
|
|
146
|
+
*/
|
|
147
|
+
export const toEffect = <A>(step: Step<A>): Effect.Effect<A> => effect(step)
|