@reventlessdev/reventless-core 3.0.0-alpha.238 → 3.0.0-alpha.240

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 (178) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/package.json +12 -12
  3. package/src/Message.res +11 -11
  4. package/src/Message.res.mjs +15 -14
  5. package/src/Projection.res +2 -2
  6. package/src/Projection.res.mjs +5 -4
  7. package/src/adapter/Monitoring/Monitoring.res +24 -2
  8. package/src/adapter/Monitoring/Monitoring.res.mjs +3 -3
  9. package/src/admin/Platform_AdminApi.res.mjs +3 -3
  10. package/src/admin/Platform_Admin_Structure.res +33 -112
  11. package/src/admin/Platform_Admin_Structure.res.mjs +8 -138
  12. package/src/admin/Platform_ComponentDefinitionsApi.res +2 -1
  13. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
  14. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +12 -12
  15. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs +14 -14
  16. package/src/components/Aggregate/Aggregate_Callback.res +2 -2
  17. package/src/components/Aggregate/Aggregate_Callback.res.mjs +4 -4
  18. package/src/components/Api/ApiAllowedStatesHelpers.res.mjs +3 -3
  19. package/src/components/Api/ApiNoApiHelpers.res.mjs +5 -5
  20. package/src/components/Api/ApiTargetStateHelpers.res.mjs +3 -3
  21. package/src/components/Api/GraphQL_FragmentGenerator.res +140 -24
  22. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +48 -21
  23. package/src/components/Api/GraphQL_SchemaInspector.res +1 -1
  24. package/src/components/Api/GraphQL_SchemaInspector.res.mjs +1 -1
  25. package/src/components/Api/MCP_SchemaGenerator.res +1 -1
  26. package/src/components/Api/MCP_SchemaGenerator.res.mjs +1 -1
  27. package/src/components/Api/SchemaType.res +6 -10
  28. package/src/components/Api/SchemaType.res.mjs +4 -4
  29. package/src/components/Api/StorageRefFields.res +1 -1
  30. package/src/components/Api/StorageRefFields.res.mjs +1 -1
  31. package/src/components/Api/SuryToJsonSchema.res +19 -0
  32. package/src/components/Api/SuryToJsonSchema.res.mjs +10 -5
  33. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -4
  34. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +22 -23
  35. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -2
  36. package/src/components/CommandTopic/CommandTopic_Callback.res.mjs +3 -4
  37. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -1
  38. package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +3 -3
  39. package/src/components/Counter/Counter.res.mjs +2 -2
  40. package/src/components/Counter/Counter_Callback.res.mjs +4 -4
  41. package/src/components/Counter/Counter_Operations.res.mjs +4 -4
  42. package/src/components/Dcb/Dcb_Builder.res +1 -1
  43. package/src/components/Dcb/Dcb_Builder.res.mjs +8 -6
  44. package/src/components/DcbEventLog/DcbEventLog_Builder.res.mjs +4 -3
  45. package/src/components/EventCollector/EventCollector_Builder.res +1 -1
  46. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +1 -2
  47. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +11 -12
  48. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +3 -4
  49. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +24 -25
  50. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +1 -1
  51. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res.mjs +3 -3
  52. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +2 -2
  53. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -4
  54. package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -1
  55. package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +3 -3
  56. package/src/plugin/api/PluginBaseFragment.res +12 -29
  57. package/src/plugin/api/PluginBaseFragment.res.mjs +3 -22
  58. package/src/plugin/component/Capability_Inference.res +118 -1
  59. package/src/plugin/component/Capability_Inference.res.mjs +128 -1
  60. package/src/plugin/component/Plugin_Builder.res +9 -2
  61. package/src/plugin/component/Plugin_Builder.res.mjs +5 -2
  62. package/src/plugin/component/Plugin_Helpers.res +11 -11
  63. package/src/plugin/component/Plugin_Helpers.res.mjs +11 -10
  64. package/src/plugin/component/Plugin_Structure.res +458 -296
  65. package/src/plugin/component/Plugin_Structure.res.mjs +259 -182
  66. package/src/plugin/component/Plugin_SubscriptionSchema.res +1 -1
  67. package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +1 -1
  68. package/src/plugin/component/SchemaWalker.res +11 -11
  69. package/src/plugin/component/SchemaWalker.res.mjs +8 -9
  70. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +15 -15
  71. package/src/plugin/lifecycle/PluginBehavior.res.mjs +55 -55
  72. package/src/plugin/lifecycle/PluginSpec.res +22 -7
  73. package/src/plugin/lifecycle/PluginSpec.res.mjs +91 -36
  74. package/src/plugin/lifecycle/PluginsReadModelSpec.res +36 -9
  75. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +33 -22
  76. package/src/util/Interstack.res +3 -3
  77. package/src/util/LogFormat.res +1 -2
  78. package/src/util/LogFormat.res.mjs +2 -2
  79. package/tests/IdentityTest.res +6 -7
  80. package/tests/IdentityTest.res.mjs +7 -9
  81. package/tests/adapter/MonitoringTest.res +30 -3
  82. package/tests/adapter/MonitoringTest.res.mjs +31 -12
  83. package/tests/adapter/RuntimeExtensionHookReachTest.res +0 -1
  84. package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +5 -7
  85. package/tests/admin/Platform_Admin_StructureTest.res +254 -0
  86. package/tests/admin/Platform_Admin_StructureTest.res.mjs +219 -0
  87. package/tests/admin/Platform_BakedManifestTest.res +1 -0
  88. package/tests/admin/Platform_BakedManifestTest.res.mjs +1 -0
  89. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +3 -0
  90. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +6 -0
  91. package/tests/admin/Platform_PluginStructuresApiTest.res +1 -0
  92. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +2 -0
  93. package/tests/aggregate/AggregateCacheTest.res +0 -1
  94. package/tests/aggregate/AggregateCacheTest.res.mjs +13 -15
  95. package/tests/aggregate/AggregateConflictTest.res +0 -1
  96. package/tests/aggregate/AggregateConflictTest.res.mjs +7 -9
  97. package/tests/aggregate/AggregateFixtures.res +0 -1
  98. package/tests/aggregate/AggregateFixtures.res.mjs +19 -21
  99. package/tests/aggregate/AggregateSnapshotTest.res +1 -2
  100. package/tests/aggregate/AggregateSnapshotTest.res.mjs +15 -16
  101. package/tests/api/ApiNoApiTest.res.mjs +17 -9
  102. package/tests/api/GraphQL_FragmentGeneratorTest.res +232 -0
  103. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +207 -55
  104. package/tests/api/PsInternalFieldsReadModel.res +21 -0
  105. package/tests/api/PsInternalFieldsReadModel.res.mjs +74 -0
  106. package/tests/api/StorageRefFieldsTest.res.mjs +13 -13
  107. package/tests/api/SuryToJsonSchemaTest.res +73 -3
  108. package/tests/api/SuryToJsonSchemaTest.res.mjs +364 -816
  109. package/tests/commandgenerator/CommandGeneratorFixtures.res +0 -1
  110. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +19 -21
  111. package/tests/commandgenerator/OwnerStampingTest.res +0 -1
  112. package/tests/commandgenerator/OwnerStampingTest.res.mjs +9 -11
  113. package/tests/commandtopic/CommandTopicCallbackFixtures.res +0 -1
  114. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +7 -9
  115. package/tests/counter/CounterFixtures.res +0 -1
  116. package/tests/counter/CounterFixtures.res.mjs +1 -4
  117. package/tests/dcb/DcbDecodeTest.res.mjs +24 -24
  118. package/tests/dcb/DcbEventLogOperationsTest.res +2 -2
  119. package/tests/dcb/DcbEventLogOperationsTest.res.mjs +3 -3
  120. package/tests/dcb/DcbEventLogSchemaTest.res.mjs +7 -7
  121. package/tests/dcb/DcbFixtures.res +0 -1
  122. package/tests/dcb/DcbFixtures.res.mjs +87 -89
  123. package/tests/dcb/DcbTagTest.res.mjs +6 -6
  124. package/tests/dcb/DcbValidationTest.res.mjs +52 -52
  125. package/tests/eventlog/EventLogFixtures.res +0 -1
  126. package/tests/eventlog/EventLogFixtures.res.mjs +7 -9
  127. package/tests/eventmapper/EventMapperFixtures.res +0 -1
  128. package/tests/eventmapper/EventMapperFixtures.res.mjs +24 -26
  129. package/tests/eventtopic/EventTopicFixtures.res +0 -1
  130. package/tests/eventtopic/EventTopicFixtures.res.mjs +7 -9
  131. package/tests/extensionpoint/ExtensionPointFixtures.res +0 -1
  132. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +10 -12
  133. package/tests/extensionpoint/ExtensionPointOperationsTest.res +1 -2
  134. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -10
  135. package/tests/logger/LogFormatTest.res +3 -2
  136. package/tests/logger/LogFormatTest.res.mjs +7 -9
  137. package/tests/message/MessageTest.res +2 -2
  138. package/tests/message/MessageTest.res.mjs +8 -7
  139. package/tests/message/MetaEnvelopeTest.res +5 -5
  140. package/tests/message/MetaEnvelopeTest.res.mjs +12 -11
  141. package/tests/plugin/ManifestVisibilityTest.res.mjs +7 -7
  142. package/tests/plugin/PluginDefinitionScalars.res +5 -3
  143. package/tests/plugin/PluginDefinitionScalars.res.mjs +2 -2
  144. package/tests/plugin/PluginLifecycleCorpusTest.res +4 -3
  145. package/tests/plugin/PluginLifecycleCorpusTest.res.mjs +3 -6
  146. package/tests/plugin/PluginSpecExperiment.res +0 -1
  147. package/tests/plugin/PluginSpecExperiment.res.mjs +12 -14
  148. package/tests/plugin/PluginStructureTest.res +292 -2
  149. package/tests/plugin/PluginStructureTest.res.mjs +391 -88
  150. package/tests/plugin/Plugin_EventQuerySchemaTest.res.mjs +3 -3
  151. package/tests/plugin/SchemaWalkerTest.res +0 -1
  152. package/tests/plugin/SchemaWalkerTest.res.mjs +17 -19
  153. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +5 -5
  154. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -8
  155. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +5 -5
  156. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +9 -9
  157. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +5 -5
  158. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -8
  159. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +15 -15
  160. package/tests/plugin/StateChangeSlice/PsTypoStore.res +31 -0
  161. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +62 -0
  162. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -8
  163. package/tests/plugin/StateChangeSlice/PsUploadImages.res +40 -0
  164. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +66 -0
  165. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +13 -12
  166. package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +8 -8
  167. package/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs +6 -6
  168. package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +10 -10
  169. package/tests/plugin/StateViewSlice/PsGatedView.res.mjs +6 -6
  170. package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +9 -9
  171. package/tests/plugin/StateViewSlice/PsShipmentsView.res.mjs +11 -10
  172. package/tests/plugin/pluginDefinitionRequiredScalars.txt +15 -0
  173. package/tests/querydb/QueryDbFixtures.res +0 -1
  174. package/tests/querydb/QueryDbFixtures.res.mjs +5 -7
  175. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +0 -1
  176. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +4 -6
  177. package/tests/util/CommandPublisherTest.res +0 -1
  178. package/tests/util/CommandPublisherTest.res.mjs +4 -6
