@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.
Files changed (97) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/package.json +4 -3
  3. package/run-check-lifecycle.mjs +2 -1
  4. package/run-prepare-accounts.mjs +3 -0
  5. package/src/AnsiStyle.res +0 -1
  6. package/src/LogPrefix.res +4 -13
  7. package/src/components/AutomationSlice.res +1 -5
  8. package/src/components/CapabilityManifest.res +6 -3
  9. package/src/components/CapabilityManifest.res.mjs +13 -2
  10. package/src/components/DcbDecode.res +6 -2
  11. package/src/components/DcbScopeInference.res +16 -12
  12. package/src/components/DcbTag.res +74 -46
  13. package/src/components/DcbValidation.res +114 -89
  14. package/src/components/DisplayName.res +4 -2
  15. package/src/components/ExtensionPoint.res +0 -1
  16. package/src/components/FieldDefault.res +2 -4
  17. package/src/components/InboundTranslationSlice.res +0 -2
  18. package/src/components/OutboundTranslationSlice.res +10 -8
  19. package/src/components/Owner.res +4 -4
  20. package/src/components/Plugin.res +10 -4
  21. package/src/components/ReadModel.res +0 -1
  22. package/src/components/Reference.res +2 -8
  23. package/src/components/Sensitive.res +4 -4
  24. package/src/components/StateAnnotations.res +4 -2
  25. package/src/components/StateChangeSlice.res +0 -2
  26. package/src/components/StateViewSlice.res +0 -2
  27. package/src/components/TaggedUnion.res +1 -2
  28. package/src/components/Task.res +0 -1
  29. package/src/components/TraitCertificate.res +2 -2
  30. package/src/components/TraitManifest.res +1 -1
  31. package/src/generator/CertifyTrait.res +2 -4
  32. package/src/generator/Codegen.res +77 -68
  33. package/src/generator/Discovery.res +29 -12
  34. package/src/generator/GraftTrait.res +17 -9
  35. package/src/generator/Pairing.res +39 -13
  36. package/src/generator/PlatformCodegen.res +14 -11
  37. package/src/generator/PlatformCodegen.res.mjs +5 -0
  38. package/src/generator/PlatformManifests.res +1 -3
  39. package/src/generator/PluginGenerator.res +0 -1
  40. package/src/generator/TraitManifestCli.res +7 -7
  41. package/src/lifecycle/CheckLifecycleModel.res +206 -148
  42. package/src/lifecycle/CheckLifecycleModel.res.mjs +23 -5
  43. package/src/semantic/Bytes.res +0 -1
  44. package/src/semantic/CalendarDate.res +2 -2
  45. package/src/semantic/Capabilities.res +21 -6
  46. package/src/semantic/Capabilities.res.mjs +18 -12
  47. package/src/semantic/CapabilityNeed.res +16 -5
  48. package/src/semantic/CapabilityNeed.res.mjs +9 -4
  49. package/src/semantic/CaptionedImage.res +0 -1
  50. package/src/semantic/Color.res +0 -1
  51. package/src/semantic/Currency.res +189 -170
  52. package/src/semantic/DateRange.res +3 -4
  53. package/src/semantic/DateTime.res +5 -5
  54. package/src/semantic/Duration.res +2 -2
  55. package/src/semantic/Email.res +0 -1
  56. package/src/semantic/FileRef.res +2 -2
  57. package/src/semantic/GeoPoint.res +17 -21
  58. package/src/semantic/Geocoding.res +8 -9
  59. package/src/semantic/Geolocation.res +6 -9
  60. package/src/semantic/IdentityProvider.res +127 -0
  61. package/src/semantic/IdentityProvider.res.mjs +65 -0
  62. package/src/semantic/ImageRef.res +2 -2
  63. package/src/semantic/MemberRef.res +0 -1
  64. package/src/semantic/Messaging.res +141 -21
  65. package/src/semantic/Messaging.res.mjs +51 -8
  66. package/src/semantic/Money.res +23 -20
  67. package/src/semantic/Offload.res +6 -4
  68. package/src/semantic/Percent.res +3 -3
  69. package/src/semantic/Phone.res +0 -1
  70. package/src/semantic/RowImage.res +4 -3
  71. package/src/semantic/Secrets.res +76 -0
  72. package/src/semantic/Secrets.res.mjs +49 -0
  73. package/src/semantic/Semantic.res +8 -11
  74. package/src/semantic/StorageRef.res +17 -17
  75. package/src/semantic/Template.res +5 -7
  76. package/src/semantic/UploadableFile.res +0 -1
  77. package/src/semantic/UploadableImage.res +0 -1
  78. package/src/semantic/Url.res +3 -3
  79. package/src/types/AccountsManifest.res +281 -0
  80. package/src/types/AccountsManifest.res.mjs +311 -0
  81. package/src/types/AdminGroup.res +27 -0
  82. package/src/types/AdminGroup.res.mjs +9 -0
  83. package/src/types/Authorization.res +1 -2
  84. package/src/types/Handler.res +0 -1
  85. package/src/types/Identity.res +1 -3
  86. package/src/types/Lifecycle.res +7 -6
  87. package/src/types/Message.res +6 -4
  88. package/src/types/OwnerScope.res +28 -3
  89. package/src/types/OwnerScope.res.mjs +12 -0
  90. package/src/types/PrepareAccounts.res +119 -0
  91. package/src/types/PrepareAccounts.res.mjs +148 -0
  92. package/src/types/Projection.res +9 -2
  93. package/src/types/StoredEvent.res +2 -4
  94. package/src/types/Transition.res +8 -4
  95. package/src/util/Util_Password.res +60 -0
  96. package/src/util/Util_Password.res.mjs +53 -0
  97. 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
