@reventlessdev/reventless-spec 3.0.0-alpha.117 → 3.0.0-alpha.118

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 (66) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +3 -3
  3. package/src/components/CapabilityManifest.res +1 -1
  4. package/src/components/CapabilityManifest.res.mjs +13 -12
  5. package/src/components/DcbDecode.res +8 -8
  6. package/src/components/DcbDecode.res.mjs +9 -8
  7. package/src/components/DcbTag.res +43 -42
  8. package/src/components/DcbTag.res.mjs +90 -87
  9. package/src/components/DcbValidation.res +6 -6
  10. package/src/components/DcbValidation.res.mjs +6 -6
  11. package/src/components/DisplayName.res.mjs +3 -3
  12. package/src/components/Owner.res +1 -1
  13. package/src/components/Owner.res.mjs +7 -7
  14. package/src/components/Plugin.res +8 -11
  15. package/src/components/Plugin.res.mjs +133 -134
  16. package/src/components/Reference.res.mjs +5 -5
  17. package/src/components/StateAnnotations.res +23 -0
  18. package/src/components/StateAnnotations.res.mjs +3 -3
  19. package/src/generator/PlatformCodegen.res +17 -14
  20. package/src/generator/PlatformCodegen.res.mjs +1 -1
  21. package/src/generator/PlatformGenerator.res +1 -1
  22. package/src/semantic/Bytes.res.mjs +2 -2
  23. package/src/semantic/Color.res.mjs +2 -2
  24. package/src/semantic/Currency.res.mjs +167 -167
  25. package/src/semantic/DateRange.res.mjs +2 -2
  26. package/src/semantic/Duration.res.mjs +2 -2
  27. package/src/semantic/Email.res +2 -2
  28. package/src/semantic/Email.res.mjs +3 -2
  29. package/src/semantic/FileRef.res +28 -0
  30. package/src/semantic/FileRef.res.mjs +17 -0
  31. package/src/semantic/GeoPoint.res +14 -10
  32. package/src/semantic/GeoPoint.res.mjs +10 -17
  33. package/src/semantic/ImageRef.res +51 -0
  34. package/src/semantic/ImageRef.res.mjs +17 -0
  35. package/src/semantic/Media_Ref.res +33 -0
  36. package/src/semantic/Media_Ref.res.mjs +37 -0
  37. package/src/semantic/Money.res +7 -5
  38. package/src/semantic/Money.res.mjs +6 -9
  39. package/src/semantic/Offload.res +29 -43
  40. package/src/semantic/Offload.res.mjs +18 -49
  41. package/src/semantic/Percent.res.mjs +2 -2
  42. package/src/semantic/Phone.res.mjs +2 -2
  43. package/src/semantic/Semantic.res +24 -6
  44. package/src/semantic/Semantic.res.mjs +13 -12
  45. package/src/semantic/StorageRef.res +33 -19
  46. package/src/semantic/StorageRef.res.mjs +14 -14
  47. package/src/semantic/UploadableFile.res +30 -0
  48. package/src/semantic/UploadableFile.res.mjs +23 -0
  49. package/src/semantic/UploadableImage.res +56 -0
  50. package/src/semantic/UploadableImage.res.mjs +23 -0
  51. package/src/semantic/Url.res +8 -2
  52. package/src/semantic/Url.res.mjs +3 -2
  53. package/src/types/Authorization.res.mjs +7 -7
  54. package/src/types/DateTime.res.mjs +2 -2
  55. package/src/types/Id.res.mjs +6 -4
  56. package/src/types/Identity.res +0 -1
  57. package/src/types/Identity.res.mjs +12 -14
  58. package/src/types/Message.res +29 -10
  59. package/src/types/Message.res.mjs +56 -37
  60. package/src/types/ReadConsistency.res.mjs +5 -5
  61. package/src/types/Schedule.res.mjs +37 -37
  62. package/src/types/StoredEvent.res +2 -2
  63. package/src/types/StoredEvent.res.mjs +11 -10
  64. package/src/types/Visibility.res.mjs +4 -4
  65. package/src/util/Util_Sury.res +32 -0
  66. package/src/util/Util_Sury.res.mjs +39 -0
