@reventlessdev/reventless-core 3.0.0-alpha.258 → 3.0.0-alpha.260

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +9 -9
  3. package/src/Projection.res +158 -40
  4. package/src/Projection.res.mjs +107 -28
  5. package/src/admin/UiFragmentRegistry/{StateChangeSlice → StateChange}/UiFragmentRegistry.res.mjs +1 -1
  6. package/src/admin/UiFragmentRegistry/{StateChangeSlice → StateChange}/UiFragmentRegistry_Behavior.res.mjs +1 -1
  7. package/src/admin/UiFragmentRegistry/{StateViewSlice → StateView}/UiFragments.res.mjs +1 -1
  8. package/src/admin/UiFragmentRegistry/{StateViewSlice → StateView}/UiFragments_Projection.res.mjs +1 -1
  9. package/src/components/Api/GraphQL_FragmentGenerator.res +16 -2
  10. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +13 -5
  11. package/src/components/Api/SchemaType.res +81 -1
  12. package/src/components/Api/SchemaType.res.mjs +82 -3
  13. package/src/components/Api/SuryToJsonSchema.res +10 -0
  14. package/src/components/Api/SuryToJsonSchema.res.mjs +135 -110
  15. package/src/components/ReadModel/ReadModel_Callback.res +5 -2
  16. package/src/components/ReadModel/ReadModel_Callback.res.mjs +2 -5
  17. package/src/components/StateViewSlice/StateViewSlice_Builder.res +7 -6
  18. package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +2 -5
  19. package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
  20. package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +1 -1
  21. package/src/plugin/component/Plugin_Structure.res +9 -46
  22. package/src/plugin/component/Plugin_Structure.res.mjs +8 -40
  23. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +1 -1
  24. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +1 -1
  25. package/tests/api/BrandedMarkerCompositionTest.res +74 -0
  26. package/tests/api/BrandedMarkerCompositionTest.res.mjs +74 -0
  27. package/tests/api/PsBrandedMarkersReadModel.res +24 -0
  28. package/tests/api/PsBrandedMarkersReadModel.res.mjs +87 -0
  29. package/tests/api/SuryToJsonSchemaTest.res +91 -3
  30. package/tests/api/SuryToJsonSchemaTest.res.mjs +66 -4
  31. package/tests/plugin/PluginStructureAccessTest.res.mjs +3 -3
  32. package/tests/plugin/PluginStructureTest.res +24 -5
  33. package/tests/plugin/PluginStructureTest.res.mjs +32 -26
  34. package/tests/plugin/{StateChangeSlice → StateChange}/PsAttachInvoice.res.mjs +1 -1
  35. package/tests/plugin/{StateChangeSlice → StateChange}/PsChangePhoto.res.mjs +1 -1
  36. package/tests/plugin/{StateChangeSlice → StateChange}/PsDispatchShipment.res.mjs +1 -1
  37. package/tests/plugin/{StateChangeSlice → StateChange}/PsGatedCommands.res.mjs +1 -1
  38. package/tests/plugin/{StateChangeSlice → StateChange}/PsPlaceOrder.res.mjs +1 -1
  39. package/tests/plugin/{StateChangeSlice → StateChange}/PsReserveStock.res.mjs +1 -1
  40. package/tests/plugin/{StateChangeSlice → StateChange}/PsShipOrder.res.mjs +1 -1
  41. package/tests/plugin/{StateChangeSlice → StateChange}/PsTransitionSwitch.res.mjs +1 -1
  42. package/tests/plugin/{StateChangeSlice → StateChange}/PsTypoStore.res.mjs +1 -1
  43. package/tests/plugin/{StateChangeSlice → StateChange}/PsUploadAvatar.res.mjs +1 -1
  44. package/tests/plugin/{StateChangeSlice → StateChange}/PsUploadImages.res.mjs +1 -1
  45. package/tests/plugin/{StateViewSlice → StateView}/PsAnnotatedView.res +4 -2
  46. package/tests/plugin/{StateViewSlice → StateView}/PsAnnotatedView.res.mjs +8 -4
  47. package/tests/plugin/{StateViewSlice → StateView}/PsAvailableProductsView.res.mjs +1 -1
  48. package/tests/plugin/{StateViewSlice → StateView}/PsCategoriesView.res.mjs +1 -1
  49. package/tests/plugin/{StateViewSlice → StateView}/PsCustomersView.res.mjs +1 -1
  50. package/tests/plugin/{StateViewSlice → StateView}/PsGatedView.res.mjs +1 -1
  51. package/tests/plugin/{StateViewSlice → StateView}/PsOrdersView.res.mjs +1 -1
  52. package/tests/plugin/{StateViewSlice → StateView}/PsShipmentsView.res.mjs +1 -1
  53. package/tests/projection/LifecycleTrailTest.res +139 -0
  54. package/tests/projection/LifecycleTrailTest.res.mjs +280 -0
  55. /package/src/admin/UiFragmentRegistry/{StateChangeSlice → StateChange}/UiFragmentRegistry.res +0 -0
  56. /package/src/admin/UiFragmentRegistry/{StateChangeSlice → StateChange}/UiFragmentRegistry_Behavior.res +0 -0
  57. /package/src/admin/UiFragmentRegistry/{StateViewSlice → StateView}/UiFragments.res +0 -0
  58. /package/src/admin/UiFragmentRegistry/{StateViewSlice → StateView}/UiFragments_Projection.res +0 -0
  59. /package/tests/plugin/{StateChangeSlice → StateChange}/PsAttachInvoice.res +0 -0
  60. /package/tests/plugin/{StateChangeSlice → StateChange}/PsChangePhoto.res +0 -0
  61. /package/tests/plugin/{StateChangeSlice → StateChange}/PsDispatchShipment.res +0 -0
  62. /package/tests/plugin/{StateChangeSlice → StateChange}/PsGatedCommands.res +0 -0
  63. /package/tests/plugin/{StateChangeSlice → StateChange}/PsPlaceOrder.res +0 -0
  64. /package/tests/plugin/{StateChangeSlice → StateChange}/PsReserveStock.res +0 -0
  65. /package/tests/plugin/{StateChangeSlice → StateChange}/PsShipOrder.res +0 -0
  66. /package/tests/plugin/{StateChangeSlice → StateChange}/PsTransitionSwitch.res +0 -0
  67. /package/tests/plugin/{StateChangeSlice → StateChange}/PsTypoStore.res +0 -0
  68. /package/tests/plugin/{StateChangeSlice → StateChange}/PsUploadAvatar.res +0 -0
  69. /package/tests/plugin/{StateChangeSlice → StateChange}/PsUploadImages.res +0 -0
  70. /package/tests/plugin/{StateViewSlice → StateView}/PsAvailableProductsView.res +0 -0
  71. /package/tests/plugin/{StateViewSlice → StateView}/PsCategoriesView.res +0 -0
  72. /package/tests/plugin/{StateViewSlice → StateView}/PsCustomersView.res +0 -0
  73. /package/tests/plugin/{StateViewSlice → StateView}/PsGatedView.res +0 -0
  74. /package/tests/plugin/{StateViewSlice → StateView}/PsOrdersView.res +0 -0
  75. /package/tests/plugin/{StateViewSlice → StateView}/PsShipmentsView.res +0 -0
