@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 +7 -0
- package/package.json +1 -1
- package/src/semantic/Semantic.res +155 -12
- package/src/semantic/Semantic.res.mjs +160 -13
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
|
@@ -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
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{moduleName: "
|
|
159
|
-
{moduleName: "
|
|
160
|
-
|
|
161
|
-
{moduleName: "
|
|
162
|
-
{moduleName: "
|
|
163
|
-
{moduleName: "
|
|
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
|
-
/*
|
|
419
|
+
/* valueTypes Not a pure module */
|