@@ -0,0 +1,37 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Semantic$Reventless from "./Semantic.res.mjs";
4
+
5
+ function hasWebScheme(raw) {
6
+ let lower = raw.toLowerCase();
7
+ if (lower.startsWith("http://")) {
8
+ return true;
9
+ } else {
10
+ return lower.startsWith("https://");
11
+ }
12
+ }
13
+
14
+ function check(what, raw) {
15
+ if (raw === "") {
16
+ return {
17
+ TAG: "Error",
18
+ _0: `expected a ` + what + ` reference, got an empty string`
19
+ };
20
+ } else if (hasWebScheme(raw) || raw.startsWith("/") && !raw.startsWith("//")) {
21
+ return {
22
+ TAG: "Ok",
23
+ _0: raw
24
+ };
25
+ } else {
26
+ return {
27
+ TAG: "Error",
28
+ _0: `expected an http:// or https:// address, or an origin-relative path, got ` + Semantic$Reventless.showString(raw) + `. A data: URI is not a ` + what + ` reference — it inlines the bytes into the event log, permanently.`
29
+ };
30
+ }
31
+ }
32
+
33
+ export {
34
+ hasWebScheme,
35
+ check,
36
+ }
37
+ /* Semantic-Reventless Not a pure module */
@@ -79,11 +79,13 @@ let validateAmount = (amount: float): result<float, string> =>
79
79
  `Cannot access 'v0' before initialization`). Refining the field is both the
80
80
  honest placement and the one that works. */
81
81
  let amountSchema: S.t<float> =
82
- S.float->S.refine(s => amount =>
83
- switch validateAmount(amount) {
84
- | Ok(_) => ()
85
- | Error(why) => s.fail(why)
86
- }
82
+ S.float->S.refine(
83
+ amount =>
84
+ switch validateAmount(amount) {
85
+ | Ok(_) => true
86
+ | Error(_) => false
87
+ },
88
+ ~error="expected a monetary amount",
87
89
  )
88
90
 
89
91
  @schema
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
+ import * as Sury from "sury";
4
5
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
6
  import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
6
7
  import * as Currency$Reventless from "./Currency.res.mjs";
@@ -27,16 +28,12 @@ function validateAmount(amount) {
27
28
  }
28
29
  }
29
30
 
30
- let amountSchema = S.refine(S.float, s => (amount => {
31
- let why = validateAmount(amount);
32
- if (why.TAG === "Ok") {
33
- return;
34
- } else {
35
- return s.fail(why._0, undefined);
36
- }
37
- }));
31
+ let amountSchema = S.refine(Sury.float, amount => {
32
+ let match = validateAmount(amount);
33
+ return match.TAG === "Ok";
34
+ }, "expected a monetary amount", undefined);
38
35
 