@@ -132,6 +132,21 @@ let deriveObjectTypeWithNested = (
132
132
  ): array<string> =>
133
133
  switch SchemaType.fromSuryObject(~typeName, schema) {
134
134
  | Some(fields) =>
135
+ // `@internal` joins the caller's exclusions rather than replacing them. The
136
+ // caller's list is a decision made where the query entry is assembled; this
137
+ // one is a declaration on the field itself, and the schema is the better
138
+ // place for it — a hand-written list has to be kept in step with the record
139
+ // by whoever next edits it, which is the drift that made a hand-maintained
140
+ // `pluginExcludeFields` necessary in the first place.
141
+ //
142
+ // This is the single point for the SDL: every generated object type is
143
+ // derived here, so a field declared internal is absent from all of them
144
+ // without any entry having to say so.
145
+ let declaredInternal =
146
+ Reventless.StateAnnotations.getSpec(schema)
147
+ ->Option.flatMap(spec => spec.internal)
148
+ ->Option.getOr([])
149
+ let excludeFields = Array.concat(excludeFields, declaredInternal)
135
150
  let filteredFields = if excludeFields->Array.length > 0 {
136
151
  let d = Dict.make()
137
152
  fields
@@ -411,9 +426,16 @@ let deriveObjectQueryField = (
411
426
  ~subIdField: option<string>=?,
412
427
  ): string =>
413
428
  if includeIdParam {
429
+ // `includeRetired` reaches this door for the reason it reaches the list: a
430
+ // caller the server would serve a retired row to had, until it did, no way
431
+ // to ask for one *singly*. The archive toggle put such rows on screen and
432
+ // clicking one read a door that refuses them to everybody — `decideRetired`
433
+ // withholds from `Elevated` and `System` too until they ask, and here there
434
+ // was nothing to ask with.
414
435
  switch subIdField {
415
- | Some(sortField) => ` ${singleFieldName}(id: ID!, ${sortField}: String!): ${typeName}`
416
- | None => ` ${singleFieldName}(id: ID!): ${typeName}`
436
+ | Some(sortField) =>
437
+ ` ${singleFieldName}(id: ID!, ${sortField}: String!, includeRetired: Boolean): ${typeName}`
438
+ | None => ` ${singleFieldName}(id: ID!, includeRetired: Boolean): ${typeName}`
417
439
  }
418
440
  } else {
419
441
  ` ${singleFieldName}: ${typeName}`
@@ -434,7 +456,38 @@ let deriveByIdsQueryField = (
434
456
  ~listFieldName: string,
435
457
  ~returnTypeName: string,
436
458
  ): string =>
437
- ` ${listFieldName}ByIds(ids: [String!]!): [${returnTypeName}!]!`
459
+ ` ${listFieldName}ByIds(ids: [String!]!, includeRetired: Boolean): [${returnTypeName}!]!`
460
+
461
+ // ── The reference door ─────────────────────────────────────────────────────
462
+ //
463
+ // What a caller holding a pointer to a row may learn about it: its id, the label
464
+ // a reference resolves to, and — where the view declares a retirement — whether
465
+ // this row is in one and which state that is.
466
+ //
467
+ // The narrowness is the *type's*, which is the whole reason this is a separate
468
+ // field rather than an argument on the by-ids door. A rule that answered a
469
+ // retired row on the existing door "as long as only the safe fields were asked
470
+ // for" would have to be re-implemented, identically, in four backends and to
471
+ // survive aliases, fragments and `__typename`. Here a caller cannot ask for a
472
+ // price, because the type has no price.
473
+ //
474
+ // Emitted for every view, not only for the ones that opted in, on exactly the
475
+ // reasoning `includeRetired` states above: a field that appears and disappears
476
+ // with an annotation makes adding or removing that annotation a breaking schema
477
+ // change, and forces every client to feature-detect. What the annotation decides
478
+ // is not whether the door exists but whether a *retired* row comes through it —
479
+ // without it this is a cheap label read that narrows exactly as every other door
480
+ // does.
481
+ let deriveRefTypeSdl = (~returnTypeName: string): string =>
482
+ `type ${returnTypeName}Ref {\n id: ID!\n label: String!\n retired: Boolean!\n retiredState: String\n}`
483
+
484
+ // `retiredState` is null in two cases that do not need telling apart by a
485
+ // consumer: a live row, and a boolean-form retirement, where the field is the
486
+ // state and `retired: true` has already said everything there is to say.
487
+ let deriveRefsQueryField = (
488
+ ~listFieldName: string,
489
+ ~returnTypeName: string,
490
+ ): string => ` ${listFieldName}Refs(ids: [ID!]!): [${returnTypeName}Ref!]!`
438
491
 
439
492
  // `includeRetired` sits beside the paging arguments rather than inside `filter`,
440
493
  // and that placement is the point. `filter` is the caller's description of the
@@ -459,6 +512,63 @@ let deriveConnectionQueryField = (
459
512
  ` ${listFieldName}(filter: ${filterTypeName}${orderByArg}, first: Int, after: String, last: Int, before: String, includeRetired: Boolean): ${singularTypeName}Connection!`
460
513
  }
461
514
 
515
+ // ── The by-index door ──────────────────────────────────────────────────────
516
+ //
517
+ // One derivation, called by every backend that serves this field, because the
518
+ // alternative was tried and did not survive contact. The field used to be
519
+ // emitted here for AppSync and again, independently, in the local adapter, and
520
+ // the two agreed on neither its name, its argument nor its return type. Both
521
+ // were wrong in the same way: each disagreed with the resolver standing behind
522
+ // it. The AppSync shape declared `id: ID!` while its resolver read the index
523
+ // field, so the value could not be passed at all; the local shape promised
524
+ // `[String]` while its resolver returned whole rows, so every call failed to
525
+ // serialise. A door that answers on no backend is a door whose signature can be
526
+ // chosen on the merits, which is what the one below is.
527
+
528
+ /** The row field an index is keyed on.
529
+
530
+ For a named index (`@index("byOwner") ownerId`) the index name and the field it
531
+ indexes differ, and it is the *field* every resolver filters on; for an unnamed
532
+ index the two are the same string. */
533
+ let indexKeyField = (indexConfig: Reventless.ReadModel.indexConfig): string =>
534
+ indexConfig.idField->Option.getOr(indexConfig.index)
535
+
536
+ /** `<single>By<Index>`, dropping a leading `by` from the index name so
537
+ `@index("byOwner")` reads `XByOwner` rather than `XByByOwner`. */
538
+ let indexQueryFieldName = (~singleFieldName: string, ~index: string): string => {
539
+ let stripped = if index->String.startsWith("by") && index->String.length > 2 {
540
+ index->String.slice(~start=2, ~end=index->String.length)
541
+ } else {
542
+ index
543
+ }
544
+ singleFieldName ++ "By" ++ stripped->String.capitalize
545
+ }
546
+
547
+ /** The by-index door's signature.
548
+
549
+ Paging and `includeRetired` are here for the reasons `deriveConnectionQueryField`
550
+ gives above — this door reads a secondary index that can carry as many rows as
551
+ the list, and an elevated caller that can widen every other door but this one
552
+ would find the archive reachable by id and by list and not by the index that
553
+ exists to look rows up. */
554
+ let deriveIndexQueryField = (
555
+ ~singleFieldName: string,
556
+ ~indexConfig: Reventless.ReadModel.indexConfig,
557
+ ~connectionTypeName: string,
558
+ ): string => {
559
+ let fieldName = indexQueryFieldName(~singleFieldName, ~index=indexConfig.index)
560
+ let keyField = indexKeyField(indexConfig)
561
+ // An index with a sort key can be narrowed to one exact value on it. Optional,
562
+ // unlike the partition argument: naming the index value is what the door is
563
+ // for, narrowing further is a refinement. The AppSync sort template has read
564
+ // this argument all along, against an SDL that never offered it.
565
+ let sortArg = switch indexConfig.subIdField {
566
+ | Some(sortField) => `${sortField}: String, `
567
+ | None => ""
568
+ }
569
+ ` ${fieldName}(${keyField}: String!, ${sortArg}first: Int, after: String, last: Int, before: String, includeRetired: Boolean): ${connectionTypeName}!`
570
+ }
571
+
462
572
  // ── Mutation field derivation ──────────────────────────────────────────────
463
573
 
464
574
  let deriveMutationFieldFromObject = (
@@ -546,7 +656,7 @@ let generate = (
546
656
  // schema shape — see `mutationSchemaEntry.injectIdArg`.
547
657
  let injectIdArg = entry.injectIdArg->Option.getOr(true)
548
658
  switch schema {
549
- | Union({anyOf}) =>
659
+ | AnyOf({anyOf}) =>
550
660
  // Map each fieldName to its variant in anyOf by matching the constructor
551
661
  // name. Position-based pairing (anyOf index ↔ fieldNames index) breaks
552
662
  // when `@noApi` filters out some variants but the schema still carries
@@ -555,17 +665,8 @@ let generate = (
555
665
  // index 1, leaking a stale `_0: Platform_Plugin_Deactivate_0` arg.
556
666
  let variantNames = anyOf->Array.map(v =>
557
667
  switch v {
558
- | Object({items}) =>
559
- items
560
- ->Array.find(item => item.location == "TAG")
561
- ->Option.flatMap(item =>
562
- switch item.schema {
563
- | String({const}) => Some(const)
564
- | _ => None
565
- }
566
- )
567
- ->Option.getOr("")
568
- | String({const}) => const
668
+ | Object({properties}) => SchemaWalker.tagConstOf(properties)->Option.getOr("")
669
+ | String({const: ?Some(const)}) => const
569
670
  | _ => ""
570
671
  }
571
672
  )
@@ -652,6 +753,23 @@ let generate = (
652
753
  ~returnTypeName=entry.returnTypeName,
653
754
  ),
654
755
  )
756
+
757
+ // The reference door, under the by-ids condition because it is the same
758
+ // read. Emitted here rather than per-backend so the field a backend
759
+ // provisions a resolver for is one this SDL declares: the AppSync adapter
760
+ // registers `<list>Refs` for every queryable on exactly that premise, and
761
+ // a door named in one half and not the other fails the deploy outright.
762
+ let refTypeName = entry.returnTypeName ++ "Ref"
763
+ if !(seenTypes->Set.has(refTypeName)) {
764
+ seenTypes->Set.add(refTypeName)
765
+ types->Array.push(deriveRefTypeSdl(~returnTypeName=entry.returnTypeName))
766
+ }
767
+ queries->Array.push(
768
+ deriveRefsQueryField(
769
+ ~listFieldName=entry.listFieldName,
770
+ ~returnTypeName=entry.returnTypeName,
771
+ ),
772
+ )
655
773
  }
656
774
 
657
775
  let listFieldName = entry.listFieldName
@@ -677,17 +795,15 @@ let generate = (
677
795
  switch entry.indexQueries {
678
796
  | Some(indexes) =>
679
797
  let connectionTypeName = entry.returnTypeName ++ "Connection"
680
- indexes->Array.forEach(({index}) => {
681
- let stripped = if index->String.startsWith("by") && index->String.length > 2 {
682
- index->String.slice(~start=2, ~end=index->String.length)
683
- } else {
684
- index
685
- }
686
- let fieldName = entry.singleFieldName ++ "By" ++ stripped->String.capitalize
798
+ indexes->Array.forEach(indexConfig =>
687
799
  queries->Array.push(
688
- ` ${fieldName}(id: ID!, first: Int, after: String, last: Int, before: String): ${connectionTypeName}!`,
800
+ deriveIndexQueryField(
801
+ ~singleFieldName=entry.singleFieldName,
802
+ ~indexConfig,
803
+ ~connectionTypeName,
804
+ ),
689
805
  )
690
- })
806
+ )
691
807
  | None => ()
692
808
  }
693
809
 
@@ -5,6 +5,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
6
6
  import * as Api_Naming$ReventlessCore from "./Api_Naming.res.mjs";
7
7
  import * as SchemaType$ReventlessCore from "./SchemaType.res.mjs";
8
+ import * as SchemaWalker$ReventlessCore from "../../plugin/component/SchemaWalker.res.mjs";
8
9
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
9
10
  import * as GraphQL_Stitcher$ReventlessCore from "./GraphQL_Stitcher.res.mjs";
10
11
 
@@ -116,12 +117,14 @@ function deriveObjectTypeWithNested(typeName, excludeFieldsOpt, includeIdParamOp
116
117
  if (fields === undefined) {
117
118
  return [];
118
119
  }
120
+ let declaredInternal = Stdlib_Option.getOr(Stdlib_Option.flatMap(StateAnnotations$Reventless.getSpec(schema), spec => spec.internal), []);
121
+ let excludeFields$1 = excludeFields.concat(declaredInternal);
119
122
  let filteredFields;
120
- if (excludeFields.length !== 0) {
123
+ if (excludeFields$1.length !== 0) {
121
124
  let d = {};
122
125
  Object.entries(fields).forEach(param => {
123
126
  let k = param[0];
124
- if (!excludeFields.some(ex => ex === k)) {
127
+ if (!excludeFields$1.some(ex => ex === k)) {
125
128
  d[k] = param[1];
126
129
  return;
127
130
  }
@@ -354,9 +357,9 @@ function deriveObjectQueryField(singleFieldName, typeName, includeIdParamOpt, su
354
357
  let includeIdParam = includeIdParamOpt !== undefined ? includeIdParamOpt : true;
355
358
  if (includeIdParam) {
356
359
  if (subIdField !== undefined) {
357
- return ` ` + singleFieldName + `(id: ID!, ` + subIdField + `: String!): ` + typeName;
360
+ return ` ` + singleFieldName + `(id: ID!, ` + subIdField + `: String!, includeRetired: Boolean): ` + typeName;
358
361
  } else {
359
- return ` ` + singleFieldName + `(id: ID!): ` + typeName;
362
+ return ` ` + singleFieldName + `(id: ID!, includeRetired: Boolean): ` + typeName;
360
363
  }
361
364
  } else {
362
365
  return ` ` + singleFieldName + `: ` + typeName;
@@ -368,7 +371,15 @@ function deriveListQueryField(listFieldName, pluralTypeName) {
368
371
  }
369
372
 
370
373
  function deriveByIdsQueryField(listFieldName, returnTypeName) {
371
- return ` ` + listFieldName + `ByIds(ids: [String!]!): [` + returnTypeName + `!]!`;
374
+ return ` ` + listFieldName + `ByIds(ids: [String!]!, includeRetired: Boolean): [` + returnTypeName + `!]!`;
375
+ }
376
+
377
+ function deriveRefTypeSdl(returnTypeName) {
378
+ return `type ` + returnTypeName + `Ref {\n id: ID!\n label: String!\n retired: Boolean!\n retiredState: String\n}`;
379
+ }
380
+
381
+ function deriveRefsQueryField(listFieldName, returnTypeName) {
382
+ return ` ` + listFieldName + `Refs(ids: [ID!]!): [` + returnTypeName + `Ref!]!`;
372
383
  }
373
384
 
374
385
  function deriveConnectionQueryField(listFieldName, singularTypeName, filterTypeName, hasOrderByOpt) {
@@ -377,6 +388,23 @@ function deriveConnectionQueryField(listFieldName, singularTypeName, filterTypeN
377
388
  return ` ` + listFieldName + `(filter: ` + filterTypeName + orderByArg + `, first: Int, after: String, last: Int, before: String, includeRetired: Boolean): ` + singularTypeName + `Connection!`;
378
389
  }
379
390
 
391
+ function indexKeyField(indexConfig) {
392
+ return Stdlib_Option.getOr(indexConfig.idField, indexConfig.index);
393
+ }
394
+
395
+ function indexQueryFieldName(singleFieldName, index) {
396
+ let stripped = index.startsWith("by") && index.length > 2 ? index.slice(2, index.length) : index;
397
+ return singleFieldName + "By" + Stdlib_String.capitalize(stripped);
398
+ }
399
+
400
+ function deriveIndexQueryField(singleFieldName, indexConfig, connectionTypeName) {
401
+ let fieldName = indexQueryFieldName(singleFieldName, indexConfig.index);
402
+ let keyField = indexKeyField(indexConfig);
403
+ let sortField = indexConfig.subIdField;
404
+ let sortArg = sortField !== undefined ? sortField + `: String, ` : "";
405
+ return ` ` + fieldName + `(` + keyField + `: String!, ` + sortArg + `first: Int, after: String, last: Int, before: String, includeRetired: Boolean): ` + connectionTypeName + `!`;
406
+ }
407
+
380
408
  function deriveMutationFieldFromObject(fieldName, collectedTypes, seenTypes, variantSchema) {
381
409
  let fields = SchemaType$ReventlessCore.fromSuryObject(fieldName, variantSchema);
382
410
  if (fields === undefined) {
@@ -419,7 +447,7 @@ function generate(mutationEntries, queryEntries) {
419
447
  } else {
420
448
  return;
421
449
  }
422
- case "union" :
450
+ case "anyOf" :
423
451
  let anyOf = schema.anyOf;
424
452
  let variantNames = anyOf.map(v => {
425
453
  switch (v.type) {
@@ -431,16 +459,7 @@ function generate(mutationEntries, queryEntries) {
431
459
  return "";
432
460
  }
433
461
  case "object" :
434
- return Stdlib_Option.getOr(Stdlib_Option.flatMap(v.items.find(item => item.location === "TAG"), item => {
435
- let match = item.schema;
436
- if (match.type !== "string") {
437
- return;
438
- }
439
- let $$const = match.const;
440
- if ($$const !== undefined) {
441
- return $$const;
442
- }
443
- }), "");
462
+ return Stdlib_Option.getOr(SchemaWalker$ReventlessCore.tagConstOf(v.properties), "");
444
463
  default:
445
464
  return "";
446
465
  }
@@ -481,6 +500,12 @@ function generate(mutationEntries, queryEntries) {
481
500
  queries.push(singleField);
482
501
  if (includeIdParam && entry.subIdField === undefined) {
483
502
  queries.push(deriveByIdsQueryField(entry.listFieldName, entry.returnTypeName));
503
+ let refTypeName = entry.returnTypeName + "Ref";
504
+ if (!seenTypes.has(refTypeName)) {
505
+ seenTypes.add(refTypeName);
506
+ types.push(deriveRefTypeSdl(entry.returnTypeName));
507
+ }
508
+ queries.push(deriveRefsQueryField(entry.listFieldName, entry.returnTypeName));
484
509
  }
485
510
  let listFieldName = entry.listFieldName;
486
511
  let _sf = entry.subIdField;
@@ -495,11 +520,8 @@ function generate(mutationEntries, queryEntries) {
495
520
  let indexes = entry.indexQueries;
496
521
  if (indexes !== undefined) {
497
522
  let connectionTypeName = entry.returnTypeName + "Connection";
498
- indexes.forEach(param => {
499
- let index = param.index;
500
- let stripped = index.startsWith("by") && index.length > 2 ? index.slice(2, index.length) : index;
501
- let fieldName = entry.singleFieldName + "By" + Stdlib_String.capitalize(stripped);
502
- queries.push(` ` + fieldName + `(id: ID!, first: Int, after: String, last: Int, before: String): ` + connectionTypeName + `!`);
523
+ indexes.forEach(indexConfig => {
524
+ queries.push(deriveIndexQueryField(entry.singleFieldName, indexConfig, connectionTypeName));
503
525
  });
504
526
  }
505
527
  if (connectionSpec) {
@@ -579,7 +601,12 @@ export {
579
601
  deriveObjectQueryField,
580
602
  deriveListQueryField,
581
603
  deriveByIdsQueryField,
604
+ deriveRefTypeSdl,
605
+ deriveRefsQueryField,
582
606
  deriveConnectionQueryField,
607
+ indexKeyField,
608
+ indexQueryFieldName,
609
+ deriveIndexQueryField,
583
610
  deriveMutationFieldFromObject,
584
611
  mutationArgTypes,
585
612
  generate,
@@ -29,7 +29,7 @@ let inspectMutationFields = (~fieldPrefix: string, commandSchema: S.t<'a>): arra
29
29
  let collectedTypes: array<string> = []
30
30
  let seenTypes = Set.make()
31
31
  switch schema {
32
- | Union({anyOf}) =>
32
+ | AnyOf({anyOf}) =>
33
33
  let constructorNames = Reventless.DcbTag.extractAllVariantNames(commandSchema)
34
34
  anyOf->Array.forEachWithIndex((variantSchema, i) => {
35
35
  let fieldName = switch constructorNames->Array.get(i) {
@@ -27,7 +27,7 @@ function inspectMutationFields(fieldPrefix, commandSchema) {
27
27
  fields.push(field);
28
28
  });
29
29
  break;
30
- case "union" :
30
+ case "anyOf" :
31
31
  let constructorNames = DcbTag$Reventless.extractAllVariantNames(commandSchema);
32
32
  commandSchema.anyOf.forEach((variantSchema, i) => {
33
33
  let name = constructorNames[i];
@@ -52,7 +52,7 @@ let generateTools = (
52
52
  let entryDescription = entry.description->Option.getOr("")
53
53
 
54
54
  switch schema {
55
- | Union({anyOf}) =>
55
+ | AnyOf({anyOf}) =>
56
56
  // Aggregate commands: each variant is a separate tool.
57
57
  // Inject an "id" property (aggregate instance ID) since aggregate
58
58
  // commands target a specific instance.
@@ -33,7 +33,7 @@ function generateTools(pluginName, mutationEntries) {
33
33
  inputSchema: inputSchema
34
34
  });
35
35
  return;
36
- case "union" :
36
+ case "anyOf" :
37
37
  schema.anyOf.forEach((variantSchema, i) => {
38
38
  let fieldName = Stdlib_Option.getOr(entry.fieldNames[i], "");
39
39
  if (fieldName.length <= 0) {
@@ -99,15 +99,11 @@ and shapeOf = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): s
99
99
  | BigInt(_) => ScalarBigInt
100
100
  | Array({items, additionalItems}) =>
101
101
  let itemType = switch items->Array.get(0) {
102
- | Some({schema: itemSchema}) =>
103
- fromSury(
104
- ~parentName,
105
- ~fieldName,
106
- itemSchema->(Obj.magic: S.t<unknown> => S.t<unknown>),
107
- )
102
+ | Some(itemSchema) => fromSury(~parentName, ~fieldName, itemSchema)
108
103
  | None =>
109
- // sury stores the item schema in additionalItems (not items) for S.array().
110
- // additionalItems is @unboxed: Strip="strip", Strict="strict", Schema(t) = the schema object.
104
+ // For a homogeneous S.array() sury stores the element schema in
105
+ // additionalItems (items holds fixed tuple positions). additionalItems is
106
+ // @unboxed: Strip="strip" | Strict="strict" | Schema(t) = the schema object.
111
107
  switch additionalItems {
112
108
  | Schema(s) => fromSury(~parentName, ~fieldName, s)
113
109
  | _ => ScalarString
@@ -135,7 +131,7 @@ and shapeOf = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): s
135
131
  } else {
136
132
  ObjectRef(nestedName, fields)
137
133
  }
138
- | Union({anyOf}) =>
134
+ | AnyOf({anyOf}) =>
139
135
  let nonNullVariants = anyOf->Array.filter(v =>
140
136
  switch v {
141
137
  | Null(_) | Undefined(_) => false
@@ -196,7 +192,7 @@ let optionalFieldNames = (schema: S.t<unknown>): array<string> =>
196
192
  ->Dict.toArray
197
193
  ->Array.filterMap(((propName, propSchema)) =>
198
194
  switch propSchema {
199
- | Union({anyOf}) =>
195
+ | AnyOf({anyOf}) =>
200
196
  anyOf->Array.some(v =>
201
197
  switch v {
202
198
  | Null(_) | Undefined(_) => true
@@ -92,8 +92,8 @@ function shapeOf(parentName, fieldName, schema) {
92
92
  };
93
93
  case "array" :
94
94
  let additionalItems = schema.additionalItems;
95
- let match = schema.items[0];
96
- let itemType = match !== undefined ? fromSury(parentName, fieldName, match.schema) : (
95
+ let itemSchema = schema.items[0];
96
+ let itemType = itemSchema !== undefined ? fromSury(parentName, fieldName, itemSchema) : (
97
97
  additionalItems === "strip" || additionalItems === "strict" ? "ScalarString" : fromSury(parentName, fieldName, additionalItems)
98
98
  );
99
99
  let itemType$1 = (isIdsFieldName(fieldName) || isIdFieldName(fieldName)) && itemType === "ScalarString" ? "EntityId" : itemType;
@@ -120,7 +120,7 @@ function shapeOf(parentName, fieldName, schema) {
120
120
  _1: fields
121
121
  };
122
122
  }
123
- case "union" :
123
+ case "anyOf" :
124
124
  let anyOf = schema.anyOf;
125
125
  let nonNullVariants = anyOf.filter(v => {
126
126
  switch (v.type) {
@@ -173,7 +173,7 @@ function optionalFieldNames(schema) {
173
173
  if (schema.type === "object") {
174
174
  return Stdlib_Array.filterMap(Object.entries(schema.properties), param => {
175
175
  let propSchema = param[1];
176
- if (propSchema.type === "union" && propSchema.anyOf.some(v => {
176
+ if (propSchema.type === "anyOf" && propSchema.anyOf.some(v => {
177
177
  switch (v.type) {
178
178
  case "null" :
179
179
  case "undefined" :
@@ -78,7 +78,7 @@ to qualify a store the annotation left unqualified.
78
78
  */
79
79
  let fromEventSchema = (~plugin: string, eventSchema: S.t<unknown>): array<eventRefFields> =>
80
80
  switch eventSchema {
81
- | Union({anyOf}) => anyOf->Array.filterMap(v => fromVariant(~plugin, v))
81
+ | AnyOf({anyOf}) => anyOf->Array.filterMap(v => fromVariant(~plugin, v))
82
82
  | other => fromVariant(~plugin, other)->Option.mapOr([], e => [e])
83
83
  }
84
84
 
@@ -44,7 +44,7 @@ function fromVariant(plugin, variant) {
44
44
  }
45
45
 
46
46
  function fromEventSchema(plugin, eventSchema) {
47
- if (eventSchema.type === "union") {
47
+ if (eventSchema.type === "anyOf") {
48
48
  return Stdlib_Array.filterMap(eventSchema.anyOf, v => fromVariant(plugin, v));
49
49
  } else {
50
50
  return Stdlib_Option.mapOr(fromVariant(plugin, eventSchema), [], e => [e]);
@@ -137,6 +137,14 @@ let mergeAnnotations = (
137
137
  }
138
138
  let entries =
139
139
  r.label === "" ? entries : Array.concat([("label", JSON.Encode.string(r.label))], entries)
140
+ // `namedWhenRetired` travels only when true, on the omit-the-default rule
141
+ // the keys above follow: false is what every record said before the opt-in
142
+ // existed, and writing it would put a key on every retirement to say
143
+ // nothing changed.
144
+ let entries =
145
+ r.namedWhenRetired
146
+ ? Array.concat(entries, [("namedWhenRetired", JSON.Encode.bool(true))])
147
+ : entries
140
148
  obj->Dict.set("x-reventless-retired", JSON.Encode.object(Dict.fromArray(entries)))
141
149
  | _ => ()
142
150
  }
@@ -255,8 +263,19 @@ and objectRefToJsonSchema = (
255
263
  ): JSON.t => {
256
264
  let props = Dict.make()
257
265
  let required: array<string> = []
266
+ // `@internal` fields are dropped rather than annotated. A marker would be the
267
+ // right answer for a field that is on the surface and merely unwanted (that is
268
+ // `@hidden`); this one is not on the surface at all, so there is nothing for a
269
+ // consumer to describe and an entry here would advertise a field the SDL does
270
+ // not declare — which is precisely the mismatch that made read-model queries
271
+ // reference fields the server rejects.
272
+ //
273
+ // This is the single point for every JSON-Schema consumer: `queryableDef.schema`,
274
+ // the MCP generator and the manifest all derive through this walk.
275
+ let internal = annotations->Option.flatMap(spec => spec.internal)->Option.getOr([])
258
276
  fields
259
277
  ->Dict.toArray
278
+ ->Array.filter(((fieldName, _)) => !(internal->Array.includes(fieldName)))
260
279
  ->Array.forEach(((fieldName, fieldType)) => {
261
280
  let baseSchema = fromSchemaType(fieldType)
262
281
  let withAnnotations = switch annotations {
@@ -119,20 +119,24 @@ function mergeAnnotations(fieldSchema, fieldName, spec) {
119
119
  "label",
120
120
  r.label
121
121
  ]].concat(entries$1);
122
- obj["x-reventless-retired"] = Object.fromEntries(entries$3);
122
+ let entries$4 = r.namedWhenRetired ? entries$3.concat([[
123
+ "namedWhenRetired",
124
+ true
125
+ ]]) : entries$3;
126
+ obj["x-reventless-retired"] = Object.fromEntries(entries$4);
123
127
  }
124
128
  let match$4 = spec.metric.find(param => param[0] === fieldName);
125
129
  if (match$4 !== undefined) {
126
130
  let m = match$4[1];
127
- let entries$4 = [[
131
+ let entries$5 = [[
128
132
  "aggregate",
129
133
  m.aggregate
130
134
  ]];
131
- let entries$5 = m.label === "" ? entries$4 : entries$4.concat([[
135
+ let entries$6 = m.label === "" ? entries$5 : entries$5.concat([[
132
136
  "label",
133
137
  m.label
134
138
  ]]);
135
- obj["x-reventless-metric"] = Object.fromEntries(entries$5);
139
+ obj["x-reventless-metric"] = Object.fromEntries(entries$6);
136
140
  }
137
141
  return obj;
138
142
  }
@@ -277,7 +281,8 @@ function objectRefToJsonSchema(annotations, optionalOpt, ownersOpt, fields) {
277
281
  let owners = ownersOpt !== undefined ? ownersOpt : [];
278
282
  let props = {};
279
283
  let required = [];
280
- Object.entries(fields).forEach(param => {
284
+ let internal = Stdlib_Option.getOr(Stdlib_Option.flatMap(annotations, spec => spec.internal), []);
285
+ Object.entries(fields).filter(param => !internal.includes(param[0])).forEach(param => {
281
286
  let fieldType = param[1];
282
287
  let fieldName = param[0];
283
288
  let baseSchema = fromSchemaType(fieldType);
@@ -1,4 +1,3 @@
1
- S.enableJson()
2
1
  // AutomationSlice callback — implements the TODO list pattern with mixed-source
3
2
  // dispatch (Plan 04).
4
3
  //
@@ -143,7 +142,7 @@ module Make = (
143
142
  | Some(_) => () // Already exists — skip
144
143
  | None =>
145
144
  let row: todoRow = {
146
- item: item->S.reverseConvertToJsonOrThrow(Spec.todoItemSchema),
145
+ item: item->Reventless.Util_Sury.toJson(Spec.todoItemSchema),
147
146
  status: Pending,
148
147
  createdAt: now(),
149
148
  retryCount: 0,
@@ -198,7 +197,7 @@ module Make = (
198
197
  let commands = []
199
198
 
200
199
  pending->Array.forEach(((id, row)) => {
201
- let item = try row.item->S.parseJsonOrThrow(Spec.todoItemSchema)->Some catch {
200
+ let item = try row.item->Reventless.Util_Sury.fromJson(Spec.todoItemSchema)->Some catch {
202
201
  | exn =>
203
202
  let errMsg =
204
203
  exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown")
@@ -214,7 +213,7 @@ module Make = (
214
213
  | Some((targetId, command)) =>
215
214
  todoItems->Dict.set(id, {...row, status: Processing, processedAt: now()})
216
215
  let commandJson = try command
217
- ->S.reverseConvertToJsonOrThrow(Spec.commandSchema)
216
+ ->Reventless.Util_Sury.toJson(Spec.commandSchema)
218
217
  ->Some catch {
219
218
  | exn =>
220
219
  let errMsg =