@reventlessdev/reventless-spec 3.0.0-alpha.127 → 3.0.0-alpha.129
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 +52 -0
- package/package.json +2 -2
- package/schema/platform-api.graphql +1 -0
- package/src/AnsiStyle.res +31 -7
- package/src/AnsiStyle.res.mjs +16 -2
- package/src/components/Plugin.res +66 -84
- package/src/components/Plugin.res.mjs +46 -81
- package/src/components/Sensitive.res +137 -0
- package/src/components/Sensitive.res.mjs +74 -0
- package/src/generator/GraftTrait.res +5 -4
- package/src/generator/GraftTrait.res.mjs +4 -4
- package/src/semantic/Bytes.res +21 -21
- package/src/semantic/Bytes.res.mjs +35 -0
- package/src/semantic/CaptionedImage.res +100 -0
- package/src/semantic/CaptionedImage.res.mjs +35 -0
- package/src/semantic/Duration.res +22 -24
- package/src/semantic/Duration.res.mjs +43 -0
- package/src/semantic/MemberRef.res +119 -0
- package/src/semantic/MemberRef.res.mjs +57 -0
- package/src/semantic/Offload.res.mjs +2 -2
- package/src/semantic/Percent.res +9 -20
- package/src/semantic/Percent.res.mjs +5 -0
- package/src/semantic/Semantic.res +63 -0
- package/src/semantic/Semantic.res.mjs +39 -0
- package/src/semantic/StorageRef.res.mjs +1 -1
- package/src/semantic/Template.res +412 -0
- package/src/semantic/Template.res.mjs +545 -0
- package/src/types/Message.res +14 -12
- package/src/types/Message.res.mjs +2 -1
|
@@ -49,26 +49,18 @@ let apiTargetSchema = Sury.union([
|
|
|
49
49
|
|
|
50
50
|
let apiSchemaFragmentOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginApiFragments", undefined, apiSchemaFragmentSchema);
|
|
51
51
|
|
|
52
|
-
let dcbEventLogOptionSchema = Sury.$nullAsOption(dcbEventLogDefinitionSchema);
|
|
53
|
-
|
|
54
|
-
let stringOptionSchema = Sury.$nullAsOption(Sury.string);
|
|
55
|
-
|
|
56
|
-
let stringArrayOptionSchema = Sury.$nullAsOption(Sury.array(Sury.string));
|
|
57
|
-
|
|
58
|
-
let boolOptionSchema = Sury.$nullAsOption(Sury.bool);
|
|
59
|
-
|
|
60
52
|
let panelManifestEntrySchema = Sury.$schema(s => ({
|
|
61
53
|
fragmentId: s.m(Sury.string),
|
|
62
54
|
title: s.m(Sury.string),
|
|
63
55
|
description: s.m(Sury.string),
|
|
64
56
|
positions: s.m(Sury.array(Sury.string)),
|
|
65
|
-
requiredAccess: s.m(
|
|
57
|
+
requiredAccess: s.m(Sury.$option(Sury.string))
|
|
66
58
|
}));
|
|
67
59
|
|
|
68
60
|
let menuEntrySchema = Sury.$schema(s => ({
|
|
69
61
|
label: s.m(Sury.string),
|
|
70
|
-
icon: s.m(
|
|
71
|
-
group: s.m(
|
|
62
|
+
icon: s.m(Sury.$option(Sury.string)),
|
|
63
|
+
group: s.m(Sury.$option(Sury.string)),
|
|
72
64
|
sortOrder: s.m(Sury.int)
|
|
73
65
|
}));
|
|
74
66
|
|
|
@@ -76,7 +68,7 @@ let pageManifestEntrySchema = Sury.$schema(s => ({
|
|
|
76
68
|
fragmentId: s.m(Sury.string),
|
|
77
69
|
title: s.m(Sury.string),
|
|
78
70
|
menuEntry: s.m(menuEntrySchema),
|
|
79
|
-
requiredAccess: s.m(
|
|
71
|
+
requiredAccess: s.m(Sury.$option(Sury.string))
|
|
80
72
|
}));
|
|
81
73
|
|
|
82
74
|
let uiFragmentManifestSchema = Sury.$schema(s => ({
|
|
@@ -85,7 +77,7 @@ let uiFragmentManifestSchema = Sury.$schema(s => ({
|
|
|
85
77
|
pages: s.m(Sury.array(pageManifestEntrySchema))
|
|
86
78
|
}));
|
|
87
79
|
|
|
88
|
-
let uiFragmentManifestOptionSchema = Sury.$
|
|
80
|
+
let uiFragmentManifestOptionSchema = Sury.$option(uiFragmentManifestSchema);
|
|
89
81
|
|
|
90
82
|
let commandLevelSchema = Sury.union([
|
|
91
83
|
Sury.literal("Collection"),
|
|
@@ -95,21 +87,21 @@ let commandLevelSchema = Sury.union([
|
|
|
95
87
|
let fieldReferenceSchema = Sury.$schema(s => ({
|
|
96
88
|
fieldName: s.m(Sury.string),
|
|
97
89
|
entity: s.m(Sury.string),
|
|
98
|
-
plugin: s.m(
|
|
90
|
+
plugin: s.m(Sury.$option(Sury.string))
|
|
99
91
|
}));
|
|
100
92
|
|
|
101
93
|
let commandDefSchema = Sury.$schema(s => ({
|
|
102
94
|
name: s.m(Sury.string),
|
|
103
95
|
schema: s.m(Sury.string),
|
|
104
96
|
level: s.m(commandLevelSchema),
|
|
105
|
-
aggregateIdField: s.m(
|
|
97
|
+
aggregateIdField: s.m(Sury.$option(Sury.string)),
|
|
106
98
|
mutationField: s.m(Sury.string),
|
|
107
99
|
references: s.m(Sury.array(fieldReferenceSchema)),
|
|
108
|
-
allowedStates: s.m(
|
|
109
|
-
targetState: s.m(
|
|
110
|
-
apiExposed: s.m(
|
|
111
|
-
requiredAccess: s.m(
|
|
112
|
-
ownerField: s.m(
|
|
100
|
+
allowedStates: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
101
|
+
targetState: s.m(Sury.$option(Sury.string)),
|
|
102
|
+
apiExposed: s.m(Sury.$option(Sury.bool)),
|
|
103
|
+
requiredAccess: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
104
|
+
ownerField: s.m(Sury.$option(Sury.string))
|
|
113
105
|
}));
|
|
114
106
|
|
|
115
107
|
let queryableDefSchema = Sury.$schema(s => ({
|
|
@@ -120,18 +112,18 @@ let queryableDefSchema = Sury.$schema(s => ({
|
|
|
120
112
|
linkedWriteSide: s.m(Sury.array(Sury.string)),
|
|
121
113
|
labelField: s.m(Sury.string),
|
|
122
114
|
searchableFields: s.m(Sury.array(Sury.string)),
|
|
123
|
-
labelFieldSource: s.m(
|
|
124
|
-
lifecycleField: s.m(
|
|
125
|
-
ownerField: s.m(
|
|
126
|
-
retiredField: s.m(
|
|
127
|
-
retiredValues: s.m(
|
|
128
|
-
namedWhenRetired: s.m(
|
|
129
|
-
visibility: s.m(
|
|
130
|
-
chapter: s.m(
|
|
131
|
-
singleQueryField: s.m(
|
|
132
|
-
idField: s.m(
|
|
133
|
-
idFieldSource: s.m(
|
|
134
|
-
requiredAccess: s.m(
|
|
115
|
+
labelFieldSource: s.m(Sury.$option(Sury.string)),
|
|
116
|
+
lifecycleField: s.m(Sury.$option(Sury.string)),
|
|
117
|
+
ownerField: s.m(Sury.$option(Sury.string)),
|
|
118
|
+
retiredField: s.m(Sury.$option(Sury.string)),
|
|
119
|
+
retiredValues: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
120
|
+
namedWhenRetired: s.m(Sury.$option(Sury.bool)),
|
|
121
|
+
visibility: s.m(Sury.$option(Sury.string)),
|
|
122
|
+
chapter: s.m(Sury.$option(Sury.string)),
|
|
123
|
+
singleQueryField: s.m(Sury.$option(Sury.string)),
|
|
124
|
+
idField: s.m(Sury.$option(Sury.string)),
|
|
125
|
+
idFieldSource: s.m(Sury.$option(Sury.string)),
|
|
126
|
+
requiredAccess: s.m(Sury.$option(Sury.array(Sury.string)))
|
|
135
127
|
}));
|
|
136
128
|
|
|
137
129
|
let eventDefSchema = Sury.$schema(s => ({
|
|
@@ -152,10 +144,10 @@ let writableDefSchema = Sury.$schema(s => ({
|
|
|
152
144
|
producedEventTypes: s.m(Sury.array(Sury.string)),
|
|
153
145
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
154
146
|
linkedViews: s.m(Sury.array(Sury.string)),
|
|
155
|
-
consistencyRead: s.m(
|
|
147
|
+
consistencyRead: s.m(Sury.$option(Sury.string)),
|
|
156
148
|
events: s.m(Sury.array(eventDefSchema)),
|
|
157
149
|
errors: s.m(Sury.array(errorDefSchema)),
|
|
158
|
-
chapter: s.m(
|
|
150
|
+
chapter: s.m(Sury.$option(Sury.string))
|
|
159
151
|
}));
|
|
160
152
|
|
|
161
153
|
let automationSliceDefSchema = Sury.$schema(s => ({
|
|
@@ -163,24 +155,25 @@ let automationSliceDefSchema = Sury.$schema(s => ({
|
|
|
163
155
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
164
156
|
producedCommandTypes: s.m(Sury.array(Sury.string)),
|
|
165
157
|
targetName: s.m(Sury.string),
|
|
166
|
-
chapter: s.m(
|
|
158
|
+
chapter: s.m(Sury.$option(Sury.string))
|
|
167
159
|
}));
|
|
168
160
|
|
|
169
161
|
let outboundTranslationSliceDefSchema = Sury.$schema(s => ({
|
|
170
162
|
name: s.m(Sury.string),
|
|
171
163
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
172
164
|
inboundCommandTypes: s.m(Sury.array(Sury.string)),
|
|
173
|
-
targetName: s.m(
|
|
174
|
-
externalSystem: s.m(
|
|
175
|
-
chapter: s.m(
|
|
165
|
+
targetName: s.m(Sury.$option(Sury.string)),
|
|
166
|
+
externalSystem: s.m(Sury.$option(Sury.string)),
|
|
167
|
+
chapter: s.m(Sury.$option(Sury.string)),
|
|
168
|
+
consumedSources: s.m(Sury.$option(Sury.array(Sury.string)))
|
|
176
169
|
}));
|
|
177
170
|
|
|
178
171
|
let inboundTranslationSliceDefSchema = Sury.$schema(s => ({
|
|
179
172
|
name: s.m(Sury.string),
|
|
180
173
|
commandTypes: s.m(Sury.array(Sury.string)),
|
|
181
174
|
targetName: s.m(Sury.string),
|
|
182
|
-
externalSystem: s.m(
|
|
183
|
-
chapter: s.m(
|
|
175
|
+
externalSystem: s.m(Sury.$option(Sury.string)),
|
|
176
|
+
chapter: s.m(Sury.$option(Sury.string))
|
|
184
177
|
}));
|
|
185
178
|
|
|
186
179
|
let publishedEventDefSchema = Sury.$schema(s => ({
|
|
@@ -188,65 +181,51 @@ let publishedEventDefSchema = Sury.$schema(s => ({
|
|
|
188
181
|
fromEventTypes: s.m(Sury.array(Sury.string))
|
|
189
182
|
}));
|
|
190
183
|
|
|
191
|
-
let publishedEventDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(publishedEventDefSchema));
|
|
192
|
-
|
|
193
184
|
let acceptedCommandDefSchema = Sury.$schema(s => ({
|
|
194
185
|
name: s.m(Sury.string),
|
|
195
186
|
toCommandTypes: s.m(Sury.array(Sury.string))
|
|
196
187
|
}));
|
|
197
188
|
|
|
198
|
-
let acceptedCommandDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(acceptedCommandDefSchema));
|
|
199
|
-
|
|
200
189
|
let handledEventDefSchema = Sury.$schema(s => ({
|
|
201
190
|
name: s.m(Sury.string),
|
|
202
191
|
toCommandTypes: s.m(Sury.array(Sury.string))
|
|
203
192
|
}));
|
|
204
193
|
|
|
205
|
-
let handledEventDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(handledEventDefSchema));
|
|
206
|
-
|
|
207
194
|
let issuedCommandDefSchema = Sury.$schema(s => ({
|
|
208
195
|
name: s.m(Sury.string),
|
|
209
196
|
fromEventTypes: s.m(Sury.array(Sury.string))
|
|
210
197
|
}));
|
|
211
198
|
|
|
212
|
-
let issuedCommandDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(issuedCommandDefSchema));
|
|
213
|
-
|
|
214
199
|
let extensionDefSchema = Sury.$schema(s => ({
|
|
215
200
|
name: s.m(Sury.string),
|
|
216
201
|
delegateNames: s.m(Sury.array(Sury.string)),
|
|
217
202
|
eventTypes: s.m(Sury.array(Sury.string)),
|
|
218
203
|
commandTypes: s.m(Sury.array(Sury.string)),
|
|
219
|
-
handledEvents: s.m(
|
|
220
|
-
issuedCommands: s.m(
|
|
204
|
+
handledEvents: s.m(Sury.$option(Sury.array(handledEventDefSchema))),
|
|
205
|
+
issuedCommands: s.m(Sury.$option(Sury.array(issuedCommandDefSchema)))
|
|
221
206
|
}));
|
|
222
207
|
|
|
223
208
|
let extensionPointDefSchema = Sury.$schema(s => ({
|
|
224
209
|
name: s.m(Sury.string),
|
|
225
210
|
delegateNames: s.m(Sury.array(Sury.string)),
|
|
226
211
|
sourceEventTypes: s.m(Sury.array(Sury.string)),
|
|
227
|
-
commandTypes: s.m(
|
|
228
|
-
publishedEvents: s.m(
|
|
229
|
-
acceptedCommands: s.m(
|
|
212
|
+
commandTypes: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
213
|
+
publishedEvents: s.m(Sury.$option(Sury.array(publishedEventDefSchema))),
|
|
214
|
+
acceptedCommands: s.m(Sury.$option(Sury.array(acceptedCommandDefSchema)))
|
|
230
215
|
}));
|
|
231
216
|
|
|
232
|
-
let extensionPointDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(extensionPointDefSchema));
|
|
233
|
-
|
|
234
217
|
let requiredStoreDeclarationSchema = Sury.$schema(s => ({
|
|
235
218
|
store: s.m(Sury.string),
|
|
236
219
|
component: s.m(Sury.string),
|
|
237
220
|
field: s.m(Sury.string),
|
|
238
|
-
annotation: s.m(
|
|
221
|
+
annotation: s.m(Sury.$option(Sury.string))
|
|
239
222
|
}));
|
|
240
223
|
|
|
241
|
-
let requiredStoreDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(requiredStoreDeclarationSchema));
|
|
242
|
-
|
|
243
224
|
let requiredCapabilityDeclarationSchema = Sury.$schema(s => ({
|
|
244
225
|
capability: s.m(Sury.string),
|
|
245
226
|
component: s.m(Sury.string)
|
|
246
227
|
}));
|
|
247
228
|
|
|
248
|
-
let requiredCapabilityDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(requiredCapabilityDeclarationSchema));
|
|
249
|
-
|
|
250
229
|
let traitDeclarationSchema = Sury.$schema(s => ({
|
|
251
230
|
trait: s.m(Sury.string),
|
|
252
231
|
version: s.m(Sury.string),
|
|
@@ -254,8 +233,6 @@ let traitDeclarationSchema = Sury.$schema(s => ({
|
|
|
254
233
|
component: s.m(Sury.string)
|
|
255
234
|
}));
|
|
256
235
|
|
|
257
|
-
let traitDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(traitDeclarationSchema));
|
|
258
|
-
|
|
259
236
|
let pluginStructureSchema = Sury.$schema(s => ({
|
|
260
237
|
readModels: s.m(Sury.array(queryableDefSchema)),
|
|
261
238
|
stateViewSlices: s.m(Sury.array(queryableDefSchema)),
|
|
@@ -265,11 +242,11 @@ let pluginStructureSchema = Sury.$schema(s => ({
|
|
|
265
242
|
outboundTranslationSlices: s.m(Sury.array(outboundTranslationSliceDefSchema)),
|
|
266
243
|
inboundTranslationSlices: s.m(Sury.array(inboundTranslationSliceDefSchema)),
|
|
267
244
|
extensions: s.m(Sury.array(extensionDefSchema)),
|
|
268
|
-
extensionPoints: s.m(
|
|
269
|
-
requiredStores: s.m(
|
|
270
|
-
requiredStoreDeclarations: s.m(
|
|
271
|
-
requiredCapabilities: s.m(
|
|
272
|
-
traitDeclarations: s.m(
|
|
245
|
+
extensionPoints: s.m(Sury.$option(Sury.array(extensionPointDefSchema))),
|
|
246
|
+
requiredStores: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
247
|
+
requiredStoreDeclarations: s.m(Sury.$option(Sury.array(requiredStoreDeclarationSchema))),
|
|
248
|
+
requiredCapabilities: s.m(Sury.$option(Sury.array(requiredCapabilityDeclarationSchema))),
|
|
249
|
+
traitDeclarations: s.m(Sury.$option(Sury.array(traitDeclarationSchema)))
|
|
273
250
|
}));
|
|
274
251
|
|
|
275
252
|
let pluginStructureOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginStructures", undefined, pluginStructureSchema);
|
|
@@ -283,9 +260,9 @@ let pluginDefinitionSchema = Sury.$schema(s => ({
|
|
|
283
260
|
eventCollector: s.m(Sury.string),
|
|
284
261
|
extensionProtocols: s.m(Sury.array(extensionProtocolSchema)),
|
|
285
262
|
apiSchemaFragment: s.m(apiSchemaFragmentOffloadSchema),
|
|
286
|
-
apiTarget: s.m(
|
|
263
|
+
apiTarget: s.m(Sury.$option(Sury.string)),
|
|
287
264
|
structure: s.m(pluginStructureOffloadSchema),
|
|
288
|
-
dcbEventLog: s.m(
|
|
265
|
+
dcbEventLog: s.m(Sury.$option(dcbEventLogDefinitionSchema)),
|
|
289
266
|
kind: s.m(pluginKindSchema)
|
|
290
267
|
}));
|
|
291
268
|
|
|
@@ -300,10 +277,6 @@ export {
|
|
|
300
277
|
apiSchemaFragmentSchema,
|
|
301
278
|
apiTargetSchema,
|
|
302
279
|
apiSchemaFragmentOffloadSchema,
|
|
303
|
-
dcbEventLogOptionSchema,
|
|
304
|
-
stringOptionSchema,
|
|
305
|
-
stringArrayOptionSchema,
|
|
306
|
-
boolOptionSchema,
|
|
307
280
|
panelManifestEntrySchema,
|
|
308
281
|
menuEntrySchema,
|
|
309
282
|
pageManifestEntrySchema,
|
|
@@ -320,22 +293,14 @@ export {
|
|
|
320
293
|
outboundTranslationSliceDefSchema,
|
|
321
294
|
inboundTranslationSliceDefSchema,
|
|
322
295
|
publishedEventDefSchema,
|
|
323
|
-
publishedEventDefArrayOptionSchema,
|
|
324
296
|
acceptedCommandDefSchema,
|
|
325
|
-
acceptedCommandDefArrayOptionSchema,
|
|
326
297
|
handledEventDefSchema,
|
|
327
|
-
handledEventDefArrayOptionSchema,
|
|
328
298
|
issuedCommandDefSchema,
|
|
329
|
-
issuedCommandDefArrayOptionSchema,
|
|
330
299
|
extensionDefSchema,
|
|
331
300
|
extensionPointDefSchema,
|
|
332
|
-
extensionPointDefArrayOptionSchema,
|
|
333
301
|
requiredStoreDeclarationSchema,
|
|
334
|
-
requiredStoreDeclarationArrayOptionSchema,
|
|
335
302
|
requiredCapabilityDeclarationSchema,
|
|
336
|
-
requiredCapabilityDeclarationArrayOptionSchema,
|
|
337
303
|
traitDeclarationSchema,
|
|
338
|
-
traitDeclarationArrayOptionSchema,
|
|
339
304
|
pluginStructureSchema,
|
|
340
305
|
pluginStructureOffloadSchema,
|
|
341
306
|
pluginDefinitionSchema,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Marks a field whose value must not be rendered into content a person receives.
|
|
3
|
+
|
|
4
|
+
`@sensitive` is a *position*, not a type: it says "whatever this field holds, do
|
|
5
|
+
not put it in an outbound message". What the field **is** — an email, a token, a
|
|
6
|
+
reference — is declared separately, exactly as owning and tagging are separate
|
|
7
|
+
facts about one field.
|
|
8
|
+
|
|
9
|
+
It is declared where the field is declared, and read by anything that composes
|
|
10
|
+
text somebody will read. That is the whole reason it lives here rather than
|
|
11
|
+
beside whichever consumer happens to need it first: a marking that exists in one
|
|
12
|
+
consumer is a marking the rest of the system cannot honour, and the domain model
|
|
13
|
+
is the only place that knows which values are sensitive.
|
|
14
|
+
|
|
15
|
+
## Absent means "not stated", not "safe"
|
|
16
|
+
|
|
17
|
+
A reader that misses the marker leaks the value, so the failure is *open* — the
|
|
18
|
+
same shape as `Owner`, and the reason `isFieldSensitive` follows the wrappers
|
|
19
|
+
rather than leaving each consumer to unwrap for itself.
|
|
20
|
+
|
|
21
|
+
## Some fields need no annotation
|
|
22
|
+
|
|
23
|
+
A field whose semantic already says it is a contact detail is sensitive without
|
|
24
|
+
anybody writing it down — see `impliedBySemantic`. The annotation is for the
|
|
25
|
+
values a type cannot betray: a token, a note, a reference somebody chose.
|
|
26
|
+
|
|
27
|
+
`@sensitive` is the authoring form and is sugar over the constructors below, the
|
|
28
|
+
way `@owner` is sugar over `Owner`'s. Write `@s.matches(Sensitive.mark(…))` by
|
|
29
|
+
hand where the ppx shorthand cannot reach — a file with no `@@reventless.spec`
|
|
30
|
+
annotation, or a field whose type is neither `string` nor `option<string>`.
|
|
31
|
+
|
|
32
|
+
@example
|
|
33
|
+
```rescript
|
|
34
|
+
@schema type event =
|
|
35
|
+
PasswordReset({
|
|
36
|
+
@owner customerId: string,
|
|
37
|
+
@sensitive resetToken: string,
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
*/
|
|
41
|
+
let sensitiveId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(
|
|
42
|
+
~namespace="reventless",
|
|
43
|
+
~name="sensitive",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
Layers the marker onto a schema that already says something else.
|
|
48
|
+
|
|
49
|
+
Sensitivity is independent of everything else a field declares — it may equally
|
|
50
|
+
be a DCB tag, an owner, or a reference — but a field carries at most one
|
|
51
|
+
`@s.matches`, so this composes by *wrapping* what the field already resolved to
|
|
52
|
+
rather than replacing it. Replacing would silently drop the field's tag, and a
|
|
53
|
+
dropped tag is a decision read that quietly misses events.
|
|
54
|
+
*/
|
|
55
|
+
let mark = (schema: S.t<'a>): S.t<'a> => schema->S.Metadata.set(~id=sensitiveId, true)
|
|
56
|
+
|
|
57
|
+
/** A string field that must not be rendered outbound. */
|
|
58
|
+
let string: S.t<string> = S.string->mark
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
An `option<string>` field that must not be rendered outbound.
|
|
62
|
+
|
|
63
|
+
Needed because `@s.matches` on an explicitly-`option`-typed field must supply the
|
|
64
|
+
whole field schema, wrapper included. The `f?: string` form needs nothing extra:
|
|
65
|
+
sury wraps the annotated inner schema itself, and `isFieldSensitive` looks
|
|
66
|
+
through that wrapper either way.
|
|
67
|
+
*/
|
|
68
|
+
let optionString: S.t<option<string>> = S.option(string)
|
|
69
|
+
|
|
70
|
+
/** Whether this exact schema carries the marker. Does not look through wrappers. */
|
|
71
|
+
let isSensitive = (schema: S.t<unknown>): bool =>
|
|
72
|
+
S.Metadata.get(schema, ~id=sensitiveId)->Option.getOr(false)
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
Whether a *field* is sensitive, wherever inside the field's type the marker sits.
|
|
76
|
+
|
|
77
|
+
An optional field keeps its marker inside the union wrapper, so a walk reading
|
|
78
|
+
only the outer schema answers `false` for `token?: string` — and here that means
|
|
79
|
+
the value is rendered into a message rather than withheld. Object properties are
|
|
80
|
+
deliberately not followed, for the same reason `Owner` does not follow them: a
|
|
81
|
+
marker on a nested record's field belongs to that field, and attributing it to
|
|
82
|
+
the enclosing one would withhold a whole record because one leaf is private.
|
|
83
|
+
*/
|
|
84
|
+
let isFieldSensitive = (schema: S.t<unknown>): bool =>
|
|
85
|
+
isSensitive(schema) ||
|
|
86
|
+
switch schema->Semantic.unwrapOptional {
|
|
87
|
+
| Some(inner) => isSensitive(inner)
|
|
88
|
+
| None => false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
The sensitive fields declared on an object schema, in declaration order.
|
|
93
|
+
|
|
94
|
+
Threaded to the JSON-Schema walk the way `Owner.fieldNames` is, and for the same
|
|
95
|
+
reason: the IR is shape-driven and sensitivity is not a shape — the field stays a
|
|
96
|
+
plain string either way. Reading it off the sury schema here also keeps it
|
|
97
|
+
available on command and event variants, which carry no annotation spec.
|
|
98
|
+
*/
|
|
99
|
+
let fieldNamesOfProperties = (properties: dict<S.t<unknown>>): array<string> =>
|
|
100
|
+
properties
|
|
101
|
+
->Dict.toArray
|
|
102
|
+
->Array.filterMap(((propName, propSchema)) =>
|
|
103
|
+
isFieldSensitive(propSchema) ? Some(propName) : None
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
let fieldNames = (schema: S.t<unknown>): array<string> =>
|
|
107
|
+
switch schema {
|
|
108
|
+
| Object({properties}) => fieldNamesOfProperties(properties)
|
|
109
|
+
| _ => []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
The sensitive fields of one constructor of a command or event union.
|
|
114
|
+
|
|
115
|
+
The form a renderer actually needs: it composes text about **one** occurrence, so
|
|
116
|
+
only that variant's fields have anything to say. `OrderPlaced` and
|
|
117
|
+
`PasswordReset` live in the same union and know nothing about each other's
|
|
118
|
+
fields — resolving by TAG here rather than at the call site is what stops a
|
|
119
|
+
template from consulting the wrong arm.
|
|
120
|
+
|
|
121
|
+
Answers `[]` for an unknown tag and for a payload-less variant, both of which
|
|
122
|
+
mean the same thing to a caller: nothing on this event is withheld.
|
|
123
|
+
*/
|
|
124
|
+
let variantFieldNames = (schema: S.t<unknown>, ~variant: string): array<string> =>
|
|
125
|
+
schema->Semantic.unionVariant(~variant)->Option.mapOr([], fieldNames)
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
The semantics that are sensitive without an annotation.
|
|
129
|
+
|
|
130
|
+
A contact detail is one whether or not anybody remembered to mark it, so the
|
|
131
|
+
rule is stated once here rather than left to each consumer to remember. Kept
|
|
132
|
+
deliberately short: it holds only the semantics whose *whole meaning* is "how to
|
|
133
|
+
reach a particular person". A postal address or a display name can be sensitive
|
|
134
|
+
in context, and context is exactly what an annotation is for.
|
|
135
|
+
*/
|
|
136
|
+
let impliedBySemantic = (semanticId: string): bool =>
|
|
137
|
+
semanticId === Semantic.Id.email || semanticId === Semantic.Id.phone
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
|
|
7
|
+
|
|
8
|
+
let sensitiveId = Sury.$Metadata_Id_make("reventless", "sensitive");
|
|
9
|
+
|
|
10
|
+
function mark(schema) {
|
|
11
|
+
return Sury.$Metadata_set(schema, sensitiveId, true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let string = Sury.$Metadata_set(Sury.string, sensitiveId, true);
|
|
15
|
+
|
|
16
|
+
let optionString = Sury.$option(string);
|
|
17
|
+
|
|
18
|
+
function isSensitive(schema) {
|
|
19
|
+
return Stdlib_Option.getOr(Sury.$Metadata_get(schema, sensitiveId), false);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isFieldSensitive(schema) {
|
|
23
|
+
if (isSensitive(schema)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
let inner = Semantic$Reventless.unwrapOptional(schema);
|
|
27
|
+
if (inner !== undefined) {
|
|
28
|
+
return isSensitive(inner);
|
|
29
|
+
} else {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fieldNamesOfProperties(properties) {
|
|
35
|
+
return Stdlib_Array.filterMap(Object.entries(properties), param => {
|
|
36
|
+
if (isFieldSensitive(param[1])) {
|
|
37
|
+
return param[0];
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fieldNames(schema) {
|
|
43
|
+
if (schema.type === "object") {
|
|
44
|
+
return fieldNamesOfProperties(schema.properties);
|
|
45
|
+
} else {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function variantFieldNames(schema, variant) {
|
|
51
|
+
return Stdlib_Option.mapOr(Semantic$Reventless.unionVariant(schema, variant), [], fieldNames);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function impliedBySemantic(semanticId) {
|
|
55
|
+
if (semanticId === Semantic$Reventless.Id.email) {
|
|
56
|
+
return true;
|
|
57
|
+
} else {
|
|
58
|
+
return semanticId === Semantic$Reventless.Id.phone;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
sensitiveId,
|
|
64
|
+
mark,
|
|
65
|
+
string,
|
|
66
|
+
optionString,
|
|
67
|
+
isSensitive,
|
|
68
|
+
isFieldSensitive,
|
|
69
|
+
fieldNamesOfProperties,
|
|
70
|
+
fieldNames,
|
|
71
|
+
variantFieldNames,
|
|
72
|
+
impliedBySemantic,
|
|
73
|
+
}
|
|
74
|
+
/* sensitiveId Not a pure module */
|
|
@@ -55,7 +55,7 @@ let parseFlags = (args: array<string>): dict<JSON.t> => {
|
|
|
55
55
|
switch args->Array.get(i) {
|
|
56
56
|
| None => ()
|
|
57
57
|
| Some(arg) if arg->String.startsWith("--") =>
|
|
58
|
-
let key = arg->String.
|
|
58
|
+
let key = arg->String.slice(~start=2, ~end=arg->String.length)
|
|
59
59
|
switch args->Array.get(i + 1) {
|
|
60
60
|
| Some(value) if !(value->String.startsWith("--")) =>
|
|
61
61
|
out->Dict.set(key, JSON.Encode.string(value))
|
|
@@ -107,14 +107,14 @@ let arrayFieldNames = (schema: S.t<unknown>): array<string> =>
|
|
|
107
107
|
// ── Entry point ──────────────────────────────────────────────────────────────
|
|
108
108
|
|
|
109
109
|
let main = async () => {
|
|
110
|
-
let argv = NodeProcess.argv->Array.
|
|
110
|
+
let argv = NodeProcess.argv->Array.slice(~start=2, ~end=NodeProcess.argv->Array.length)
|
|
111
111
|
switch argv->Array.get(0) {
|
|
112
112
|
| None | Some("") | Some("--help") | Some("-h") => {
|
|
113
113
|
Console.log(usage)
|
|
114
114
|
NodeProcess.exit(argv->Array.length == 0 ? 1 : 0)
|
|
115
115
|
}
|
|
116
116
|
| Some(traitPackage) => {
|
|
117
|
-
let flags = parseFlags(argv->Array.
|
|
117
|
+
let flags = parseFlags(argv->Array.slice(~start=1, ~end=argv->Array.length))
|
|
118
118
|
let stringFlag = key => flags->Dict.get(key)->Option.flatMap(JSON.Decode.string)
|
|
119
119
|
let into = stringFlag("into")
|
|
120
120
|
let tests = stringFlag("tests")
|
|
@@ -136,7 +136,8 @@ let main = async () => {
|
|
|
136
136
|
->String.replace("trait-", "")
|
|
137
137
|
->String.split("-")
|
|
138
138
|
->Array.map(part =>
|
|
139
|
-
part->String.charAt(0)->String.toUpperCase ++
|
|
139
|
+
part->String.charAt(0)->String.toUpperCase ++
|
|
140
|
+
part->String.slice(~start=1, ~end=part->String.length)
|
|
140
141
|
)
|
|
141
142
|
->Array.join("") ++ "_Scaffold"
|
|
142
143
|
let specifier = `${traitPackage}/src/${scaffoldModule}.res.mjs`
|
|
@@ -36,7 +36,7 @@ function parseFlags(args) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
if (arg.startsWith("--")) {
|
|
39
|
-
let key = arg.slice(2);
|
|
39
|
+
let key = arg.slice(2, arg.length);
|
|
40
40
|
let value = args[i + 1 | 0];
|
|
41
41
|
if (value !== undefined && !value.startsWith("--")) {
|
|
42
42
|
out[key] = value;
|
|
@@ -83,7 +83,7 @@ function arrayFieldNames(schema) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
async function main() {
|
|
86
|
-
let argv = process.argv.slice(2);
|
|
86
|
+
let argv = process.argv.slice(2, process.argv.length);
|
|
87
87
|
let traitPackage = argv[0];
|
|
88
88
|
if (traitPackage !== undefined) {
|
|
89
89
|
switch (traitPackage) {
|
|
@@ -92,7 +92,7 @@ async function main() {
|
|
|
92
92
|
case "-h" :
|
|
93
93
|
break;
|
|
94
94
|
default:
|
|
95
|
-
let flags = parseFlags(argv.slice(1));
|
|
95
|
+
let flags = parseFlags(argv.slice(1, argv.length));
|
|
96
96
|
let stringFlag = key => Stdlib_Option.flatMap(flags[key], Stdlib_JSON.Decode.string);
|
|
97
97
|
let into = stringFlag("into");
|
|
98
98
|
let tests = stringFlag("tests");
|
|
@@ -103,7 +103,7 @@ async function main() {
|
|
|
103
103
|
if (tests === undefined) {
|
|
104
104
|
return fail("--into and --tests are both required.\n\n" + usage);
|
|
105
105
|
}
|
|
106
|
-
let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("") + "_Scaffold";
|
|
106
|
+
let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1, part.length)).join("") + "_Scaffold";
|
|
107
107
|
let specifier = traitPackage + `/src/` + scaffoldModule + `.res.mjs`;
|
|
108
108
|
let modulePath;
|
|
109
109
|
try {
|
package/src/semantic/Bytes.res
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
Marks a numeric field as a count of bytes.
|
|
2
|
+
Marks a numeric field as a count of bytes: finite, whole, zero or greater.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
wants to be. It is the wrong one here anyway: ReScript's `int` is int32, and
|
|
8
|
-
sury enforces that, so an `int` byte count silently caps at 2,147,483,647 — just
|
|
9
|
-
under 2 GiB. A type whose stated job is file sizes cannot stop at 2 GB.
|
|
10
|
-
|
|
11
|
-
`float` is a JS number, exact for every integer up to 2^53 — nine petabytes,
|
|
12
|
-
which is enough. The discreteness `int` would have given for free is recovered
|
|
13
|
-
by checking it: the grammar below rejects a fractional byte count, so the type
|
|
14
|
-
still means what `int` meant, minus the ceiling.
|
|
15
|
-
|
|
16
|
-
The wire is unaffected either way — both are JSON numbers — so this is a source
|
|
17
|
-
choice, not a format one. A field genuinely bounded below 2 GiB can still be
|
|
18
|
-
declared `int` and left unmarked; this type is for the ones that are not.
|
|
19
|
-
|
|
20
|
-
## The grammar
|
|
21
|
-
|
|
22
|
-
A finite, whole number, zero or greater. Zero is a real byte count — an empty
|
|
23
|
-
object — so it is accepted.
|
|
4
|
+
`float` rather than `int` because ReScript's `int` is int32, which caps a byte
|
|
5
|
+
count just under 2 GiB; wholeness is recovered by the grammar below. The wire is
|
|
6
|
+
a JSON number either way.
|
|
24
7
|
|
|
25
8
|
@example
|
|
26
9
|
```rescript
|
|
@@ -52,3 +35,20 @@ let fromFloat = (raw: float): result<t, string> =>
|
|
|
52
35
|
|
|
53
36
|
/** The sury schema for a byte-count field. Use with `@s.matches(Reventless.Bytes.schema)`. */
|
|
54
37
|
let schema: S.t<t> = S.float->Semantic.refined(~id=Semantic.Id.bytes, ~check=fromFloat)
|
|
38
|
+
|
|
39
|
+
/** Binary, because a byte count is what a filesystem reports. */
|
|
40
|
+
let step = 1024.0
|
|
41
|
+
let units = ["B", "KB", "MB", "GB", "TB", "PB"]
|
|
42
|
+
|
|
43
|
+
/** The count as text — `"512 B"`, `"1.5 KB"`, `"2 MB"`. Locale-independent, the
|
|
44
|
+
way `Money.format` is, so one value reads the same everywhere. */
|
|
45
|
+
let format = (b: t): string => {
|
|
46
|
+
let last = Array.length(units) - 1
|
|
47
|
+
let rec reduce = (value: float, index: int): (float, string) =>
|
|
48
|
+
value < step || index >= last
|
|
49
|
+
? (value, units->Array.getUnsafe(index))
|
|
50
|
+
: reduce(value /. step, index + 1)
|
|
51
|
+
let (value, unit) = reduce(b, 0)
|
|
52
|
+
// One decimal, and only when it says something: 2097152 is "2 MB", not "2.0 MB".
|
|
53
|
+
Float.toString(Math.round(value *. 10.0) /. 10.0) ++ " " ++ unit
|
|
54
|
+
}
|
|
@@ -31,8 +31,43 @@ function fromFloat(raw) {
|
|
|
31
31
|
|
|
32
32
|
let schema = Semantic$Reventless.refined(Sury.float, Semantic$Reventless.Id.bytes, fromFloat);
|
|
33
33
|
|
|
34
|
+
let units = [
|
|
35
|
+
"B",
|
|
36
|
+
"KB",
|
|
37
|
+
"MB",
|
|
38
|
+
"GB",
|
|
39
|
+
"TB",
|
|
40
|
+
"PB"
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function format(b) {
|
|
44
|
+
let last = units.length - 1 | 0;
|
|
45
|
+
let reduce = (_value, _index) => {
|
|
46
|
+
while (true) {
|
|
47
|
+
let index = _index;
|
|
48
|
+
let value = _value;
|
|
49
|
+
if (value < 1024.0 || index >= last) {
|
|
50
|
+
return [
|
|
51
|
+
value,
|
|
52
|
+
units[index]
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
_index = index + 1 | 0;
|
|
56
|
+
_value = value / 1024.0;
|
|
57
|
+
continue;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
let match = reduce(b, 0);
|
|
61
|
+
return (Math.round(match[0] * 10.0) / 10.0).toString() + " " + match[1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let step = 1024.0;
|
|
65
|
+
|
|
34
66
|
export {
|
|
35
67
|
fromFloat,
|
|
36
68
|
schema,
|
|
69
|
+
step,
|
|
70
|
+
units,
|
|
71
|
+
format,
|
|
37
72
|
}
|
|
38
73
|
/* schema Not a pure module */
|