39
- let schema = S.schema(s => ({
36
+ let schema = Sury.$schema(s => ({
40
37
  amount: s.m(amountSchema),
41
38
  currency: s.m(Currency$Reventless.schema)
42
39
  }));
@@ -63,17 +63,6 @@ type payload<'a> =
63
63
  | Inline('a)
64
64
  | Offloaded(offloadedRef)
65
65
 
66
- /** `nullableAsOption` emits `T | undefined | null`, which fails
67
- `jsonableValidation` inside an event union; `js_nullable` emits `T | null`,
68
- which is JSON-safe there. Same reason `Plugin.res` reaches for it. */
69
- @module("sury/src/Sury.res.mjs")
70
- external _jsNullable: (S.t<'a>, unit) => S.t<option<'a>> = "js_nullable"
71
-
72
- /** The codec builds on `S.json`, which sury 11 gates behind an explicit enable.
73
- Doing it here (at module load, before any `schema` call) makes the primitive
74
- self-contained: importing `Offload` is enough, no consumer has to remember. */
75
- S.enableJson()
76
-
77
66
  /** The object key under which an `Offloaded` value hides. Reserved: no ReScript
78
67
  record field encodes to a key starting with `$`, so it cannot collide with an
79
68
  inline payload's own fields. */
@@ -86,37 +75,30 @@ Parameterised by the inner value's schema because the `Inline` arm round-trips
86
75
  through it. The `Offloaded` arm round-trips through `offloadedRefSchema` under the
87
76
  sentinel key. See the module doc for why this is untagged.
88
77
  */
78
+ // Both arms are *declared* (`S.object` / `S.shape`) rather than hand-written as
79
+ // `S.transform` pairs, and that is load-bearing, not a style choice. A transform
80
+ // arm is opaque: sury cannot see what it accepts, so it must offer every value to
81
+ // the arm's own serializer and let it reject. Two things follow, both of which bit
82
+ // this codec on 11.0.0-rc.0 and neither of which the type checker can see:
83
+ //
84
+ // - an absent optional value reaches the arms as a raw `undefined`, and the first
85
+ // serializer to read its payload dereferences it — an uncatchable `TypeError`;
86
+ // - a json-typed arm cannot chain into a non-JSON target, so encoding an
87
+ // `Offloaded` value to `S.jsonString` fails where encoding it to `S.json` works.
88
+ //
89
+ // A declared arm has neither problem: sury derives both directions from the shape,
90
+ // discriminates on it, and never runs user code to find out. One transform arm is
91
+ // enough to bring both failures back, so keep every arm declarative.
89
92
  let schema = (inner: S.t<'a>): S.t<payload<'a>> => {
90
- // Offloaded arm: recognised by the reserved sentinel key, strict, and tried
91
- // first so an offloaded value is never mistaken for an inline one. The ref is
92
- // a fixed new shape that needs no healing, so a direct json-transform is fine.
93
- let offloadedArm = S.json->S.transform(s => {
94
- parser: json =>
95
- switch json->JSON.Decode.object->Option.flatMap(dict => dict->Dict.get(sentinelKey)) {
96
- | Some(refJson) => Offloaded(refJson->S.parseJsonOrThrow(offloadedRefSchema))
97
- | None => s.fail("not an offloaded reference")
98
- },
99
- serializer: payload =>
100
- switch payload {
101
- | Offloaded(ref) =>
102
- Dict.fromArray([(sentinelKey, ref->S.reverseConvertToJsonOrThrow(offloadedRefSchema))])
103
- ->JSON.Encode.object
104
- | Inline(_) => s.fail("not an offloaded reference")
105
- },
106
- })
93
+ // Offloaded arm: an object carrying the reserved sentinel key, tried first so an
94
+ // offloaded value is never mistaken for an inline one.
95
+ let offloadedArm = S.object(s => Offloaded(s.field(sentinelKey, offloadedRefSchema)))
107
96
  // Inline arm: the inner schema applied through sury's own pipeline, so it
108
97
  // inherits whatever tolerance the surrounding decode uses — the lifecycle
109
98
  // Message decoder heals older payloads with missing fields. This is why the
110
99
  // codec is a union rather than one json-transform with a nested
111
100
  // parseJsonOrThrow: a nested parse runs strict and breaks the frozen corpus.
112
- let inlineArm = inner->S.transform(s => {
113
- parser: value => Inline(value),
114
- serializer: payload =>
115
- switch payload {
116
- | Inline(value) => value
117
- | Offloaded(_) => s.fail("not an inline value")
118
- },
119
- })
101
+ let inlineArm = inner->S.shape(value => Inline(value))
120
102
  S.union([offloadedArm, inlineArm])
121
103
  }
122
104
 
@@ -136,10 +118,14 @@ let forStore = (
136
118
  schema(inner)->Semantic.mark(~id=Semantic.Id.offload, ~payload=StoredIn({plugin, store, threshold}))
137
119
 
138
120
  /**
139
- The codec wrapped for an **optional** field (`js_nullable`), plus the `StoredIn`
140
- marker. This is the common case: offloadable fields are usually optional (absent
141
- for older protocol versions, say). The marker sits on the outer schema, where
121
+ The codec wrapped for an **optional** field, plus the `StoredIn` marker. This is
122
+ the common case: offloadable fields are usually optional (absent for older
123
+ protocol versions, say). The marker sits on the outer schema, where
142
124
  `Semantic.get` reads it first.
125
+
126
+ `nullAsOption` keeps the wire form `T | null` — byte-identical to every stored
127
+ message — and makes the union advertise `has.null`, which is what
128
+ `Message.fillMissingDefaults` reads to heal an absent field to `None` on replay.
143
129
  */
144
130
  let optionSchema = (
145
131
  ~plugin: option<string>=?,
@@ -147,7 +133,7 @@ let optionSchema = (
147
133
  ~threshold: option<int>=?,
148
134
  inner: S.t<'a>,
149
135
  ): S.t<option<payload<'a>>> =>
150
- _jsNullable(schema(inner), ())->Semantic.mark(
136
+ S.nullAsOption(schema(inner))->Semantic.mark(
151
137
  ~id=Semantic.Id.offload,
152
138
  ~payload=StoredIn({plugin, store, threshold}),
153
139
  )
@@ -158,7 +144,7 @@ let optionSchema = (
158
144
  e.g. the plugin read model, whose DynamoDB write path marshals the raw value
159
145
  and would otherwise persist the variant's runtime `{TAG, _0}` shape. */
160
146
  let toJson = (inner: S.t<'a>, payload: payload<'a>): JSON.t =>
161
- payload->S.reverseConvertToJsonOrThrow(schema(inner))
147
+ payload->Util_Sury.toJson(schema(inner))
162
148
 
163
149
  /** The inline value, if this payload is `Inline`. `None` for `Offloaded` — a
164
150
  caller that must handle both arms uses `resolve` (async, fetches the ref); a
@@ -202,7 +188,7 @@ let prepare = (
202
188
  ~hash: string => string,
203
189
  ~upload: (~key: string, ~bytes: string) => promise<unit>,
204
190
  ): promise<payload<'a>> => {
205
- let bytes = value->S.reverseConvertToJsonStringOrThrow(schema)
191
+ let bytes = value->Util_Sury.toJsonString(schema)
206
192
  if bytes->String.length < threshold {
207
193
  Promise.resolve(Inline(value))
208
194
  } else {
@@ -227,7 +213,7 @@ let resolve = (payload: payload<'a>, ~schema: S.t<'a>, ~fetch: string => promise
227
213
  switch payload {
228
214
  | Inline(value) => Promise.resolve(value)
229
215
  | Offloaded({key}) =>
230
- fetch(key)->Promise.then(bytes => Promise.resolve(bytes->S.parseJsonStringOrThrow(schema)))
216
+ fetch(key)->Promise.then(bytes => Promise.resolve(bytes->Util_Sury.fromJsonString(schema)))
231
217
  }
232
218
 
233
219
  /**
@@ -1,61 +1,30 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
4
- import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
3
+ import * as Sury from "sury";
5
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
5
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
7
6
  import * as Semantic$Reventless from "./Semantic.res.mjs";
8
- import * as SuryResMjs from "sury/src/Sury.res.mjs";
7
+ import * as Util_Sury$Reventless from "../util/Util_Sury.res.mjs";
9
8
 
10
- let offloadedRefSchema = S.schema(s => ({
11
- store: s.m(S.string),
12
- key: s.m(S.string),
13
- hash: s.m(S.string),
14
- bytes: s.m(S.int)
9
+ let offloadedRefSchema = Sury.$schema(s => ({
10
+ store: s.m(Sury.string),
11
+ key: s.m(Sury.string),
12
+ hash: s.m(Sury.string),
13
+ bytes: s.m(Sury.int)
15
14
  }));
16
15
 
17
- S.enableJson();
18
-
19
16
  let sentinelKey = "$offload";
20
17
 
21
18
  function schema(inner) {
22
- let offloadedArm = S.transform(S.json, s => ({
23
- p: json => {
24
- let refJson = Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), dict => dict[sentinelKey]);
25
- if (refJson !== undefined) {
26
- return {
27
- TAG: "Offloaded",
28
- _0: S.parseJsonOrThrow(refJson, offloadedRefSchema)
29
- };
30
- } else {
31
- return s.fail("not an offloaded reference", undefined);
32
- }
33
- },
34
- s: payload => {
35
- if (payload.TAG === "Inline") {
36
- return s.fail("not an offloaded reference", undefined);
37
- } else {
38
- return Object.fromEntries([[
39
- sentinelKey,
40
- S.reverseConvertToJsonOrThrow(payload._0, offloadedRefSchema)
41
- ]]);
42
- }
43
- }
19
+ let offloadedArm = Sury.object(s => ({
20
+ TAG: "Offloaded",
21
+ _0: s.f(sentinelKey, offloadedRefSchema)
44
22
  }));
45
- let inlineArm = S.transform(inner, s => ({
46
- p: value => ({
47
- TAG: "Inline",
48
- _0: value
49
- }),
50
- s: payload => {
51
- if (payload.TAG === "Inline") {
52
- return payload._0;
53
- } else {
54
- return s.fail("not an inline value", undefined);
55
- }
56
- }
23
+ let inlineArm = Sury.shape(inner, value => ({
24
+ TAG: "Inline",
25
+ _0: value
57
26
  }));
58
- return S.union([
27
+ return Sury.union([
59
28
  offloadedArm,
60
29
  inlineArm
61
30
  ]);
@@ -73,7 +42,7 @@ function forStore(plugin, store, threshold, inner) {
73
42
  }
74
43
 
75
44
  function optionSchema(plugin, store, threshold, inner) {
76
- return Semantic$Reventless.mark(SuryResMjs.js_nullable(schema(inner)), Semantic$Reventless.Id.offload, {
45
+ return Semantic$Reventless.mark(Sury.$nullAsOption(schema(inner)), Semantic$Reventless.Id.offload, {
77
46
  TAG: "StoredIn",
78
47
  _0: {
79
48
  plugin: plugin,
@@ -84,7 +53,7 @@ function optionSchema(plugin, store, threshold, inner) {
84
53
  }
85
54
 
86
55
  function toJson(inner, payload) {
87
- return S.reverseConvertToJsonOrThrow(payload, schema(inner));
56
+ return Util_Sury$Reventless.toJson(payload, schema(inner));
88
57
  }
89
58
 
90
59
  function getInline(payload) {
@@ -94,7 +63,7 @@ function getInline(payload) {
94
63
  }
95
64
 
96
65
  function prepare(value, schema, store, threshold, hash, upload) {
97
- let bytes = S.reverseConvertToJsonStringOrThrow(value, schema, undefined);
66
+ let bytes = Util_Sury$Reventless.toJsonString(value, schema, undefined);
98
67
  if (bytes.length < threshold) {
99
68
  return Promise.resolve({
100
69
  TAG: "Inline",
@@ -118,7 +87,7 @@ function resolve(payload, schema, fetch) {
118
87
  if (payload.TAG === "Inline") {
119
88
  return Promise.resolve(payload._0);
120
89
  } else {
121
- return fetch(payload._0.key).then(bytes => Promise.resolve(S.parseJsonStringOrThrow(bytes, schema)));
90
+ return fetch(payload._0.key).then(bytes => Promise.resolve(Util_Sury$Reventless.fromJsonString(bytes, schema)));
122
91
  }
123
92
  }
124
93
 
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
3
+ import * as Sury from "sury";
4
4
  import * as Semantic$Reventless from "./Semantic.res.mjs";
5
5
 
6
6
  function fromFloat(raw) {
@@ -24,7 +24,7 @@ function fromFloat(raw) {
24
24
  }
25
25
  }
26
26
 
27
- let schema = Semantic$Reventless.refined(S.float, Semantic$Reventless.Id.percent, fromFloat);
27
+ let schema = Semantic$Reventless.refined(Sury.float, Semantic$Reventless.Id.percent, fromFloat);
28
28
 
29
29
  export {
30
30
  fromFloat,
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
3
+ import * as Sury from "sury";
4
4
  import * as Semantic$Reventless from "./Semantic.res.mjs";
5
5
 
6
6
  let e164 = /^\+[1-9]\d{0,14}$/;
@@ -19,7 +19,7 @@ function fromString(raw) {
19
19
  }
20
20
  }
21
21
 
22
- let schema = Semantic$Reventless.refined(S.string, Semantic$Reventless.Id.phone, fromString);
22
+ let schema = Semantic$Reventless.refined(Sury.string, Semantic$Reventless.Id.phone, fromString);
23
23
 
24
24
  export {
25
25
  e164,
@@ -57,6 +57,18 @@ module Id = {
57
57
  // value rather than an always-a-ref path string.
58
58
  let offload = "offload"
59
59
 
60
+ // The uploadable family: the same `StoredIn` declaration as `storageRef`,
61
+ // plus a statement about what the value *is*, so one declaration picks both a
62
+ // renderer and an upload endpoint. The store is derived from the field name by
63
+ // the ppx rather than written on the type.
64
+ let uploadableImage = "uploadableImage"
65
+ let uploadableFile = "uploadableFile"
66
+
67
+ // The same content facts with no store attached, for values the platform
68
+ // reads but does not own. Nothing is provisioned for these.
69
+ let imageRef = "imageRef"
70
+ let fileRef = "fileRef"
71
+
60
72
  // The branded scalars. Each refines a `string` or a number without changing
61
73
  // its shape, so a field gains one of these without anything stored changing.
62
74
  let email = "email"
@@ -102,13 +114,19 @@ hand-rolling a second check beside it. Deriving it here makes that structural:
102
114
  there is one place a grammar can be written, so there is nowhere for a second
103
115
  one to drift.
104
116
  */
117
+ // sury's refiner is a predicate with a fixed message, so the per-value reason
118
+ // `check` returns is not threaded into the schema error; call the scalar's own
119
+ // `fromString`/`fromFloat` directly when the caller needs to report which rule
120
+ // the value broke.
105
121
  let refined = (base: S.t<'a>, ~id: string, ~check: 'a => result<'a, string>): S.t<'a> =>
106
122
  base
107
- ->S.refine(s => value =>
108
- switch check(value) {
109
- | Ok(_) => ()
110
- | Error(why) => s.fail(why)
111
- }
123
+ ->S.refine(
124
+ value =>
125
+ switch check(value) {
126
+ | Ok(_) => true
127
+ | Error(_) => false
128
+ },
129
+ ~error=`expected a valid ${id}`,
112
130
  )
113
131
  ->mark(~id)
114
132
 
@@ -131,7 +149,7 @@ that could stand for the whole.
131
149
  */
132
150
  let unwrapOptional = (schema: S.t<unknown>): option<S.t<unknown>> =>
133
151
  switch schema {
134
- | Union({anyOf}) =>
152
+ | AnyOf({anyOf}) =>
135
153
  switch anyOf->Array.filter(v =>
136
154
  switch v {
137
155
  | Null(_) | Undefined(_) => false
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
+ import * as Sury from "sury";
4
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
6
 
6
7
  let Id = {
@@ -8,6 +9,10 @@ let Id = {
8
9
  reference: "reference",
9
10
  storageRef: "storageRef",
10
11
  offload: "offload",
12
+ uploadableImage: "uploadableImage",
13
+ uploadableFile: "uploadableFile",
14
+ imageRef: "imageRef",
15
+ fileRef: "fileRef",
11
16
  email: "email",
12
17
  phone: "phone",
13
18
  url: "url",
@@ -20,25 +25,21 @@ let Id = {
20
25
  geoPoint: "geoPoint"
21
26
  };
22
27
 
23
- let semanticId = S.Metadata.Id.make("reventless", "semantic");
28
+ let semanticId = Sury.$Metadata_Id_make("reventless", "semantic");
24
29
 
25
30
  function mark(schema, id, payloadOpt) {
26
31
  let payload = payloadOpt !== undefined ? payloadOpt : "Plain";
27
- return S.Metadata.set(schema, semanticId, {
32
+ return Sury.$Metadata_set(schema, semanticId, {
28
33
  id: id,
29
34
  payload: payload
30
35
  });
31
36
  }
32
37
 
33
38
  function refined(base, id, check) {
34
- return mark(S.refine(base, s => (value => {
35
- let why = check(value);
36
- if (why.TAG === "Ok") {
37
- return;
38
- } else {
39
- return s.fail(why._0, undefined);
40
- }
41
- })), id, undefined);
39
+ return mark(S.refine(base, value => {
40
+ let match = check(value);
41
+ return match.TAG === "Ok";
42
+ }, `expected a valid ` + id, undefined), id, undefined);
42
43
  }
43
44
 
44
45
  function showString(raw) {
@@ -46,7 +47,7 @@ function showString(raw) {
46
47
  }
47
48
 
48
49
  function unwrapOptional(schema) {
49
- if (schema.type !== "union") {
50
+ if (schema.type !== "anyOf") {
50
51
  return;
51
52
  }
52
53
  let match = schema.anyOf.filter(v => {
@@ -66,7 +67,7 @@ function unwrapOptional(schema) {
66
67
  }
67
68
 
68
69
  function getFrom(schema) {
69
- let found = S.Metadata.get(schema, semanticId);
70
+ let found = Sury.$Metadata_get(schema, semanticId);
70
71
  if (found !== undefined) {
71
72
  return found;
72
73
  } else {
@@ -89,6 +89,35 @@ let fromString = (raw: string): result<t, string> =>
89
89
  }
90
90
  }
91
91
 
92
+ /**
93
+ The refinement a ref-holding field validates against, carrying no semantic yet.
94
+ The `Uploadable*` types re-label this rather than restating the grammar, so a
95
+ re-labelling cannot validate differently from what it re-labels.
96
+ */
97
+ // The empty string is admitted as the "no object" sentinel. The fields this
98
+ // marks are non-optional today, and a producer with nothing to reference —
99
+ // a supplier feed carrying no image, say — already writes `""` to mean
100
+ // absence. Rejecting it here would break a legitimate existing value and
101
+ // force an event-schema change, which this marker exists to avoid: it
102
+ // refines an existing `string` field without altering what is stored.
103
+ //
104
+ // Note this is a strictly weaker guarantee than `fromString`, which stays
105
+ // exact. Making these fields properly optional would let the sentinel go.
106
+ //
107
+ // sury's refiner is a predicate with a fixed message, so the per-value reason
108
+ // `fromString` returns is not threaded through; call `fromString` directly
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
+ )
120
+
92
121
  /**
93
122
  The sury schema for a field holding a ref into a named store.
94
123
 
@@ -96,25 +125,10 @@ Prefer the `@storageRef("<store>")` ppx shorthand over writing this by hand.
96
125
  Qualify the store as `"<plugin>.<store>"` to point at another plugin's store.
97
126
  */
98
127
  let forStore = (~plugin: option<string>=?, ~store: string): S.t<t> =>
99
- S.string
100
- ->S.refine(s => value =>
101
- // The empty string is admitted as the "no object" sentinel. The fields this
102
- // marks are non-optional today, and a producer with nothing to reference —
103
- // a supplier feed carrying no image, say — already writes `""` to mean
104
- // absence. Rejecting it here would break a legitimate existing value and
105
- // force an event-schema change, which this marker exists to avoid: it
106
- // refines an existing `string` field without altering what is stored.
107
- //
108
- // Note this is a strictly weaker guarantee than `fromString`, which stays
109
- // exact. Making these fields properly optional would let the sentinel go.
110
- if value !== "" {
111
- switch fromString(value) {
112
- | Ok(_) => ()
113
- | Error(why) => s.fail(why)
114
- }
115
- }
128
+ refinement->Semantic.mark(
129
+ ~id=Semantic.Id.storageRef,
130
+ ~payload=StoredIn({plugin, store, threshold: None}),
116
131
  )
117
- ->Semantic.mark(~id=Semantic.Id.storageRef, ~payload=StoredIn({plugin, store, threshold: None}))
118
132
 
119
133
  /** The store a field's schema declares its refs live in, if any. */
120
134
  let getStore = (schema: S.t<'a>): option<Semantic.storeTarget> =>
@@ -152,7 +166,7 @@ let getFieldStore = (schema: S.t<'a>): option<(Semantic.storeTarget, arity)> =>
152
166
  // and a tuple's positional schemas in `items`; read both so the marker is
153
167
  // found wherever the element sits.
154
168
  switch items->Array.get(0) {
155
- | Some({schema: itemSchema}) => getStore(itemSchema)
169
+ | Some(itemSchema) => getStore(itemSchema)
156
170
  | None =>
157
171
  switch additionalItems {
158
172
  | Schema(itemSchema) => getStore(itemSchema)
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
+ import * as Sury from "sury";
4
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
6
  import * as Semantic$Reventless from "./Semantic.res.mjs";
6
7
 
@@ -44,18 +45,16 @@ function fromString(raw) {
44
45
  }
45
46
  }
46
47
 
48
+ let refinement = S.refine(Sury.string, value => {
49
+ if (value === "") {
50
+ return true;
51
+ }
52
+ let match = fromString(value);
53
+ return match.TAG === "Ok";
54
+ }, `expected an origin-relative storage ref: "/" followed by a prefix and an object path, or "" for no object`, undefined);
55
+
47
56
  function forStore(plugin, store) {
48
- return Semantic$Reventless.mark(S.refine(S.string, s => (value => {
49
- if (value === "") {
50
- return;
51
- }
52
- let why = fromString(value);
53
- if (why.TAG === "Ok") {
54
- return;
55
- } else {
56
- return s.fail(why._0, undefined);
57
- }
58
- })), Semantic$Reventless.Id.storageRef, {
57
+ return Semantic$Reventless.mark(refinement, Semantic$Reventless.Id.storageRef, {
59
58
  TAG: "StoredIn",
60
59
  _0: {
61
60
  plugin: plugin,
@@ -90,9 +89,9 @@ function getFieldStore(schema) {
90
89
  return;
91
90
  }
92
91
  let additionalItems = schema.additionalItems;
93
- let match = schema.items[0];
92
+ let itemSchema = schema.items[0];
94
93
  let tmp;
95
- tmp = match !== undefined ? getStore(match.schema) : (
94
+ tmp = itemSchema !== undefined ? getStore(itemSchema) : (
96
95
  additionalItems === "strip" || additionalItems === "strict" ? undefined : getStore(additionalItems)
97
96
  );
98
97
  return Stdlib_Option.map(tmp, target => [
@@ -104,8 +103,9 @@ function getFieldStore(schema) {
104
103
  export {
105
104
  segmentIsSafe,
106
105
  fromString,
106
+ refinement,
107
107
  forStore,
108
108
  getStore,
109
109
  getFieldStore,
110
110
  }
111
- /* S Not a pure module */
111
+ /* refinement Not a pure module */
@@ -0,0 +1,30 @@
1
+ /**
2
+ A file the platform stores, referenced by the path its store minted.
3
+
4
+ The non-image arm of the uploadable family — same store derivation, same stored
5
+ representation, no media-type restriction on what may be uploaded. See
6
+ {!UploadableImage} for why the family exists and what the derived store costs.
7
+
8
+ @example
9
+ ```rescript
10
+ @schema type command =
11
+ | AddSupplierDatasheet({
12
+ supplierId: @s.matches(DcbTag.string) string,
13
+ datasheet: UploadableFile.t,
14
+ })
15
+ ```
16
+ */
17
+
18
+ type t = string
19
+
20
+ external unsafe: string => t = "%identity"
21
+ external toString: t => string = "%identity"
22
+
23
+ let fromString = (raw: string): result<t, string> => StorageRef.fromString(raw)
24
+
25
+ /** The schema for a field of this type, bound to the store the ppx derived. */
26
+ let forField = (~plugin: option<string>=?, ~store: string): S.t<t> =>
27
+ StorageRef.refinement->Semantic.mark(
28
+ ~id=Semantic.Id.uploadableFile,
29
+ ~payload=StoredIn({plugin, store, threshold: None}),
30
+ )
@@ -0,0 +1,23 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Semantic$Reventless from "./Semantic.res.mjs";
4
+ import * as StorageRef$Reventless from "./StorageRef.res.mjs";
5
+
6
+ let fromString = StorageRef$Reventless.fromString;
7
+
8
+ function forField(plugin, store) {
9
+ return Semantic$Reventless.mark(StorageRef$Reventless.refinement, Semantic$Reventless.Id.uploadableFile, {
10
+ TAG: "StoredIn",
11
+ _0: {
12
+ plugin: plugin,
13
+ store: store,
14
+ threshold: undefined
15
+ }
16
+ });
17
+ }
18
+
19
+ export {
20
+ fromString,
21
+ forField,
22
+ }
23
+ /* Semantic-Reventless Not a pure module */