@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.
@@ -0,0 +1,545 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
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 Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
7
+ import * as Bytes$Reventless from "./Bytes.res.mjs";
8
+ import * as Money$Reventless from "./Money.res.mjs";
9
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
10
+ import * as Percent$Reventless from "./Percent.res.mjs";
11
+ import * as Duration$Reventless from "./Duration.res.mjs";
12
+ import * as GeoPoint$Reventless from "./GeoPoint.res.mjs";
13
+ import * as Semantic$Reventless from "./Semantic.res.mjs";
14
+ import * as DateRange$Reventless from "./DateRange.res.mjs";
15
+ import * as Sensitive$Reventless from "../components/Sensitive.res.mjs";
16
+
17
+ let rawFormatter = "raw";
18
+
19
+ let formatters = [
20
+ rawFormatter,
21
+ Semantic$Reventless.Id.money,
22
+ Semantic$Reventless.Id.percent,
23
+ Semantic$Reventless.Id.bytes,
24
+ Semantic$Reventless.Id.duration,
25
+ Semantic$Reventless.Id.dateRange,
26
+ Semantic$Reventless.Id.geoPoint
27
+ ];
28
+
29
+ let segmentGrammar = /^[A-Za-z0-9_]+$/;
30
+
31
+ let whitespace = /\s+/g;
32
+
33
+ function parsePath(raw, insideEach) {
34
+ let text = raw.trim();
35
+ if (text === "") {
36
+ return {
37
+ TAG: "Error",
38
+ _0: "a tag with no path"
39
+ };
40
+ }
41
+ let relative = text.startsWith(".");
42
+ if (relative && !insideEach) {
43
+ return {
44
+ TAG: "Error",
45
+ _0: `"` + text + `" is item-relative, and there is no item outside an "each"`
46
+ };
47
+ }
48
+ let body = relative ? text.slice(1) : text;
49
+ let segments = body === "" ? [] : body.split(".");
50
+ if (segments.every(segment => segmentGrammar.test(segment))) {
51
+ return {
52
+ TAG: "Ok",
53
+ _0: {
54
+ text: text,
55
+ relative: relative,
56
+ segments: segments
57
+ }
58
+ };
59
+ } else {
60
+ return {
61
+ TAG: "Error",
62
+ _0: `"` + text + `" is not a path — segments are letters, digits and underscores, separated by dots`
63
+ };
64
+ }
65
+ }
66
+
67
+ function tokenize(source) {
68
+ let tokens = [];
69
+ let rest = source;
70
+ let failure;
71
+ let running = true;
72
+ while (running) {
73
+ let current = rest;
74
+ let start = current.indexOf("{{");
75
+ if (start !== -1) {
76
+ let before = current.slice(0, start);
77
+ if (before !== "") {
78
+ tokens.push({
79
+ TAG: "TextToken",
80
+ _0: before
81
+ });
82
+ }
83
+ let after = current.slice(start + 2 | 0);
84
+ let stop = after.indexOf("}}");
85
+ if (stop !== -1) {
86
+ tokens.push({
87
+ TAG: "TagToken",
88
+ _0: after.slice(0, stop).replace(whitespace, " ").trim()
89
+ });
90
+ rest = after.slice(stop + 2 | 0);
91
+ } else {
92
+ failure = `an opening "{{" with no closing "}}"`;
93
+ running = false;
94
+ }
95
+ } else {
96
+ if (current !== "") {
97
+ tokens.push({
98
+ TAG: "TextToken",
99
+ _0: current
100
+ });
101
+ }
102
+ running = false;
103
+ }
104
+ };
105
+ let message = failure;
106
+ if (message !== undefined) {
107
+ return {
108
+ TAG: "Error",
109
+ _0: message
110
+ };
111
+ } else {
112
+ return {
113
+ TAG: "Ok",
114
+ _0: tokens
115
+ };
116
+ }
117
+ }
118
+
119
+ function blockWord(kind) {
120
+ if (kind === "IfBlock") {
121
+ return "if";
122
+ } else {
123
+ return "each";
124
+ }
125
+ }
126
+
127
+ function parse(source) {
128
+ let message = tokenize(source);
129
+ if (message.TAG !== "Ok") {
130
+ return {
131
+ TAG: "Error",
132
+ _0: message._0
133
+ };
134
+ }
135
+ let root = [];
136
+ let stack = [];
137
+ let failure = {
138
+ contents: undefined
139
+ };
140
+ let fail = message => {
141
+ if (Stdlib_Option.isNone(failure.contents)) {
142
+ failure.contents = message;
143
+ return;
144
+ }
145
+ };
146
+ let body = () => {
147
+ let frame = stack.at(-1);
148
+ if (frame !== undefined) {
149
+ return frame.body;
150
+ } else {
151
+ return root;
152
+ }
153
+ };
154
+ let insideEach = () => stack.some(frame => frame.kind === "EachBlock");
155
+ let addField = (rawPath, formatter) => {
156
+ let message = parsePath(rawPath, insideEach());
157
+ if (message.TAG !== "Ok") {
158
+ return fail(message._0);
159
+ }
160
+ body().push({
161
+ TAG: "Field",
162
+ path: message._0,
163
+ formatter: formatter
164
+ });
165
+ };
166
+ let openBlock = (kind, rawPath) => {
167
+ if (kind === "EachBlock" && insideEach()) {
168
+ return fail(`an "each" inside an "each" — iteration is one level deep`);
169
+ }
170
+ let message = parsePath(rawPath, insideEach());
171
+ if (message.TAG !== "Ok") {
172
+ return fail(message._0);
173
+ }
174
+ stack.push({
175
+ kind: kind,
176
+ path: message._0,
177
+ body: []
178
+ });
179
+ };
180
+ message._0.forEach(token => {
181
+ if (!Stdlib_Option.isNone(failure.contents)) {
182
+ return;
183
+ }
184
+ if (token.TAG === "TextToken") {
185
+ body().push({
186
+ TAG: "Text",
187
+ _0: token._0
188
+ });
189
+ return;
190
+ }
191
+ let tag = token._0;
192
+ if (tag.startsWith("#")) {
193
+ let rest = tag.slice(1).trim();
194
+ let at = rest.indexOf(" ");
195
+ if (at === -1) {
196
+ return fail(`"{{# ` + rest + ` }}" opens a block with no path`);
197
+ }
198
+ let word = rest.slice(0, at);
199
+ switch (word) {
200
+ case "each" :
201
+ return openBlock("EachBlock", rest.slice(at + 1 | 0));
202
+ case "if" :
203
+ return openBlock("IfBlock", rest.slice(at + 1 | 0));
204
+ default:
205
+ return fail(`"` + word + `" is not a block — "if" and "each" are`);
206
+ }
207
+ } else {
208
+ if (tag.startsWith("/")) {
209
+ let word$1 = tag.slice(1).trim();
210
+ let frame = stack.pop();
211
+ if (frame === undefined) {
212
+ return fail(`a closing "{{/ ` + word$1 + ` }}" with nothing open`);
213
+ }
214
+ if (blockWord(frame.kind) !== word$1) {
215
+ return fail(`"{{/ ` + word$1 + ` }}" closes a "` + blockWord(frame.kind) + `"`);
216
+ }
217
+ let match = frame.kind;
218
+ let tmp;
219
+ tmp = match === "IfBlock" ? ({
220
+ TAG: "If",
221
+ path: frame.path,
222
+ body: frame.body
223
+ }) : ({
224
+ TAG: "Each",
225
+ path: frame.path,
226
+ body: frame.body
227
+ });
228
+ body().push(tmp);
229
+ return;
230
+ }
231
+ let match$1 = tag.split("|");
232
+ let len = match$1.length;
233
+ if (len >= 3) {
234
+ return fail(`"` + tag + `" names more than one formatter`);
235
+ }
236
+ switch (len) {
237
+ case 0 :
238
+ return fail(`"` + tag + `" names more than one formatter`);
239
+ case 1 :
240
+ let rawPath = match$1[0];
241
+ return addField(rawPath, undefined);
242
+ case 2 :
243
+ let rawPath$1 = match$1[0];
244
+ let name = match$1[1];
245
+ let formatter = name.trim();
246
+ if (formatters.includes(formatter)) {
247
+ return addField(rawPath$1, formatter);
248
+ } else {
249
+ return fail(`"` + formatter + `" is not a formatter — one of ` + formatters.join(", "));
250
+ }
251
+ }
252
+ }
253
+ });
254
+ let message$1 = failure.contents;
255
+ if (message$1 !== undefined) {
256
+ return {
257
+ TAG: "Error",
258
+ _0: message$1
259
+ };
260
+ }
261
+ let frame = stack.at(-1);
262
+ if (frame !== undefined) {
263
+ return {
264
+ TAG: "Error",
265
+ _0: `"{{# ` + blockWord(frame.kind) + ` ` + frame.path.text + ` }}" was never closed`
266
+ };
267
+ } else {
268
+ return {
269
+ TAG: "Ok",
270
+ _0: root
271
+ };
272
+ }
273
+ }
274
+
275
+ let nothing = {
276
+ value: undefined,
277
+ schema: undefined
278
+ };
279
+
280
+ function missing(path) {
281
+ return `[missing: ` + path.text + `]`;
282
+ }
283
+
284
+ function withheld(path) {
285
+ return `[withheld: ` + path.text + `]`;
286
+ }
287
+
288
+ function unwrap(schema) {
289
+ return Stdlib_Option.getOr(Semantic$Reventless.unwrapOptional(schema), schema);
290
+ }
291
+
292
+ function propertyOf(schema, name) {
293
+ let match = Stdlib_Option.getOr(Semantic$Reventless.unwrapOptional(schema), schema);
294
+ if (match.type === "object") {
295
+ return match.properties[name];
296
+ }
297
+ }
298
+
299
+ function elementOf(schema) {
300
+ let match = Stdlib_Option.getOr(Semantic$Reventless.unwrapOptional(schema), schema);
301
+ if (match.type !== "array") {
302
+ return;
303
+ }
304
+ let item = match.additionalItems;
305
+ if (item === "strip" || item === "strict") {
306
+ return;
307
+ } else {
308
+ return item;
309
+ }
310
+ }
311
+
312
+ function step(scope, segment) {
313
+ let match = scope.value;
314
+ let tmp;
315
+ tmp = typeof match === "object" && match !== null && !Array.isArray(match) ? match[segment] : undefined;
316
+ return {
317
+ value: tmp,
318
+ schema: Stdlib_Option.flatMap(scope.schema, __x => propertyOf(__x, segment))
319
+ };
320
+ }
321
+
322
+ function resolve(path, root, item) {
323
+ return Stdlib_Array.reduce(path.segments, path.relative ? Stdlib_Option.getOr(item, nothing) : root, step);
324
+ }
325
+
326
+ function lookup(payload, dotted) {
327
+ return Stdlib_Array.reduce(dotted.split("."), {
328
+ value: payload,
329
+ schema: undefined
330
+ }, step).value;
331
+ }
332
+
333
+ function isWithheld(schema) {
334
+ if (schema === undefined) {
335
+ return false;
336
+ }
337
+ if (Sensitive$Reventless.isFieldSensitive(schema)) {
338
+ return true;
339
+ }
340
+ let match = Semantic$Reventless.getFrom(schema);
341
+ if (match !== undefined) {
342
+ return Sensitive$Reventless.impliedBySemantic(match.id);
343
+ } else {
344
+ return false;
345
+ }
346
+ }
347
+
348
+ function parseSafely(json, schema) {
349
+ try {
350
+ return Primitive_option.some(S.parseOrThrow(json, schema));
351
+ } catch (exn) {
352
+ return;
353
+ }
354
+ }
355
+
356
+ function formatBySemantic(json, id) {
357
+ if (id === Semantic$Reventless.Id.money) {
358
+ return Stdlib_Option.map(parseSafely(json, Money$Reventless.schema), Money$Reventless.format);
359
+ } else if (id === Semantic$Reventless.Id.dateRange) {
360
+ return Stdlib_Option.map(parseSafely(json, DateRange$Reventless.schema), DateRange$Reventless.format);
361
+ } else if (id === Semantic$Reventless.Id.geoPoint) {
362
+ return Stdlib_Option.map(parseSafely(json, GeoPoint$Reventless.schema), GeoPoint$Reventless.format);
363
+ } else if (typeof json === "number") {
364
+ if (id === Semantic$Reventless.Id.percent) {
365
+ return Percent$Reventless.format(json);
366
+ } else if (id === Semantic$Reventless.Id.bytes) {
367
+ return Bytes$Reventless.format(json);
368
+ } else if (id === Semantic$Reventless.Id.duration) {
369
+ return Duration$Reventless.format(json | 0);
370
+ } else {
371
+ return;
372
+ }
373
+ } else {
374
+ return;
375
+ }
376
+ }
377
+
378
+ function scalar(value) {
379
+ if (value === null) {
380
+ return "";
381
+ }
382
+ if (Array.isArray(value)) {
383
+ return JSON.stringify(value);
384
+ }
385
+ switch (typeof value) {
386
+ case "boolean" :
387
+ if (value) {
388
+ return "true";
389
+ } else {
390
+ return "false";
391
+ }
392
+ case "string" :
393
+ return value;
394
+ case "number" :
395
+ return value.toString();
396
+ case "object" :
397
+ return JSON.stringify(value);
398
+ }
399
+ }
400
+
401
+ function truthy(value) {
402
+ if (value === undefined) {
403
+ return false;
404
+ }
405
+ if (value === null) {
406
+ return false;
407
+ }
408
+ if (Array.isArray(value)) {
409
+ return value.length !== 0;
410
+ }
411
+ switch (typeof value) {
412
+ case "boolean" :
413
+ return value;
414
+ case "string" :
415
+ return value !== "";
416
+ case "number" :
417
+ return value !== 0.0;
418
+ case "object" :
419
+ return true;
420
+ }
421
+ }
422
+
423
+ function renderField(path, formatter, root, item) {
424
+ let scope = resolve(path, root, item);
425
+ if (isWithheld(scope.schema)) {
426
+ return withheld(path);
427
+ }
428
+ let value = scope.value;
429
+ if (value === undefined) {
430
+ return missing(path);
431
+ }
432
+ if (value === null) {
433
+ return missing(path);
434
+ }
435
+ if (formatter !== undefined) {
436
+ if (formatter === rawFormatter) {
437
+ return scalar(value);
438
+ } else {
439
+ return Stdlib_Option.getOr(formatBySemantic(value, formatter), scalar(value));
440
+ }
441
+ }
442
+ let match = Stdlib_Option.flatMap(scope.schema, Semantic$Reventless.getFrom);
443
+ if (match !== undefined) {
444
+ return Stdlib_Option.getOr(formatBySemantic(value, match.id), scalar(value));
445
+ } else {
446
+ return scalar(value);
447
+ }
448
+ }
449
+
450
+ function renderNodes(nodes, root, item, out) {
451
+ nodes.forEach(node => {
452
+ switch (node.TAG) {
453
+ case "Text" :
454
+ out.push(node._0);
455
+ return;
456
+ case "Field" :
457
+ out.push(renderField(node.path, node.formatter, root, item));
458
+ return;
459
+ case "If" :
460
+ if (truthy(resolve(node.path, root, item).value)) {
461
+ return renderNodes(node.body, root, item, out);
462
+ } else {
463
+ return;
464
+ }
465
+ case "Each" :
466
+ let body = node.body;
467
+ let path = node.path;
468
+ let scope = resolve(path, root, item);
469
+ if (isWithheld(scope.schema)) {
470
+ out.push(withheld(path));
471
+ return;
472
+ }
473
+ let match = scope.value;
474
+ if (Array.isArray(match)) {
475
+ let element = Stdlib_Option.flatMap(scope.schema, elementOf);
476
+ let total = match.length;
477
+ let shown = total > 100 ? 100 : total;
478
+ for (let index = 0; index < shown; ++index) {
479
+ renderNodes(body, root, {
480
+ value: match[index],
481
+ schema: element
482
+ }, out);
483
+ }
484
+ if (total > shown) {
485
+ out.push(`[… ` + (total - shown | 0).toString() + ` more]`);
486
+ return;
487
+ } else {
488
+ return;
489
+ }
490
+ }
491
+ out.push(missing(path));
492
+ return;
493
+ break;
494
+ }
495
+ });
496
+ }
497
+
498
+ function render(template, payload, schema) {
499
+ let out = [];
500
+ renderNodes(template, {
501
+ value: payload,
502
+ schema: schema
503
+ }, undefined, out);
504
+ return out.join("");
505
+ }
506
+
507
+ function renderSource(source, payload, schema) {
508
+ return Stdlib_Result.map(parse(source), template => render(template, payload, schema));
509
+ }
510
+
511
+ let variantSchema = Semantic$Reventless.unionVariant;
512
+
513
+ let maxItems = 100;
514
+
515
+ export {
516
+ rawFormatter,
517
+ formatters,
518
+ segmentGrammar,
519
+ whitespace,
520
+ parsePath,
521
+ tokenize,
522
+ blockWord,
523
+ parse,
524
+ nothing,
525
+ missing,
526
+ withheld,
527
+ maxItems,
528
+ unwrap,
529
+ propertyOf,
530
+ elementOf,
531
+ step,
532
+ resolve,
533
+ lookup,
534
+ isWithheld,
535
+ parseSafely,
536
+ formatBySemantic,
537
+ scalar,
538
+ truthy,
539
+ renderField,
540
+ renderNodes,
541
+ render,
542
+ renderSource,
543
+ variantSchema,
544
+ }
545
+ /* S Not a pure module */
@@ -141,22 +141,23 @@ type commandJson = {
141
141
 
142
142
  // ── Schema-migration-on-read ──────────────────────────────────────────────────
143
143
  // Nested `@schema` types (notably `pluginDefinition`/`pluginStructure`) gain fields
144
- // over time — `kind`, `chapter`, `events`, `extensionPoints`, `apiExposed`, … Because
145
- // those types are JSON-encoded inside union-variant payloads, every optional field must
146
- // use the `js_nullable` (`T | null`) encoding: it is the only JSON-safe optional form,
147
- // since `S.option`/`nullableAsOption` carry `undefined`, which fails sury's
148
- // `jsonableValidation` inside a union variant. That encoding is *present-required on
149
- // decode*, so ONE message persisted before a field was added SuryError-bricks decode. For
150
- // an aggregate that rehydrates from its own event log (the Plugin lifecycle aggregate),
151
- // that single event then freezes EVERY later heartbeat/redetect/connect on that instance
152
- // — a silent lifecycle freeze with no error surfaced near the operator.
144
+ // over time — `kind`, `chapter`, `events`, `extensionPoints`, `apiExposed`, … A field
145
+ // that is not optional is *present-required on decode*, so ONE message persisted before
146
+ // it was added SuryError-bricks decode. For an aggregate that rehydrates from its own
147
+ // event log (the Plugin lifecycle aggregate), that single event then freezes EVERY later
148
+ // heartbeat/redetect/connect on that instance — a silent lifecycle freeze with no error
149
+ // surfaced near the operator.
153
150
  //
154
151
  // We heal on read. Strict decode stays the fast path (unchanged for every current
155
152
  // message); only when it throws do we schema-guide the raw JSON and retry once. The fill
156
153
  // walks the target sury schema and inserts, for any absent field, the value that field's
157
- // schema expects: `null` for a `T | null` union (→ `None`), `[]` for a missing array, the
154
+ // schema expects: `null` for a `T | null` union (→ `None`), nothing at all for an
155
+ // `option` (a union admitting `undefined` — also `None`), `[]` for a missing array, the
158
156
  // first variant of a mandatory enum (`kind` → `Domain`), a filled `{}` for a missing
159
- // nested object, and a zero value for a missing scalar. It descends only into values
157
+ // nested object, and a zero value for a missing scalar. The `undefined` arm mirrors the
158
+ // `null` one and must precede the enum/object guesses below it: an absent `option<enum>`
159
+ // would otherwise heal to that enum's first variant and an absent `option<record>` to a
160
+ // filled `{}`, turning `None` into a `Some` of an invented value. It descends only into values
160
161
  // actually present, matches tagged-union members by their `TAG` const, is purely additive
161
162
  // (clones via a JSON round-trip; never re-encodes through the schema), is idempotent on
162
163
  // valid data, and falls back to the ORIGINAL error when the fill doesn't resolve the
@@ -229,6 +230,7 @@ let fillMissingDefaults: (S.t<'a>, JSON.t, array<string>) => JSON.t = %raw(`func
229
230
  var has=schema.has||{};
230
231
  if(value===undefined){
231
232
  if(has.null) return null;
233
+ if(has.undefined) return undefined;
232
234
  var c=firstConst(schema.anyOf); if(c!==undefined) return c;
233
235
  var obj=(schema.anyOf||[]).find(function(s){return s.type==="object";}); if(obj) return fill(obj,{},path);
234
236
  return undefined;
@@ -273,7 +275,7 @@ let parseJsonTolerant = (json, schema) =>
273
275
  ->Int.toString} missing scalar field(s): ${scalarFills->Array.join(
274
276
  ", ",
275
277
  )}. A required scalar was added to a persisted type after this message was ` ++
276
- `written; the value above is fabricated, not recovered. Prefer a js_nullable (T | null) field.`,
278
+ `written; the value above is fabricated, not recovered. Prefer an optional field.`,
277
279
  )
278
280
  }
279
281
  value
@@ -87,6 +87,7 @@ let fillMissingDefaults = (function(schema, json, scalarFills){
87
87
  var has=schema.has||{};
88
88
  if(value===undefined){
89
89
  if(has.null) return null;
90
+ if(has.undefined) return undefined;
90
91
  var c=firstConst(schema.anyOf); if(c!==undefined) return c;
91
92
  var obj=(schema.anyOf||[]).find(function(s){return s.type==="object";}); if(obj) return fill(obj,{},path);
92
93
  return undefined;
@@ -126,7 +127,7 @@ function parseJsonTolerant(json, schema) {
126
127
  throw firstErr;
127
128
  }
128
129
  if (scalarFills.length !== 0) {
129
- console.warn(`[reventless] decoded a stored message by inventing ` + scalarFills.length.toString() + ` missing scalar field(s): ` + scalarFills.join(", ") + `. A required scalar was added to a persisted type after this message was written; the value above is fabricated, not recovered. Prefer a js_nullable (T | null) field.`);
130
+ console.warn(`[reventless] decoded a stored message by inventing ` + scalarFills.length.toString() + ` missing scalar field(s): ` + scalarFills.join(", ") + `. A required scalar was added to a persisted type after this message was written; the value above is fabricated, not recovered. Prefer an optional field.`);
130
131
  }
131
132
  return value;
132
133
  }