@reventlessdev/reventless-spec 3.0.0-alpha.132 → 3.0.0-alpha.134

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 (80) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +2 -2
  3. package/src/AnsiStyle.res +0 -1
  4. package/src/LogPrefix.res +4 -13
  5. package/src/components/AutomationSlice.res +1 -5
  6. package/src/components/CapabilityManifest.res +2 -3
  7. package/src/components/DcbDecode.res +6 -2
  8. package/src/components/DcbScopeInference.res +16 -12
  9. package/src/components/DcbTag.res +74 -46
  10. package/src/components/DcbValidation.res +114 -89
  11. package/src/components/DisplayName.res +4 -2
  12. package/src/components/ExtensionPoint.res +0 -1
  13. package/src/components/FieldDefault.res +52 -0
  14. package/src/components/FieldDefault.res.mjs +50 -0
  15. package/src/components/InboundTranslationSlice.res +0 -2
  16. package/src/components/OutboundTranslationSlice.res +10 -8
  17. package/src/components/Owner.res +4 -4
  18. package/src/components/Plugin.res +10 -4
  19. package/src/components/ReadModel.res +0 -1
  20. package/src/components/Reference.res +2 -8
  21. package/src/components/Sensitive.res +4 -4
  22. package/src/components/StateAnnotations.res +4 -2
  23. package/src/components/StateChangeSlice.res +0 -2
  24. package/src/components/StateViewSlice.res +0 -2
  25. package/src/components/TaggedUnion.res +1 -2
  26. package/src/components/Task.res +0 -1
  27. package/src/components/TraitCertificate.res +2 -2
  28. package/src/components/TraitManifest.res +1 -1
  29. package/src/generator/CertifyTrait.res +2 -4
  30. package/src/generator/Codegen.res +77 -68
  31. package/src/generator/Discovery.res +29 -12
  32. package/src/generator/GraftTrait.res +17 -9
  33. package/src/generator/Pairing.res +39 -13
  34. package/src/generator/PlatformCodegen.res +13 -11
  35. package/src/generator/PlatformManifests.res +1 -3
  36. package/src/generator/PluginGenerator.res +0 -1
  37. package/src/generator/TraitManifestCli.res +7 -7
  38. package/src/lifecycle/CheckLifecycleModel.res +173 -145
  39. package/src/lifecycle/CheckLifecycleModel.res.mjs +17 -2
  40. package/src/semantic/Bytes.res +0 -1
  41. package/src/semantic/CalendarDate.res +2 -2
  42. package/src/semantic/Capabilities.res +4 -6
  43. package/src/semantic/Capabilities.res.mjs +9 -11
  44. package/src/semantic/CapabilityNeed.res +4 -5
  45. package/src/semantic/CaptionedImage.res +0 -1
  46. package/src/semantic/Color.res +0 -1
  47. package/src/semantic/Currency.res +189 -170
  48. package/src/semantic/DateRange.res +3 -4
  49. package/src/semantic/DateTime.res +5 -5
  50. package/src/semantic/Duration.res +2 -2
  51. package/src/semantic/Email.res +0 -1
  52. package/src/semantic/FileRef.res +2 -2
  53. package/src/semantic/GeoPoint.res +17 -21
  54. package/src/semantic/Geocoding.res +8 -9
  55. package/src/semantic/Geolocation.res +6 -9
  56. package/src/semantic/ImageRef.res +2 -2
  57. package/src/semantic/MemberRef.res +0 -1
  58. package/src/semantic/Messaging.res +141 -21
  59. package/src/semantic/Messaging.res.mjs +51 -8
  60. package/src/semantic/Money.res +23 -20
  61. package/src/semantic/Offload.res +6 -4
  62. package/src/semantic/Percent.res +3 -3
  63. package/src/semantic/Phone.res +0 -1
  64. package/src/semantic/RowImage.res +4 -3
  65. package/src/semantic/Semantic.res +8 -11
  66. package/src/semantic/StorageRef.res +17 -17
  67. package/src/semantic/Template.res +5 -7
  68. package/src/semantic/UploadableFile.res +0 -1
  69. package/src/semantic/UploadableImage.res +0 -1
  70. package/src/semantic/Url.res +3 -3
  71. package/src/types/Authorization.res +1 -2
  72. package/src/types/Handler.res +0 -1
  73. package/src/types/Identity.res +1 -3
  74. package/src/types/Lifecycle.res +7 -6
  75. package/src/types/Message.res +6 -4
  76. package/src/types/OwnerScope.res +1 -3
  77. package/src/types/Projection.res +9 -2
  78. package/src/types/StoredEvent.res +2 -4
  79. package/src/types/Transition.res +8 -4
  80. package/src/util/Util_Sury.res +1 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.134 (2026-09-09)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **spec:** let the formatter settle the lifecycle model's layout ([09e1742](https://github.com/ReventlessDev/reventless-core/commit/09e17429c24d3b84ee6d324b0641d62db4f0d9f9))