- switch (failure.TAG) {
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
- return provider.channels.includes(channelOf(recipient));
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 */
@@ -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(amount)}. There is no such thing as a fraction of the ` ++
83
- `smallest unit — a major amount converts with Money.ofMajor.`,
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
- switch validateAmount(amount) {
102
- | Ok(_) => true
103
- | Error(_) => false
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> = schema->Semantic.mark(~id=Semantic.Id.money)
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(a)}: they are different currencies. ` ++
212
- `Converting between them needs a rate, which is not something an amount carries.`,
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(into)} parts: ` ++
270
- `a split is into at least one part.`,
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
- | _ => Some(amounts->Array.reduce(Ok(zero(~currency=(amounts->Array.getUnsafe(0)).currency)), (
297
- acc,
298
- m,
299
- ) => acc->Result.flatMap(total => add(total, m))))
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
  }
@@ -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
- let schema = (inner: S.t<'a>): S.t<payload<'a>> => {
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(~id=Semantic.Id.offload, ~payload=StoredIn({plugin, store, threshold}))
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
@@ -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(raw)}. ` ++
31
- `This scale is 0–100, not 0–1 — a fraction multiplies by 100 first.`,
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)
@@ -30,7 +30,6 @@ which is the E.164 limit. No spaces, no punctuation, no leading zero after the
30
30
  })
31
31
  ```
32
32
  */
33
-
34
33
  /** The number's representation. Transparent `string`; see `Email.t`. */
35
34
  type t = string
36
35
 
@@ -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
- | /** The field's own value is the ref. */ Scalar
30
- | /** The ref is one member of the field's record. */ Member(string)
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
- let refined = (base: S.t<'a>, ~id: string, ~check: 'a => result<'a, string>): S.t<'a> =>
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
- switch check(value) {
132
- | Ok(_) => true
133
- | Error(_) => false
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->JSON.Encode.string->JSON.stringify}. External URLs and data: URIs are not storage refs.`,
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
- let refinement: S.t<t> =
111
- S.string->S.refine(
112
- value =>
113
- value === "" ||
114
- switch fromString(value) {
115
- | Ok(_) => true
116
- | Error(_) => false
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
- | /** A `string` field: one ref. */ Single
143
- | /** An `array<string>` field: zero or more refs. */ Multiple
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
- switch Semantic.getFrom(field) {
272
- | Some({id}) => Sensitive.impliedBySemantic(id)
273
- | None => false
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> =>
@@ -14,7 +14,6 @@ representation, no media-type restriction on what may be uploaded. See
14
14
  })
15
15
  ```
16
16
  */
17
-
18
17
  type t = string
19
18
 
20
19
  external unsafe: string => t = "%identity"
@@ -31,7 +31,6 @@ does, and only because the store is derived from it.
31
31
  })
32
32
  ```
33
33
  */
34
-
35
34
  /** Transparent `string`, for `StorageRef.t`'s reason: the marker refines an
36
35
  existing field rather than replacing it, so nothing stored changes. */
37
36
  type t = string
@@ -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(raw)}. ` ++
60
- `Use Email or Phone for mailto:/tel:, and a storage ref for an uploaded object.`,
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 _ =>