@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.
@@ -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(stringOptionSchema)
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(stringOptionSchema),
71
- group: s.m(stringOptionSchema),
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(stringOptionSchema)
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.$nullAsOption(uiFragmentManifestSchema);
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(stringOptionSchema)
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(stringOptionSchema),
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(stringArrayOptionSchema),
109
- targetState: s.m(stringOptionSchema),
110
- apiExposed: s.m(boolOptionSchema),
111
- requiredAccess: s.m(stringArrayOptionSchema),
112
- ownerField: s.m(stringOptionSchema)
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(stringOptionSchema),
124
- lifecycleField: s.m(stringOptionSchema),
125
- ownerField: s.m(stringOptionSchema),
126
- retiredField: s.m(stringOptionSchema),
127
- retiredValues: s.m(stringArrayOptionSchema),
128
- namedWhenRetired: s.m(boolOptionSchema),
129
- visibility: s.m(stringOptionSchema),
130
- chapter: s.m(stringOptionSchema),
131
- singleQueryField: s.m(stringOptionSchema),
132
- idField: s.m(stringOptionSchema),
133
- idFieldSource: s.m(stringOptionSchema),
134
- requiredAccess: s.m(stringArrayOptionSchema)
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(stringOptionSchema),
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(stringOptionSchema)
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(stringOptionSchema)
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(stringOptionSchema),
174
- externalSystem: s.m(stringOptionSchema),
175
- chapter: s.m(stringOptionSchema)
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(stringOptionSchema),
183
- chapter: s.m(stringOptionSchema)
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(handledEventDefArrayOptionSchema),
220
- issuedCommands: s.m(issuedCommandDefArrayOptionSchema)
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(stringArrayOptionSchema),
228
- publishedEvents: s.m(publishedEventDefArrayOptionSchema),
229
- acceptedCommands: s.m(acceptedCommandDefArrayOptionSchema)
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(stringOptionSchema)
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(extensionPointDefArrayOptionSchema),
269
- requiredStores: s.m(stringArrayOptionSchema),
270
- requiredStoreDeclarations: s.m(requiredStoreDeclarationArrayOptionSchema),
271
- requiredCapabilities: s.m(requiredCapabilityDeclarationArrayOptionSchema),
272
- traitDeclarations: s.m(traitDeclarationArrayOptionSchema)
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(stringOptionSchema),
263
+ apiTarget: s.m(Sury.$option(Sury.string)),
287
264
  structure: s.m(pluginStructureOffloadSchema),
288
- dcbEventLog: s.m(dcbEventLogOptionSchema),
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.sliceToEnd(~start=2)
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.sliceToEnd(~start=2)
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.sliceToEnd(~start=1))
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 ++ part->String.sliceToEnd(~start=1)
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 {
@@ -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
- ## Why `float` and not `int`
5
-
6
- A byte count is a whole number and cannot be negative, so `int` is the type it
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 */