11
+
12
+
13
+ # 3.0.0-alpha.133 (2026-09-08)
14
+
15
+ ### Features
16
+
17
+ * **spec:** a field declares the value a form opens it on ([1d541fe](https://github.com/ReventlessDev/reventless-core/commit/1d541fe497e1df2ff48b72e26a06a1b8fd3c9e06))
18
+
19
+
6
20
  # 3.0.0-alpha.132 (2026-09-08)
7
21
 
8
22
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.132",
3
+ "version": "3.0.0-alpha.134",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "sury": "11.0.0-rc.2",
26
26
  "sury-ppx": "11.0.0-rc.2",
27
27
  "yaml": "^2.8.3",
28
- "@reventlessdev/rescript-node": "2.0.0-alpha.9"
28
+ "@reventlessdev/rescript-node": "2.0.0-alpha.11"
29
29
  },
30
30
  "devDependencies": {
31
31
  "rescript": "12.3.0",
package/src/AnsiStyle.res CHANGED
@@ -7,7 +7,6 @@
7
7
  depend on it, so the sink decision lives here as the single source of
8
8
  truth. `useAnsi()` ⇒ TTY text mode (colour/bold); `isJsonSink()` ⇒
9
9
  structured JSON for any non-TTY collector (CloudWatch, Datadog, …). */
10
-
11
10
  @val external _isTty: option<bool> = "process.stdout.isTTY"
12
11
  @val external _logFormat: option<string> = "process.env.REVENTLESS_LOG_FORMAT"
13
12
 
package/src/LogPrefix.res CHANGED
@@ -18,10 +18,9 @@ Resolution priority for a comp string like `"Kind(Name)"`:
18
18
  `Plugin_Builder` registers every component (and the plugin's self-name) when
19
19
  constructing a plugin, so all transformation candidates resolve to a
20
20
  real plugin name. */
21
-
22
- // Ambient plugin name. Set inside `Plugin_Builder.Make.construct` so
21
+ let // Ambient plugin name. Set inside `Plugin_Builder.Make.construct` so
23
22
  // synchronous logs during construction carry a `[Name]` prefix.
24
- let currentPluginName: ref<option<string>> = ref(None)
23
+ currentPluginName: ref<option<string>> = ref(None)
25
24
 
26
25
  // Registry: component name → owning plugin name. Populated at construct time.
27
26
  let componentPluginRegistry: ref<dict<string>> = ref(Dict.make())
@@ -40,14 +39,7 @@ let hashStr = (s: string): int => {
40
39
  }
41
40
 
42
41
  // Foreground colors that don't collide with level indicators (cyan / yellow / red).
43
- let pluginColors = [
44
- "\x1b[32m", // green
45
- "\x1b[34m", // blue
46
- "\x1b[35m", // magenta
47
- "\x1b[92m", // bright green
48
- "\x1b[94m", // bright blue
49
- "\x1b[95m", // bright magenta
50
- ]
42
+ let pluginColors = ["\x1b[32m", "\x1b[34m", "\x1b[35m", "\x1b[92m", "\x1b[94m", "\x1b[95m"] // green // blue // magenta // bright green // bright blue // bright magenta
51
43
 
52
44
  let pluginColor = (name: string): string =>
53
45
  pluginColors->Array.getUnsafe(mod(hashStr(name), pluginColors->Array.length))
@@ -99,8 +91,7 @@ let lastDotSegment = (s: string): option<string> => {
99
91
  }
100
92
  }
101
93
 
102
- let lookup = (name: string): option<string> =>
103
- componentPluginRegistry.contents->Dict.get(name)
94
+ let lookup = (name: string): option<string> => componentPluginRegistry.contents->Dict.get(name)
104
95
 
105
96
  // Last-resort candidate: the inner name is a registered component name carrying a
106
97
  // component-kind suffix (`Customers` + `ReadModel` → `CustomersReadModel`), which is
@@ -42,7 +42,6 @@ let maxRetries = 3
42
42
  let heartbeatInterval = 60
43
43
  ```
44
44
  */
45
-
46
45
  /**
47
46
  The lean Spec for an AutomationSlice — types, identity, schemas, sweep config.
48
47
 
@@ -196,9 +195,7 @@ module type Automation = {
196
195
  + `module type Mapping = M.Mapping` produces exactly this shape; legacy
197
196
  3-file files re-export it via `module type Mapping = <Stem>_Mappings.Mapping`.
198
197
  */
199
- module type Mapping = Mapping
200
- with type todoItem = Spec.todoItem
201
- and type command = Spec.command
198
+ module type Mapping = Mapping with type todoItem = Spec.todoItem and type command = Spec.command
202
199
 
203
200
  /**
204
201
  The per-source mappings registered with this slice. Each entry is a first-class
@@ -318,4 +315,3 @@ module Mappings = {
318
315
  and type command = Target.command
319
316
  }
320
317
  }
321
-
@@ -12,7 +12,6 @@ spell one fact differently.
12
12
  The platform generator unions these files across a deployment's plugins and
13
13
  emits the platform's capability list from them.
14
14
  */
15
-
16
15
  @schema
17
16
  type kind =
18
17
  | ObjectStore
@@ -83,8 +82,8 @@ let fromStructure = (structure: Plugin.pluginStructure): t => {
83
82
  | Messaging => Messaging
84
83
  },
85
84
  key,
86
- declaredBy: needs->Array.filterMap(d =>
87
- d.capability == key ? Some({component: d.component}) : None
85
+ declaredBy: needs->Array.filterMap(
86
+ d => d.capability == key ? Some({component: d.component}) : None,
88
87
  ),
89
88
  })
90
89
  )
@@ -59,13 +59,17 @@ external unsafeCast: 'a => 'b = "%identity"
59
59
  // Build a map of TAG name -> constructed payload-less variant value
60
60
  // by encoding each payload-less variant through sury and storing the result.
61
61
  // This is done once per schema, not per event.
62
- let buildPayloadLessConstructors = (schema: S.t<'event>, lookup: dict<variantKind>): dict<'event> => {
62
+ let buildPayloadLessConstructors = (schema: S.t<'event>, lookup: dict<variantKind>): dict<
63
+ 'event,
64
+ > => {
63
65
  let constructors = Dict.make()
64
66
  // For payload-less variants, we need to figure out what ReScript value
65
67
  // corresponds to each TAG name. sury's schema for a union with payload-less
66
68
  // variants encodes them as bare strings. So `| ItemCreated` serializes to
67
69
  // JSON string "ItemCreated". We can use Util_Sury.fromJson with the string.
68
- lookup->Dict.toArray->Array.forEach(((tagName, kind)) =>
70
+ lookup
71
+ ->Dict.toArray
72
+ ->Array.forEach(((tagName, kind)) =>
69
73
  switch kind {
70
74
  | PayloadLess =>
71
75
  try {
@@ -29,7 +29,6 @@ The three rules (over the representation):
29
29
  on `ProductAdded`) are payload ⇒ not indexed ⇒ the sibling-leak GSI write never
30
30
  happens.
31
31
  */
32
-
33
32
  /** A `*Id` / `*Ids`-shaped field, identified by name only (no schema, no tag flag). */
34
33
  type idField = {name: string, isList: bool}
35
34
 
@@ -210,15 +209,19 @@ let infer = (slices: array<sliceShape>): derived => {
210
209
  partitionBlockers(s)
211
210
  ->Array.map(((key, arms)) => `${arms->Array.join("/")} declares ${key}`)
212
211
  ->Array.join("; ")
213
- let _ = ambiguities->Array.push((
214
- s.sliceName,
215
- `no own partition key — every produced *Id is read from a foreign producer (${blame}). If that field is this slice's own partition, remove it from the consumed arm; if the slice really is a pure join, add an explicit @partitionTag`,
216
- ))
212
+ let _ =
213
+ ambiguities->Array.push((
214
+ s.sliceName,
215
+ `no own partition key — every produced *Id is read from a foreign producer (${blame}). If that field is this slice's own partition, remove it from the consumed arm; if the slice really is a pure join, add an explicit @partitionTag`,
216
+ ))
217
217
  | many =>