@@ -10,6 +10,13 @@ type rec schemaType =
10
10
  | ScalarBigInt
11
11
  | EntityId
12
12
  | DateTime
13
+ | /** A calendar day, with no instant in it. A shape of its own rather than a
14
+ plain string so `format: "date"` reaches the JSON Schema — but, unlike
15
+ `DateTime`, it is not excluded from the generic semantic wrapper below,
16
+ so it carries `x-reventless-semantic` as well. `DateTime`'s exclusion
17
+ exists because its bare-`format` output predates the marker and is a
18
+ published contract; a new semantic has no such history. */
19
+ CalendarDate
13
20
  | Nullable(schemaType)
14
21
  | ArrayOf(schemaType)
15
22
  | ObjectRef(string, dict<schemaType>)
@@ -20,6 +27,10 @@ type rec schemaType =
20
27
  are *not* expressed this way: they long predate the generic marker and
21
28
  their JSON Schema `format` output is a published contract. */
22
29
  Semantic(Reventless.Semantic.t, schemaType)
30
+ | /** A field whose type declares the value it opens with. Wraps the shape the
31
+ value has, as `Semantic` does, so a consumer that cares only about shape
32
+ unwraps: a default changes where a form starts, never what the field is. */
33
+ Defaulted(JSON.t, schemaType)
23
34
  | /** A variant used as a field: the union's name, and one arm per constructor
24
35
  keyed by the `TAG` sury discriminates on. Each arm is the `ObjectRef` its
25
36
  member type is emitted from, so the arm's own name travels with it and
@@ -32,6 +43,7 @@ type rec schemaType =
32
43
 
33
44
  let isTagged = Reventless.DcbTag.isTagged
34
45
  let isDateTime = Reventless.DateTime.isDateTime
46
+ let isCalendarDate = Reventless.CalendarDate.isCalendarDate
35
47
 
36
48
  let isIdFieldName = (name: string): bool => {
37
49
  let lower = String.toLowerCase(name)
@@ -74,6 +86,17 @@ let canonicalName = (id: string): option<string> =>
74
86
  ->Option.map(((_, name)) => name)
75
87
 
76
88
  let rec fromSury = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): schemaType => {
89
+ // Outermost, so it wraps whatever the field turns out to be, a semantic
90
+ // composite included — the other order buries the marker under a shape whose
91
+ // consumers unwrap it.
92
+ let shape = withoutDefault(~parentName, ~fieldName, schema)
93
+ switch Reventless.FieldDefault.getFrom(schema) {
94
+ | Some(value) => Defaulted(value, shape)
95
+ | None => shape
96
+ }
97
+ }
98
+
99
+ and withoutDefault = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): schemaType => {
77
100
  // Semantics the IR already has a dedicated shape for keep it: `dateTime` and
78
101
  // `reference` are read below via `isDateTime` / `getTarget`, both of which now
79
102
  // consult the generic marker, and their `format` output is a published
@@ -107,6 +130,7 @@ and shapeOf = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): s
107
130
  switch schema {
108
131
  | String({const: ?Some(_)}) => ScalarString
109
132
  | String(_) if isDateTime(schema) => DateTime
133
+ | String(_) if isCalendarDate(schema) => CalendarDate
110
134
  | String(_) => isIdFieldName(fieldName) ? EntityId : ScalarString
111
135
  // `Int32` and `Port` are both whole-number formats; anything else (or no
112
136
  // format at all) is a `float` as far as the schema is concerned.
@@ -348,15 +372,71 @@ let optionalFieldNames = (schema: S.t<unknown>): array<string> =>
348
372
  | _ => []
349
373
  }
350
374
 
375
+ // A lifecycle trail's entries are `{state, at}`, and `state` holds the record's
376
+ // own lifecycle. Walked under the lifecycle field's name so it resolves to the
377
+ // enum that field already emits, rather than a second copy under the trail's
378
+ // path — the trail and the current state must read as one vocabulary.
379
+ let trailShape = (
380
+ ~parentName: string,
381
+ ~trailField: string,
382
+ ~lifecycleField: string,
383
+ schema: S.t<unknown>,
384
+ ): schemaType => {
385
+ let entryName =
386
+ parentName ++
387
+ trailField->String.charAt(0)->String.toUpperCase ++
388
+ trailField->String.slice(~start=1, ~end=trailField->String.length)
389
+ let entrySchema = switch schema {
390
+ | Array({additionalItems: Schema(item)}) => Some(item)
391
+ | _ => None
392
+ }
393
+ switch entrySchema {
394
+ | Some(Object({properties})) =>
395
+ let fields = Dict.make()
396
+ properties
397
+ ->Dict.toArray
398
+ ->Array.forEach(((propName, propSchema)) =>
399
+ fields->Dict.set(
400
+ propName,
401
+ propName === "state"
402
+ ? fromSury(~parentName, ~fieldName=lifecycleField, propSchema)
403
+ : fromSury(~parentName=entryName, ~fieldName=propName, propSchema),
404
+ )
405
+ )
406
+ let shape = ArrayOf(ObjectRef(entryName, fields))
407
+ // Walking the entries by shape skips the semantic dispatch `fromSury` does,
408
+ // so the marker is re-applied here. Without it the trail reaches a consumer
409
+ // as an unremarkable array of objects, and recognising it would come down to
410
+ // the field's name.
411
+ switch Reventless.Semantic.get(schema) {
412
+ | Some(sem) => Semantic(sem, shape)
413
+ | None => shape
414
+ }
415
+ | _ => fromSury(~parentName, ~fieldName=trailField, schema)
416
+ }
417
+ }
418
+
351
419
  let fromSuryObject = (~typeName: string, schema: S.t<unknown>): option<dict<schemaType>> =>
352
420
  switch schema {
353
421
  | Object({properties}) =>
422
+ let trail = switch (
423
+ Reventless.Lifecycle.Trail.fieldName(schema),
424
+ Reventless.Lifecycle.fieldName(schema),
425
+ ) {
426
+ | (Some(trailField), Some(lifecycleField)) => Some((trailField, lifecycleField))
427
+ | _ => None
428
+ }
354
429
  let fields = Dict.make()
355
430
  properties
356
431
  ->Dict.toArray
357
432
  ->Array.forEach(((propName, propSchema)) => {
358
433
  if propName !== "TAG" {
359
- fields->Dict.set(propName, fromSury(~parentName=typeName, ~fieldName=propName, propSchema))
434
+ let shape = switch trail {
435
+ | Some((trailField, lifecycleField)) if trailField === propName =>
436
+ trailShape(~parentName=typeName, ~trailField, ~lifecycleField, propSchema)
437
+ | _ => fromSury(~parentName=typeName, ~fieldName=propName, propSchema)
438
+ }
439
+ fields->Dict.set(propName, shape)
360
440
  }
361
441
  })
362
442
  Some(fields)
@@ -3,10 +3,13 @@
3
3
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
6
- import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
6
+ import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/semantic/DateTime.res.mjs";
7
7
  import * as Semantic$Reventless from "@reventlessdev/reventless-spec/src/semantic/Semantic.res.mjs";
8
+ import * as Lifecycle$Reventless from "@reventlessdev/reventless-spec/src/types/Lifecycle.res.mjs";
8
9
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
9
10
  import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
11
+ import * as CalendarDate$Reventless from "@reventlessdev/reventless-spec/src/semantic/CalendarDate.res.mjs";
12
+ import * as FieldDefault$Reventless from "@reventlessdev/reventless-spec/src/components/FieldDefault.res.mjs";
10
13
 
11
14
  function isIdFieldName(name) {
12
15
  let lower = name.toLowerCase();
@@ -52,6 +55,20 @@ function canonicalName(id) {
52
55
  }
53
56
 
54
57
  function fromSury(parentName, fieldName, schema) {
58
+ let shape = withoutDefault(parentName, fieldName, schema);
59
+ let value = FieldDefault$Reventless.getFrom(schema);
60
+ if (value !== undefined) {
61
+ return {
62
+ TAG: "Defaulted",
63
+ _0: value,
64
+ _1: shape
65
+ };
66
+ } else {
67
+ return shape;
68
+ }
69
+ }
70
+
71
+ function withoutDefault(parentName, fieldName, schema) {
55
72
  let sem = Semantic$Reventless.get(schema);
56
73
  if (sem === undefined) {
57
74
  return shapeOf(parentName, fieldName, schema);
@@ -79,6 +96,8 @@ function shapeOf(parentName, fieldName, schema) {
79
96
  return "ScalarString";
80
97
  } else if (DateTime$Reventless.isDateTime(schema)) {
81
98
  return "DateTime";
99
+ } else if (CalendarDate$Reventless.isCalendarDate(schema)) {
100
+ return "CalendarDate";
82
101
  } else if (isIdFieldName(fieldName)) {
83
102
  return "EntityId";
84
103
  } else {
@@ -324,17 +343,72 @@ function optionalFieldNames(schema) {
324
343
  }
325
344
  }
326
345
 
346
+ function trailShape(parentName, trailField, lifecycleField, schema) {
347
+ let entryName = parentName + trailField.charAt(0).toUpperCase() + trailField.slice(1, trailField.length);
348
+ let entrySchema;
349
+ if (schema.type === "array") {
350
+ let item = schema.additionalItems;
351
+ entrySchema = item === "strip" || item === "strict" ? undefined : item;
352
+ } else {
353
+ entrySchema = undefined;
354
+ }
355
+ if (entrySchema === undefined) {
356
+ return fromSury(parentName, trailField, schema);
357
+ }
358
+ if (entrySchema.type !== "object") {
359
+ return fromSury(parentName, trailField, schema);
360
+ }
361
+ let fields = {};
362
+ Object.entries(entrySchema.properties).forEach(param => {
363
+ let propSchema = param[1];
364
+ let propName = param[0];
365
+ fields[propName] = propName === "state" ? fromSury(parentName, lifecycleField, propSchema) : fromSury(entryName, propName, propSchema);
366
+ });
367
+ let shape = {
368
+ TAG: "ArrayOf",
369
+ _0: {
370
+ TAG: "ObjectRef",
371
+ _0: entryName,
372
+ _1: fields
373
+ }
374
+ };
375
+ let sem = Semantic$Reventless.get(schema);
376
+ if (sem !== undefined) {
377
+ return {
378
+ TAG: "Semantic",
379
+ _0: sem,
380
+ _1: shape
381
+ };
382
+ } else {
383
+ return shape;
384
+ }
385
+ }
386
+
327
387
  function fromSuryObject(typeName, schema) {
328
388
  if (schema.type !== "object") {
329
389
  return;
330
390
  }
391
+ let match = Lifecycle$Reventless.Trail.fieldName(schema);
392
+ let match$1 = Lifecycle$Reventless.fieldName(schema);
393
+ let trail = match !== undefined && match$1 !== undefined ? [
394
+ match,
395
+ match$1
396
+ ] : undefined;
331
397
  let fields = {};
332
398
  Object.entries(schema.properties).forEach(param => {
333
399
  let propName = param[0];
334
- if (propName !== "TAG") {
335
- fields[propName] = fromSury(typeName, propName, param[1]);
400
+ if (propName === "TAG") {
336
401
  return;
337
402
  }
403
+ let propSchema = param[1];
404
+ let shape;
405
+ if (trail !== undefined) {
406
+ let trailField = trail[0];
407
+ shape = trailField === propName ? trailShape(typeName, trailField, trail[1], propSchema) : fromSury(typeName, propName, propSchema);
408
+ } else {
409
+ shape = fromSury(typeName, propName, propSchema);
410
+ }
411
+ fields[propName] = shape;
338
412
  });
339
413
  return fields;
340
414
  }
@@ -343,21 +417,26 @@ let isTagged = DcbTag$Reventless.isTagged;
343
417
 
344
418
  let isDateTime = DateTime$Reventless.isDateTime;
345
419
 
420
+ let isCalendarDate = CalendarDate$Reventless.isCalendarDate;
421
+
346
422
  let maxReportDepth = 12;
347
423
 
348
424
  export {
349
425
  isTagged,
350
426
  isDateTime,
427
+ isCalendarDate,
351
428
  isIdFieldName,
352
429
  isIdsFieldName,
353
430
  semanticCompositeNames,
354
431
  canonicalName,
355
432
  fromSury,
433
+ withoutDefault,
356
434
  shapeOf,
357
435
  maxReportDepth,
358
436
  collectUnclassifiedUnions,
359
437
  unclassifiedUnions,
360
438
  optionalFieldNames,
439
+ trailShape,
361
440
  fromSuryObject,
362
441
  }
363
442
  /* DcbTag-Reventless Not a pure module */
