@reventlessdev/reventless-spec 3.0.0-alpha.128 → 3.0.0-alpha.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,26 +49,18 @@ let apiTargetSchema = Sury.union([
49
49
 
50
50
  let apiSchemaFragmentOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginApiFragments", undefined, apiSchemaFragmentSchema);
51
51
 
52
- let dcbEventLogOptionSchema = Sury.$nullAsOption(dcbEventLogDefinitionSchema);
53
-
54
- let stringOptionSchema = Sury.$nullAsOption(Sury.string);
55
-
56
- let stringArrayOptionSchema = Sury.$nullAsOption(Sury.array(Sury.string));
57
-
58
- let boolOptionSchema = Sury.$nullAsOption(Sury.bool);
59
-
60
52
  let panelManifestEntrySchema = Sury.$schema(s => ({
61
53
  fragmentId: s.m(Sury.string),
62
54
  title: s.m(Sury.string),
63
55
  description: s.m(Sury.string),
64
56
  positions: s.m(Sury.array(Sury.string)),
65
- requiredAccess: s.m(stringOptionSchema)
57
+ requiredAccess: s.m(Sury.$option(Sury.string))
66
58
  }));
67
59
 
68
60
  let menuEntrySchema = Sury.$schema(s => ({
69
61
  label: s.m(Sury.string),
70
- icon: s.m(stringOptionSchema),
71
- group: s.m(stringOptionSchema),
62
+ icon: s.m(Sury.$option(Sury.string)),
63
+ group: s.m(Sury.$option(Sury.string)),
72
64
  sortOrder: s.m(Sury.int)
73
65
  }));
74
66
 
@@ -76,7 +68,7 @@ let pageManifestEntrySchema = Sury.$schema(s => ({
76
68
  fragmentId: s.m(Sury.string),
77
69
  title: s.m(Sury.string),
78
70
  menuEntry: s.m(menuEntrySchema),
79
- requiredAccess: s.m(stringOptionSchema)
71
+ requiredAccess: s.m(Sury.$option(Sury.string))
80
72
  }));
81
73
 
82
74
  let uiFragmentManifestSchema = Sury.$schema(s => ({
@@ -85,7 +77,7 @@ let uiFragmentManifestSchema = Sury.$schema(s => ({
85
77
  pages: s.m(Sury.array(pageManifestEntrySchema))
86
78
  }));
87
79
 
88
- let uiFragmentManifestOptionSchema = Sury.$nullAsOption(uiFragmentManifestSchema);
80
+ let uiFragmentManifestOptionSchema = Sury.$option(uiFragmentManifestSchema);
89
81
 
90
82
  let commandLevelSchema = Sury.union([
91
83
  Sury.literal("Collection"),
@@ -95,21 +87,22 @@ let commandLevelSchema = Sury.union([
95
87
  let fieldReferenceSchema = Sury.$schema(s => ({
96
88
  fieldName: s.m(Sury.string),
97
89
  entity: s.m(Sury.string),
98
- plugin: s.m(stringOptionSchema)
90
+ plugin: s.m(Sury.$option(Sury.string))
99
91
  }));
100
92
 
101
93
  let commandDefSchema = Sury.$schema(s => ({
102
94
  name: s.m(Sury.string),
103
95
  schema: s.m(Sury.string),
104
96
  level: s.m(commandLevelSchema),
105
- aggregateIdField: s.m(stringOptionSchema),
97
+ aggregateIdField: s.m(Sury.$option(Sury.string)),
106
98
  mutationField: s.m(Sury.string),
107
99
  references: s.m(Sury.array(fieldReferenceSchema)),
108
- allowedStates: s.m(stringArrayOptionSchema),
109
- targetState: s.m(stringOptionSchema),
110
- apiExposed: s.m(boolOptionSchema),
111
- requiredAccess: s.m(stringArrayOptionSchema),
112
- ownerField: s.m(stringOptionSchema)
100
+ allowedStates: s.m(Sury.$option(Sury.array(Sury.string))),
101
+ targetState: s.m(Sury.$option(Sury.string)),
102
+ allowedStatesSource: s.m(Sury.$option(Sury.string)),
103
+ apiExposed: s.m(Sury.$option(Sury.bool)),
104
+ requiredAccess: s.m(Sury.$option(Sury.array(Sury.string))),
105
+ ownerField: s.m(Sury.$option(Sury.string))
113
106
  }));
114
107
 
115
108
  let queryableDefSchema = Sury.$schema(s => ({
@@ -120,18 +113,18 @@ let queryableDefSchema = Sury.$schema(s => ({
120
113
  linkedWriteSide: s.m(Sury.array(Sury.string)),
121
114
  labelField: s.m(Sury.string),
122
115
  searchableFields: s.m(Sury.array(Sury.string)),
123
- labelFieldSource: s.m(stringOptionSchema),
124
- lifecycleField: s.m(stringOptionSchema),
125
- ownerField: s.m(stringOptionSchema),
126
- retiredField: s.m(stringOptionSchema),
127
- retiredValues: s.m(stringArrayOptionSchema),
128
- namedWhenRetired: s.m(boolOptionSchema),
129
- visibility: s.m(stringOptionSchema),
130
- chapter: s.m(stringOptionSchema),
131
- singleQueryField: s.m(stringOptionSchema),
132
- idField: s.m(stringOptionSchema),
133
- idFieldSource: s.m(stringOptionSchema),
134
- requiredAccess: s.m(stringArrayOptionSchema)
116
+ labelFieldSource: s.m(Sury.$option(Sury.string)),
117
+ lifecycleField: s.m(Sury.$option(Sury.string)),
118
+ ownerField: s.m(Sury.$option(Sury.string)),
119
+ retiredField: s.m(Sury.$option(Sury.string)),
120
+ retiredValues: s.m(Sury.$option(Sury.array(Sury.string))),
121
+ namedWhenRetired: s.m(Sury.$option(Sury.bool)),
122
+ visibility: s.m(Sury.$option(Sury.string)),
123
+ chapter: s.m(Sury.$option(Sury.string)),
124
+ singleQueryField: s.m(Sury.$option(Sury.string)),
125
+ idField: s.m(Sury.$option(Sury.string)),
126
+ idFieldSource: s.m(Sury.$option(Sury.string)),
127
+ requiredAccess: s.m(Sury.$option(Sury.array(Sury.string)))
135
128
  }));
136
129
 
137
130
  let eventDefSchema = Sury.$schema(s => ({
@@ -152,10 +145,10 @@ let writableDefSchema = Sury.$schema(s => ({
152
145
  producedEventTypes: s.m(Sury.array(Sury.string)),
153
146
  consumedEventTypes: s.m(Sury.array(Sury.string)),
154
147
  linkedViews: s.m(Sury.array(Sury.string)),
155
- consistencyRead: s.m(stringOptionSchema),
148
+ consistencyRead: s.m(Sury.$option(Sury.string)),
156
149
  events: s.m(Sury.array(eventDefSchema)),
157
150
  errors: s.m(Sury.array(errorDefSchema)),
158
- chapter: s.m(stringOptionSchema)
151
+ chapter: s.m(Sury.$option(Sury.string))
159
152
  }));
160
153
 
161
154
  let automationSliceDefSchema = Sury.$schema(s => ({
@@ -163,25 +156,25 @@ let automationSliceDefSchema = Sury.$schema(s => ({
163
156
  consumedEventTypes: s.m(Sury.array(Sury.string)),
164
157
  producedCommandTypes: s.m(Sury.array(Sury.string)),
165
158
  targetName: s.m(Sury.string),
166
- chapter: s.m(stringOptionSchema)
159
+ chapter: s.m(Sury.$option(Sury.string))
167
160
  }));
168
161
 
169
162
  let outboundTranslationSliceDefSchema = Sury.$schema(s => ({
170
163
  name: s.m(Sury.string),
171
164
  consumedEventTypes: s.m(Sury.array(Sury.string)),
172
165
  inboundCommandTypes: s.m(Sury.array(Sury.string)),
173
- targetName: s.m(stringOptionSchema),
174
- externalSystem: s.m(stringOptionSchema),
175
- chapter: s.m(stringOptionSchema),
176
- consumedSources: s.m(stringArrayOptionSchema)
166
+ targetName: s.m(Sury.$option(Sury.string)),
167
+ externalSystem: s.m(Sury.$option(Sury.string)),
168
+ chapter: s.m(Sury.$option(Sury.string)),
169
+ consumedSources: s.m(Sury.$option(Sury.array(Sury.string)))
177
170
  }));
178
171
 
179
172
  let inboundTranslationSliceDefSchema = Sury.$schema(s => ({
180
173
  name: s.m(Sury.string),
181
174
  commandTypes: s.m(Sury.array(Sury.string)),
182
175
  targetName: s.m(Sury.string),
183
- externalSystem: s.m(stringOptionSchema),
184
- chapter: s.m(stringOptionSchema)
176
+ externalSystem: s.m(Sury.$option(Sury.string)),
177
+ chapter: s.m(Sury.$option(Sury.string))
185
178
  }));
186
179
 
187
180
  let publishedEventDefSchema = Sury.$schema(s => ({
@@ -189,65 +182,51 @@ let publishedEventDefSchema = Sury.$schema(s => ({
189
182
  fromEventTypes: s.m(Sury.array(Sury.string))
190
183
  }));
191
184
 
192
- let publishedEventDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(publishedEventDefSchema));
193
-
194
185
  let acceptedCommandDefSchema = Sury.$schema(s => ({
195
186
  name: s.m(Sury.string),
196
187
  toCommandTypes: s.m(Sury.array(Sury.string))
197
188
  }));
198
189
 
199
- let acceptedCommandDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(acceptedCommandDefSchema));
200
-
201
190
  let handledEventDefSchema = Sury.$schema(s => ({
202
191
  name: s.m(Sury.string),
203
192
  toCommandTypes: s.m(Sury.array(Sury.string))
204
193
  }));
205
194
 
206
- let handledEventDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(handledEventDefSchema));
207
-
208
195
  let issuedCommandDefSchema = Sury.$schema(s => ({
209
196
  name: s.m(Sury.string),
210
197
  fromEventTypes: s.m(Sury.array(Sury.string))
211
198
  }));
212
199
 
213
- let issuedCommandDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(issuedCommandDefSchema));
214
-
215
200
  let extensionDefSchema = Sury.$schema(s => ({
216
201
  name: s.m(Sury.string),
217
202
  delegateNames: s.m(Sury.array(Sury.string)),
218
203
  eventTypes: s.m(Sury.array(Sury.string)),
219
204
  commandTypes: s.m(Sury.array(Sury.string)),
220
- handledEvents: s.m(handledEventDefArrayOptionSchema),
221
- issuedCommands: s.m(issuedCommandDefArrayOptionSchema)
205
+ handledEvents: s.m(Sury.$option(Sury.array(handledEventDefSchema))),
206
+ issuedCommands: s.m(Sury.$option(Sury.array(issuedCommandDefSchema)))
222
207
  }));
223
208
 
224
209
  let extensionPointDefSchema = Sury.$schema(s => ({
225
210
  name: s.m(Sury.string),
226
211
  delegateNames: s.m(Sury.array(Sury.string)),
227
212
  sourceEventTypes: s.m(Sury.array(Sury.string)),
228
- commandTypes: s.m(stringArrayOptionSchema),
229
- publishedEvents: s.m(publishedEventDefArrayOptionSchema),
230
- acceptedCommands: s.m(acceptedCommandDefArrayOptionSchema)
213
+ commandTypes: s.m(Sury.$option(Sury.array(Sury.string))),
214
+ publishedEvents: s.m(Sury.$option(Sury.array(publishedEventDefSchema))),
215
+ acceptedCommands: s.m(Sury.$option(Sury.array(acceptedCommandDefSchema)))
231
216
  }));
232
217
 
233
- let extensionPointDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(extensionPointDefSchema));
234
-
235
218
  let requiredStoreDeclarationSchema = Sury.$schema(s => ({
236
219
  store: s.m(Sury.string),
237
220
  component: s.m(Sury.string),
238
221
  field: s.m(Sury.string),
239
- annotation: s.m(stringOptionSchema)
222
+ annotation: s.m(Sury.$option(Sury.string))
240
223
  }));
241
224
 
242
- let requiredStoreDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(requiredStoreDeclarationSchema));
243
-
244
225
  let requiredCapabilityDeclarationSchema = Sury.$schema(s => ({
245
226
  capability: s.m(Sury.string),
246
227
  component: s.m(Sury.string)
247
228
  }));
248
229
 
249
- let requiredCapabilityDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(requiredCapabilityDeclarationSchema));
250
-
251
230
  let traitDeclarationSchema = Sury.$schema(s => ({
252
231
  trait: s.m(Sury.string),
253
232
  version: s.m(Sury.string),
@@ -255,8 +234,6 @@ let traitDeclarationSchema = Sury.$schema(s => ({
255
234
  component: s.m(Sury.string)
256
235
  }));
257
236
 
258
- let traitDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(traitDeclarationSchema));
259
-
260
237
  let pluginStructureSchema = Sury.$schema(s => ({
261
238
  readModels: s.m(Sury.array(queryableDefSchema)),
262
239
  stateViewSlices: s.m(Sury.array(queryableDefSchema)),
@@ -266,11 +243,11 @@ let pluginStructureSchema = Sury.$schema(s => ({
266
243
  outboundTranslationSlices: s.m(Sury.array(outboundTranslationSliceDefSchema)),
267
244
  inboundTranslationSlices: s.m(Sury.array(inboundTranslationSliceDefSchema)),
268
245
  extensions: s.m(Sury.array(extensionDefSchema)),
269
- extensionPoints: s.m(extensionPointDefArrayOptionSchema),
270
- requiredStores: s.m(stringArrayOptionSchema),
271
- requiredStoreDeclarations: s.m(requiredStoreDeclarationArrayOptionSchema),
272
- requiredCapabilities: s.m(requiredCapabilityDeclarationArrayOptionSchema),
273
- traitDeclarations: s.m(traitDeclarationArrayOptionSchema)
246
+ extensionPoints: s.m(Sury.$option(Sury.array(extensionPointDefSchema))),
247
+ requiredStores: s.m(Sury.$option(Sury.array(Sury.string))),
248
+ requiredStoreDeclarations: s.m(Sury.$option(Sury.array(requiredStoreDeclarationSchema))),
249
+ requiredCapabilities: s.m(Sury.$option(Sury.array(requiredCapabilityDeclarationSchema))),
250
+ traitDeclarations: s.m(Sury.$option(Sury.array(traitDeclarationSchema)))
274
251
  }));
275
252
 
276
253
  let pluginStructureOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginStructures", undefined, pluginStructureSchema);
@@ -284,9 +261,9 @@ let pluginDefinitionSchema = Sury.$schema(s => ({
284
261
  eventCollector: s.m(Sury.string),
285
262
  extensionProtocols: s.m(Sury.array(extensionProtocolSchema)),
286
263
  apiSchemaFragment: s.m(apiSchemaFragmentOffloadSchema),
287
- apiTarget: s.m(stringOptionSchema),
264
+ apiTarget: s.m(Sury.$option(Sury.string)),
288
265
  structure: s.m(pluginStructureOffloadSchema),
289
- dcbEventLog: s.m(dcbEventLogOptionSchema),
266
+ dcbEventLog: s.m(Sury.$option(dcbEventLogDefinitionSchema)),
290
267
  kind: s.m(pluginKindSchema)
291
268
  }));
292
269
 
@@ -301,10 +278,6 @@ export {
301
278
  apiSchemaFragmentSchema,
302
279
  apiTargetSchema,
303
280
  apiSchemaFragmentOffloadSchema,
304
- dcbEventLogOptionSchema,
305
- stringOptionSchema,
306
- stringArrayOptionSchema,
307
- boolOptionSchema,
308
281
  panelManifestEntrySchema,
309
282
  menuEntrySchema,
310
283
  pageManifestEntrySchema,
@@ -321,22 +294,14 @@ export {
321
294
  outboundTranslationSliceDefSchema,
322
295
  inboundTranslationSliceDefSchema,
323
296
  publishedEventDefSchema,
324
- publishedEventDefArrayOptionSchema,
325
297
  acceptedCommandDefSchema,
326
- acceptedCommandDefArrayOptionSchema,
327
298
  handledEventDefSchema,
328
- handledEventDefArrayOptionSchema,
329
299
  issuedCommandDefSchema,
330
- issuedCommandDefArrayOptionSchema,
331
300
  extensionDefSchema,
332
301
  extensionPointDefSchema,
333
- extensionPointDefArrayOptionSchema,
334
302
  requiredStoreDeclarationSchema,
335
- requiredStoreDeclarationArrayOptionSchema,
336
303
  requiredCapabilityDeclarationSchema,
337
- requiredCapabilityDeclarationArrayOptionSchema,
338
304
  traitDeclarationSchema,
339
- traitDeclarationArrayOptionSchema,
340
305
  pluginStructureSchema,
341
306
  pluginStructureOffloadSchema,
342
307
  pluginDefinitionSchema,
@@ -62,6 +62,11 @@ Only wrappers around the field's own value are followed — the optional union a
62
62
  the array element, to any depth. Object properties are not: a reference declared
63
63
  on a nested record's field belongs to that field, and attributing it to the
64
64
  enclosing one would name the wrong field.
65
+
66
+ That is still the right answer to the question this asks. A caller collecting
67
+ *every* reference a command or event declares — including the ones on the fields
68
+ of records it holds — wants [`collectFieldTargets`], which names each one by its
69
+ own path.
65
70
  */
66
71
  let rec getFieldTarget = (schema: S.t<unknown>): option<target> =>
67
72
  switch getTarget(schema) {
@@ -75,6 +80,49 @@ let rec getFieldTarget = (schema: S.t<unknown>): option<target> =>
75
80
  }
76
81
  }
77
82
 
83
+ /**
84
+ Every reference a field declares, each paired with the path that names it.
85
+
86
+ A reference on the field's own value keeps the bare field name (`customerId`), so
87
+ nothing that reads today's paths has to learn the new form to go on working. One
88
+ declared on a field of a record the field holds is named by its path:
89
+ `lineItems[].productId` — `[]` for "each element of", `.` for "property of", and
90
+ no index, because a marker lives on the element *schema* and so applies to every
91
+ element or to none.
92
+
93
+ ⚠️ A path is a **widened** contract, not a compatible one, for a consumer that
94
+ uses the name as a dictionary key into a value's properties: such a consumer
95
+ simply misses a nested reference and falls back to whatever it does for an
96
+ unreferenced field. That degradation is the correct one, but it is a degradation.
97
+
98
+ Only one record deep, matching [`DcbTag.nestedRecordProperties`], which is the
99
+ walk shared with the tag extraction so a marker is found in the same places by
100
+ both.
101
+ */
102
+ let collectFieldTargets = (
103
+ fieldName: string,
104
+ schema: S.t<unknown>,
105
+ ): array<(string, target)> =>
106
+ switch getFieldTarget(schema) {
107
+ | Some(target) => [(fieldName, target)]
108
+ | None =>
109
+ switch DcbTag.nestedRecordProperties(schema) {
110
+ | None => []
111
+ | Some((properties, isList)) =>
112
+ let prefix = isList ? fieldName ++ "[]" : fieldName
113
+ properties
114
+ ->Dict.toArray
115
+ ->Array.filterMap(((propName, propSchema)) =>
116
+ propName === "TAG"
117
+ ? None
118
+ : getFieldTarget(propSchema)->Option.map(target => (
119
+ `${prefix}.${propName}`,
120
+ target,
121
+ ))
122
+ )
123
+ }
124
+ }
125
+
78
126
  /**
79
127
  Like `to_` but does not imply DCB tag semantics.
80
128
  Use with `@ref("Entity") @noDcbTag` when the field references another entity
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Sury from "sury";
4
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
6
  import * as DcbTag$Reventless from "./DcbTag.res.mjs";
6
7
  import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
@@ -53,6 +54,32 @@ function getFieldTarget(_schema) {
53
54
  };
54
55
  }
55
56
 
57
+ function collectFieldTargets(fieldName, schema) {
58
+ let target = getFieldTarget(schema);
59
+ if (target !== undefined) {
60
+ return [[
61
+ fieldName,
62
+ target
63
+ ]];
64
+ }
65
+ let match = DcbTag$Reventless.nestedRecordProperties(schema);
66
+ if (match === undefined) {
67
+ return [];
68
+ }
69
+ let prefix = match[1] ? fieldName + "[]" : fieldName;
70
+ return Stdlib_Array.filterMap(Object.entries(match[0]), param => {
71
+ let propName = param[0];
72
+ if (propName === "TAG") {
73
+ return;
74
+ } else {
75
+ return Stdlib_Option.map(getFieldTarget(param[1]), target => [
76
+ prefix + `.` + propName,
77
+ target
78
+ ]);
79
+ }
80
+ });
81
+ }
82
+
56
83
  function toWithoutDcbTag(plugin, entity) {
57
84
  return Semantic$Reventless.mark(Sury.string, Semantic$Reventless.Id.reference, {
58
85
  TAG: "ReferenceTo",
@@ -67,6 +94,7 @@ export {
67
94
  to_,
68
95
  getTarget,
69
96
  getFieldTarget,
97
+ collectFieldTargets,
70
98
  toWithoutDcbTag,
71
99
  }
72
100
  /* sury Not a pure module */
@@ -350,6 +350,7 @@ let renderPluginStructureCall = (
350
350
  ~extensions: array<string>,
351
351
  ~extensionPoints: array<Pairing.extensionPointDef>,
352
352
  ~componentChapters: array<(string, string)>,
353
+ ~hasLifecycleModel: bool,
353
354
  ): option<array<string>> => {
354
355
  // The structure call carries the EP *mapping* files (one per Delegate
355
356
  // connection), not the wrapped ExtensionPoint module — Plugin_Structure reads
@@ -428,6 +429,15 @@ let renderPluginStructureCall = (
428
429
  )
429
430
  ls->Array.push(" ~componentChapters=Dict.fromArray([" ++ entries->Array.join(", ") ++ "]),")
430
431
  }
432
+ // What the plugin's own scenarios say about each command's lifecycle edge.
433
+ // Written by `check:lifecycle:update`, not here: the sidecars it harvests are
434
+ // produced by the build this generator runs *before*, so a generator-written
435
+ // model would always be one build stale and a cold clone would have none at
436
+ // all. Referenced only when the file is on disk, so a plugin that has never
437
+ // been harvested keeps a byte-identical generated Plugin.res.
438
+ if hasLifecycleModel {
439
+ ls->Array.push(" ~lifecycleModel=LifecycleModel.model,")
440
+ }
431
441
  ls->Array.push(" )")
432
442
  Some(ls)
433
443
  }
@@ -595,6 +605,7 @@ let renderComposition = (
595
605
  ~config: Config.config,
596
606
  ~resolved: Pairing.resolved,
597
607
  ~componentChapters: array<(string, string)>,
608
+ ~hasLifecycleModel: bool,
598
609
  ): string => {
599
610
  let lines: array<string> = []
600
611
 
@@ -740,6 +751,7 @@ let renderComposition = (
740
751
  ~extensions=resolved.extensions,
741
752
  ~extensionPoints=resolved.extensionPoints,
742
753
  ~componentChapters,
754
+ ~hasLifecycleModel,
743
755
  )
744
756
  let hasPluginStructure = pluginStructureLines->Option.isSome
745
757
  switch pluginStructureLines {
@@ -833,6 +845,7 @@ let render = (
833
845
  ~config: Config.config,
834
846
  ~resolved: Pairing.resolved,
835
847
  ~discovered: array<Discovery.discoveredFile>,
848
+ ~hasLifecycleModel: bool=false,
836
849
  ): string => {
837
850
  validateUniqueSpecStems(~discovered)
838
851
  validateSliceTargets(~resolved)
@@ -858,6 +871,6 @@ let render = (
858
871
  )
859
872
  switch config.variant {
860
873
  | Aws({compositionNamespace}) => renderAwsWrapper(~compositionNamespace)
861
- | Composition => renderComposition(~config, ~resolved, ~componentChapters)
874
+ | Composition => renderComposition(~config, ~resolved, ~componentChapters, ~hasLifecycleModel)
862
875
  }
863
876
  }
@@ -239,7 +239,7 @@ function renderTaskMakeParam(tasks) {
239
239
  return " ~tasks=[" + entries.join(", ") + "],";
240
240
  }
241
241
 
242
- function renderPluginStructureCall(name, aggregates, readModels, stateViewSlices, stateViewSlicesStream, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters) {
242
+ function renderPluginStructureCall(name, aggregates, readModels, stateViewSlices, stateViewSlicesStream, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters, hasLifecycleModel) {
243
243
  let epMappingStems = extensionPoints.flatMap(param => param.mappings);
244
244
  let hasComponents = aggregates.length !== 0 || readModels.length !== 0 || stateViewSlices.length !== 0 || stateViewSlicesStream.length !== 0 || stateChangeSlices.length !== 0 || automationSlices.length !== 0 || outboundTranslationSlices.length !== 0 || inboundTranslationSlices.length !== 0 || extensions.length !== 0 || epMappingStems.length !== 0;
245
245
  if (!hasComponents) {
@@ -295,6 +295,9 @@ function renderPluginStructureCall(name, aggregates, readModels, stateViewSlices
295
295
  let entries$8 = componentChapters.map(param => "(\"" + param[0] + "\", \"" + param[1] + "\")");
296
296
  ls.push(" ~componentChapters=Dict.fromArray([" + entries$8.join(", ") + "]),");
297
297
  }
298
+ if (hasLifecycleModel) {
299
+ ls.push(" ~lifecycleModel=LifecycleModel.model,");
300
+ }
298
301
  ls.push(" )");
299
302
  return ls;
300
303
  }
@@ -394,7 +397,7 @@ function renderAwsWrapper(compositionNamespace) {
394
397
  ].join("\n");
395
398
  }
396
399
 
397
- function renderComposition(config, resolved, componentChapters) {
400
+ function renderComposition(config, resolved, componentChapters, hasLifecycleModel) {
398
401
  let lines = [];
399
402
  lines.push("// AUTO-GENERATED — do not edit. Run `npm run generate` to update.");
400
403
  lines.push("");
@@ -459,7 +462,7 @@ function renderComposition(config, resolved, componentChapters) {
459
462
  lines.push(" // Extensions");
460
463
  push(renderExtensions(resolved.extensions));
461
464
  }
462
- let pluginStructureLines = renderPluginStructureCall(config.name, resolved.aggregates, resolved.readModels, resolved.stateViewSlices, resolved.stateViewSlicesStream, resolved.stateChangeSlices, resolved.automationSlices, resolved.outboundTranslationSlices, resolved.inboundTranslationSlices, resolved.extensions, resolved.extensionPoints, componentChapters);
465
+ let pluginStructureLines = renderPluginStructureCall(config.name, resolved.aggregates, resolved.readModels, resolved.stateViewSlices, resolved.stateViewSlicesStream, resolved.stateChangeSlices, resolved.automationSlices, resolved.outboundTranslationSlices, resolved.inboundTranslationSlices, resolved.extensions, resolved.extensionPoints, componentChapters, hasLifecycleModel);
463
466
  let hasPluginStructure = Stdlib_Option.isSome(pluginStructureLines);
464
467
  if (pluginStructureLines !== undefined) {
465
468
  pluginStructureLines.forEach(l => {
@@ -517,7 +520,8 @@ function renderComposition(config, resolved, componentChapters) {
517
520
  return lines.join("\n");
518
521
  }
519
522
 
520
- function render(config, resolved, discovered) {
523
+ function render(config, resolved, discovered, hasLifecycleModelOpt) {
524
+ let hasLifecycleModel = hasLifecycleModelOpt !== undefined ? hasLifecycleModelOpt : false;
521
525
  validateUniqueSpecStems(discovered);
522
526
  validateSliceTargets(resolved);
523
527
  let componentStems = [
@@ -533,7 +537,7 @@ function render(config, resolved, discovered) {
533
537
  let componentChapters = Discovery$Reventless.chaptersByStem(discovered).filter(param => componentStems.includes(param[0]));
534
538
  let match = config.variant;
535
539
  if (typeof match !== "object") {
536
- return renderComposition(config, resolved, componentChapters);
540
+ return renderComposition(config, resolved, componentChapters, hasLifecycleModel);
537
541
  } else {
538
542
  return renderAwsWrapper(match.compositionNamespace);
539
543
  }
@@ -55,7 +55,7 @@ let parseFlags = (args: array<string>): dict<JSON.t> => {
55
55
  switch args->Array.get(i) {
56
56
  | None => ()
57
57
  | Some(arg) if arg->String.startsWith("--") =>
58
- let key = arg->String.sliceToEnd(~start=2)
58
+ let key = arg->String.slice(~start=2, ~end=arg->String.length)
59
59
  switch args->Array.get(i + 1) {
60
60
  | Some(value) if !(value->String.startsWith("--")) =>
61
61
  out->Dict.set(key, JSON.Encode.string(value))
@@ -107,14 +107,14 @@ let arrayFieldNames = (schema: S.t<unknown>): array<string> =>
107
107
  // ── Entry point ──────────────────────────────────────────────────────────────
108
108
 
109
109
  let main = async () => {
110
- let argv = NodeProcess.argv->Array.sliceToEnd(~start=2)
110
+ let argv = NodeProcess.argv->Array.slice(~start=2, ~end=NodeProcess.argv->Array.length)
111
111
  switch argv->Array.get(0) {
112
112
  | None | Some("") | Some("--help") | Some("-h") => {
113
113
  Console.log(usage)
114
114
  NodeProcess.exit(argv->Array.length == 0 ? 1 : 0)
115
115
  }
116
116
  | Some(traitPackage) => {
117
- let flags = parseFlags(argv->Array.sliceToEnd(~start=1))
117
+ let flags = parseFlags(argv->Array.slice(~start=1, ~end=argv->Array.length))
118
118
  let stringFlag = key => flags->Dict.get(key)->Option.flatMap(JSON.Decode.string)
119
119
  let into = stringFlag("into")
120
120
  let tests = stringFlag("tests")
@@ -136,7 +136,8 @@ let main = async () => {
136
136
  ->String.replace("trait-", "")
137
137
  ->String.split("-")
138
138
  ->Array.map(part =>
139
- part->String.charAt(0)->String.toUpperCase ++ part->String.sliceToEnd(~start=1)
139
+ part->String.charAt(0)->String.toUpperCase ++
140
+ part->String.slice(~start=1, ~end=part->String.length)
140
141
  )
141
142
  ->Array.join("") ++ "_Scaffold"
142
143
  let specifier = `${traitPackage}/src/${scaffoldModule}.res.mjs`
@@ -36,7 +36,7 @@ function parseFlags(args) {
36
36
  return;
37
37
  }
38
38
  if (arg.startsWith("--")) {
39
- let key = arg.slice(2);
39
+ let key = arg.slice(2, arg.length);
40
40
  let value = args[i + 1 | 0];
41
41
  if (value !== undefined && !value.startsWith("--")) {
42
42
  out[key] = value;
@@ -83,7 +83,7 @@ function arrayFieldNames(schema) {
83
83
  }
84
84
 
85
85
  async function main() {
86
- let argv = process.argv.slice(2);
86
+ let argv = process.argv.slice(2, process.argv.length);
87
87
  let traitPackage = argv[0];
88
88
  if (traitPackage !== undefined) {
89
89
  switch (traitPackage) {
@@ -92,7 +92,7 @@ async function main() {
92
92
  case "-h" :
93
93
  break;
94
94
  default:
95
- let flags = parseFlags(argv.slice(1));
95
+ let flags = parseFlags(argv.slice(1, argv.length));
96
96
  let stringFlag = key => Stdlib_Option.flatMap(flags[key], Stdlib_JSON.Decode.string);
97
97
  let into = stringFlag("into");
98
98
  let tests = stringFlag("tests");
@@ -103,7 +103,7 @@ async function main() {
103
103
  if (tests === undefined) {
104
104
  return fail("--into and --tests are both required.\n\n" + usage);
105
105
  }
106
- let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("") + "_Scaffold";
106
+ let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1, part.length)).join("") + "_Scaffold";
107
107
  let specifier = traitPackage + `/src/` + scaffoldModule + `.res.mjs`;
108
108
  let modulePath;
109
109
  try {
@@ -34,7 +34,10 @@ let () = {
34
34
  let config = {...Config.read(~srcDir), variant}
35
35
  let discovered = Discovery.scan(~srcDir, ~exclude=config.exclude)
36
36
  let resolved = Pairing.resolve(discovered, ~srcDir)
37
- let source = Codegen.render(~config, ~resolved, ~discovered)
37
+ // Sits at the src root rather than in a kind folder, so `Discovery` never
38
+ // sees it and it is not a component. Its presence is the whole question.
39
+ let hasLifecycleModel = NodePath.join([srcDir, "LifecycleModel.res"])->NodeFs.existsSync
40
+ let source = Codegen.render(~config, ~resolved, ~discovered, ~hasLifecycleModel)
38
41
 
39
42
  let outputDir = switch variant {
40
43
  | Config.Composition => srcDir
@@ -56,7 +56,8 @@ if (srcDirArg === "") {
56
56
  };
57
57
  let discovered = Discovery$Reventless.scan(srcDir, config_exclude);
58
58
  let resolved = Pairing$Reventless.resolve(discovered, srcDir);
59
- let source = Codegen$Reventless.render(config, resolved, discovered);
59
+ let hasLifecycleModel = Nodefs.existsSync(Nodepath.join(srcDir, "LifecycleModel.res"));
60
+ let source = Codegen$Reventless.render(config, resolved, discovered, hasLifecycleModel);
60
61
  let outputDir;
61
62
  outputDir = typeof variant !== "object" ? srcDir : Nodepath.join(process.cwd(), "src");
62
63
  let pluginPath = Nodepath.join(outputDir, "Plugin.res");