@reventlessdev/reventless-spec 3.0.0-alpha.141 → 3.0.0-alpha.142

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 CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.142 (2026-09-22)
7
+
8
+ ### Features
9
+
10
+ * **spec:** every semantic type says how to write a value ([b661296](https://github.com/ReventlessDev/reventless-core/commit/b661296c1b7c94109e2187efdf48f5643c507f28))
11
+
12
+
6
13
  # 3.0.0-alpha.141 (2026-09-21)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.141",
3
+ "version": "3.0.0-alpha.142",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -133,6 +133,11 @@ type brandedString = {
133
133
  derive. Offering one as a plain type pick emits code that does not
134
134
  compile. */
135
135
  hasDerivableSchema: bool,
136
+ /** A value the module accepts, as the string itself (not as source): a tool
137
+ writes it as a string literal. An obvious example, never plausible data,
138
+ because a new scenario starts from it. For the modules without a derivable
139
+ schema it is a value their schema accepts for any store or collection. */
140
+ sample: string,
136
141
  }
137
142
 
138
143
  /** Every transparent-string semantic, so a consumer can ask what they are
@@ -149,18 +154,156 @@ type brandedString = {
149
154
  recomputes them rather than trusting this list — so adding a module without
150
155
  adding it here fails, and so does the ppx's copy drifting from either. */
151
156
  let brandedStrings: array<brandedString> = [
152
- {moduleName: "DateTime", id: Id.dateTime, hasDerivableSchema: true},
153
- {moduleName: "CalendarDate", id: Id.date, hasDerivableSchema: true},
154
- {moduleName: "Email", id: Id.email, hasDerivableSchema: true},
155
- {moduleName: "Phone", id: Id.phone, hasDerivableSchema: true},
156
- {moduleName: "Url", id: Id.url, hasDerivableSchema: true},
157
- {moduleName: "Color", id: Id.color, hasDerivableSchema: true},
158
- {moduleName: "FileRef", id: Id.fileRef, hasDerivableSchema: true},
159
- {moduleName: "ImageRef", id: Id.imageRef, hasDerivableSchema: true},
160
- {moduleName: "MemberRef", id: Id.memberRef, hasDerivableSchema: false},
161
- {moduleName: "StorageRef", id: Id.storageRef, hasDerivableSchema: false},
162
- {moduleName: "UploadableFile", id: Id.uploadableFile, hasDerivableSchema: false},
163
- {moduleName: "UploadableImage", id: Id.uploadableImage, hasDerivableSchema: false},
157
+ {
158
+ moduleName: "DateTime",
159
+ id: Id.dateTime,
160
+ hasDerivableSchema: true,
161
+ sample: "2024-01-01T00:00:00Z",
162
+ },
163
+ {moduleName: "CalendarDate", id: Id.date, hasDerivableSchema: true, sample: "2024-01-01"},
164
+ {moduleName: "Email", id: Id.email, hasDerivableSchema: true, sample: "ada@example.com"},
165
+ // 555-0100…0199 is the North American range reserved for fiction.
166
+ {moduleName: "Phone", id: Id.phone, hasDerivableSchema: true, sample: "+15555550100"},
167
+ {moduleName: "Url", id: Id.url, hasDerivableSchema: true, sample: "https://example.com"},
168
+ {moduleName: "Color", id: Id.color, hasDerivableSchema: true, sample: "#1e90ff"},
169
+ {
170
+ moduleName: "FileRef",
171
+ id: Id.fileRef,
172
+ hasDerivableSchema: true,
173
+ sample: "https://example.com/example.pdf",
174
+ },
175
+ {
176
+ moduleName: "ImageRef",
177
+ id: Id.imageRef,
178
+ hasDerivableSchema: true,
179
+ sample: "https://example.com/example.png",
180
+ },
181
+ {
182
+ moduleName: "MemberRef",
183
+ id: Id.memberRef,
184
+ hasDerivableSchema: false,
185
+ sample: "/example/example.png",
186
+ },
187
+ {
188
+ moduleName: "StorageRef",
189
+ id: Id.storageRef,
190
+ hasDerivableSchema: false,
191
+ sample: "/example/example.txt",
192
+ },
193
+ {
194
+ moduleName: "UploadableFile",
195
+ id: Id.uploadableFile,
196
+ hasDerivableSchema: false,
197
+ sample: "/example/example.pdf",
198
+ },
199
+ {
200
+ moduleName: "UploadableImage",
201
+ id: Id.uploadableImage,
202
+ hasDerivableSchema: false,
203
+ sample: "/example/example.png",
204
+ },
205
+ ]
206
+
207
+ /** How a value of a `valueType` is put together, so a tool can offer an input
208
+ that fits it. */
209
+ type valueShape =
210
+ /** A number literal. `integer` says whether it is written `3600` or `50.0` —
211
+ a float field does not accept an int literal. */
212
+ | Number({integer: bool})
213
+ /** One of a closed set of codes, each written as the module's constructor. */
214
+ | Codes({codes: array<string>})
215
+ /** A value built from named parts: `(part, module or primitive)`. A module
216
+ names another `semantic/` module (its `sample` fills the part); a primitive
217
+ is `float`, `int` or `string`. */
218
+ | Parts({parts: array<(string, string)>})
219
+
220
+ /** A semantic whose value is not a bare string: how to write one. */
221
+ type valueType = {
222
+ moduleName: string,
223
+ shape: valueShape,
224
+ /** A value, written as ReScript source, fully qualified: what a tool emits.
225
+ An obvious example, never plausible data. */
226
+ sample: string,
227
+ /** How to write one. `$part` is replaced by that part's source: the module's
228
+ constructor where it returns `t` (`Money.make`), a record literal where the
229
+ constructor returns `result`, since a test value should not need
230
+ unwrapping. A `Number` has the one part `$value`, a `Codes` the one part
231
+ `$code`. */
232
+ writer: string,
233
+ }
234
+
235
+ /** Every semantic whose `type t` is not a bare string and which types a field a
236
+ scenario fills. The type names cannot say how to write one — `Money.t` is a
237
+ record, `Duration.t` an int — so a tool reads it here instead of guessing.
238
+
239
+ `SemanticValueTypesTest` compiles every sample, runs it through its module's
240
+ schema, and checks `Currency`'s codes against `Currency.all`. */
241
+ let valueTypes: array<valueType> = [
242
+ {
243
+ moduleName: "Money",
244
+ shape: Parts({parts: [("amount", "float"), ("currency", "Currency")]}),
245
+ // Whole minor units: 1000 is 10.00 EUR.
246
+ sample: "Reventless.Money.make(~amount=1000.0, ~currency=Reventless.Currency.EUR)",
247
+ writer: "Reventless.Money.make(~amount=$amount, ~currency=$currency)",
248
+ },
249
+ {
250
+ moduleName: "Currency",
251
+ shape: Codes({codes: Currency.all->Array.map(Currency.toString)}),
252
+ sample: "Reventless.Currency.EUR",
253
+ writer: "Reventless.Currency.$code",
254
+ },
255
+ {
256
+ moduleName: "DateRange",
257
+ shape: Parts({parts: [("start", "DateTime"), ("end_", "DateTime")]}),
258
+ sample: `{Reventless.DateRange.start: "2024-01-01T00:00:00Z", end_: "2024-01-02T00:00:00Z"}`,
259
+ writer: "{Reventless.DateRange.start: $start, end_: $end_}",
260
+ },
261
+ {
262
+ moduleName: "GeoPoint",
263
+ shape: Parts({parts: [("lat", "float"), ("lng", "float")]}),
264
+ sample: "{Reventless.GeoPoint.lat: 0.0, lng: 0.0}",
265
+ writer: "{Reventless.GeoPoint.lat: $lat, lng: $lng}",
266
+ },
267
+ {
268
+ moduleName: "CaptionedImage",
269
+ // `altText` and `caption` are optional fields; the writer gives both.
270
+ shape: Parts({
271
+ parts: [("ref", "UploadableImage"), ("altText", "string"), ("caption", "string")],
272
+ }),
273
+ sample: `{Reventless.CaptionedImage.ref: "/example/example.png", altText: "An example image", caption: "An example caption"}`,
274
+ writer: "{Reventless.CaptionedImage.ref: $ref, altText: $altText, caption: $caption}",
275
+ },
276
+ {
277
+ moduleName: "Duration",
278
+ shape: Number({integer: true}),
279
+ // Seconds: one hour.
280
+ sample: "3600",
281
+ writer: "$value",
282
+ },
283
+ {moduleName: "Percent", shape: Number({integer: false}), sample: "50.0", writer: "$value"},
284
+ {moduleName: "Bytes", shape: Number({integer: false}), sample: "1024.0", writer: "$value"},
285
+ ]
286
+
287
+ /** `semantic/` modules whose `type t` types a field, but whose shape
288
+ `valueShape` cannot express yet. `Geolocation.t` is a union of cases with
289
+ payloads. A tool asks for its value as ReScript source. */
290
+ let valueTypesWithoutWriter: array<string> = ["Geolocation"]
291
+
292
+ /** `semantic/` modules that are not a field's value: capabilities, helpers and
293
+ wrappers the ppx applies. Listed so the coverage test can tell a forgotten
294
+ module from a deliberate omission. */
295
+ let nonValueModules: array<string> = [
296
+ "Capabilities",
297
+ "CapabilityNeed",
298
+ "Geocoding",
299
+ "IdentityProvider",
300
+ "Media_Ref",
301
+ "Messaging",
302
+ "Offload",
303
+ "RowImage",
304
+ "Secrets",
305
+ "Semantic",
306
+ "Template",
164
307
  ]
165
308
 
166
309
  let semanticId: S.Metadata.Id.t<t> = S.Metadata.Id.make(~namespace="reventless", ~name="semantic")
@@ -3,6 +3,7 @@
3
3
  import * as S from "sury/src/S.res.mjs";
4
4
  import * as Sury from "sury";
5
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Currency$Reventless from "./Currency.res.mjs";
6
7
 
7
8
  let dateTime = "dateTime";
8
9
 
@@ -59,65 +60,208 @@ let brandedStrings = [
59
60
  {
60
61
  moduleName: "DateTime",
61
62
  id: dateTime,
62
- hasDerivableSchema: true
63
+ hasDerivableSchema: true,
64
+ sample: "2024-01-01T00:00:00Z"
63
65
  },
64
66
  {
65
67
  moduleName: "CalendarDate",
66
68
  id: date,
67
- hasDerivableSchema: true
69
+ hasDerivableSchema: true,
70
+ sample: "2024-01-01"
68
71
  },
69
72
  {
70
73
  moduleName: "Email",
71
74
  id: email,
72
- hasDerivableSchema: true
75
+ hasDerivableSchema: true,
76
+ sample: "ada@example.com"
73
77
  },
74
78
  {
75
79
  moduleName: "Phone",
76
80
  id: phone,
77
- hasDerivableSchema: true
81
+ hasDerivableSchema: true,
82
+ sample: "+15555550100"
78
83
  },
79
84
  {
80
85
  moduleName: "Url",
81
86
  id: url,
82
- hasDerivableSchema: true
87
+ hasDerivableSchema: true,
88
+ sample: "https://example.com"
83
89
  },
84
90
  {
85
91
  moduleName: "Color",
86
92
  id: color,
87
- hasDerivableSchema: true
93
+ hasDerivableSchema: true,
94
+ sample: "#1e90ff"
88
95
  },
89
96
  {
90
97
  moduleName: "FileRef",
91
98
  id: fileRef,
92
- hasDerivableSchema: true
99
+ hasDerivableSchema: true,
100
+ sample: "https://example.com/example.pdf"
93
101
  },
94
102
  {
95
103
  moduleName: "ImageRef",
96
104
  id: imageRef,
97
- hasDerivableSchema: true
105
+ hasDerivableSchema: true,
106
+ sample: "https://example.com/example.png"
98
107
  },
99
108
  {
100
109
  moduleName: "MemberRef",
101
110
  id: memberRef,
102
- hasDerivableSchema: false
111
+ hasDerivableSchema: false,
112
+ sample: "/example/example.png"
103
113
  },
104
114
  {
105
115
  moduleName: "StorageRef",
106
116
  id: storageRef,
107
- hasDerivableSchema: false
117
+ hasDerivableSchema: false,
118
+ sample: "/example/example.txt"
108
119
  },
109
120
  {
110
121
  moduleName: "UploadableFile",
111
122
  id: uploadableFile,
112
- hasDerivableSchema: false
123
+ hasDerivableSchema: false,
124
+ sample: "/example/example.pdf"
113
125
  },
114
126
  {
115
127
  moduleName: "UploadableImage",
116
128
  id: uploadableImage,
117
- hasDerivableSchema: false
129
+ hasDerivableSchema: false,
130
+ sample: "/example/example.png"
118
131
  }
119
132
  ];
120
133
 
134
+ let valueTypes = [
135
+ {
136
+ moduleName: "Money",
137
+ shape: {
138
+ TAG: "Parts",
139
+ parts: [
140
+ [
141
+ "amount",
142
+ "float"
143
+ ],
144
+ [
145
+ "currency",
146
+ "Currency"
147
+ ]
148
+ ]
149
+ },
150
+ sample: "Reventless.Money.make(~amount=1000.0, ~currency=Reventless.Currency.EUR)",
151
+ writer: "Reventless.Money.make(~amount=$amount, ~currency=$currency)"
152
+ },
153
+ {
154
+ moduleName: "Currency",
155
+ shape: {
156
+ TAG: "Codes",
157
+ codes: Currency$Reventless.all.map(Currency$Reventless.toString)
158
+ },
159
+ sample: "Reventless.Currency.EUR",
160
+ writer: "Reventless.Currency.$code"
161
+ },
162
+ {
163
+ moduleName: "DateRange",
164
+ shape: {
165
+ TAG: "Parts",
166
+ parts: [
167
+ [
168
+ "start",
169
+ "DateTime"
170
+ ],
171
+ [
172
+ "end_",
173
+ "DateTime"
174
+ ]
175
+ ]
176
+ },
177
+ sample: `{Reventless.DateRange.start: "2024-01-01T00:00:00Z", end_: "2024-01-02T00:00:00Z"}`,
178
+ writer: "{Reventless.DateRange.start: $start, end_: $end_}"
179
+ },
180
+ {
181
+ moduleName: "GeoPoint",
182
+ shape: {
183
+ TAG: "Parts",
184
+ parts: [
185
+ [
186
+ "lat",
187
+ "float"
188
+ ],
189
+ [
190
+ "lng",
191
+ "float"
192
+ ]
193
+ ]
194
+ },
195
+ sample: "{Reventless.GeoPoint.lat: 0.0, lng: 0.0}",
196
+ writer: "{Reventless.GeoPoint.lat: $lat, lng: $lng}"
197
+ },
198
+ {
199
+ moduleName: "CaptionedImage",
200
+ shape: {
201
+ TAG: "Parts",
202
+ parts: [
203
+ [
204
+ "ref",
205
+ "UploadableImage"
206
+ ],
207
+ [
208
+ "altText",
209
+ "string"
210
+ ],
211
+ [
212
+ "caption",
213
+ "string"
214
+ ]
215
+ ]
216
+ },
217
+ sample: `{Reventless.CaptionedImage.ref: "/example/example.png", altText: "An example image", caption: "An example caption"}`,
218
+ writer: "{Reventless.CaptionedImage.ref: $ref, altText: $altText, caption: $caption}"
219
+ },
220
+ {
221
+ moduleName: "Duration",
222
+ shape: {
223
+ TAG: "Number",
224
+ integer: true
225
+ },
226
+ sample: "3600",
227
+ writer: "$value"
228
+ },
229
+ {
230
+ moduleName: "Percent",
231
+ shape: {
232
+ TAG: "Number",
233
+ integer: false
234
+ },
235
+ sample: "50.0",
236
+ writer: "$value"
237
+ },
238
+ {
239
+ moduleName: "Bytes",
240
+ shape: {
241
+ TAG: "Number",
242
+ integer: false
243
+ },
244
+ sample: "1024.0",
245
+ writer: "$value"
246
+ }
247
+ ];
248
+
249
+ let valueTypesWithoutWriter = ["Geolocation"];
250
+
251
+ let nonValueModules = [
252
+ "Capabilities",
253
+ "CapabilityNeed",
254
+ "Geocoding",
255
+ "IdentityProvider",
256
+ "Media_Ref",
257
+ "Messaging",
258
+ "Offload",
259
+ "RowImage",
260
+ "Secrets",
261
+ "Semantic",
262
+ "Template"
263
+ ];
264
+
121
265
  let semanticId = Sury.$Metadata_Id_make("reventless", "semantic");
122
266
 
123
267
  function mark(schema, id, payloadOpt) {
@@ -257,6 +401,9 @@ function has(fieldSchema, id) {
257
401
  export {
258
402
  Id,
259
403
  brandedStrings,
404
+ valueTypes,
405
+ valueTypesWithoutWriter,
406
+ nonValueModules,
260
407
  semanticId,
261
408
  mark,
262
409
  refined,
@@ -269,4 +416,4 @@ export {
269
416
  fieldIdentityKey,
270
417
  has,
271
418
  }
272
- /* semanticId Not a pure module */
419
+ /* valueTypes Not a pure module */