@@ -207,6 +207,7 @@ let rec fromSchemaType = (~inputNames: bool=false, st: SchemaType.schemaType): J
207
207
  | ScalarBigInt => jsonObject([("type", str("integer"))])
208
208
  | EntityId => jsonObject([("type", str("string")), ("format", str("uuid"))])
209
209
  | DateTime => jsonObject([("type", str("string")), ("format", str("date-time"))])
210
+ | CalendarDate => jsonObject([("type", str("string")), ("format", str("date"))])
210
211
  | Nullable(inner) =>
211
212
  let innerSchema = fromSchemaType(~inputNames, inner)
212
213
  jsonObject([("oneOf", JSON.Encode.array([innerSchema, jsonObject([("type", str("null"))])]))])
@@ -233,6 +234,15 @@ let rec fromSchemaType = (~inputNames: bool=false, st: SchemaType.schemaType): J
233
234
  | _ => base
234
235
  }
235
236
  base->withSemantic(sem)
237
+ // JSON Schema's own keyword, not an `x-reventless-` extension: the vocabulary
238
+ // already has a word for this one, so any client reads it.
239
+ | Defaulted(value, inner) =>
240
+ switch fromSchemaType(~inputNames, inner)->JSON.Decode.object {
241
+ | Some(obj) =>
242
+ obj->Dict.set("default", value)
243
+ JSON.Encode.object(obj)
244
+ | None => fromSchemaType(~inputNames, inner)
245
+ }
236
246
  // A union has no input form — see the fragment generator, which emits `String`
