@nmnmcc/ability 1.0.1 → 2.0.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/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 +271 -269
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +68 -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 +286 -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 +255 -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 +93 -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 +386 -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 +596 -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 +484 -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 +181 -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/Resource.js
CHANGED
|
@@ -1,760 +1,11 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
-
import
|
|
3
|
-
import * as Option from "effect/Option";
|
|
4
|
-
import * as Schema from "effect/Schema";
|
|
5
|
-
import { Class as Class$1 } from "effect/Data";
|
|
6
|
-
import * as HashMap from "effect/HashMap";
|
|
7
|
-
import * as HashSet from "effect/HashSet";
|
|
8
|
-
import { hasProperty, isObject } from "effect/Predicate";
|
|
9
|
-
import * as SchemaAST from "effect/SchemaAST";
|
|
2
|
+
import { makeClass, makeStruct } from "./internal/resource.js";
|
|
10
3
|
// src/Resource.ts
|
|
11
|
-
/**
|
|
12
|
-
* A resource is a kind of thing that a person may act on, such as a document
|
|
13
|
-
* or an invoice. Each resource has a kind name, a list of actions, a Schema for its
|
|
14
|
-
* data, and an optional path.
|
|
15
|
-
*
|
|
16
|
-
* Make a resource in one of two ways:
|
|
17
|
-
* - {@link Class} makes a Schema class, in the same way as
|
|
18
|
-
* `Schema.TaggedClass`. The class is a Schema, a constructor, and a resource
|
|
19
|
-
* at the same time.
|
|
20
|
-
* - {@link make} makes a resource from plain fields. Its values are plain
|
|
21
|
-
* objects with a `_tag` field.
|
|
22
|
-
*
|
|
23
|
-
* Every value of a resource has a `_tag` field with the kind name. The library
|
|
24
|
-
* uses this field to find the resource of a value. It does not guess from the
|
|
25
|
-
* constructor name.
|
|
26
|
-
*
|
|
27
|
-
* A path puts a resource inside a tree, such as
|
|
28
|
-
* `["org", organizationId, "team", teamId, "doc", id]`. A condition can
|
|
29
|
-
* then ask if a value is under a part of the tree. See `Condition.under`.
|
|
30
|
-
*
|
|
31
|
-
* @stability stable
|
|
32
|
-
* @since 0.3.0
|
|
33
|
-
*/
|
|
34
4
|
var Resource_exports = /* @__PURE__ */ __exportAll({
|
|
35
5
|
Class: () => Class,
|
|
36
|
-
anyDomain: () => anyDomain,
|
|
37
|
-
descriptor: () => descriptor,
|
|
38
|
-
domainOf: () => domainOf,
|
|
39
|
-
fieldPaths: () => fieldPaths,
|
|
40
|
-
hasGetter: () => hasGetter,
|
|
41
|
-
hasMatchingGetter: () => hasMatchingGetter,
|
|
42
|
-
hasMatchingPath: () => hasMatchingPath,
|
|
43
|
-
hasNoValue: () => hasNoValue,
|
|
44
|
-
hasPath: () => hasPath,
|
|
45
|
-
hasValue: () => hasValue,
|
|
46
|
-
isPlainField: () => isPlainField,
|
|
47
|
-
isResource: () => isResource,
|
|
48
|
-
kindOf: () => kindOf,
|
|
49
6
|
make: () => make
|
|
50
7
|
});
|
|
51
8
|
/**
|
|
52
|
-
* The class of the runtime data of a resource. {@link isResource} uses it to
|
|
53
|
-
* know a resource.
|
|
54
|
-
*/
|
|
55
|
-
var ResourceDescriptor = class extends Class$1 {};
|
|
56
|
-
/** The prototypes from `prototype` up, below `Object.prototype`. */
|
|
57
|
-
const prototypesFrom = (prototype) => Arr.unfold(prototype, (current) => typeof current === "object" && current !== null && current !== Object.prototype ? Option.some([current, Object.getPrototypeOf(current)]) : Option.none());
|
|
58
|
-
/**
|
|
59
|
-
* Gives the prototype of the class of a class node, or `undefined`. The node
|
|
60
|
-
* does not keep the class: a class Schema with an annotation or a check, or
|
|
61
|
-
* one inside `Schema.suspend`, is a new Schema with the same node. So the
|
|
62
|
-
* walk asks the node to make a value with no check, and reads its prototype.
|
|
63
|
-
*
|
|
64
|
-
* This uses the `"~constructor"` annotation of Effect, which is not a public
|
|
65
|
-
* API: it gives a link whose decode makes a value of the class from its
|
|
66
|
-
* fields without a check. When that is not so, the walk gives `undefined`,
|
|
67
|
-
* and the check of a policy says no to the getter.
|
|
68
|
-
*/
|
|
69
|
-
const classPrototype = (ast) => {
|
|
70
|
-
const make = ast.annotations?.["~constructor"];
|
|
71
|
-
if (typeof make !== "function") return;
|
|
72
|
-
try {
|
|
73
|
-
const made = make(ast.typeParameters);
|
|
74
|
-
const decode = hasProperty(made, "link") && hasProperty(made.link, "transformation") && hasProperty(made.link.transformation, "decode") ? made.link.transformation.decode : void 0;
|
|
75
|
-
const value = hasProperty(decode, "transform") && typeof decode.transform === "function" ? decode.transform({}) : void 0;
|
|
76
|
-
return isObject(value) ? Object.getPrototypeOf(value) : void 0;
|
|
77
|
-
} catch {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* Tests if a path names a getter of a class Schema, such as a getter of a
|
|
83
|
-
* `Schema.Class` or a `Principal.Class`, at its root or in a nested field. A
|
|
84
|
-
* condition reads such a field, and the types of a condition take it, so the
|
|
85
|
-
* check of a policy takes it too. It goes into the fields of a struct, the
|
|
86
|
-
* values of a record, the members of a union, `Schema.suspend`, and a class
|
|
87
|
-
* with an annotation or a check.
|
|
88
|
-
*
|
|
89
|
-
* @stability stable
|
|
90
|
-
* @category predicates
|
|
91
|
-
* @since 1.0.0
|
|
92
|
-
*/
|
|
93
|
-
const hasGetter = (schema, path) => !isTooLong(path) && Schema.isSchema(schema) && getterAt(schema.ast, path.split("."), 0, (part, name) => part === name, /* @__PURE__ */ new Map());
|
|
94
|
-
/**
|
|
95
|
-
* The most parts that a field path may have. The walks below call themselves
|
|
96
|
-
* for each part, and a path comes from the input of a policy. So a very long
|
|
97
|
-
* path, as in a recursive Schema, could fill the stack.
|
|
98
|
-
*/
|
|
99
|
-
const maxPathParts = 256;
|
|
100
|
-
/** Tests if a field path has more than {@link maxPathParts} parts. */
|
|
101
|
-
const isTooLong = (path) => path.split(".", 257).length > maxPathParts;
|
|
102
|
-
/**
|
|
103
|
-
* Tests if a Schema has a getter of a class at a path whose parts fit
|
|
104
|
-
* `parts`, as `hasGetter` does for one path. A part fits a name when `fits`
|
|
105
|
-
* says so, as a part with `*` fits many names. A part `**` stands for any
|
|
106
|
-
* number of parts, also none.
|
|
107
|
-
*
|
|
108
|
-
* @stability stable
|
|
109
|
-
* @category predicates
|
|
110
|
-
* @since 1.0.0
|
|
111
|
-
*/
|
|
112
|
-
const hasMatchingGetter = (schema, parts, fits) => parts.length <= maxPathParts && Schema.isSchema(schema) && getterAt(schema.ast, parts, 0, fits, /* @__PURE__ */ new Map());
|
|
113
|
-
/** The names of the getters of a prototype and the prototypes above it. */
|
|
114
|
-
const getterNames = (prototype) => Arr.flatMap(prototypesFrom(prototype), (current) => Arr.filter(Object.getOwnPropertyNames(current), (name) => Object.getOwnPropertyDescriptor(current, name)?.get !== void 0));
|
|
115
|
-
const getterAt = (ast, names, index, fits, memo) => rememberAt(memo, ast, index, false, () => {
|
|
116
|
-
const name = names[index];
|
|
117
|
-
if (name === void 0) return false;
|
|
118
|
-
const last = index === names.length - 1;
|
|
119
|
-
const next = (type, at) => getterAt(type, names, at, fits, memo);
|
|
120
|
-
if (name === "**" && !last && next(ast, index + 1)) return true;
|
|
121
|
-
switch (ast._tag) {
|
|
122
|
-
case "Union": return Arr.some(ast.types, (type) => next(type, index));
|
|
123
|
-
case "Suspend": return next(ast.thunk(), index);
|
|
124
|
-
case "Declaration": {
|
|
125
|
-
const inner = classStruct(ast);
|
|
126
|
-
if (inner === void 0) return false;
|
|
127
|
-
return last ? Arr.some(getterNames(classPrototype(ast)), (getter) => fits(name, getter)) : next(inner, index);
|
|
128
|
-
}
|
|
129
|
-
case "Objects": return !last && (Arr.some(ast.propertySignatures, (property) => typeof property.name === "string" && (name === "**" && next(property.type, index) || fits(name, property.name) && next(property.type, index + 1))) || Arr.some(ast.indexSignatures, (signature) => name === "**" ? hasSomeKey(signature.parameter) && (next(signature.type, index) || next(signature.type, index + 1)) : Arr.some([name.replaceAll("*", ""), name.replaceAll("*", "a")], (key) => fits(name, key) && keyFits(signature.parameter, key)) && next(signature.type, index + 1)));
|
|
130
|
-
default: return false;
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
/**
|
|
134
|
-
* Tests if a record takes a key. A key of an object is a string, also a key
|
|
135
|
-
* that a number Schema takes: `{ 1: "a" }` has the key `"1"`. So a key that
|
|
136
|
-
* is the text of a number is tried as that number too.
|
|
137
|
-
*/
|
|
138
|
-
const keyFits = (parameter, key) => {
|
|
139
|
-
const is = Schema.is(Schema.make(parameter));
|
|
140
|
-
return is(key) || key !== "" && String(Number(key)) === key && is(Number(key));
|
|
141
|
-
};
|
|
142
|
-
/**
|
|
143
|
-
* Gives the struct of a `Schema.Class`. A class is a declaration with one
|
|
144
|
-
* struct as its type parameter, and Effect marks it with a `"~constructor"`
|
|
145
|
-
* annotation, also through more codec steps, as in
|
|
146
|
-
* `Schema.fromJsonString(User)`. Other declarations, such as
|
|
147
|
-
* `Schema.OptionFromNullOr` or `Schema.ReadonlyMap`, have no such mark, and
|
|
148
|
-
* the fields of their type parameter are not fields of the value.
|
|
149
|
-
*/
|
|
150
|
-
const classStruct = (ast) => {
|
|
151
|
-
const [inner] = ast.typeParameters;
|
|
152
|
-
return ast.annotations?.["~constructor"] !== void 0 && ast.typeParameters.length === 1 && inner?._tag === "Objects" ? inner : void 0;
|
|
153
|
-
};
|
|
154
|
-
/**
|
|
155
|
-
* Gives the structs inside a Schema, with the `Schema.suspend` nodes that are
|
|
156
|
-
* open on the way to each one. The open nodes are in a native `Set`, because
|
|
157
|
-
* it must find a node by the node itself: `Equal` compares Schema nodes by
|
|
158
|
-
* structure, and a recursive Schema has no end. It looks through optional keys, unions such as
|
|
159
|
-
* `Schema.NullOr`, `Schema.suspend`, and `Schema.Class`. A suspend node that
|
|
160
|
-
* is already open is not opened again, so a recursive Schema has an end.
|
|
161
|
-
*/
|
|
162
|
-
const structsOf = (ast, open) => {
|
|
163
|
-
switch (ast._tag) {
|
|
164
|
-
case "Objects": return ast.propertySignatures.length === 0 ? [] : [[ast, open]];
|
|
165
|
-
case "Union": return ast.types.flatMap((type) => structsOf(type, open));
|
|
166
|
-
case "Suspend": return open.has(ast) ? [] : structsOf(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
|
|
167
|
-
case "Declaration": {
|
|
168
|
-
const inner = classStruct(ast);
|
|
169
|
-
return inner === void 0 ? [] : structsOf(inner, open);
|
|
170
|
-
}
|
|
171
|
-
default: return [];
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
/** Gives the kind of value that a field holds. See {@link FieldShape}. */
|
|
175
|
-
const shapeOf = (ast, open = /* @__PURE__ */ new Set()) => {
|
|
176
|
-
switch (ast._tag) {
|
|
177
|
-
case "Objects": return ast.indexSignatures.length > 0 ? "record" : "other";
|
|
178
|
-
case "Union": {
|
|
179
|
-
const shapes = ast.types.map((type) => shapeOf(type, open));
|
|
180
|
-
return shapes.every((shape) => shape === "scalar") ? "scalar" : shapes.includes("record") ? "record" : "other";
|
|
181
|
-
}
|
|
182
|
-
case "Suspend": return open.has(ast) ? "other" : shapeOf(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
|
|
183
|
-
default: return isScalar(ast) ? "scalar" : "other";
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
const isScalar = (ast) => {
|
|
187
|
-
switch (ast._tag) {
|
|
188
|
-
case "String":
|
|
189
|
-
case "Number":
|
|
190
|
-
case "Boolean":
|
|
191
|
-
case "BigInt":
|
|
192
|
-
case "Symbol":
|
|
193
|
-
case "UniqueSymbol":
|
|
194
|
-
case "Literal":
|
|
195
|
-
case "Enum":
|
|
196
|
-
case "TemplateLiteral":
|
|
197
|
-
case "Null":
|
|
198
|
-
case "Undefined": return true;
|
|
199
|
-
default: return false;
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
/**
|
|
203
|
-
* Tests if a Schema holds, in some branch, a value that is not a struct: a
|
|
204
|
-
* scalar, or a value such as a record or an array.
|
|
205
|
-
*/
|
|
206
|
-
const holdsValue = (ast, open) => {
|
|
207
|
-
switch (ast._tag) {
|
|
208
|
-
case "Objects": return ast.propertySignatures.length === 0 || ast.indexSignatures.length > 0;
|
|
209
|
-
case "Union": return ast.types.some((type) => holdsValue(type, open));
|
|
210
|
-
case "Suspend": return !open.has(ast) && holdsValue(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
|
|
211
|
-
case "Declaration": return classStruct(ast) === void 0;
|
|
212
|
-
default: return true;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
/**
|
|
216
|
-
* Gives the path of every field of a Schema, with a dot between the names of
|
|
217
|
-
* nested fields. A field of a union may come two times: one time as a field
|
|
218
|
-
* that holds a value, and one time as a field that holds a struct.
|
|
219
|
-
*/
|
|
220
|
-
const walkFields = (ast, prefix = "", open = /* @__PURE__ */ new Set()) => Arr.flatMap(structsOf(ast, open), ([struct, inner]) => Arr.flatMap(struct.propertySignatures, (property) => {
|
|
221
|
-
const name = property.name;
|
|
222
|
-
if (typeof name !== "string") return [];
|
|
223
|
-
if (name.includes(".")) throw new Error(`A field name may not have a dot: "${name}"`);
|
|
224
|
-
if (name === "") throw new Error("A field name may not be empty");
|
|
225
|
-
const path = prefix === "" ? name : `${prefix}.${name}`;
|
|
226
|
-
const type = property.type;
|
|
227
|
-
return [...holdsValue(type, inner) ? [{
|
|
228
|
-
path,
|
|
229
|
-
leaf: true,
|
|
230
|
-
type
|
|
231
|
-
}] : [], ...structsOf(type, inner).length > 0 ? [{
|
|
232
|
-
path,
|
|
233
|
-
leaf: false,
|
|
234
|
-
type
|
|
235
|
-
}, ...walkFields(type, path, inner)] : []];
|
|
236
|
-
}));
|
|
237
|
-
/**
|
|
238
|
-
* Gives the paths of all the fields of a Schema, with dots for nested fields.
|
|
239
|
-
* A nested struct gives its own path and the paths of its fields. Arrays are
|
|
240
|
-
* left out. It throws when a field name has a dot or is empty, because such a
|
|
241
|
-
* name would give two fields one path, or a path with an empty part.
|
|
242
|
-
*
|
|
243
|
-
* @stability stable
|
|
244
|
-
* @category getters
|
|
245
|
-
* @since 1.0.0
|
|
246
|
-
*/
|
|
247
|
-
const fieldPaths = (schema) => Arr.dedupe(Arr.map(walkFields(schema.ast), (visit) => visit.path));
|
|
248
|
-
/** Tests if a Schema holds only plain scalars: see {@link isPlainField}. */
|
|
249
|
-
/**
|
|
250
|
-
* The ids of the checks that the search knows. `domainOf` reads them, so a
|
|
251
|
-
* field with only these checks has an exact domain.
|
|
252
|
-
*/
|
|
253
|
-
const knownChecks = HashSet.make("effect/schema/isInt", "effect/schema/isFinite");
|
|
254
|
-
/**
|
|
255
|
-
* Tests if each check of a Schema node is a check that the search knows. A
|
|
256
|
-
* check such as `isGreaterThan(0)` stops some values, and the search does not
|
|
257
|
-
* see it, so it could find a value that the Schema does not take.
|
|
258
|
-
*/
|
|
259
|
-
const hasOnlyKnownChecks = (checks) => Arr.every(checks ?? [], (check) => check._tag === "FilterGroup" ? hasOnlyKnownChecks(check.checks) : HashSet.has(knownChecks, check.annotations?.representation?.id));
|
|
260
|
-
const isPlainScalar = (ast) => {
|
|
261
|
-
if (ast._tag === "Number" ? !hasOnlyKnownChecks(ast.checks) : (ast.checks ?? []).length > 0) return false;
|
|
262
|
-
switch (ast._tag) {
|
|
263
|
-
case "String":
|
|
264
|
-
case "Number":
|
|
265
|
-
case "Boolean":
|
|
266
|
-
case "Literal":
|
|
267
|
-
case "Null":
|
|
268
|
-
case "Undefined": return true;
|
|
269
|
-
case "Union": return ast.options?.mode !== "oneOf" && Arr.every(ast.types, isPlainScalar);
|
|
270
|
-
default: return false;
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
/**
|
|
274
|
-
* Tests if a path names a plain scalar field of a Schema: a string, a number,
|
|
275
|
-
* a boolean, a literal, `null`, or an optional field or a union of these. Its
|
|
276
|
-
* only checks may be integer and finite checks: another check, such as
|
|
277
|
-
* `isGreaterThan(0)`, stops values that the search does not know about. On
|
|
278
|
-
* the way, each part must be a required field of a plain struct with no
|
|
279
|
-
* checks, with a name that is not a name of `Object.prototype`.
|
|
280
|
-
*
|
|
281
|
-
* `Policy.coverage` and `Condition.satisfiable` answer `All`, `None` or
|
|
282
|
-
* `Some` only when each field of the condition is such a field. Their model of
|
|
283
|
-
* the values of a field is exact for these Schemas. For other Schemas, such as
|
|
284
|
-
* classes, records, unions of structs or declarations, a value may have data
|
|
285
|
-
* that the Schema does not show, and the answer is `Unknown`.
|
|
286
|
-
*
|
|
287
|
-
* @stability stable
|
|
288
|
-
* @category predicates
|
|
289
|
-
* @since 1.0.0
|
|
290
|
-
*/
|
|
291
|
-
const isPlainField = (schema, path) => !isTooLong(path) && plainAt(schema.ast, path.split("."));
|
|
292
|
-
const plainAt = (ast, names) => {
|
|
293
|
-
const [name, ...rest] = names;
|
|
294
|
-
if (name === void 0 || ast._tag !== "Objects" || ast.indexSignatures.length > 0 || (ast.checks ?? []).length > 0 || name in Object.prototype) return false;
|
|
295
|
-
const property = ast.propertySignatures.find((signature) => signature.name === name);
|
|
296
|
-
if (property === void 0) return false;
|
|
297
|
-
return rest.length === 0 ? isPlainScalar(property.type) : !SchemaAST.isOptional(property.type) && plainAt(property.type, rest);
|
|
298
|
-
};
|
|
299
|
-
/**
|
|
300
|
-
* Tests if a path names a field of a Schema that holds a value with no named
|
|
301
|
-
* fields: a scalar, or a value such as a record or an array. It goes into
|
|
302
|
-
* records by their keys, and into recursive Schemas as far as the path goes.
|
|
303
|
-
* So it finds paths that {@link fieldPaths} does not list.
|
|
304
|
-
*
|
|
305
|
-
* @stability stable
|
|
306
|
-
* @category predicates
|
|
307
|
-
* @since 1.0.0
|
|
308
|
-
*/
|
|
309
|
-
const hasPath = (schema, path) => !isTooLong(path) && resolves(schema.ast, path.split("."), 0, /* @__PURE__ */ new Map());
|
|
310
|
-
/**
|
|
311
|
-
* Gives the answer for a node at a place from the memo, or works it out and
|
|
312
|
-
* keeps it. While the work runs, the memo holds `pending`, so a walk that
|
|
313
|
-
* comes back to the node at the same place, with no step on the path, gets
|
|
314
|
-
* `pending` and stops.
|
|
315
|
-
*/
|
|
316
|
-
const rememberAt = (memo, ast, index, pending, work) => {
|
|
317
|
-
const places = memo.get(ast) ?? /* @__PURE__ */ new Map();
|
|
318
|
-
memo.set(ast, places);
|
|
319
|
-
if (places.has(index)) return places.get(index);
|
|
320
|
-
places.set(index, pending);
|
|
321
|
-
const result = work();
|
|
322
|
-
places.set(index, result);
|
|
323
|
-
return result;
|
|
324
|
-
};
|
|
325
|
-
const resolves = (ast, names, index, memo) => rememberAt(memo, ast, index, false, () => {
|
|
326
|
-
if (index === names.length) return index > 0 && holdsValue(ast, /* @__PURE__ */ new Set());
|
|
327
|
-
const name = names[index];
|
|
328
|
-
switch (ast._tag) {
|
|
329
|
-
case "Objects": {
|
|
330
|
-
const property = ast.propertySignatures.find((signature) => signature.name === name);
|
|
331
|
-
if (property !== void 0) return resolves(property.type, names, index + 1, memo);
|
|
332
|
-
return ast.indexSignatures.some((signature) => keyFits(signature.parameter, name) && resolves(signature.type, names, index + 1, memo));
|
|
333
|
-
}
|
|
334
|
-
case "Union": return ast.types.some((type) => resolves(type, names, index, memo));
|
|
335
|
-
case "Suspend": return resolves(ast.thunk(), names, index, memo);
|
|
336
|
-
case "Declaration": {
|
|
337
|
-
const inner = classStruct(ast);
|
|
338
|
-
return inner !== void 0 && resolves(inner, names, index, memo);
|
|
339
|
-
}
|
|
340
|
-
default: return false;
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
/**
|
|
344
|
-
* Tests if a Schema has a field whose path fits a pattern. `parts` are the
|
|
345
|
-
* parts of the pattern, split at the dots. A part `**` stands for any number
|
|
346
|
-
* of parts, also none. Another part fits a name when `fits` says so, as a
|
|
347
|
-
* part with `*` fits many names. For a record, the walk asks if the key
|
|
348
|
-
* Schema takes a key that fits the part. For a template literal key Schema,
|
|
349
|
-
* with parts that are literals and strings, the answer is exact. For another key Schema, the walk tries sample keys, so it may miss a
|
|
350
|
-
* key, and then the answer is `false`.
|
|
351
|
-
*
|
|
352
|
-
* @stability stable
|
|
353
|
-
* @category predicates
|
|
354
|
-
* @since 1.0.0
|
|
355
|
-
*/
|
|
356
|
-
const hasMatchingPath = (schema, parts, fits) => parts.length <= maxPathParts && matchesFrom(schema.ast, parts, 0, false, fits, /* @__PURE__ */ new Map());
|
|
357
|
-
const matchesFrom = (ast, parts, index, stepped, fits, memo) => rememberAt(memo, ast, index * 2 + (stepped ? 1 : 0), false, () => {
|
|
358
|
-
const next = (type, at, step) => matchesFrom(type, parts, at, step, fits, memo);
|
|
359
|
-
switch (ast._tag) {
|
|
360
|
-
case "Union": return Arr.some(ast.types, (type) => next(type, index, stepped));
|
|
361
|
-
case "Suspend": return next(ast.thunk(), index, stepped);
|
|
362
|
-
case "Declaration": {
|
|
363
|
-
const inner = classStruct(ast);
|
|
364
|
-
if (inner !== void 0) return next(inner, index, stepped);
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (index === parts.length) return stepped && holdsValue(ast, /* @__PURE__ */ new Set());
|
|
369
|
-
const part = parts[index];
|
|
370
|
-
if (part === "**" && next(ast, index + 1, stepped)) return true;
|
|
371
|
-
if (ast._tag !== "Objects") return false;
|
|
372
|
-
const fromPart = [part.replaceAll("*", ""), part.replaceAll("*", "a")];
|
|
373
|
-
return Arr.some(ast.propertySignatures, (property) => typeof property.name === "string" && (part === "**" ? next(property.type, index, true) : fits(part, property.name) && next(property.type, index + 1, true))) || Arr.some(ast.indexSignatures, (signature) => part === "**" ? hasSomeKey(signature.parameter) && next(signature.type, index, true) : (globMeetsKeys(part, signature.parameter) ?? Arr.some(Arr.dedupe([...fromPart, ...sampleKeys(signature.parameter)]), (key) => keyFits(signature.parameter, key) && fits(part, key))) && next(signature.type, index + 1, true));
|
|
374
|
-
});
|
|
375
|
-
/**
|
|
376
|
-
* Tests if a key Schema takes some key: the exact search for a template
|
|
377
|
-
* literal, or a sample key.
|
|
378
|
-
*/
|
|
379
|
-
const hasSomeKey = (parameter) => globMeetsKeys("*", parameter) ?? Arr.some(Arr.dedupe([
|
|
380
|
-
"",
|
|
381
|
-
"a",
|
|
382
|
-
...sampleKeys(parameter)
|
|
383
|
-
]), (key) => keyFits(parameter, key));
|
|
384
|
-
/** The longest key pattern that the exact search of keys takes. */
|
|
385
|
-
const maxExactKeyLength = 256;
|
|
386
|
-
const charsOf = (text) => Arr.map(Array.from(text), (char) => ({
|
|
387
|
-
_tag: "Char",
|
|
388
|
-
char
|
|
389
|
-
}));
|
|
390
|
-
/**
|
|
391
|
-
* Gives the token lists of a template literal, one for each choice of its
|
|
392
|
-
* unions of literals, or `undefined` when the lists can not be exact: a part
|
|
393
|
-
* that this walk does not know, such as a number, a part with checks, or
|
|
394
|
-
* more than 64 lists.
|
|
395
|
-
*/
|
|
396
|
-
const templateTokens = (ast) => {
|
|
397
|
-
const partTokens = (part) => {
|
|
398
|
-
if ((part.checks ?? []).length > 0) return;
|
|
399
|
-
switch (part._tag) {
|
|
400
|
-
case "Literal": {
|
|
401
|
-
const text = String(part.literal);
|
|
402
|
-
return /[\uD800-\uDFFF]/.test(text) ? void 0 : [charsOf(text)];
|
|
403
|
-
}
|
|
404
|
-
case "String": return [[{ _tag: "Any" }]];
|
|
405
|
-
case "Number": return;
|
|
406
|
-
case "Union": {
|
|
407
|
-
if (part.options?.mode === "oneOf") return;
|
|
408
|
-
const choices = Arr.map(part.types, partTokens);
|
|
409
|
-
return Arr.every(choices, (choice) => choice !== void 0) ? Arr.flatten(choices) : void 0;
|
|
410
|
-
}
|
|
411
|
-
default: return;
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
return Arr.reduce(ast.parts, [[]], (lists, part) => {
|
|
415
|
-
const choices = partTokens(part);
|
|
416
|
-
if (lists === void 0 || choices === void 0) return;
|
|
417
|
-
const all = Arr.flatMap(lists, (list) => Arr.map(choices, (choice) => [...list, ...choice]));
|
|
418
|
-
return all.length > 64 || Arr.some(all, (list) => list.length > maxExactKeyLength) ? void 0 : all;
|
|
419
|
-
});
|
|
420
|
-
};
|
|
421
|
-
/**
|
|
422
|
-
* Tests if a key pattern and a list of template tokens share a key. It walks
|
|
423
|
-
* the pairs of places in the two lists, with a few characters that stand for
|
|
424
|
-
* all others, so the answer is exact for these tokens.
|
|
425
|
-
*/
|
|
426
|
-
const tokensMeet = (glob, template) => {
|
|
427
|
-
const key = (t, g) => t * (glob.length + 1) + g;
|
|
428
|
-
const closeOf = (t, g) => [
|
|
429
|
-
[t, g],
|
|
430
|
-
...template[t]?._tag === "Any" ? closeOf(t + 1, g) : [],
|
|
431
|
-
...glob[g]?._tag === "Star" ? closeOf(t, g + 1) : []
|
|
432
|
-
];
|
|
433
|
-
const alphabet = Arr.dedupe([
|
|
434
|
-
"a",
|
|
435
|
-
"0",
|
|
436
|
-
...Arr.flatMap(glob, (token) => token._tag === "Char" ? [token.char] : []),
|
|
437
|
-
...Arr.flatMap(template, (token) => token._tag === "Char" ? [token.char] : [])
|
|
438
|
-
]);
|
|
439
|
-
const stepTemplate = (t, char) => {
|
|
440
|
-
const token = template[t];
|
|
441
|
-
return token === void 0 ? [] : token._tag === "Any" ? [t] : token.char === char ? [t + 1] : [];
|
|
442
|
-
};
|
|
443
|
-
const stepGlob = (g, char) => {
|
|
444
|
-
const token = glob[g];
|
|
445
|
-
return token === void 0 ? [] : token._tag === "Char" ? token.char === char ? [g + 1] : [] : char === "." ? [] : [g];
|
|
446
|
-
};
|
|
447
|
-
const search = (frontier, seen) => {
|
|
448
|
-
const fresh = Array.from(HashMap.values(HashMap.fromIterable(Arr.map(Arr.filter(Arr.flatMap(frontier, ([t, g]) => closeOf(t, g)), ([t, g]) => !HashSet.has(seen, key(t, g))), (pair) => [key(pair[0], pair[1]), pair]))));
|
|
449
|
-
if (fresh.length === 0) return false;
|
|
450
|
-
if (Arr.some(fresh, ([t, g]) => t === template.length && g === glob.length)) return true;
|
|
451
|
-
const next = HashSet.union(seen, HashSet.fromIterable(Arr.map(fresh, ([t, g]) => key(t, g))));
|
|
452
|
-
return search(Arr.flatMap(fresh, ([t, g]) => Arr.flatMap(alphabet, (char) => Arr.flatMap(stepTemplate(t, char), (t2) => Arr.map(stepGlob(g, char), (g2) => [t2, g2])))), next);
|
|
453
|
-
};
|
|
454
|
-
return search([[0, 0]], HashSet.empty());
|
|
455
|
-
};
|
|
456
|
-
/**
|
|
457
|
-
* Tests if a key pattern fits a key of a template literal key Schema, or
|
|
458
|
-
* `undefined` when the search is not exact for the Schema. The search calls
|
|
459
|
-
* itself one time for each character of a key, so a very long part gives
|
|
460
|
-
* `undefined` too.
|
|
461
|
-
*/
|
|
462
|
-
const globMeetsKeys = (part, ast) => {
|
|
463
|
-
if (ast._tag !== "TemplateLiteral" || (ast.checks ?? []).length > 0 || part.length > maxExactKeyLength) return;
|
|
464
|
-
const lists = templateTokens(ast);
|
|
465
|
-
if (lists === void 0) return;
|
|
466
|
-
const glob = Arr.map(part.match(/\*+|[^*]/gu) ?? [], (token) => token.startsWith("*") ? { _tag: "Star" } : {
|
|
467
|
-
_tag: "Char",
|
|
468
|
-
char: token
|
|
469
|
-
});
|
|
470
|
-
return Arr.some(lists, (list) => tokensMeet(glob, list));
|
|
471
|
-
};
|
|
472
|
-
/**
|
|
473
|
-
* A few keys that a key Schema of a record takes: each part of a template
|
|
474
|
-
* literal gives its literal, or a short text for a string or a number. These
|
|
475
|
-
* are samples. A key that is not a sample may fit too, so a "no" from a test
|
|
476
|
-
* with them is only "not found".
|
|
477
|
-
*/
|
|
478
|
-
const sampleKeys = (ast) => {
|
|
479
|
-
switch (ast._tag) {
|
|
480
|
-
case "String": return ["", "a"];
|
|
481
|
-
case "Number": return ["0", "1"];
|
|
482
|
-
case "Literal": return [String(ast.literal)];
|
|
483
|
-
case "Union": return Arr.flatMap(ast.types, sampleKeys);
|
|
484
|
-
case "TemplateLiteral": return Arr.reduce(ast.parts, [""], (keys, part) => Arr.take(Arr.flatMap(keys, (key) => Arr.map(Arr.take(sampleKeys(part), 2), (end) => key + end)), 64));
|
|
485
|
-
default: return [];
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
/**
|
|
489
|
-
* Tests if a Schema surely has a value: each required field, at each depth,
|
|
490
|
-
* has a type that has values, with no checks other than integer and finite
|
|
491
|
-
* checks on numbers. A check such as a minimum length may stop every value,
|
|
492
|
-
* and the library can not see that, so such a Schema gives `false`. So does a
|
|
493
|
-
* required field that holds the struct itself again.
|
|
494
|
-
*
|
|
495
|
-
* @stability stable
|
|
496
|
-
* @category predicates
|
|
497
|
-
* @since 1.0.0
|
|
498
|
-
*/
|
|
499
|
-
const hasValue = (schema) => isSurelyInhabited(schema.ast, /* @__PURE__ */ new Set());
|
|
500
|
-
const isSurelyInhabited = (ast, open) => {
|
|
501
|
-
if (ast._tag === "Number" ? !hasOnlyKnownChecks(ast.checks) : (ast.checks ?? []).length > 0) return false;
|
|
502
|
-
switch (ast._tag) {
|
|
503
|
-
case "Never": return false;
|
|
504
|
-
case "Objects": return (ast.propertySignatures.length === 0 || ast.indexSignatures.length === 0) && Arr.every(ast.propertySignatures, (property) => SchemaAST.isOptional(property.type) || isSurelyInhabited(property.type, open));
|
|
505
|
-
case "Union": return ast.options?.mode !== "oneOf" && Arr.some(ast.types, (type) => isSurelyInhabited(type, open));
|
|
506
|
-
case "Suspend": return !open.has(ast) && isSurelyInhabited(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
|
|
507
|
-
case "Arrays": return Arr.every([...Arr.filter(ast.elements, (element) => !SchemaAST.isOptional(element)), ...ast.rest.slice(1)], (element) => isSurelyInhabited(element, open));
|
|
508
|
-
case "Declaration": {
|
|
509
|
-
const inner = classStruct(ast);
|
|
510
|
-
if (inner !== void 0) return isSurelyInhabited(inner, open);
|
|
511
|
-
const id = declarationId(ast);
|
|
512
|
-
if (HashSet.has(wrapperDeclarations, id)) return Arr.some(ast.typeParameters, (type) => isSurelyInhabited(type, open));
|
|
513
|
-
return HashSet.has(inhabitedDeclarations, id);
|
|
514
|
-
}
|
|
515
|
-
case "Enum": return ast.enums.length > 0;
|
|
516
|
-
case "TemplateLiteral": return Arr.every(ast.parts, (part) => isSurelyInhabited(part, open));
|
|
517
|
-
case "String":
|
|
518
|
-
case "Number":
|
|
519
|
-
case "Boolean":
|
|
520
|
-
case "BigInt":
|
|
521
|
-
case "Symbol":
|
|
522
|
-
case "UniqueSymbol":
|
|
523
|
-
case "Literal":
|
|
524
|
-
case "Null":
|
|
525
|
-
case "Undefined":
|
|
526
|
-
case "Void":
|
|
527
|
-
case "Unknown":
|
|
528
|
-
case "Any":
|
|
529
|
-
case "ObjectKeyword": return true;
|
|
530
|
-
default: return false;
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
/** Declarations of Effect that hold a value of a type parameter. */
|
|
534
|
-
const wrapperDeclarations = HashSet.make("effect/schema/Redacted", "effect/schema/Result");
|
|
535
|
-
/** Declarations of Effect that have a value for any type parameters. */
|
|
536
|
-
const inhabitedDeclarations = HashSet.make("effect/schema/Date", "effect/schema/URL", "effect/schema/Uint8Array", "effect/schema/Duration", "effect/schema/DateTimeUtc", "effect/schema/BigDecimal", "effect/schema/Cause", "effect/schema/Chunk", "effect/schema/HashMap", "effect/schema/HashSet", "effect/schema/ReadonlyMap", "effect/schema/ReadonlySet", "effect/schema/Option", "effect/schema/File", "effect/schema/FormData", "effect/schema/RegExp", "effect/schema/Json", "effect/schema/ByteSize", "effect/schema/DateTimeZoned", "effect/schema/Error", "effect/schema/Exit", "effect/schema/CauseReason", "effect/schema/Graph", "effect/schema/SemVer", "effect/schema/SemVerRange", "effect/schema/TimeZone", "effect/schema/TimeZoneNamed", "effect/schema/TimeZoneOffset", "effect/schema/URLSearchParams");
|
|
537
|
-
/**
|
|
538
|
-
* Tests if a Schema has no value at all: a required field of it, at some
|
|
539
|
-
* depth, has no value, as `Schema.Never` has none. A required field that
|
|
540
|
-
* holds the struct itself again, with no way to stop, has no finite value
|
|
541
|
-
* either. Other checks, such as a minimum and a maximum that do not meet, are
|
|
542
|
-
* not seen, so a "no" does not prove that the Schema has a value.
|
|
543
|
-
*
|
|
544
|
-
* @stability stable
|
|
545
|
-
* @category predicates
|
|
546
|
-
* @since 1.0.0
|
|
547
|
-
*/
|
|
548
|
-
const hasNoValue = (schema) => isEmptyType(schema.ast, /* @__PURE__ */ new Set());
|
|
549
|
-
const isEmptyType = (ast, open) => {
|
|
550
|
-
switch (ast._tag) {
|
|
551
|
-
case "Never": return true;
|
|
552
|
-
case "Objects": return Arr.some(ast.propertySignatures, (property) => !SchemaAST.isOptional(property.type) && isEmptyType(property.type, open));
|
|
553
|
-
case "Union": return Arr.every(ast.types, (type) => isEmptyType(type, open));
|
|
554
|
-
case "Suspend": return open.has(ast) || isEmptyType(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
|
|
555
|
-
case "Declaration": {
|
|
556
|
-
const inner = classStruct(ast);
|
|
557
|
-
return inner !== void 0 && isEmptyType(inner, open);
|
|
558
|
-
}
|
|
559
|
-
default: return false;
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
const numberOrder = [
|
|
563
|
-
"none",
|
|
564
|
-
"integer",
|
|
565
|
-
"finite",
|
|
566
|
-
"any"
|
|
567
|
-
];
|
|
568
|
-
const emptyDomain = {
|
|
569
|
-
null: false,
|
|
570
|
-
object: false,
|
|
571
|
-
boolean: false,
|
|
572
|
-
string: false,
|
|
573
|
-
number: "none",
|
|
574
|
-
literals: []
|
|
575
|
-
};
|
|
576
|
-
const nullDomain = {
|
|
577
|
-
...emptyDomain,
|
|
578
|
-
null: true
|
|
579
|
-
};
|
|
580
|
-
const objectDomain = {
|
|
581
|
-
...emptyDomain,
|
|
582
|
-
object: true
|
|
583
|
-
};
|
|
584
|
-
/**
|
|
585
|
-
* The domain of a field with no Schema: any value.
|
|
586
|
-
*
|
|
587
|
-
* @stability stable
|
|
588
|
-
* @category constants
|
|
589
|
-
* @since 1.0.0
|
|
590
|
-
*/
|
|
591
|
-
const anyDomain = {
|
|
592
|
-
null: true,
|
|
593
|
-
object: true,
|
|
594
|
-
boolean: true,
|
|
595
|
-
string: true,
|
|
596
|
-
number: "any",
|
|
597
|
-
literals: []
|
|
598
|
-
};
|
|
599
|
-
const joinDomains = (domains) => domains.reduce((left, right) => ({
|
|
600
|
-
null: left.null || right.null,
|
|
601
|
-
object: left.object || right.object,
|
|
602
|
-
boolean: left.boolean || right.boolean,
|
|
603
|
-
string: left.string || right.string,
|
|
604
|
-
number: numberOrder.indexOf(left.number) >= numberOrder.indexOf(right.number) ? left.number : right.number,
|
|
605
|
-
literals: [...left.literals, ...right.literals]
|
|
606
|
-
}), emptyDomain);
|
|
607
|
-
const hasCheck = (checks, id) => checks.some((check) => check.annotations?.representation?.id === id || check._tag === "FilterGroup" && hasCheck(check.checks, id));
|
|
608
|
-
/**
|
|
609
|
-
* The ids of the declarations of Effect that hold only values with parts.
|
|
610
|
-
* Effect puts the id in the `representation` annotation. A declaration that
|
|
611
|
-
* is not in this list may hold a scalar, as `Schema.Json` may.
|
|
612
|
-
*/
|
|
613
|
-
const objectDeclarations = HashSet.make("effect/schema/Date", "effect/schema/URL", "effect/schema/Uint8Array", "effect/schema/Duration", "effect/schema/DateTimeUtc", "effect/schema/BigDecimal", "effect/schema/Redacted", "effect/schema/Cause", "effect/schema/Chunk", "effect/schema/HashMap", "effect/schema/HashSet", "effect/schema/ReadonlyMap", "effect/schema/ReadonlySet", "effect/schema/Option", "effect/schema/Result", "effect/schema/File", "effect/schema/FormData", "effect/schema/RegExp");
|
|
614
|
-
const declarationId = (ast) => {
|
|
615
|
-
const representation = ast.annotations?.["representation"];
|
|
616
|
-
return hasProperty(representation, "id") && typeof representation.id === "string" ? representation.id : void 0;
|
|
617
|
-
};
|
|
618
|
-
/** The domain of the values of a Schema node. */
|
|
619
|
-
const domainOfType = (ast, open) => {
|
|
620
|
-
switch (ast._tag) {
|
|
621
|
-
case "Null":
|
|
622
|
-
case "Undefined": return nullDomain;
|
|
623
|
-
case "Never": return emptyDomain;
|
|
624
|
-
case "Void":
|
|
625
|
-
case "Unknown":
|
|
626
|
-
case "Any": return anyDomain;
|
|
627
|
-
case "String":
|
|
628
|
-
case "TemplateLiteral": return {
|
|
629
|
-
...emptyDomain,
|
|
630
|
-
string: true
|
|
631
|
-
};
|
|
632
|
-
case "Number": {
|
|
633
|
-
const checks = ast.checks ?? [];
|
|
634
|
-
return {
|
|
635
|
-
...emptyDomain,
|
|
636
|
-
number: hasCheck(checks, "effect/schema/isInt") ? "integer" : hasCheck(checks, "effect/schema/isFinite") ? "finite" : "any"
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
case "Boolean": return {
|
|
640
|
-
...emptyDomain,
|
|
641
|
-
boolean: true
|
|
642
|
-
};
|
|
643
|
-
case "Literal": return typeof ast.literal === "bigint" ? objectDomain : {
|
|
644
|
-
...emptyDomain,
|
|
645
|
-
literals: [ast.literal]
|
|
646
|
-
};
|
|
647
|
-
case "Enum": return {
|
|
648
|
-
...emptyDomain,
|
|
649
|
-
literals: ast.enums.map(([, value]) => value)
|
|
650
|
-
};
|
|
651
|
-
case "Union": return joinDomains(ast.types.map((type) => domainOfType(type, open)));
|
|
652
|
-
case "Suspend": return open.has(ast) ? anyDomain : domainOfType(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
|
|
653
|
-
case "Objects": return ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0 ? anyDomain : objectDomain;
|
|
654
|
-
case "Declaration": return classStruct(ast) !== void 0 || HashSet.has(objectDeclarations, declarationId(ast)) ? objectDomain : anyDomain;
|
|
655
|
-
default: return objectDomain;
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
const domainAt = (ast, names, index, memo) => rememberAt(memo, ast, index, anyDomain, () => {
|
|
659
|
-
if (index === names.length) return domainOfType(ast, /* @__PURE__ */ new Set());
|
|
660
|
-
const name = names[index];
|
|
661
|
-
switch (ast._tag) {
|
|
662
|
-
case "Objects": {
|
|
663
|
-
const property = ast.propertySignatures.find((signature) => signature.name === name);
|
|
664
|
-
if (property !== void 0) {
|
|
665
|
-
const inner = domainAt(property.type, names, index + 1, memo);
|
|
666
|
-
return SchemaAST.isOptional(property.type) || name in Object.prototype ? joinDomains([nullDomain, inner]) : inner;
|
|
667
|
-
}
|
|
668
|
-
return anyDomain;
|
|
669
|
-
}
|
|
670
|
-
case "Union": return joinDomains(ast.types.map((type) => domainAt(type, names, index, memo)));
|
|
671
|
-
case "Suspend": return domainAt(ast.thunk(), names, index, memo);
|
|
672
|
-
case "Declaration": {
|
|
673
|
-
const inner = classStruct(ast);
|
|
674
|
-
return inner === void 0 ? anyDomain : domainAt(inner, names, index, memo);
|
|
675
|
-
}
|
|
676
|
-
case "Arrays":
|
|
677
|
-
case "ObjectKeyword":
|
|
678
|
-
case "Unknown":
|
|
679
|
-
case "Any": return anyDomain;
|
|
680
|
-
default: return nullDomain;
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
/**
|
|
684
|
-
* Gives the domain of a field of a Schema, by its path with dots. A path that
|
|
685
|
-
* the Schema does not name gives `null`, as a condition reads such a field.
|
|
686
|
-
*
|
|
687
|
-
* @stability stable
|
|
688
|
-
* @category getters
|
|
689
|
-
* @since 1.0.0
|
|
690
|
-
*/
|
|
691
|
-
const domainOf = (schema, path) => isTooLong(path) ? anyDomain : domainAt(schema.ast, path.split("."), 0, /* @__PURE__ */ new Map());
|
|
692
|
-
/**
|
|
693
|
-
* Checks the names of the top fields of a resource at once, when it is made.
|
|
694
|
-
* The names of nested fields are checked when the descriptor is made.
|
|
695
|
-
*/
|
|
696
|
-
const checkFields = (kind, options) => {
|
|
697
|
-
if (Object.hasOwn(options.fields, "_tag")) throw new Error(`The resource "${kind}" may not have a field "_tag"`);
|
|
698
|
-
Arr.map(Object.keys(options.fields), (name) => {
|
|
699
|
-
if (name.includes(".")) throw new Error(`A field name may not have a dot: "${name}"`);
|
|
700
|
-
if (name === "") throw new Error("A field name may not be empty");
|
|
701
|
-
});
|
|
702
|
-
};
|
|
703
|
-
/**
|
|
704
|
-
* Gives a value that is made on first use, and then kept. The descriptor of a
|
|
705
|
-
* resource reads its Schema, and a recursive Schema refers to the resource
|
|
706
|
-
* class, so it can not be read while the class is made. The cell belongs to
|
|
707
|
-
* one resource.
|
|
708
|
-
*/
|
|
709
|
-
const lazy = (make) => {
|
|
710
|
-
const cell = {};
|
|
711
|
-
return () => {
|
|
712
|
-
if (!("value" in cell)) cell.value = make();
|
|
713
|
-
return cell.value;
|
|
714
|
-
};
|
|
715
|
-
};
|
|
716
|
-
/**
|
|
717
|
-
* Checks each part of the path of a resource: a string, or a reference to a
|
|
718
|
-
* top-level field of the resource. A reference to another field would read
|
|
719
|
-
* nothing, and a rule on the path could then match a value by mistake.
|
|
720
|
-
*/
|
|
721
|
-
const checkPath = (kind, fields, path) => Arr.map(Array.from(path), (segment) => {
|
|
722
|
-
if (typeof segment === "string") return segment;
|
|
723
|
-
if (hasProperty(segment, "_tag") && segment._tag === "Field" && hasProperty(segment, "field") && typeof segment.field === "string" && Object.hasOwn(fields, segment.field)) return {
|
|
724
|
-
_tag: "Field",
|
|
725
|
-
field: segment.field
|
|
726
|
-
};
|
|
727
|
-
throw new globalThis.Error(`A part of the path of "${kind}" is not a string or a field of the resource`);
|
|
728
|
-
});
|
|
729
|
-
/**
|
|
730
|
-
* Copies the options of a resource. The descriptor is made later, when it is
|
|
731
|
-
* first read, so a change to the objects of the caller before then must not
|
|
732
|
-
* change it: the Schema of the values and the descriptor must agree.
|
|
733
|
-
*/
|
|
734
|
-
const snapshot = (options) => ({
|
|
735
|
-
...options,
|
|
736
|
-
actions: [...options.actions],
|
|
737
|
-
fields: { ...options.fields }
|
|
738
|
-
});
|
|
739
|
-
const makeDescriptor = (kind, options) => {
|
|
740
|
-
const refs = Object.fromEntries(Arr.map(Object.keys(options.fields), (key) => [key, {
|
|
741
|
-
_tag: "Field",
|
|
742
|
-
field: key
|
|
743
|
-
}]));
|
|
744
|
-
const visits = walkFields(Schema.Struct(options.fields).ast);
|
|
745
|
-
const leaves = Arr.filter(visits, (visit) => visit.leaf);
|
|
746
|
-
const structs = HashSet.fromIterable(Arr.map(Arr.filter(visits, (visit) => !visit.leaf), (visit) => visit.path));
|
|
747
|
-
const open = Arr.filter(leaves, (visit) => shapeOf(visit.type) !== "scalar" || HashSet.has(structs, visit.path));
|
|
748
|
-
return new ResourceDescriptor({
|
|
749
|
-
kind,
|
|
750
|
-
actions: [...options.actions],
|
|
751
|
-
path: options.path === void 0 ? void 0 : checkPath(kind, options.fields, options.path(refs)),
|
|
752
|
-
fieldPaths: Arr.dedupe(Arr.map(leaves, (visit) => visit.path)),
|
|
753
|
-
openPaths: Arr.dedupe(Arr.map(open, (visit) => visit.path)),
|
|
754
|
-
schema: Schema.TaggedStruct(kind, options.fields)
|
|
755
|
-
});
|
|
756
|
-
};
|
|
757
|
-
/**
|
|
758
9
|
* Makes a resource as a Schema class. Use it in the same way as
|
|
759
10
|
* `Schema.TaggedClass`.
|
|
760
11
|
*
|
|
@@ -779,19 +30,7 @@ const makeDescriptor = (kind, options) => {
|
|
|
779
30
|
* @category constructors
|
|
780
31
|
* @since 0.3.0
|
|
781
32
|
*/
|
|
782
|
-
const Class =
|
|
783
|
-
const options = snapshot(input);
|
|
784
|
-
checkFields(kind, options);
|
|
785
|
-
const Base = Schema.TaggedClass(identifier)(kind, options.fields);
|
|
786
|
-
const descriptor = lazy(() => makeDescriptor(kind, options));
|
|
787
|
-
return class extends Base {
|
|
788
|
-
static get descriptor() {
|
|
789
|
-
return descriptor();
|
|
790
|
-
}
|
|
791
|
-
static kind = kind;
|
|
792
|
-
static actions = [...options.actions];
|
|
793
|
-
};
|
|
794
|
-
});
|
|
33
|
+
const Class = makeClass;
|
|
795
34
|
/**
|
|
796
35
|
* Makes a resource from plain fields. Its values are plain objects with a
|
|
797
36
|
* `_tag` field.
|
|
@@ -800,45 +39,7 @@ const Class = ((identifier) => (kind, input) => {
|
|
|
800
39
|
* @category constructors
|
|
801
40
|
* @since 0.3.0
|
|
802
41
|
*/
|
|
803
|
-
const make =
|
|
804
|
-
|
|
805
|
-
checkFields(kind, options);
|
|
806
|
-
const schema = Schema.TaggedStruct(kind, options.fields);
|
|
807
|
-
const descriptor = lazy(() => makeDescriptor(kind, options));
|
|
808
|
-
return {
|
|
809
|
-
get descriptor() {
|
|
810
|
-
return descriptor();
|
|
811
|
-
},
|
|
812
|
-
kind,
|
|
813
|
-
actions: [...options.actions],
|
|
814
|
-
schema,
|
|
815
|
-
make: (input) => schema.make(input)
|
|
816
|
-
};
|
|
817
|
-
});
|
|
818
|
-
/**
|
|
819
|
-
* Tests if a value is a resource.
|
|
820
|
-
*
|
|
821
|
-
* @stability stable
|
|
822
|
-
* @category guards
|
|
823
|
-
* @since 0.3.0
|
|
824
|
-
*/
|
|
825
|
-
const isResource = (u) => hasProperty(u, "descriptor") && u.descriptor instanceof ResourceDescriptor;
|
|
826
|
-
/**
|
|
827
|
-
* Gets the runtime data of a resource.
|
|
828
|
-
*
|
|
829
|
-
* @stability stable
|
|
830
|
-
* @category getters
|
|
831
|
-
* @since 0.3.0
|
|
832
|
-
*/
|
|
833
|
-
const descriptor = (resource) => resource.descriptor;
|
|
834
|
-
/**
|
|
835
|
-
* Gives the kind name of a resource value, from its `_tag` field.
|
|
836
|
-
*
|
|
837
|
-
* @stability stable
|
|
838
|
-
* @category getters
|
|
839
|
-
* @since 1.0.0
|
|
840
|
-
*/
|
|
841
|
-
const kindOf = (value) => hasProperty(value, "_tag") && typeof value._tag === "string" ? value._tag : void 0;
|
|
842
|
-
export { Class, Resource_exports, anyDomain, descriptor, domainOf, fieldPaths, hasGetter, hasMatchingGetter, hasMatchingPath, hasNoValue, hasPath, hasValue, isPlainField, isResource, kindOf, make };
|
|
42
|
+
const make = makeStruct;
|
|
43
|
+
export { Class, Resource_exports, make };
|
|
843
44
|
|
|
844
45
|
//# sourceMappingURL=Resource.js.map
|