@reventlessdev/reventless-spec 3.0.0-alpha.100

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 (139) hide show
  1. package/CHANGELOG.md +931 -0
  2. package/LICENSE +202 -0
  3. package/README.md +109 -0
  4. package/package.json +49 -0
  5. package/rescript.json +32 -0
  6. package/run-generator.mjs +2 -0
  7. package/run-platform-generator.mjs +2 -0
  8. package/scripts/generate-currency.mjs +215 -0
  9. package/scripts/iso-4217-list-one.xml +1956 -0
  10. package/src/AnsiStyle.res +40 -0
  11. package/src/AnsiStyle.res.mjs +54 -0
  12. package/src/LogPrefix.res +192 -0
  13. package/src/LogPrefix.res.mjs +159 -0
  14. package/src/PackageVersion.res +67 -0
  15. package/src/PackageVersion.res.mjs +81 -0
  16. package/src/components/Aggregate.res +64 -0
  17. package/src/components/Aggregate.res.mjs +2 -0
  18. package/src/components/AutomationSlice.res +279 -0
  19. package/src/components/AutomationSlice.res.mjs +30 -0
  20. package/src/components/CapabilityManifest.res +74 -0
  21. package/src/components/CapabilityManifest.res.mjs +61 -0
  22. package/src/components/ComponentKind.res +99 -0
  23. package/src/components/ComponentKind.res.mjs +125 -0
  24. package/src/components/Counter.res +24 -0
  25. package/src/components/Counter.res.mjs +2 -0
  26. package/src/components/DcbDecode.res +118 -0
  27. package/src/components/DcbDecode.res.mjs +100 -0
  28. package/src/components/DcbScopeInference.res +244 -0
  29. package/src/components/DcbScopeInference.res.mjs +177 -0
  30. package/src/components/DcbTag.res +1335 -0
  31. package/src/components/DcbTag.res.mjs +898 -0
  32. package/src/components/DcbValidation.res +427 -0
  33. package/src/components/DcbValidation.res.mjs +423 -0
  34. package/src/components/DisplayName.res +40 -0
  35. package/src/components/DisplayName.res.mjs +26 -0
  36. package/src/components/ExtensionPoint.res +27 -0
  37. package/src/components/ExtensionPoint.res.mjs +2 -0
  38. package/src/components/InboundTranslationSlice.res +85 -0
  39. package/src/components/InboundTranslationSlice.res.mjs +2 -0
  40. package/src/components/OutboundTranslationSlice.res +153 -0
  41. package/src/components/OutboundTranslationSlice.res.mjs +2 -0
  42. package/src/components/Plugin.res +538 -0
  43. package/src/components/Plugin.res.mjs +264 -0
  44. package/src/components/PluginName.res +39 -0
  45. package/src/components/PluginName.res.mjs +45 -0
  46. package/src/components/ReadModel.res +199 -0
  47. package/src/components/ReadModel.res.mjs +18 -0
  48. package/src/components/Reference.res +55 -0
  49. package/src/components/Reference.res.mjs +50 -0
  50. package/src/components/Snapshot.res +26 -0
  51. package/src/components/Snapshot.res.mjs +2 -0
  52. package/src/components/StateAnnotations.res +97 -0
  53. package/src/components/StateAnnotations.res.mjs +15 -0
  54. package/src/components/StateChangeSlice.res +131 -0
  55. package/src/components/StateChangeSlice.res.mjs +2 -0
  56. package/src/components/StateViewSlice.res +123 -0
  57. package/src/components/StateViewSlice.res.mjs +2 -0
  58. package/src/components/Task.res +62 -0
  59. package/src/components/Task.res.mjs +2 -0
  60. package/src/generator/Codegen.res +842 -0
  61. package/src/generator/Codegen.res.mjs +565 -0
  62. package/src/generator/Config.res +106 -0
  63. package/src/generator/Config.res.mjs +69 -0
  64. package/src/generator/Discovery.res +230 -0
  65. package/src/generator/Discovery.res.mjs +198 -0
  66. package/src/generator/Generator_Node.res +14 -0
  67. package/src/generator/Generator_Node.res.mjs +18 -0
  68. package/src/generator/Pairing.res +460 -0
  69. package/src/generator/Pairing.res.mjs +415 -0
  70. package/src/generator/PlatformCodegen.res +207 -0
  71. package/src/generator/PlatformCodegen.res.mjs +154 -0
  72. package/src/generator/PlatformGenerator.res +126 -0
  73. package/src/generator/PlatformGenerator.res.mjs +114 -0
  74. package/src/generator/PlatformManifests.res +203 -0
  75. package/src/generator/PlatformManifests.res.mjs +212 -0
  76. package/src/generator/PluginGenerator.res +57 -0
  77. package/src/generator/PluginGenerator.res.mjs +73 -0
  78. package/src/semantic/Bytes.res +54 -0
  79. package/src/semantic/Bytes.res.mjs +38 -0
  80. package/src/semantic/Capabilities.res +43 -0
  81. package/src/semantic/Capabilities.res.mjs +17 -0
  82. package/src/semantic/Color.res +51 -0
  83. package/src/semantic/Color.res.mjs +29 -0
  84. package/src/semantic/Currency.res +598 -0
  85. package/src/semantic/Currency.res.mjs +743 -0
  86. package/src/semantic/DateRange.res +148 -0
  87. package/src/semantic/DateRange.res.mjs +74 -0
  88. package/src/semantic/Duration.res +53 -0
  89. package/src/semantic/Duration.res.mjs +26 -0
  90. package/src/semantic/Email.res +51 -0
  91. package/src/semantic/Email.res.mjs +31 -0
  92. package/src/semantic/GeoPoint.res +226 -0
  93. package/src/semantic/GeoPoint.res.mjs +190 -0
  94. package/src/semantic/Geocoding.res +127 -0
  95. package/src/semantic/Geocoding.res.mjs +36 -0
  96. package/src/semantic/Money.res +196 -0
  97. package/src/semantic/Money.res.mjs +138 -0
  98. package/src/semantic/Offload.res +294 -0
  99. package/src/semantic/Offload.res.mjs +191 -0
  100. package/src/semantic/Percent.res +53 -0
  101. package/src/semantic/Percent.res.mjs +33 -0
  102. package/src/semantic/Phone.res +55 -0
  103. package/src/semantic/Phone.res.mjs +29 -0
  104. package/src/semantic/Semantic.res +162 -0
  105. package/src/semantic/Semantic.res.mjs +95 -0
  106. package/src/semantic/StorageRef.res +164 -0
  107. package/src/semantic/StorageRef.res.mjs +111 -0
  108. package/src/semantic/Url.res +66 -0
  109. package/src/semantic/Url.res.mjs +48 -0
  110. package/src/types/Authorization.res +23 -0
  111. package/src/types/Authorization.res.mjs +33 -0
  112. package/src/types/Behavior.res +86 -0
  113. package/src/types/Behavior.res.mjs +2 -0
  114. package/src/types/DateTime.res +29 -0
  115. package/src/types/DateTime.res.mjs +16 -0
  116. package/src/types/EventMapping.res +100 -0
  117. package/src/types/EventMapping.res.mjs +15 -0
  118. package/src/types/Handler.res +30 -0
  119. package/src/types/Handler.res.mjs +2 -0
  120. package/src/types/Id.res +75 -0
  121. package/src/types/Id.res.mjs +37 -0
  122. package/src/types/Identity.res +46 -0
  123. package/src/types/Identity.res.mjs +51 -0
  124. package/src/types/Message.res +326 -0
  125. package/src/types/Message.res.mjs +186 -0
  126. package/src/types/Projection.res +220 -0
  127. package/src/types/Projection.res.mjs +44 -0
  128. package/src/types/QueryEngine.res +123 -0
  129. package/src/types/QueryEngine.res.mjs +12 -0
  130. package/src/types/ReadConsistency.res +38 -0
  131. package/src/types/ReadConsistency.res.mjs +29 -0
  132. package/src/types/Schedule.res +65 -0
  133. package/src/types/Schedule.res.mjs +68 -0
  134. package/src/types/SideEffect.res +45 -0
  135. package/src/types/SideEffect.res.mjs +2 -0
  136. package/src/types/StoredEvent.res +46 -0
  137. package/src/types/StoredEvent.res.mjs +32 -0
  138. package/src/types/Visibility.res +24 -0
  139. package/src/types/Visibility.res.mjs +25 -0