218
- let _ = ambiguities->Array.push((
219
- s.sliceName,
220
- `multiple candidate partition keys (${many->Array.join(", ")}) — add an explicit @partitionTag`,
221
- ))
218
+ let _ =
219
+ ambiguities->Array.push((
220
+ s.sliceName,
221
+ `multiple candidate partition keys (${many->Array.join(
222
+ ", ",
223
+ )}) — add an explicit @partitionTag`,
224
+ ))
222
225
  }
223
226
  }
224
227
  })
@@ -247,7 +250,9 @@ let infer = (slices: array<sliceShape>): derived => {
247
250
  slices->Array.forEach(s => {
248
251
  let own = partitionBySlice->Dict.get(s.sliceName)
249
252
  let scalar = commandScalarKeys(s)
250
- s->consumedKeys->Array.forEach(k =>
253
+ s
254
+ ->consumedKeys
255
+ ->Array.forEach(k =>
251
256
  if ownedPartitionKeys->Set.has(k) && Some(k) != own && scalar->Array.includes(k) {
252
257
  crossKeys->Set.add(k)
253
258
  }
@@ -275,8 +280,7 @@ let infer = (slices: array<sliceShape>): derived => {
275
280
  let own = partitionBySlice->Dict.get(s.sliceName)
276
281
  s.produced->Array.forEach(e => {
277
282
  let readKeys = readKeysByEventType->Dict.get(e.eventType)->Option.getOr([])
278
- let indexed =
279
- e->keysOfEvent->Array.filter(k => Some(k) == own || readKeys->Array.includes(k))
283
+ let indexed = e->keysOfEvent->Array.filter(k => Some(k) == own || readKeys->Array.includes(k))
280
284
  tagKeysByEventType->Dict.set(e.eventType, dedupSorted(indexed))
281
285
  })
282
286
  })
@@ -91,7 +91,10 @@ type appendCondition = {
91
91
  let dcbTagId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(~namespace="dcb", ~name="tag")
92
92
 
93
93
  /** Internal sury metadata ID used to mark the partition tag field. */
94
- let dcbPartitionTagId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(~namespace="dcb", ~name="partitionTag")
94
+ let dcbPartitionTagId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(
95
+ ~namespace="dcb",
96
+ ~name="partitionTag",
97
+ )
95
98
 
96
99
  /**
97
100
  Internal sury metadata ID marking a tag field as *cross-partition* — readable
@@ -101,22 +104,27 @@ partition-scoped behaviour. The scope is a property of the tag *key* and must
101
104
  agree across every event type that carries it (the fence-scope half depends on
102
105
  it). See `docs/analysis/dcb-consistency-check-issues.md` Issue 13.
103
106
  */
104
- let dcbCrossPartitionId: S.Metadata.Id.t<bool> =
105
- S.Metadata.Id.make(~namespace="dcb", ~name="crossPartition")
107
+ let dcbCrossPartitionId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(
108
+ ~namespace="dcb",
109
+ ~name="crossPartition",
110
+ )
106
111
 
107
112
  /** Metadata value for a composite partition member field. */
108
113
  type compositePartitionMemberMeta = {position: int, sep: string}
109
114
 
110
115
  /** Internal sury metadata ID used to mark a composite partition member field. */
111
- let dcbCompositePartitionMemberId: S.Metadata.Id.t<compositePartitionMemberMeta> =
112
- S.Metadata.Id.make(~namespace="dcb", ~name="compositePartitionMember")
116
+ let dcbCompositePartitionMemberId: S.Metadata.Id.t<
117
+ compositePartitionMemberMeta,
118
+ > = S.Metadata.Id.make(~namespace="dcb", ~name="compositePartitionMember")
113
119
 
114
120
  /**
115
121
  Internal sury metadata ID carrying an explicit tag-key override. When present,
116
122
  tag extraction uses the stored string as the tag `key` instead of the field name.
117
123
  */
118
- let dcbTagKeyOverrideId: S.Metadata.Id.t<string> =
119
- S.Metadata.Id.make(~namespace="dcb", ~name="tagKeyOverride")
124
+ let dcbTagKeyOverrideId: S.Metadata.Id.t<string> = S.Metadata.Id.make(
125
+ ~namespace="dcb",
126
+ ~name="tagKeyOverride",
127
+ )
120
128
 
121
129
  /**
122
130
  A sury string schema annotated as a DCB tag field.
@@ -139,7 +147,8 @@ and array element types.
139
147
  })
140
148
  ```
141
149
  */
142
- let string: S.t<string> = S.string->S.Metadata.set(~id=dcbTagId, true)
150
+ let string: S.t<string> =
151
+ S.string->S.Metadata.set(~id=dcbTagId, true)
143
152
 
144
153
  /**
145
154
  A sury string schema annotated as a DCB tag field with an explicit tag-key override.
@@ -169,7 +178,8 @@ let stringForKey = (~key: string): S.t<string> =>
169
178
  A sury int schema annotated as a DCB tag field.
170
179
  Use with `@s.matches(DcbTag.int)` on integer fields that should be extracted as tags.
171
180
  */
172
- let int: S.t<int> = S.int->S.Metadata.set(~id=dcbTagId, true)
181
+ let int: S.t<int> =
182
+ S.int->S.Metadata.set(~id=dcbTagId, true)
173
183
 
174
184
  /**
175
185
  A sury string schema annotated as both a DCB tag field AND the partition key.
@@ -518,12 +528,15 @@ let extractTagsFromJson = (schema: S.t<unknown>, json: JSON.t): array<tag> =>
518
528
  | AnyOf({anyOf}) =>
519
529
  switch json->JSON.Decode.object {
520
530
  | Some(jsonDict) =>
521
- let jsonTag = jsonDict->Dict.get("TAG")->Option.flatMap(j =>
522
- switch j {
523
- | JSON.String(s) => Some(s)
524
- | _ => None
525
- }
526
- )
531
+ let jsonTag =
532
+ jsonDict
533
+ ->Dict.get("TAG")
534
+ ->Option.flatMap(j =>
535
+ switch j {
536
+ | JSON.String(s) => Some(s)
537
+ | _ => None
538
+ }
539
+ )
527
540
  anyOf->Array.reduce([], (acc, variantSchema) =>
528
541
  if acc->Array.length > 0 {
529
542
  acc
@@ -674,10 +687,7 @@ Fields holding records are descended into on the same terms as in
674
687
  `extractTagsFromProperties` — the two walks agree there and differ only on scalar
675
688
  arrays; see that function.
676
689
  */
677
- let extractTagsFromPropertiesExpanded = (
678
- properties: dict<S.t<unknown>>,
679
- jsonDict: dict<JSON.t>,
680
- ) =>
690
+ let extractTagsFromPropertiesExpanded = (properties: dict<S.t<unknown>>, jsonDict: dict<JSON.t>) =>
681
691
  properties
682
692
  ->Dict.toArray
683
693
  ->Array.flatMap(((fieldName, fieldSchema)) =>
@@ -705,12 +715,15 @@ let extractTagsFromJsonExpanded = (schema: S.t<unknown>, json: JSON.t): array<ta
705
715
  | AnyOf({anyOf}) =>
706
716
  switch json->JSON.Decode.object {
707
717
  | Some(jsonDict) =>
708
- let jsonTag = jsonDict->Dict.get("TAG")->Option.flatMap(j =>
709
- switch j {
710
- | JSON.String(s) => Some(s)
711
- | _ => None
712
- }
713
- )
718
+ let jsonTag =
719
+ jsonDict
720
+ ->Dict.get("TAG")
721
+ ->Option.flatMap(j =>
722
+ switch j {
723
+ | JSON.String(s) => Some(s)
724
+ | _ => None
725
+ }
726
+ )
714
727
  anyOf->Array.reduce([], (acc, variantSchema) =>
715
728
  if acc->Array.length > 0 {
716
729
  acc
@@ -873,8 +886,7 @@ let narrowEventTypesForTags = (
873
886
  eventTypes->Array.filter(eventType =>
874
887
  switch tagKeysByEventType->Dict.get(eventType) {
875
888
  | None => true
876
- | Some(producedKeys) =>
877
- tags->Array.every(tag => producedKeys->Array.includes(tag.key))
889
+ | Some(producedKeys) => tags->Array.every(tag => producedKeys->Array.includes(tag.key))
878
890
  }
879
891
  )
880
892
 
@@ -922,7 +934,8 @@ let buildQueryFromCommand = (
922
934
  ~tagKeysByEventType: dict<array<string>>=Dict.make(),
923
935
  ~crossPartitionTagKeys: array<string>=[],
924
936
  ): query => {
925
- let typesForTags = clauseTags => narrowEventTypesForTags(eventTypes, clauseTags, tagKeysByEventType)
937
+ let typesForTags = clauseTags =>
938
+ narrowEventTypesForTags(eventTypes, clauseTags, tagKeysByEventType)
926
939
  if hasTaggedArrayFields(schema) {
927
940
  // Array fields already fan out per element into single-tag clauses, so a
928
941
  // cross-partition array tag is already its own clause (the adapter routes it
@@ -940,8 +953,7 @@ let buildQueryFromCommand = (
940
953
  // must read "all of the course" AND "all of the student" as two single-tag
941
954
  // reads — a composite read of the exact `{course, student}` pair is neither.
942
955
  // Without a cross-partition tag the default composite clause is preserved.
943
- let hasCrossPartition =
944
- tags->Array.some(tag => crossPartitionTagKeys->Array.includes(tag.key))
956
+ let hasCrossPartition = tags->Array.some(tag => crossPartitionTagKeys->Array.includes(tag.key))
945
957
  if hasCrossPartition && tags->Array.length > 1 {
946
958
  tags->Array.map(tag => {
947
959
  let clauseTags = [{key: tag.key, value: tag.value}]
@@ -1347,9 +1359,9 @@ type compositePartitionFieldInfo = {name: string, position: int, sep: string}
1347
1359
  Extracts all composite partition member fields from a single object-variant schema.
1348
1360
  Returns an array sorted by `position`.
1349
1361
  */
1350
- let extractCompositePartitionFieldsFromProperties = (
1351
- properties: dict<S.t<unknown>>,
1352
- ): array<compositePartitionFieldInfo> =>
1362
+ let extractCompositePartitionFieldsFromProperties = (properties: dict<S.t<unknown>>): array<
1363
+ compositePartitionFieldInfo,
1364
+ > =>
1353
1365
  properties
1354
1366
  ->Dict.toArray
1355
1367
  ->Array.filterMap(((fieldName, fieldSchema)) =>
@@ -1396,7 +1408,15 @@ let getCompositePartitionKeyValue = (tags: array<tag>, spec: compositePartitionS
1396
1408
  spec.keys
1397
1409
  ->Array.mapWithIndex((fieldName, i) => {
1398
1410
  let v =
1399
- tags->Array.findMap(t => if t.key == fieldName {Some(t.value)} else {None})->Option.getOr("")
1411
+ tags
1412
+ ->Array.findMap(t =>
1413
+ if t.key == fieldName {
1414
+ Some(t.value)
1415
+ } else {
1416
+ None
1417
+ }
1418
+ )
1419
+ ->Option.getOr("")
1400
1420
  if i == 0 {
1401
1421
  v
1402
1422
  } else {
@@ -1450,7 +1470,9 @@ let derivePartitionTag = (
1450
1470
 
1451
1471
  let allPartitionFields = {
1452
1472
  let seen = Set.make()
1453
- schemas->Array.flatMap(schema => extractPartitionTagFields(schema))->Array.filter(f => {
1473
+ schemas
1474
+ ->Array.flatMap(schema => extractPartitionTagFields(schema))
1475
+ ->Array.filter(f => {
1454
1476
  if seen->Set.has(f) {
1455
1477
  false
1456
1478
  } else {
@@ -1461,27 +1483,28 @@ let derivePartitionTag = (
1461
1483
  }
1462
1484
 
1463
1485
  if hasComposite && allPartitionFields->Array.length > 0 {
1464
- JsError.throwWithMessage(
1465
- `DCB spec mixes @compositePartitionTag and @partitionTag — use one strategy per schema`,
1466
- )
1486
+ JsError.throwWithMessage(`DCB spec mixes @compositePartitionTag and @partitionTag — use one strategy per schema`)
1467
1487
  }
1468
1488
 
1469
1489
  if hasComposite {
1470
1490
  if allCompositeFields->Array.length < 2 {
1471
1491
  JsError.throwWithMessage(
1472
- `@compositePartitionTag requires at least 2 annotated fields — only ${allCompositeFields->Array.length->Int.toString} found`,
1492
+ `@compositePartitionTag requires at least 2 annotated fields — only ${allCompositeFields
1493
+ ->Array.length
1494
+ ->Int.toString} found`,
1473
1495
  )
1474
1496
  }
1475
1497
  let sorted = allCompositeFields->Array.toSorted((a, b) => Int.compare(a.position, b.position))
1476
1498
  let keys = sorted->Array.map(info => info.name)
1477
- let seps = sorted->Array.slice(~start=0, ~end=sorted->Array.length - 1)->Array.map(info =>
1478
- info.sep
1479
- )
1499
+ let seps =
1500
+ sorted->Array.slice(~start=0, ~end=sorted->Array.length - 1)->Array.map(info => info.sep)
1480
1501
  Composite({keys, seps})
1481
1502
  } else {
1482
1503
  let allTaggedFields = {
1483
1504
  let seen = Set.make()
1484
- schemas->Array.flatMap(schema => extractTaggedFields(schema))->Array.filter(f => {
1505
+ schemas
1506
+ ->Array.flatMap(schema => extractTaggedFields(schema))
1507
+ ->Array.filter(f => {
1485
1508
  if seen->Set.has(f) {
1486
1509
  false
1487
1510
  } else {
@@ -1492,7 +1515,8 @@ let derivePartitionTag = (
1492
1515
  }
1493
1516
 
1494
1517
  switch allTaggedFields {
1495
- | [] => JsError.throwWithMessage("DCB spec has no tagged fields — cannot derive partition tag")
1518
+ | [] =>
1519
+ JsError.throwWithMessage("DCB spec has no tagged fields — cannot derive partition tag")
1496
1520
  | [singleField] => Simple({key: singleField})
1497
1521
  | multipleFields => {
1498
1522
  let needsExplicitPartition = schemas->Array.some(schema => hasMultiTagVariant(schema))
@@ -1514,11 +1538,15 @@ let derivePartitionTag = (
1514
1538
  | [singlePartition] => Simple({key: singlePartition})
1515
1539
  | [] =>
1516
1540
  JsError.throwWithMessage(
1517
- `DCB spec has variants with multiple tagged fields (${multipleFields->Array.join(", ")}) but none is annotated with @partitionTag — affected: ${context} — mark one field as the partition key`,
1541
+ `DCB spec has variants with multiple tagged fields (${multipleFields->Array.join(
1542
+ ", ",
1543
+ )}) but none is annotated with @partitionTag — affected: ${context} — mark one field as the partition key`,
1518
1544
  )
1519
1545
  | multiplePartitions =>
1520
1546
  JsError.throwWithMessage(
1521
- `DCB spec has multiple fields annotated with @partitionTag (${multiplePartitions->Array.join(", ")}) — only one is allowed — affected: ${context}`,
1547
+ `DCB spec has multiple fields annotated with @partitionTag (${multiplePartitions->Array.join(
1548
+ ", ",
1549
+ )}) — only one is allowed — affected: ${context}`,
1522
1550
  )
1523
1551
  }
1524
1552
  } else {