237
247
  // and says so — hence no name to publish on either arm.
238
248
  // A union is `oneOf` its arms, each an object carrying the `TAG` const it is
@@ -170,119 +170,144 @@ function withGraphqlInput(schema, name) {
170
170
  }
171
171
  }
172
172
 
173
- function fromSchemaType(inputNamesOpt, st) {
174
- let inputNames = inputNamesOpt !== undefined ? inputNamesOpt : false;
175
- if (typeof st !== "object") {
176
- switch (st) {
177
- case "ScalarNumber" :
178
- return Object.fromEntries([[
179
- "type",
180
- "number"
181
- ]]);
182
- case "ScalarBoolean" :
183
- return Object.fromEntries([[
184
- "type",
185
- "boolean"
186
- ]]);
187
- case "ScalarInt" :
188
- case "ScalarBigInt" :
189
- return Object.fromEntries([[
190
- "type",
191
- "integer"
192
- ]]);
193
- case "EntityId" :
194
- return Object.fromEntries([
195
- [
196
- "type",
197
- "string"
198
- ],
199
- [
200
- "format",
201
- "uuid"
202
- ]
203
- ]);
204
- case "DateTime" :
205
- return Object.fromEntries([
206
- [
207
- "type",
208
- "string"
209
- ],
210
- [
211
- "format",
212
- "date-time"
213
- ]
214
- ]);
215
- case "ScalarString" :
216
- case "Unknown" :
217
- return Object.fromEntries([[
218
- "type",
219
- "string"
220
- ]]);
221
- }
222
- } else {
223
- switch (st.TAG) {
224
- case "Nullable" :
225
- let innerSchema = fromSchemaType(inputNames, st._0);
226
- return Object.fromEntries([[
227
- "oneOf",
173
+ function fromSchemaType(_inputNamesOpt, _st) {
174
+ while (true) {
175
+ let inputNamesOpt = _inputNamesOpt;
176
+ let st = _st;
177
+ let inputNames = inputNamesOpt !== undefined ? inputNamesOpt : false;
178
+ if (typeof st !== "object") {
179
+ switch (st) {
180
+ case "ScalarNumber" :
181
+ return Object.fromEntries([[
182
+ "type",
183
+ "number"
184
+ ]]);
185
+ case "ScalarBoolean" :
186
+ return Object.fromEntries([[
187
+ "type",
188
+ "boolean"
189
+ ]]);
190
+ case "ScalarInt" :
191
+ case "ScalarBigInt" :
192
+ return Object.fromEntries([[
193
+ "type",
194
+ "integer"
195
+ ]]);
196
+ case "EntityId" :
197
+ return Object.fromEntries([
198
+ [
199
+ "type",
200
+ "string"
201
+ ],
228
202
  [
229
- innerSchema,
230
- Object.fromEntries([[
231
- "type",
232
- "null"
233
- ]])
203
+ "format",
204
+ "uuid"
234
205
  ]
235
- ]]);
236
- case "ArrayOf" :
237
- return Object.fromEntries([
238
- [
239
- "type",
240
- "array"
241
- ],
242
- [
243
- "items",
244
- fromSchemaType(inputNames, st._0)
245
- ]
246
- ]);
247
- case "ObjectRef" :
248
- let base = objectRefToJsonSchema(undefined, undefined, undefined, undefined, inputNames, st._1);
249
- if (inputNames) {
250
- return withGraphqlInput(base, st._0);
251
- } else {
252
- return base;
253
- }
254
- case "Enum" :
255
- return Object.fromEntries([
256
- [
257
- "type",
258
- "string"
259
- ],
260
- [
261
- "enum",
262
- st._1.map(prim => prim)
263
- ]
264
- ]);
265
- case "Semantic" :
266
- let inner = st._1;
267
- let sem = st._0;
268
- let base$1 = fromSchemaType(inputNames, inner);
269
- let match = SchemaType$ReventlessCore.canonicalName(sem.id);
270
- let base$2 = inputNames && match !== undefined && typeof inner === "object" && inner.TAG === "ObjectRef" ? withGraphqlInput(base$1, match + "Input") : base$1;
271
- return withSemantic(base$2, sem);
272
- case "TaggedUnion" :
273
- let members = st._1.map(param => armToJsonSchema(inputNames, param[0], param[1]));
274
- return Object.fromEntries([
275
- [
276
- "oneOf",
277
- members
278
- ],
279
- [
280
- "x-reventless-union",
281
- st._0
282
- ]
283
- ]);
206
+ ]);
207
+ case "DateTime" :
208
+ return Object.fromEntries([
209
+ [
210
+ "type",
211
+ "string"
212
+ ],
213
+ [
214
+ "format",
215
+ "date-time"
216
+ ]
217
+ ]);
218
+ case "CalendarDate" :
219
+ return Object.fromEntries([
220
+ [
221
+ "type",
222
+ "string"
223
+ ],
224
+ [
225
+ "format",
226
+ "date"
227
+ ]
228
+ ]);
229
+ case "ScalarString" :
230
+ case "Unknown" :
231
+ return Object.fromEntries([[
232
+ "type",
233
+ "string"
234
+ ]]);
235
+ }
236
+ } else {
237
+ switch (st.TAG) {
238
+ case "Nullable" :
239
+ let innerSchema = fromSchemaType(inputNames, st._0);
240
+ return Object.fromEntries([[
241
+ "oneOf",
242
+ [
243
+ innerSchema,
244
+ Object.fromEntries([[
245
+ "type",
246
+ "null"
247
+ ]])
248
+ ]
249
+ ]]);
250
+ case "ArrayOf" :
251
+ return Object.fromEntries([
252
+ [
253
+ "type",
254
+ "array"
255
+ ],
256
+ [
257
+ "items",
258
+ fromSchemaType(inputNames, st._0)
259
+ ]
260
+ ]);
261
+ case "ObjectRef" :
262
+ let base = objectRefToJsonSchema(undefined, undefined, undefined, undefined, inputNames, st._1);
263
+ if (inputNames) {
264
+ return withGraphqlInput(base, st._0);
265
+ } else {
266
+ return base;
267
+ }
268
+ case "Enum" :
269
+ return Object.fromEntries([
270
+ [
271
+ "type",
272
+ "string"
273
+ ],
274
+ [
275
+ "enum",
276
+ st._1.map(prim => prim)
277
+ ]
278
+ ]);
279
+ case "Semantic" :
280
+ let inner = st._1;
281
+ let sem = st._0;
282
+ let base$1 = fromSchemaType(inputNames, inner);
283
+ let match = SchemaType$ReventlessCore.canonicalName(sem.id);
284
+ let base$2 = inputNames && match !== undefined && typeof inner === "object" && inner.TAG === "ObjectRef" ? withGraphqlInput(base$1, match + "Input") : base$1;
285
+ return withSemantic(base$2, sem);
286
+ case "Defaulted" :
287
+ let inner$1 = st._1;
288
+ let obj = Stdlib_JSON.Decode.object(fromSchemaType(inputNames, inner$1));
289
+ if (obj !== undefined) {
290
+ obj["default"] = st._0;
291
+ return obj;
292
+ }
293
+ _st = inner$1;
294
+ _inputNamesOpt = inputNames;
295
+ continue;
296
+ case "TaggedUnion" :
297
+ let members = st._1.map(param => armToJsonSchema(inputNames, param[0], param[1]));
298
+ return Object.fromEntries([
299
+ [
300
+ "oneOf",
301
+ members
302
+ ],
303
+ [
304
+ "x-reventless-union",
305
+ st._0
306
+ ]
307
+ ]);
308
+ }
284
309
  }
285
- }
310
+ };
286
311
  }
287
312
 
288
313
  function armToJsonSchema(inputNamesOpt, tag, armType) {
@@ -31,13 +31,16 @@ module Make = (
31
31
  json,
32
32
  )} actions:${actionsStr}`,
33
33
  )->Effect.runSync
34
- actions
34
+ // Rewritten here rather than at the apply below: a trail entry is
35
+ // stamped with this envelope's own time.
36
+ actions->Array.map(
37
+ Projection.rewriteAction(_, ~at=context.meta.time, ReadModelSpec.stateSchema),
38
+ )
35
39
  })
36
40
  ->Array.flat
37
41
  ->Array.reduce(Effect.succeed(), (acc, action) =>
38
42
  acc->Effect.flatMap(
39
43
  _ => {
40
- let action = Projection.rewriteAction(action, ReadModelSpec.stateSchema)
41
44
  Effect.promise(
42
45
  () => Projection.handleAction(~comp, action, Spec.operations, ReadModelSpec.subIdConfig),
43
46
  )->Effect.map(_ => ())
@@ -28,11 +28,8 @@ function Make(ReadModelSpec) {
28
28
  let actionsStr = LogFormat$ReventlessCore.actionNames(actions);
29
29
  let idxStr = (idx + 1 | 0).toString();
30
30
  Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, json, `handling event ` + idxStr + `/` + total + ` from ` + sourceName + `: ` + LogFormat$ReventlessCore.eventDetail(json) + ` actions:` + actionsStr));
31
- return actions;
32
- }).flat(), Effect.succeed(), (acc, action) => Effect.flatMap(acc, () => {
33
- let action$1 = Projection$ReventlessCore.rewriteAction(action, ReadModelSpec.stateSchema);
34
- return Effect.map(Effect.promise(() => Projection$ReventlessCore.handleAction(comp, action$1, Spec.operations, ReadModelSpec.subIdConfig)), param => {});
35
- }));
31
+ return actions.map(__x => Projection$ReventlessCore.rewriteAction(__x, context.meta.time, ReadModelSpec.stateSchema));
32
+ }).flat(), Effect.succeed(), (acc, action) => Effect.flatMap(acc, () => Effect.map(Effect.promise(() => Projection$ReventlessCore.handleAction(comp, action, Spec.operations, ReadModelSpec.subIdConfig)), param => {})));
36
33
  });
37
34
  return {
38
35
  EventProjector: EventProjector,
@@ -148,18 +148,19 @@ module Make = (
148
148
  ~detail=eventData,
149
149
  `handling event ${idxStr}/${total}: ${LogFormat.bold(eventType)}${fieldsStr} ${actionsStr}`,
150
150
  )->Effect.runSync
151
- actions
151
+ // A slice's state carries the same synthetic `displayName` the
152
+ // ppx injects for a read model's, and the same lifecycle trail.
153
+ // Rewritten here rather than at the apply below because the
154
+ // trail entry is stamped with this envelope's own time.
155
+ actions->Array.map(
156
+ FrameworkProjection.rewriteAction(_, ~at=meta.time, Spec.stateSchema),
157
+ )
152
158
  | None => []
153
159
  }
154
160
  })
155
161
  ->Array.flat
156
162
  ->Array.reduce(Effect.succeed(), (acc, action) =>
157
163
  acc->Effect.flatMap(_ => {
158
- // A slice's state carries the same synthetic `displayName` the
159
- // ppx injects for a read model's, so it is composed on the same
160
- // terms — without this the column stays null and every surface
161
- // names the row by its id.
162
- let action = FrameworkProjection.rewriteAction(action, Spec.stateSchema)
163
164
  Effect.promise(() =>
164
165
  FrameworkProjection.handleAction(~comp, action, projectionOps, Spec.subIdConfig)
165
166
  )->Effect.map(_ => ())
@@ -120,11 +120,8 @@ function Make(RuntimeEnvironment) {
120
120
  let f = Object.entries(dataDict).map(param => param[0] + `:` + JSON.stringify(param[1])).join(",");
121
121
  let fieldsStr = f === "" ? "" : `({` + f + `})`;
122
122
  Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, dataDict, `handling event ` + idxStr + `/` + total + `: ` + LogFormat$ReventlessCore.bold(eventType) + fieldsStr + ` ` + actionsStr));
123
- return actions;
124
- }).flat(), Effect.succeed(), (acc, action) => Effect.flatMap(acc, () => {
125
- let action$1 = Projection$ReventlessCore.rewriteAction(action, Spec.stateSchema);
126
- return Effect.map(Effect.promise(() => Projection$ReventlessCore.handleAction(comp, action$1, projectionOps, Spec.subIdConfig)), param => {});
127
- }));
123
+ return actions.map(__x => Projection$ReventlessCore.rewriteAction(__x, meta.time, Spec.stateSchema));
124
+ }).flat(), Effect.succeed(), (acc, action) => Effect.flatMap(acc, () => Effect.map(Effect.promise(() => Projection$ReventlessCore.handleAction(comp, action, projectionOps, Spec.subIdConfig)), param => {})));
128
125
  });
129
126
  let handler = SpecificEventCollector.makeHandler(ec, jsonEventsHandler);
130
127
  let resources = Component$ReventlessCore.outputs(queryDb).resources;
@@ -48,7 +48,9 @@ module Make = (
48
48
  `handling event ${idx.contents->Int.toString}/${count}: ${LogFormat.bold(raw.eventType)}(${id}) ${actionsStr}`,
49
49
  )->Effect.runSync
50
50
  allActions->Array.pushMany(
51
- actions->Array.map(FrameworkProjection.rewriteAction(_, Spec.stateSchema)),
51
+ actions->Array.map(
52
+ FrameworkProjection.rewriteAction(_, ~at=raw.meta.time, Spec.stateSchema),
53
+ ),
52
54
  )
53
55
  Some(event)
54
56
  | None => None
@@ -35,7 +35,7 @@ function Make(Spec) {
35
35
  });
36
36
  let actionsStr = LogFormat$ReventlessCore.actionNames(actions);
37
37
  Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, raw.data, `handling event ` + idx.contents.toString() + `/` + count + `: ` + LogFormat$ReventlessCore.bold(raw.eventType) + `(` + id + `) ` + actionsStr));
38
- allActions.push(...actions.map(__x => Projection$ReventlessCore.rewriteAction(__x, Spec.stateSchema)));
38
+ allActions.push(...actions.map(__x => Projection$ReventlessCore.rewriteAction(__x, raw.meta.time, Spec.stateSchema)));
39
39
  return Primitive_option.some(event);
40
40
  });
41
41
  let skipped = rawEvents.length - events.length | 0;