@@ -0,0 +1,423 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
6
+ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
7
+ import * as DcbTag$Reventless from "./DcbTag.res.mjs";
8
+
9
+ function extractVariantInfo(variantSchema) {
10
+ if (variantSchema.type !== "object") {
11
+ return;
12
+ }
13
+ let properties = variantSchema.properties;
14
+ return Stdlib_Option.map(DcbTag$Reventless.variantTagName(variantSchema.items), tagName => {
15
+ let taggedFields = Stdlib_Array.filterMap(Object.entries(properties), param => {
16
+ if (DcbTag$Reventless.isTagged(param[1])) {
17
+ return param[0];
18
+ }
19
+ });
20
+ return {
21
+ tagName: tagName,
22
+ fields: properties,
23
+ taggedFields: taggedFields
24
+ };
25
+ });
26
+ }
27
+
28
+ function extractAllVariants(schema) {
29
+ switch (schema.type) {
30
+ case "string" :
31
+ let name = schema.const;
32
+ if (name !== undefined) {
33
+ return [{
34
+ tagName: name,
35
+ fields: {},
36
+ taggedFields: []
37
+ }];
38
+ } else {
39
+ return [];
40
+ }
41
+ case "object" :
42
+ let info = extractVariantInfo(schema);
43
+ if (info !== undefined) {
44
+ return [info];
45
+ } else {
46
+ return [];
47
+ }
48
+ case "union" :
49
+ return Stdlib_Array.filterMap(schema.anyOf, extractVariantInfo);
50
+ default:
51
+ return [];
52
+ }
53
+ }
54
+
55
+ function isPayloadLess(info) {
56
+ return Object.entries(info.fields).length === 0;
57
+ }
58
+
59
+ function schemasAreCompatible(_a, _b) {
60
+ while (true) {
61
+ let b = _b;
62
+ let a = _a;
63
+ switch (a.type) {
64
+ case "string" :
65
+ return b.type === "string";
66
+ case "number" :
67
+ return b.type === "number";
68
+ case "bigint" :
69
+ return b.type === "bigint";
70
+ case "boolean" :
71
+ return b.type === "boolean";
72
+ case "array" :
73
+ let aItem = a.additionalItems;
74
+ if (aItem === "strip" || aItem === "strict") {
75
+ return false;
76
+ }
77
+ if (b.type !== "array") {
78
+ return false;
79
+ }
80
+ let bItem = b.additionalItems;
81
+ if (bItem === "strip" || bItem === "strict") {
82
+ return false;
83
+ }
84
+ _b = bItem;
85
+ _a = aItem;
86
+ continue;
87
+ case "object" :
88
+ if (b.type === "object") {
89
+ return propsCompatible(a.properties, b.properties);
90
+ } else {
91
+ return false;
92
+ }
93
+ case "union" :
94
+ if (b.type !== "union") {
95
+ return false;
96
+ }
97
+ let av = extractAllVariants(a);
98
+ let bv = extractAllVariants(b);
99
+ let at = av.map(v => v.tagName).toSorted(Primitive_string.compare);
100
+ let bt = bv.map(v => v.tagName).toSorted(Primitive_string.compare);
101
+ if (Primitive_object.equal(at, bt)) {
102
+ return at.every(tag => {
103
+ let match = av.find(v => v.tagName === tag);
104
+ let match$1 = bv.find(v => v.tagName === tag);
105
+ if (match !== undefined && match$1 !== undefined) {
106
+ return propsCompatible(match.fields, match$1.fields);
107
+ } else {
108
+ return false;
109
+ }
110
+ });
111
+ } else {
112
+ return false;
113
+ }
114
+ default:
115
+ return false;
116
+ }
117
+ };
118
+ }
119
+
120
+ function propsCompatible(aProps, bProps) {
121
+ let aKeys = Object.keys(aProps).toSorted(Primitive_string.compare);
122
+ let bKeys = Object.keys(bProps).toSorted(Primitive_string.compare);
123
+ if (Primitive_object.equal(aKeys, bKeys)) {
124
+ return aKeys.every(k => {
125
+ let match = aProps[k];
126
+ let match$1 = bProps[k];
127
+ if (match !== undefined && match$1 !== undefined) {
128
+ return schemasAreCompatible(match, match$1);
129
+ } else {
130
+ return false;
131
+ }
132
+ });
133
+ } else {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ function schemaTypeName(schema) {
139
+ switch (schema.type) {
140
+ case "never" :
141
+ return "never";
142
+ case "string" :
143
+ return "string";
144
+ case "number" :
145
+ return "float";
146
+ case "bigint" :
147
+ return "bigint";
148
+ case "boolean" :
149
+ return "bool";
150
+ case "array" :
151
+ return "array";
152
+ case "object" :
153
+ return "object";
154
+ case "union" :
155
+ return "union";
156
+ case "ref" :
157
+ return "ref";
158
+ default:
159
+ return "unknown";
160
+ }
161
+ }
162
+
163
+ function dedupeKeys(keys) {
164
+ let seen = new Set();
165
+ return keys.filter(k => {
166
+ if (seen.has(k)) {
167
+ return false;
168
+ } else {
169
+ seen.add(k);
170
+ return true;
171
+ }
172
+ });
173
+ }
174
+
175
+ function validateCompositeReads(slices, producedTagKeys) {
176
+ let warnings = [];
177
+ slices.forEach(param => {
178
+ let commandSchema = param[1];
179
+ let sliceName = param[0];
180
+ if (DcbTag$Reventless.hasTaggedArrayFields(commandSchema)) {
181
+ return;
182
+ }
183
+ let commandTagKeysByVariant = DcbTag$Reventless.extractTagKeysByEventType(commandSchema);
184
+ let consumedTypes = DcbTag$Reventless.extractVariantNames(param[2]);
185
+ Object.values(commandTagKeysByVariant).forEach(cmdKeys => {
186
+ let querySet = dedupeKeys(cmdKeys);
187
+ if (querySet.length >= 2) {
188
+ consumedTypes.forEach(consumedType => {
189
+ let producedKeys = producedTagKeys[consumedType];
190
+ if (producedKeys === undefined) {
191
+ return;
192
+ }
193
+ let producedSet = dedupeKeys(producedKeys);
194
+ let isSuperset = querySet.every(k => producedSet.includes(k));
195
+ if (!(isSuperset && producedSet.length > querySet.length)) {
196
+ return;
197
+ }
198
+ let extra = producedSet.filter(k => !querySet.includes(k));
199
+ warnings.push({
200
+ sliceName: sliceName,
201
+ message: `composite read on tags [` + querySet.join(", ") + `] will silently miss '` + consumedType + `', which also carries [` + extra.join(", ") + `] — the tag_composite key includes ALL of an event's tags, so a composite query matches only events tagged exactly [` + querySet.join(", ") + `]. Align the event's tag set with the query, or read '` + consumedType + `' via a single-tag clause.`
202
+ });
203
+ });
204
+ return;
205
+ }
206
+ });
207
+ });
208
+ return warnings;
209
+ }
210
+
211
+ function validateProducedAndConsumed(produced, consumed) {
212
+ let errors = [];
213
+ let producerMap = {};
214
+ produced.forEach(param => {
215
+ let sliceName = param[0];
216
+ let variants = extractAllVariants(param[1]);
217
+ variants.forEach(variant => {
218
+ let existing = Stdlib_Option.getOr(producerMap[variant.tagName], []);
219
+ producerMap[variant.tagName] = existing.concat([{
220
+ sliceName: sliceName,
221
+ variant: variant
222
+ }]);
223
+ });
224
+ });
225
+ Object.entries(producerMap).forEach(param => {
226
+ let entries = param[1];
227
+ if (entries.length <= 1) {
228
+ return;
229
+ }
230
+ let tagName = param[0];
231
+ let first = entries[0];
232
+ let firstFields = Object.keys(first.variant.fields).toSorted(Primitive_string.compare);
233
+ let firstTagged = first.variant.taggedFields.toSorted(Primitive_string.compare);
234
+ entries.forEach((entry, idx) => {
235
+ if (idx <= 0) {
236
+ return;
237
+ }
238
+ let entryFields = Object.keys(entry.variant.fields).toSorted(Primitive_string.compare);
239
+ let entryTagged = entry.variant.taggedFields.toSorted(Primitive_string.compare);
240
+ if (firstFields.length !== entryFields.length || firstFields.some(f => !entryFields.includes(f))) {
241
+ let missingInEntry = firstFields.filter(f => !entryFields.includes(f));
242
+ let extraInEntry = entryFields.filter(f => !firstFields.includes(f));
243
+ let parts = [];
244
+ if (missingInEntry.length !== 0) {
245
+ parts.push(`'` + first.sliceName + `' has fields [` + missingInEntry.join(", ") + `] which '` + entry.sliceName + `' is missing`);
246
+ }
247
+ if (extraInEntry.length !== 0) {
248
+ parts.push(`'` + entry.sliceName + `' has fields [` + extraInEntry.join(", ") + `] which '` + first.sliceName + `' is missing`);
249
+ }
250
+ errors.push({
251
+ sliceName: entry.sliceName,
252
+ message: `Producers '` + first.sliceName + `' and '` + entry.sliceName + `' both produce '` + tagName + `' but with different fields: ` + parts.join("; ")
253
+ });
254
+ } else {
255
+ firstFields.forEach(fieldName => {
256
+ let firstSchema = first.variant.fields[fieldName];
257
+ let entrySchema = entry.variant.fields[fieldName];
258
+ if (!schemasAreCompatible(firstSchema, entrySchema)) {
259
+ errors.push({
260
+ sliceName: entry.sliceName,
261
+ message: `Producers '` + first.sliceName + `' and '` + entry.sliceName + `' both produce '` + tagName + `' but field '` + fieldName + `' has type '` + schemaTypeName(firstSchema) + `' in '` + first.sliceName + `' and '` + schemaTypeName(entrySchema) + `' in '` + entry.sliceName + `'`
262
+ });
263
+ return;
264
+ }
265
+ });
266
+ }
267
+ if (!(firstTagged.length !== entryTagged.length || firstTagged.some(f => !entryTagged.includes(f)))) {
268
+ return;
269
+ }
270
+ let untaggedInEntry = firstTagged.filter(f => !entryTagged.includes(f));
271
+ let untaggedInFirst = entryTagged.filter(f => !firstTagged.includes(f));
272
+ let parts$1 = [];
273
+ if (untaggedInEntry.length !== 0) {
274
+ parts$1.push(`[` + untaggedInEntry.join(", ") + `] tagged in '` + first.sliceName + `' but not in '` + entry.sliceName + `'`);
275
+ }
276
+ if (untaggedInFirst.length !== 0) {
277
+ parts$1.push(`[` + untaggedInFirst.join(", ") + `] tagged in '` + entry.sliceName + `' but not in '` + first.sliceName + `'`);
278
+ }
279
+ errors.push({
280
+ sliceName: entry.sliceName,
281
+ message: `Producers '` + first.sliceName + `' and '` + entry.sliceName + `' both produce '` + tagName + `' but tag annotations differ: ` + parts$1.join("; ")
282
+ });
283
+ });
284
+ });
285
+ consumed.forEach(param => {
286
+ let sliceName = param[0];
287
+ let variants = extractAllVariants(param[1]);
288
+ variants.forEach(variant => {
289
+ let producers = producerMap[variant.tagName];
290
+ if (producers !== undefined) {
291
+ let producer = producers[0];
292
+ if (!isPayloadLess(variant)) {
293
+ Object.entries(variant.fields).forEach(param => {
294
+ let consumedFieldSchema = param[1];
295
+ let fieldName = param[0];
296
+ let producedFieldSchema = producer.variant.fields[fieldName];
297
+ if (producedFieldSchema !== undefined) {
298
+ if (!schemasAreCompatible(consumedFieldSchema, producedFieldSchema)) {
299
+ errors.push({
300
+ sliceName: sliceName,
301
+ message: `Slice '` + sliceName + `' consumes '` + variant.tagName + `.` + fieldName + `' as ` + schemaTypeName(consumedFieldSchema) + ` but producer declares it as ` + schemaTypeName(producedFieldSchema)
302
+ });
303
+ return;
304
+ } else {
305
+ return;
306
+ }
307
+ } else {
308
+ errors.push({
309
+ sliceName: sliceName,
310
+ message: `Slice '` + sliceName + `' consumes '` + variant.tagName + `.` + fieldName + `' (` + schemaTypeName(consumedFieldSchema) + `) but produced '` + variant.tagName + `' has no field '` + fieldName + `'`
311
+ });
312
+ return;
313
+ }
314
+ });
315
+ return;
316
+ } else {
317
+ return;
318
+ }
319
+ }
320
+ errors.push({
321
+ sliceName: sliceName,
322
+ message: `Slice '` + sliceName + `' consumes '` + variant.tagName + `' but no slice produces it`
323
+ });
324
+ });
325
+ });
326
+ if (errors.length !== 0) {
327
+ return {
328
+ TAG: "Error",
329
+ _0: errors
330
+ };
331
+ } else {
332
+ return {
333
+ TAG: "Ok",
334
+ _0: undefined
335
+ };
336
+ }
337
+ }
338
+
339
+ function validateCrossPartitionScope(producers) {
340
+ let perProducer = producers.map(param => {
341
+ let schema = param[1];
342
+ let cpSet = new Set();
343
+ DcbTag$Reventless.extractCrossPartitionTagKeys(schema).forEach(k => {
344
+ cpSet.add(k);
345
+ });
346
+ let carried = new Set();
347
+ Object.values(DcbTag$Reventless.extractTagKeysByEventType(schema)).forEach(ks => {
348
+ ks.forEach(k => {
349
+ carried.add(k);
350
+ });
351
+ });
352
+ return [
353
+ param[0],
354
+ cpSet,
355
+ carried
356
+ ];
357
+ });
358
+ let globalCp = new Set();
359
+ perProducer.forEach(param => {
360
+ Array.from(param[1].values()).forEach(k => {
361
+ globalCp.add(k);
362
+ });
363
+ });
364
+ let warnings = [];
365
+ perProducer.forEach(param => {
366
+ let cpSet = param[1];
367
+ let name = param[0];
368
+ Array.from(param[2].values()).toSorted(Primitive_string.compare).forEach(k => {
369
+ if (globalCp.has(k) && !cpSet.has(k)) {
370
+ warnings.push({
371
+ sliceName: name,
372
+ message: `tag '` + k + `' is declared @crossPartition by another producer but is partition-scoped here — DCB tag scope must agree across every event type that carries the tag (read-scope and fence-scope follow one global per-key flag). Add @crossPartition to '` + k + `' here, or remove it from the other producer.`
373
+ });
374
+ return;
375
+ }
376
+ });
377
+ });
378
+ return warnings;
379
+ }
380
+
381
+ function validateScopeVsInference(annotations, inferred) {
382
+ let contradictions = [];
383
+ let redundancies = [];
384
+ annotations.forEach(param => {
385
+ let name = param[0];
386
+ param[1].forEach(k => {
387
+ if (Primitive_object.equal(inferred.partitionBySlice[name], k)) {
388
+ contradictions.push({
389
+ sliceName: name,
390
+ message: `tag '` + k + `' is marked @crossPartition but inference resolves it as this slice's own partition key — a slice's own identity is never a cross-partition read. Remove the annotation.`
391
+ });
392
+ return;
393
+ } else if (inferred.crossPartitionTagKeys.includes(k)) {
394
+ redundancies.push({
395
+ sliceName: name,
396
+ message: `tag '` + k + `' is marked @crossPartition but the framework already infers it as a cross-partition read from the slice graph — the annotation is redundant and can be removed.`
397
+ });
398
+ return;
399
+ } else {
400
+ return;
401
+ }
402
+ });
403
+ });
404
+ return {
405
+ contradictions: contradictions,
406
+ redundancies: redundancies
407
+ };
408
+ }
409
+
410
+ export {
411
+ extractVariantInfo,
412
+ extractAllVariants,
413
+ isPayloadLess,
414
+ schemasAreCompatible,
415
+ propsCompatible,
416
+ schemaTypeName,
417
+ dedupeKeys,
418
+ validateCompositeReads,
419
+ validateProducedAndConsumed,
420
+ validateCrossPartitionScope,
421
+ validateScopeVsInference,
422
+ }
423
+ /* DcbTag-Reventless Not a pure module */
@@ -0,0 +1,40 @@
1
+ /**
2
+ Spec for a composite display-name derivation.
3
+
4
+ `fields` names the source fields in declaration order; `separator` is inserted
5
+ between successive non-empty parts when `computeLabel` is applied.
6
+
7
+ The ppx attaches this spec to a state schema whenever one or more fields carry
8
+ the `@displayName` attribute. The projection runtime reads the spec at write
9
+ time and composes the label into a synthetic `displayName` field on state.
10
+ */
11
+ type displayNameSpec = {
12
+ fields: array<string>,
13
+ separator: string,
14
+ }
15
+
16
+ /** Sury metadata ID used to attach a `displayNameSpec` to a state schema. */
17
+ let displayNameId: S.Metadata.Id.t<displayNameSpec> =
18
+ S.Metadata.Id.make(~namespace="reventless", ~name="displayName")
19
+
20
+ /** Returns the spec attached to a state schema, if any. */
21
+ let getSpec = (schema: S.t<unknown>): option<displayNameSpec> =>
22
+ S.Metadata.get(schema, ~id=displayNameId)
23
+
24
+ /**
25
+ Composes a display-name label from a state dict and a spec.
26
+
27
+ Reads each source field by name from the dict. Entries that are absent,
28
+ `null`, or empty strings are skipped. Remaining string values are joined
29
+ with `spec.separator`. The result never has a leading, trailing, or
30
+ doubled separator.
31
+ */
32
+ let computeLabel = (spec: displayNameSpec, state: dict<JSON.t>): string =>
33
+ spec.fields
34
+ ->Array.filterMap(fieldName =>
35
+ switch state->Dict.get(fieldName) {
36
+ | Some(JSON.String(s)) if s != "" => Some(s)
37
+ | _ => None
38
+ }
39
+ )
40
+ ->Array.join(spec.separator)
@@ -0,0 +1,26 @@
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
+
6
+ let displayNameId = S.Metadata.Id.make("reventless", "displayName");
7
+
8
+ function getSpec(schema) {
9
+ return S.Metadata.get(schema, displayNameId);
10
+ }
11
+
12
+ function computeLabel(spec, state) {
13
+ return Stdlib_Array.filterMap(spec.fields, fieldName => {
14
+ let match = state[fieldName];
15
+ if (typeof match === "string" && match !== "") {
16
+ return match;
17
+ }
18
+ }).join(spec.separator);
19
+ }
20
+
21
+ export {
22
+ displayNameId,
23
+ getSpec,
24
+ computeLabel,
25
+ }
26
+ /* displayNameId Not a pure module */
@@ -0,0 +1,27 @@
1
+ /**
2
+ Module type for an extension point's identity and schema specification.
3
+
4
+ An extension point is a bidirectional protocol boundary: it exposes a
5
+ command topic for extensions to publish to, and an event topic that
6
+ extensions subscribe to. The `directive` type is used for internal
7
+ routing signals that are not exposed as public events.
8
+
9
+ Note: `module Id = Id.String` is fixed — extension points always use string IDs.
10
+ */
11
+ module type Spec = {
12
+ module Id = Id.String
13
+
14
+ /** Logical extension point name (used as a topic name prefix). */
15
+ let name: string
16
+
17
+ /** Commands that extensions can send to this extension point. Must carry `@schema`. */
18
+ @schema
19
+ type command
20
+ /** Events that this extension point emits to subscribed extensions. Must carry `@schema`. */
21
+ @schema
22
+ type event
23
+ /** Internal routing directives (not exposed externally). Must carry `@schema`. */
24
+ @schema
25
+ type directive
26
+ }
27
+
@@ -0,0 +1,2 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+ /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
@@ -0,0 +1,85 @@
1
+ /**
2
+ Module types for a DCB inbound translation slice.
3
+
4
+ An `InboundTranslationSlice` receives external input (webhooks, API calls) and
5
+ translates it into domain commands via an anti-corruption layer.
6
+
7
+ Unlike outbound slices, inbound slices are triggered externally via
8
+ `operations.receive` rather than by subscribing to domain events.
9
+
10
+ ```
11
+ External Input -> Anti-Corruption Layer (translate) -> Command
12
+ ```
13
+
14
+ Plan 02 splits the merged spec into two module types:
15
+
16
+ - `Spec` — types, identity, schemas, target name. (No state — translation is
17
+ a pure function of external input.)
18
+ - `Translation` — the single `translate` function.
19
+
20
+ @example
21
+ ```rescript
22
+ // PaymentWebhook.res
23
+ let name = "PaymentWebhook"
24
+
25
+ @schema type externalInput = {paymentId: string, orderId: string, status: string}
26
+ @schema type command = ConfirmPayment({orderId: @s.matches(DcbTag.string) string, paymentId: string})
27
+
28
+ let translate = input => switch input.status {
29
+ | "completed" => Ok([(input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: input.paymentId}))])
30
+ | _ => Error("Unknown payment status: " ++ input.status)
31
+ }
32
+ ```
33
+ */
34
+
35
+ /**
36
+ The lean Spec for an InboundTranslationSlice — types, identity, schemas.
37
+ */
38
+ module type Spec = {
39
+ /** Logical name of this inbound translation slice (used as a component prefix). */
40
+ let name: string
41
+ let moduleUrl: string
42
+
43
+ /** The external input type received from the outside world. Must carry `@schema`. */
44
+ @schema
45
+ type externalInput
46
+
47
+ /** The command type produced by the anti-corruption layer. Must carry `@schema`. */
48
+ @schema
49
+ type command
50
+
51
+ /** Name of the aggregate or StateChangeSlice that receives the produced command. */
52
+ let targetName: string
53
+
54
+ /** Optional display name of the foreign system this anti-corruption slice receives
55
+ from (e.g. `"SupplierFeed"`). Drives the **external box** drawn outside the plugin
56
+ in the Event Graph / Context Map (see docs/plans/translation-external-boxes.md).
57
+ Auto-injected by `@@reventless.spec` defaulting to `None` — set it to name the box. */
58
+ let externalSystem: option<string>
59
+
60
+ /** Authorization rule evaluated at the GraphQL resolver entry before any
61
+ external input is translated. Auto-injected by `@@reventless.spec` and
62
+ on structurally-detected inline spec modules — defaults to
63
+ `AllowAuthenticated`. */
64
+ let commandAuthorization: command => Authorization.permission
65
+ }
66
+
67
+ /**
68
+ The Translation — the synchronous translate function.
69
+ */
70
+ module type Translation = {
71
+ module Spec: Spec
72
+
73
+ /**
74
+ Translate: convert external input into domain commands.
75
+ Returns:
76
+ - `Ok([(targetId, cmd), ...])` to publish one or more commands
77
+ - `Ok([])` for idempotent no-ops (nothing to publish)
78
+ - `Error(msg)` to reject the input
79
+ */
80
+ let translate: Spec.externalInput => result<array<(string, Spec.command)>, string>
81
+
82
+ /** File URL of this Translation module (`import.meta.url`). */
83
+ let moduleUrl: string
84
+ }
85
+
@@ -0,0 +1,2 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+ /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */