@reventlessdev/reventless-spec 3.0.0-alpha.133 → 3.0.0-alpha.135
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/CHANGELOG.md +33 -0
- package/package.json +4 -3
- package/run-check-lifecycle.mjs +2 -1
- package/run-prepare-accounts.mjs +3 -0
- package/src/AnsiStyle.res +0 -1
- package/src/LogPrefix.res +4 -13
- package/src/components/AutomationSlice.res +1 -5
- package/src/components/CapabilityManifest.res +6 -3
- package/src/components/CapabilityManifest.res.mjs +13 -2
- package/src/components/DcbDecode.res +6 -2
- package/src/components/DcbScopeInference.res +16 -12
- package/src/components/DcbTag.res +74 -46
- package/src/components/DcbValidation.res +114 -89
- package/src/components/DisplayName.res +4 -2
- package/src/components/ExtensionPoint.res +0 -1
- package/src/components/FieldDefault.res +2 -4
- package/src/components/InboundTranslationSlice.res +0 -2
- package/src/components/OutboundTranslationSlice.res +10 -8
- package/src/components/Owner.res +4 -4
- package/src/components/Plugin.res +10 -4
- package/src/components/ReadModel.res +0 -1
- package/src/components/Reference.res +2 -8
- package/src/components/Sensitive.res +4 -4
- package/src/components/StateAnnotations.res +4 -2
- package/src/components/StateChangeSlice.res +0 -2
- package/src/components/StateViewSlice.res +0 -2
- package/src/components/TaggedUnion.res +1 -2
- package/src/components/Task.res +0 -1
- package/src/components/TraitCertificate.res +2 -2
- package/src/components/TraitManifest.res +1 -1
- package/src/generator/CertifyTrait.res +2 -4
- package/src/generator/Codegen.res +77 -68
- package/src/generator/Discovery.res +29 -12
- package/src/generator/GraftTrait.res +17 -9
- package/src/generator/Pairing.res +39 -13
- package/src/generator/PlatformCodegen.res +14 -11
- package/src/generator/PlatformCodegen.res.mjs +5 -0
- package/src/generator/PlatformManifests.res +1 -3
- package/src/generator/PluginGenerator.res +0 -1
- package/src/generator/TraitManifestCli.res +7 -7
- package/src/lifecycle/CheckLifecycleModel.res +206 -148
- package/src/lifecycle/CheckLifecycleModel.res.mjs +23 -5
- package/src/semantic/Bytes.res +0 -1
- package/src/semantic/CalendarDate.res +2 -2
- package/src/semantic/Capabilities.res +21 -6
- package/src/semantic/Capabilities.res.mjs +18 -12
- package/src/semantic/CapabilityNeed.res +16 -5
- package/src/semantic/CapabilityNeed.res.mjs +9 -4
- package/src/semantic/CaptionedImage.res +0 -1
- package/src/semantic/Color.res +0 -1
- package/src/semantic/Currency.res +189 -170
- package/src/semantic/DateRange.res +3 -4
- package/src/semantic/DateTime.res +5 -5
- package/src/semantic/Duration.res +2 -2
- package/src/semantic/Email.res +0 -1
- package/src/semantic/FileRef.res +2 -2
- package/src/semantic/GeoPoint.res +17 -21
- package/src/semantic/Geocoding.res +8 -9
- package/src/semantic/Geolocation.res +6 -9
- package/src/semantic/IdentityProvider.res +127 -0
- package/src/semantic/IdentityProvider.res.mjs +65 -0
- package/src/semantic/ImageRef.res +2 -2
- package/src/semantic/MemberRef.res +0 -1
- package/src/semantic/Messaging.res +141 -21
- package/src/semantic/Messaging.res.mjs +51 -8
- package/src/semantic/Money.res +23 -20
- package/src/semantic/Offload.res +6 -4
- package/src/semantic/Percent.res +3 -3
- package/src/semantic/Phone.res +0 -1
- package/src/semantic/RowImage.res +4 -3
- package/src/semantic/Secrets.res +76 -0
- package/src/semantic/Secrets.res.mjs +49 -0
- package/src/semantic/Semantic.res +8 -11
- package/src/semantic/StorageRef.res +17 -17
- package/src/semantic/Template.res +5 -7
- package/src/semantic/UploadableFile.res +0 -1
- package/src/semantic/UploadableImage.res +0 -1
- package/src/semantic/Url.res +3 -3
- package/src/types/AccountsManifest.res +281 -0
- package/src/types/AccountsManifest.res.mjs +311 -0
- package/src/types/AdminGroup.res +27 -0
- package/src/types/AdminGroup.res.mjs +9 -0
- package/src/types/Authorization.res +1 -2
- package/src/types/Handler.res +0 -1
- package/src/types/Identity.res +1 -3
- package/src/types/Lifecycle.res +7 -6
- package/src/types/Message.res +6 -4
- package/src/types/OwnerScope.res +28 -3
- package/src/types/OwnerScope.res.mjs +12 -0
- package/src/types/PrepareAccounts.res +119 -0
- package/src/types/PrepareAccounts.res.mjs +148 -0
- package/src/types/Projection.res +9 -2
- package/src/types/StoredEvent.res +2 -4
- package/src/types/Transition.res +8 -4
- package/src/util/Util_Password.res +60 -0
- package/src/util/Util_Password.res.mjs +53 -0
- package/src/util/Util_Sury.res +1 -4
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
function serviceOf(address) {
|
|
5
|
+
switch (address.TAG) {
|
|
6
|
+
case "Apns" :
|
|
7
|
+
return "ApnsService";
|
|
8
|
+
case "Fcm" :
|
|
9
|
+
return "FcmService";
|
|
10
|
+
case "WebPush" :
|
|
11
|
+
return "WebPushService";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
function channelOf(recipient) {
|
|
5
16
|
switch (recipient.TAG) {
|
|
6
17
|
case "ToEmail" :
|
|
@@ -23,6 +34,17 @@ function channelToString(channel) {
|
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
|
|
37
|
+
function pushServiceToString(service) {
|
|
38
|
+
switch (service) {
|
|
39
|
+
case "ApnsService" :
|
|
40
|
+
return "APNs";
|
|
41
|
+
case "FcmService" :
|
|
42
|
+
return "FCM";
|
|
43
|
+
case "WebPushService" :
|
|
44
|
+
return "Web Push";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
function fromHeader(displayName, address) {
|
|
27
49
|
if (displayName === undefined) {
|
|
28
50
|
return address;
|
|
@@ -32,35 +54,56 @@ function fromHeader(displayName, address) {
|
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
function retriable(failure) {
|
|
35
|
-
|
|
36
|
-
case "Unavailable" :
|
|
37
|
-
return true;
|
|
38
|
-
case "UnsupportedChannel" :
|
|
39
|
-
case "Refused" :
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
57
|
+
return failure.TAG === "Unavailable";
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
function failureReason(failure) {
|
|
45
61
|
switch (failure.TAG) {
|
|
46
62
|
case "UnsupportedChannel" :
|
|
47
63
|
return `this deployment provisions no ` + channelToString(failure._0) + ` channel`;
|
|
64
|
+
case "UnsupportedPushService" :
|
|
65
|
+
return `this deployment provisions push, but not ` + pushServiceToString(failure._0);
|
|
48
66
|
case "Unavailable" :
|
|
49
67
|
case "Refused" :
|
|
50
68
|
return failure._0;
|
|
51
69
|
}
|
|
52
70
|
}
|
|
53
71
|
|
|
72
|
+
function makeProvider(emailAndSms, pushServices, send) {
|
|
73
|
+
return {
|
|
74
|
+
channels: emailAndSms.filter(channel => {
|
|
75
|
+
switch (channel) {
|
|
76
|
+
case "Email" :
|
|
77
|
+
case "Sms" :
|
|
78
|
+
return true;
|
|
79
|
+
case "Push" :
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}).concat(pushServices.length === 0 ? [] : ["Push"]),
|
|
83
|
+
pushServices: pushServices,
|
|
84
|
+
send: send
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
54
88
|
function supports(provider, recipient) {
|
|
55
|
-
|
|
89
|
+
switch (recipient.TAG) {
|
|
90
|
+
case "ToEmail" :
|
|
91
|
+
case "ToSms" :
|
|
92
|
+
return provider.channels.includes(channelOf(recipient));
|
|
93
|
+
case "ToPush" :
|
|
94
|
+
return provider.pushServices.includes(serviceOf(recipient._0));
|
|
95
|
+
}
|
|
56
96
|
}
|
|
57
97
|
|
|
58
98
|
export {
|
|
99
|
+
serviceOf,
|
|
59
100
|
channelOf,
|
|
60
101
|
channelToString,
|
|
102
|
+
pushServiceToString,
|
|
61
103
|
fromHeader,
|
|
62
104
|
retriable,
|
|
63
105
|
failureReason,
|
|
106
|
+
makeProvider,
|
|
64
107
|
supports,
|
|
65
108
|
}
|
|
66
109
|
/* No side effect */
|
package/src/semantic/Money.res
CHANGED
|
@@ -66,7 +66,6 @@ rewrites the wire shape, so stored events no longer decode and projections must
|
|
|
66
66
|
be rebuilt. Retyping a field in a log that has to survive needs an upcaster
|
|
67
67
|
first; a log that can be discarded can take it today.
|
|
68
68
|
*/
|
|
69
|
-
|
|
70
69
|
/**
|
|
71
70
|
Validate a minor-unit amount, saying why when it is not one.
|
|
72
71
|
|
|
@@ -79,8 +78,9 @@ let validateAmount = (amount: float): result<float, string> =>
|
|
|
79
78
|
} else if amount !== Math.trunc(amount) {
|
|
80
79
|
Error(
|
|
81
80
|
`an amount is a whole number of a currency's minor units, got ` ++
|
|
82
|
-
`${Float.toString(
|
|
83
|
-
|
|
81
|
+
`${Float.toString(
|
|
82
|
+
amount,
|
|
83
|
+
)}. There is no such thing as a fraction of the ` ++ `smallest unit — a major amount converts with Money.ofMajor.`,
|
|
84
84
|
)
|
|
85
85
|
} else {
|
|
86
86
|
Ok(amount)
|
|
@@ -96,14 +96,12 @@ let validateAmount = (amount: float): result<float, string> =>
|
|
|
96
96
|
threw `Cannot access 'v0' before initialization` — it is simply not what this
|
|
97
97
|
check wants.) */
|
|
98
98
|
let amountSchema: S.t<float> =
|
|
99
|
-
S.float->S.refine(
|
|
100
|
-
amount
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
~error="expected a monetary amount",
|
|
106
|
-
)
|
|
99
|
+
S.float->S.refine(amount =>
|
|
100
|
+
switch validateAmount(amount) {
|
|
101
|
+
| Ok(_) => true
|
|
102
|
+
| Error(_) => false
|
|
103
|
+
}
|
|
104
|
+
, ~error="expected a monetary amount")
|
|
107
105
|
|
|
108
106
|
@schema
|
|
109
107
|
type t = {
|
|
@@ -117,7 +115,8 @@ type t = {
|
|
|
117
115
|
|
|
118
116
|
Shadows the schema sury-ppx derived from the type above: the derived one is
|
|
119
117
|
the shape, and this adds the marker the shape cannot carry. */
|
|
120
|
-
let schema: S.t<t> =
|
|
118
|
+
let schema: S.t<t> =
|
|
119
|
+
schema->Semantic.mark(~id=Semantic.Id.money)
|
|
121
120
|
|
|
122
121
|
/** An amount already counted in minor units. */
|
|
123
122
|
let make = (~amount: float, ~currency: Currency.t): t => {amount, currency}
|
|
@@ -208,8 +207,9 @@ let add = (a: t, b: t): result<t, string> =>
|
|
|
208
207
|
a.currency == b.currency
|
|
209
208
|
? Ok({amount: a.amount +. b.amount, currency: a.currency})
|
|
210
209
|
: Error(
|
|
211
|
-
`cannot add ${format(b)} to ${format(
|
|
212
|
-
|
|
210
|
+
`cannot add ${format(b)} to ${format(
|
|
211
|
+
a,
|
|
212
|
+
)}: they are different currencies. ` ++ `Converting between them needs a rate, which is not something an amount carries.`,
|
|
213
213
|
)
|
|
214
214
|
|
|
215
215
|
/** The largest whole number a `float` holds exactly, 2^53 - 1. Bound here
|
|
@@ -266,8 +266,9 @@ A negative amount splits away from zero the same way: -€10.00 into three is
|
|
|
266
266
|
let allocate = (m: t, ~into: int): result<array<t>, string> =>
|
|
267
267
|
if into <= 0 {
|
|
268
268
|
Error(
|
|
269
|
-
`cannot split ${format(m)} into ${Int.toString(
|
|
270
|
-
|
|
269
|
+
`cannot split ${format(m)} into ${Int.toString(
|
|
270
|
+
into,
|
|
271
|
+
)} parts: ` ++ `a split is into at least one part.`,
|
|
271
272
|
)
|
|
272
273
|
} else {
|
|
273
274
|
let parts = Int.toFloat(into)
|
|
@@ -293,8 +294,10 @@ let allocate = (m: t, ~into: int): result<array<t>, string> =>
|
|
|
293
294
|
let sum = (amounts: array<t>): option<result<t, string>> =>
|
|
294
295
|
switch amounts {
|
|
295
296
|
| [] => None
|
|
296
|
-
| _ =>
|
|
297
|
-
|
|
298
|
-
m
|
|
299
|
-
|
|
297
|
+
| _ =>
|
|
298
|
+
Some(
|
|
299
|
+
amounts->Array.reduce(Ok(zero(~currency=(amounts->Array.getUnsafe(0)).currency)), (acc, m) =>
|
|
300
|
+
acc->Result.flatMap(total => add(total, m))
|
|
301
|
+
),
|
|
302
|
+
)
|
|
300
303
|
}
|
package/src/semantic/Offload.res
CHANGED
|
@@ -45,7 +45,6 @@ structure: @s.matches(Offload.optionSchema(~store="pluginStructures", pluginStru
|
|
|
45
45
|
option<Offload.payload<pluginStructure>>
|
|
46
46
|
```
|
|
47
47
|
*/
|
|
48
|
-
|
|
49
48
|
/** The reference an offloaded value carries: which store holds it, the
|
|
50
49
|
content-addressed key, the content hash (== the key's basis), and the byte
|
|
51
50
|
length. `hash` is redundant with `key` today (`key` is `sha256/<hash>`) but
|
|
@@ -75,7 +74,7 @@ Parameterised by the inner value's schema because the `Inline` arm round-trips
|
|
|
75
74
|
through it. The `Offloaded` arm round-trips through `offloadedRefSchema` under the
|
|
76
75
|
sentinel key. See the module doc for why this is untagged.
|
|
77
76
|
*/
|
|
78
|
-
// Both arms are *declared* (`S.object` / `S.shape`) rather than hand-written as
|
|
77
|
+
let // Both arms are *declared* (`S.object` / `S.shape`) rather than hand-written as
|
|
79
78
|
// `S.transform` pairs, and that is load-bearing, not a style choice. A transform
|
|
80
79
|
// arm is opaque: sury cannot see what it accepts, so it must offer every value to
|
|
81
80
|
// the arm's own serializer and let it reject. Two things follow, both of which bit
|
|
@@ -89,7 +88,7 @@ sentinel key. See the module doc for why this is untagged.
|
|
|
89
88
|
// A declared arm has neither problem: sury derives both directions from the shape,
|
|
90
89
|
// discriminates on it, and never runs user code to find out. One transform arm is
|
|
91
90
|
// enough to bring both failures back, so keep every arm declarative.
|
|
92
|
-
|
|
91
|
+
schema = (inner: S.t<'a>): S.t<payload<'a>> => {
|
|
93
92
|
// Offloaded arm: an object carrying the reserved sentinel key, tried first so an
|
|
94
93
|
// offloaded value is never mistaken for an inline one.
|
|
95
94
|
let offloadedArm = S.object(s => Offloaded(s.field(sentinelKey, offloadedRefSchema)))
|
|
@@ -115,7 +114,10 @@ let forStore = (
|
|
|
115
114
|
~threshold: option<int>=?,
|
|
116
115
|
inner: S.t<'a>,
|
|
117
116
|
): S.t<payload<'a>> =>
|
|
118
|
-
schema(inner)->Semantic.mark(
|
|
117
|
+
schema(inner)->Semantic.mark(
|
|
118
|
+
~id=Semantic.Id.offload,
|
|
119
|
+
~payload=StoredIn({plugin, store, threshold}),
|
|
120
|
+
)
|
|
119
121
|
|
|
120
122
|
/**
|
|
121
123
|
The codec wrapped for an **optional** field, plus the `StoredIn` marker. This is
|
package/src/semantic/Percent.res
CHANGED
|
@@ -13,7 +13,6 @@ fractions are allowed — `99.95` is a percentage.
|
|
|
13
13
|
}
|
|
14
14
|
```
|
|
15
15
|
*/
|
|
16
|
-
|
|
17
16
|
/** The percentage's representation. Transparent `float`: the marker refines an
|
|
18
17
|
existing numeric field, so nothing stored changes. */
|
|
19
18
|
type t = float
|
|
@@ -27,8 +26,9 @@ let fromFloat = (raw: float): result<t, string> =>
|
|
|
27
26
|
Error(`a percentage must be a finite number, got ${Float.toString(raw)}`)
|
|
28
27
|
} else if raw < 0.0 || raw > 100.0 {
|
|
29
28
|
Error(
|
|
30
|
-
`a percentage runs from 0 to 100, got ${Float.toString(
|
|
31
|
-
|
|
29
|
+
`a percentage runs from 0 to 100, got ${Float.toString(
|
|
30
|
+
raw,
|
|
31
|
+
)}. ` ++ `This scale is 0–100, not 0–1 — a fraction multiplies by 100 first.`,
|
|
32
32
|
)
|
|
33
33
|
} else {
|
|
34
34
|
Ok(raw)
|
package/src/semantic/Phone.res
CHANGED
|
@@ -23,11 +23,12 @@ Declaration order rather than a name rule: a view with two image fields has
|
|
|
23
23
|
already said which one comes first, and guessing from names would let a field
|
|
24
24
|
called `thumbnail` outrank the one the author put at the top.
|
|
25
25
|
*/
|
|
26
|
-
|
|
27
26
|
/** Where the ref string sits, relative to the field that carries it. */
|
|
28
27
|
type shape =
|
|
29
|
-
|
|
30
|
-
|
|
|
28
|
+
/** The field's own value is the ref. */
|
|
29
|
+
| Scalar
|
|
30
|
+
/** The ref is one member of the field's record. */
|
|
31
|
+
| Member(string)
|
|
31
32
|
|
|
32
33
|
type source = {
|
|
33
34
|
field: string,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Unguessable material, and a one-way function over it.
|
|
3
|
+
|
|
4
|
+
Injected rather than imported, and that is the whole point. A runtime global
|
|
5
|
+
would work in production and leave every consumer's *issuance* untestable: a
|
|
6
|
+
suite cannot assert what a flow does with a secret it cannot predict. Handing
|
|
7
|
+
the generator in is what lets a test pin it, exactly as an injected geocoder
|
|
8
|
+
lets a test pin a coordinate.
|
|
9
|
+
|
|
10
|
+
Not a portability seam like the others — every target runtime has a
|
|
11
|
+
cryptographic source, so this is not a provisioning choice anyone makes. It is
|
|
12
|
+
here because the domain must not reach for one directly, and because a test must
|
|
13
|
+
be able to replace it.
|
|
14
|
+
*/
|
|
15
|
+
/** What a secret is drawn from. Named rather than a charset string so a caller
|
|
16
|
+
cannot ask for an alphabet nothing checks — and so the two real cases stay
|
|
17
|
+
distinguishable: one is followed as a link, the other is retyped by a human
|
|
18
|
+
off a screen. */
|
|
19
|
+
type alphabet =
|
|
20
|
+
/** `0`–`9`. For a code someone reads and types back. */
|
|
21
|
+
| Digits
|
|
22
|
+
/** `A`–`Z`, `a`–`z`, `0`–`9`, `-`, `_`. Survives a URL untouched. */
|
|
23
|
+
| UrlSafe
|
|
24
|
+
|
|
25
|
+
/** The one way this fails: nothing here is a verdict about the caller. */
|
|
26
|
+
type failure = Unavailable(string)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
The port a caller reaches a cryptographic source through.
|
|
30
|
+
|
|
31
|
+
Both members answer `result` rather than a bare string, so a platform that
|
|
32
|
+
provisioned nothing refuses instead of returning something weak. A token is the
|
|
33
|
+
one value where a silent fallback is worse than an error: it would be accepted
|
|
34
|
+
everywhere and guessable by anyone.
|
|
35
|
+
*/
|
|
36
|
+
type t = {
|
|
37
|
+
/** `length` characters drawn uniformly from `alphabet`. */
|
|
38
|
+
randomToken: (~length: int, ~alphabet: alphabet) => result<string, failure>,
|
|
39
|
+
/** One-way, so a challenge can be stored without storing what would answer
|
|
40
|
+
it. Deterministic: the same input always gives the same digest, which is
|
|
41
|
+
what lets a presented secret be compared against a held one. */
|
|
42
|
+
hash: string => result<string, failure>,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The characters each alphabet draws from. Exposed so an implementation and a
|
|
46
|
+
test agree on the set rather than each spelling it. */
|
|
47
|
+
let characters = (alphabet: alphabet): string =>
|
|
48
|
+
switch alphabet {
|
|
49
|
+
| Digits => "0123456789"
|
|
50
|
+
| UrlSafe => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
A source that produces nothing, refusing both operations.
|
|
55
|
+
|
|
56
|
+
Named so a platform passing it is making a statement rather than filling in a
|
|
57
|
+
blank — and refusing rather than degrading, because there is no weaker token
|
|
58
|
+
that would be safe to hand back.
|
|
59
|
+
*/
|
|
60
|
+
let unavailable = (~reason: string): t => {
|
|
61
|
+
randomToken: (~length as _, ~alphabet as _) => Error(Unavailable(reason)),
|
|
62
|
+
hash: _ => Error(Unavailable(reason)),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
A source that returns what it was given, for a test that needs to know the
|
|
67
|
+
secret it is about to present.
|
|
68
|
+
|
|
69
|
+
Here rather than in each suite because a consumer testing issuance needs exactly
|
|
70
|
+
this and should not be inventing it — and because a fixed generator written per
|
|
71
|
+
suite is one that eventually differs from the real one's alphabet.
|
|
72
|
+
*/
|
|
73
|
+
let fixed = (~token: string, ~hash as hashOf: string => string): t => {
|
|
74
|
+
randomToken: (~length as _, ~alphabet as _) => Ok(token),
|
|
75
|
+
hash: input => Ok(hashOf(input)),
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function characters(alphabet) {
|
|
5
|
+
if (alphabet === "Digits") {
|
|
6
|
+
return "0123456789";
|
|
7
|
+
} else {
|
|
8
|
+
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function unavailable(reason) {
|
|
13
|
+
return {
|
|
14
|
+
randomToken: (param, param$1) => ({
|
|
15
|
+
TAG: "Error",
|
|
16
|
+
_0: {
|
|
17
|
+
TAG: "Unavailable",
|
|
18
|
+
_0: reason
|
|
19
|
+
}
|
|
20
|
+
}),
|
|
21
|
+
hash: param => ({
|
|
22
|
+
TAG: "Error",
|
|
23
|
+
_0: {
|
|
24
|
+
TAG: "Unavailable",
|
|
25
|
+
_0: reason
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fixed(token, hashOf) {
|
|
32
|
+
return {
|
|
33
|
+
randomToken: (param, param$1) => ({
|
|
34
|
+
TAG: "Ok",
|
|
35
|
+
_0: token
|
|
36
|
+
}),
|
|
37
|
+
hash: input => ({
|
|
38
|
+
TAG: "Ok",
|
|
39
|
+
_0: hashOf(input)
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
characters,
|
|
46
|
+
unavailable,
|
|
47
|
+
fixed,
|
|
48
|
+
}
|
|
49
|
+
/* No side effect */
|
|
@@ -9,7 +9,6 @@ semantic is a new value rather than a new branch.
|
|
|
9
9
|
The payload is a typed variant because the vocabulary is framework-owned and
|
|
10
10
|
closed — which also keeps `Reference.getTarget` total.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
12
|
/** Which entity a reference field points to. */
|
|
14
13
|
type referenceTarget = {entity: string, plugin: option<string>}
|
|
15
14
|
|
|
@@ -122,18 +121,16 @@ let mark = (schema: S.t<'a>, ~id: string, ~payload: payload=Plain): S.t<'a> =>
|
|
|
122
121
|
|
|
123
122
|
/** A schema that validates with `check` and carries the semantic `id`, derived
|
|
124
123
|
from the constructor so no second grammar can drift from it. */
|
|
125
|
-
// sury's refiner takes a fixed message, so `check`'s per-value reason is lost
|
|
124
|
+
let // sury's refiner takes a fixed message, so `check`'s per-value reason is lost
|
|
126
125
|
// here; call the scalar's own `fromString`/`fromFloat` to report which rule broke.
|
|
127
|
-
|
|
126
|
+
refined = (base: S.t<'a>, ~id: string, ~check: 'a => result<'a, string>): S.t<'a> =>
|
|
128
127
|
base
|
|
129
|
-
->S.refine(
|
|
130
|
-
value
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
~error=`expected a valid ${id}`,
|
|
136
|
-
)
|
|
128
|
+
->S.refine(value =>
|
|
129
|
+
switch check(value) {
|
|
130
|
+
| Ok(_) => true
|
|
131
|
+
| Error(_) => false
|
|
132
|
+
}
|
|
133
|
+
, ~error=`expected a valid ${id}`)
|
|
137
134
|
->mark(~id)
|
|
138
135
|
|
|
139
136
|
/** A value as it should read back to whoever typed it — rejection messages quote
|
|
@@ -50,7 +50,6 @@ wire format, or any stored value changing.
|
|
|
50
50
|
})
|
|
51
51
|
```
|
|
52
52
|
*/
|
|
53
|
-
|
|
54
53
|
/** The ref's representation. Transparent `string` on purpose: the marker refines
|
|
55
54
|
an existing `string` field rather than replacing it, so the field's runtime
|
|
56
55
|
representation — and therefore every stored event — is unchanged. A sealed
|
|
@@ -61,8 +60,7 @@ type t = string
|
|
|
61
60
|
external unsafe: string => t = "%identity"
|
|
62
61
|
external toString: t => string = "%identity"
|
|
63
62
|
|
|
64
|
-
let segmentIsSafe = (segment: string) =>
|
|
65
|
-
segment !== "" && segment !== "." && segment !== ".."
|
|
63
|
+
let segmentIsSafe = (segment: string) => segment !== "" && segment !== "." && segment !== ".."
|
|
66
64
|
|
|
67
65
|
/**
|
|
68
66
|
Validate a raw string as a storage ref, saying why when it is not one.
|
|
@@ -74,7 +72,9 @@ schema validation cannot drift apart.
|
|
|
74
72
|
let fromString = (raw: string): result<t, string> =>
|
|
75
73
|
if !String.startsWith(raw, "/") {
|
|
76
74
|
Error(
|
|
77
|
-
`expected an origin-relative storage ref starting with "/", got ${raw
|
|
75
|
+
`expected an origin-relative storage ref starting with "/", got ${raw
|
|
76
|
+
->JSON.Encode.string
|
|
77
|
+
->JSON.stringify}. External URLs and data: URIs are not storage refs.`,
|
|
78
78
|
)
|
|
79
79
|
} else if String.startsWith(raw, "//") {
|
|
80
80
|
Error(`protocol-relative refs are not storage refs: ${raw}`)
|
|
@@ -94,7 +94,7 @@ The refinement a ref-holding field validates against, carrying no semantic yet.
|
|
|
94
94
|
The `Uploadable*` types re-label this rather than restating the grammar, so a
|
|
95
95
|
re-labelling cannot validate differently from what it re-labels.
|
|
96
96
|
*/
|
|
97
|
-
// The empty string is admitted as the "no object" sentinel. The fields this
|
|
97
|
+
let // The empty string is admitted as the "no object" sentinel. The fields this
|
|
98
98
|
// marks are non-optional today, and a producer with nothing to reference —
|
|
99
99
|
// a supplier feed carrying no image, say — already writes `""` to mean
|
|
100
100
|
// absence. Rejecting it here would break a legitimate existing value and
|
|
@@ -107,16 +107,14 @@ re-labelling cannot validate differently from what it re-labels.
|
|
|
107
107
|
// sury's refiner is a predicate with a fixed message, so the per-value reason
|
|
108
108
|
// `fromString` returns is not threaded through; call `fromString` directly
|
|
109
109
|
// when the caller needs to report which rule the value broke.
|
|
110
|
-
|
|
111
|
-
S.string->S.refine(
|
|
112
|
-
value
|
|
113
|
-
value
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
~error=`expected an origin-relative storage ref: "/" followed by a prefix and an object path, or "" for no object`,
|
|
119
|
-
)
|
|
110
|
+
refinement: S.t<t> =
|
|
111
|
+
S.string->S.refine(value =>
|
|
112
|
+
value === "" ||
|
|
113
|
+
switch fromString(value) {
|
|
114
|
+
| Ok(_) => true
|
|
115
|
+
| Error(_) => false
|
|
116
|
+
}
|
|
117
|
+
, ~error=`expected an origin-relative storage ref: "/" followed by a prefix and an object path, or "" for no object`)
|
|
120
118
|
|
|
121
119
|
/**
|
|
122
120
|
The sury schema for a field holding a ref into a named store.
|
|
@@ -139,8 +137,10 @@ let getStore = (schema: S.t<'a>): option<Semantic.storeTarget> =>
|
|
|
139
137
|
|
|
140
138
|
/** How many refs one field holds. */
|
|
141
139
|
type arity =
|
|
142
|
-
|
|
143
|
-
|
|
|
140
|
+
/** A `string` field: one ref. */
|
|
141
|
+
| Single
|
|
142
|
+
/** An `array<string>` field: zero or more refs. */
|
|
143
|
+
| Multiple
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
146
|
The store a *field* declares, looking through an array wrapper, with the arity
|
|
@@ -6,7 +6,6 @@ Grammar: `{{ path }}`, `{{ path | formatter }}`, `{{# if path }}…{{/ if }}` an
|
|
|
6
6
|
evaluated, an unresolved path renders a visible placeholder rather than throwing,
|
|
7
7
|
and a field the schema marks `@sensitive` is withheld.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
9
|
/** A path into the payload, in **wire** field names. `text` is what was written,
|
|
11
10
|
so a placeholder can name it. */
|
|
12
11
|
type path = {text: string, relative: bool, segments: array<string>}
|
|
@@ -54,8 +53,7 @@ let parsePath = (raw: string, ~insideEach: bool): result<path, string> => {
|
|
|
54
53
|
segments->Array.every(segment => segmentGrammar->RegExp.test(segment))
|
|
55
54
|
? Ok({text, relative, segments})
|
|
56
55
|
: Error(
|
|
57
|
-
`"${text}" is not a path — segments are letters, digits and ` ++
|
|
58
|
-
`underscores, separated by dots`,
|
|
56
|
+
`"${text}" is not a path — segments are letters, digits and ` ++ `underscores, separated by dots`,
|
|
59
57
|
)
|
|
60
58
|
}
|
|
61
59
|
}
|
|
@@ -268,10 +266,10 @@ let isWithheld = (schema: option<S.t<unknown>>): bool =>
|
|
|
268
266
|
| None => false
|
|
269
267
|
| Some(field) =>
|
|
270
268
|
Sensitive.isFieldSensitive(field) ||
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
switch Semantic.getFrom(field) {
|
|
270
|
+
| Some({id}) => Sensitive.impliedBySemantic(id)
|
|
271
|
+
| None => false
|
|
272
|
+
}
|
|
275
273
|
}
|
|
276
274
|
|
|
277
275
|
let parseSafely = (json: JSON.t, schema: S.t<'a>): option<'a> =>
|
package/src/semantic/Url.res
CHANGED
|
@@ -24,7 +24,6 @@ they actually hold and render correctly on their own.
|
|
|
24
24
|
})
|
|
25
25
|
```
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
27
|
/** The address's representation. Transparent `string`; see `Email.t`. */
|
|
29
28
|
type t = string
|
|
30
29
|
|
|
@@ -56,8 +55,9 @@ let fromString = (raw: string): result<t, string> =>
|
|
|
56
55
|
Ok(value)
|
|
57
56
|
} else {
|
|
58
57
|
Error(
|
|
59
|
-
`a URL field takes an http:// or https:// address, got ${Semantic.showString(
|
|
60
|
-
|
|
58
|
+
`a URL field takes an http:// or https:// address, got ${Semantic.showString(
|
|
59
|
+
raw,
|
|
60
|
+
)}. ` ++ `Use Email or Phone for mailto:/tel:, and a storage ref for an uploaded object.`,
|
|
61
61
|
)
|
|
62
62
|
}
|
|
63
63
|
| exception _ =>
|