hecks 1.4.0 → 1.5.1
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.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/deploy/fargate.rb +666 -0
- data/lib/hecks/projections/deploy/lambda.rb +2423 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projections.rb +3 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +47 -10
- data/lib/hecks/projector.rb +101 -19
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +14 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
|
@@ -2,14 +2,16 @@ require_relative "../projector"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Projections
|
|
5
|
-
# A domain's own shape, projected as
|
|
5
|
+
# A domain's own shape, projected as Mermaid diagrams — the same
|
|
6
6
|
# trick `Projections::Reference`/`DocsProjector` already play for
|
|
7
7
|
# prose, one level further: a diagram generated from the
|
|
8
8
|
# declaration can't drift from it the way a hand-drawn one
|
|
9
9
|
# inevitably does, because there is no second copy to forget to
|
|
10
10
|
# update.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# ## Why Mermaid, not Graphviz
|
|
13
|
+
#
|
|
14
|
+
# Mermaid, not Graphviz (the two considered) — every diagram type
|
|
13
15
|
# below has a Mermaid form purpose-built for exactly what the
|
|
14
16
|
# underlying construct already is (a `lifecycle` is a state
|
|
15
17
|
# machine, `has_many`/`belongs_to` already speaks in cardinality,
|
|
@@ -23,6 +25,8 @@ module Hecks
|
|
|
23
25
|
# dependency earns its way past std") would rather not take just to
|
|
24
26
|
# draw a diagram.
|
|
25
27
|
#
|
|
28
|
+
# ## Diagram kinds
|
|
29
|
+
#
|
|
26
30
|
# Four diagram kinds, one file each per domain except lifecycles
|
|
27
31
|
# (one per lifecycle-bearing construct, since that's how a reader
|
|
28
32
|
# actually reaches for it — looking at one aggregate's states, not
|
|
@@ -62,6 +66,8 @@ module Hecks
|
|
|
62
66
|
# here that looks outward past
|
|
63
67
|
# this domain's own boundary
|
|
64
68
|
#
|
|
69
|
+
# ## Node and construct naming
|
|
70
|
+
#
|
|
65
71
|
# Construct names (aggregate/entity/command/event) are used bare,
|
|
66
72
|
# unsanitized, as Mermaid node/entity ids — safe because this
|
|
67
73
|
# language's own word grammar only ever admits simple CamelCase/
|
|
@@ -79,6 +85,14 @@ module Hecks
|
|
|
79
85
|
|
|
80
86
|
module_function
|
|
81
87
|
|
|
88
|
+
# Projects one domain's whole shape into every diagram kind it has data
|
|
89
|
+
# for, one Mermaid file per entry.
|
|
90
|
+
#
|
|
91
|
+
# @param bluebook [Bluebook::Chapter] the assembled chapter to project
|
|
92
|
+
# @param options [Hash{Symbol => Object}] projection options; only `:hecksagon`
|
|
93
|
+
# (a `Bluebook::Hecksagon`, or `nil`) is read, and only by `frameworks_diagram`
|
|
94
|
+
# @return [Hash{String => String}] each diagram's filename (such as
|
|
95
|
+
# `"relationships.mmd"`) mapped to its rendered Mermaid source
|
|
82
96
|
def call(bluebook:, options: {})
|
|
83
97
|
files = {}
|
|
84
98
|
|
|
@@ -125,18 +139,35 @@ module Hecks
|
|
|
125
139
|
|
|
126
140
|
# ── shared ────────────────────────────────────────────────────────
|
|
127
141
|
|
|
142
|
+
# Every aggregate and entity in this domain, flattened into one list —
|
|
143
|
+
# everything a lifecycle/relationship/command diagram walks alike.
|
|
144
|
+
#
|
|
128
145
|
# An entity can carry its own lifecycle, relationship, or command
|
|
129
146
|
# too — its own `lifecycle`/`reference_to`/`command` block,
|
|
130
147
|
# addressed through its holding aggregate the same way
|
|
131
148
|
# `DocsProjector` already treats an aggregate and its entities
|
|
132
149
|
# alike. Walking both here means a domain's entity gaining any of
|
|
133
150
|
# these needs no change to this file.
|
|
151
|
+
#
|
|
152
|
+
# @param bluebook [Bluebook::Chapter] the chapter to walk
|
|
153
|
+
# @return [Array<Bluebook::Aggregate, Class>] every aggregate, and every entity
|
|
154
|
+
# class (a `Bluebook::Entity` subclass) nested under it, in declaration order
|
|
134
155
|
def holders(bluebook)
|
|
135
156
|
bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
|
|
136
157
|
end
|
|
137
158
|
|
|
159
|
+
# The holders (see `holders`) that declare their own `lifecycle` block —
|
|
160
|
+
# one diagram file is projected per lifecycle-bearing holder, not one
|
|
161
|
+
# per domain.
|
|
162
|
+
#
|
|
163
|
+
# @param bluebook [Bluebook::Chapter] the chapter to walk
|
|
164
|
+
# @return [Array<Bluebook::Aggregate, Class>] every holder with a non-nil
|
|
165
|
+
# `lifecycle`
|
|
138
166
|
def holders_with_lifecycle(bluebook) = holders(bluebook).select(&:lifecycle)
|
|
139
167
|
|
|
168
|
+
# Renders the two-line `%%` comment banner every generated diagram file
|
|
169
|
+
# opens with, warning against hand edits and naming how to regenerate it.
|
|
170
|
+
#
|
|
140
171
|
# `chapter_name` drives the re-run hint always — that's the one
|
|
141
172
|
# argument `bin/project_diagrams` actually takes, regardless of
|
|
142
173
|
# which single aggregate/entity `subject` happens to name. Passing
|
|
@@ -144,6 +175,10 @@ module Hecks
|
|
|
144
175
|
# `Order_lifecycle.mmd` telling a reader to run
|
|
145
176
|
# `bin/project_diagrams <domain-path> Order` — a chapter name
|
|
146
177
|
# Hecks.boot has never heard of.
|
|
178
|
+
#
|
|
179
|
+
# @param chapter_name [String] the domain's chapter name, used in the re-run hint
|
|
180
|
+
# @param subject [String] prose naming what this specific file was generated from
|
|
181
|
+
# @return [String] the two-line `%%`-commented Mermaid banner, newline-terminated
|
|
147
182
|
def header(chapter_name, subject)
|
|
148
183
|
<<~HEADER
|
|
149
184
|
%% GENERATED by bin/project_diagrams from #{subject} — DO NOT EDIT BY HAND.
|
|
@@ -153,6 +188,15 @@ module Hecks
|
|
|
153
188
|
|
|
154
189
|
# ── lifecycle -> stateDiagram-v2 ─────────────────────────────────
|
|
155
190
|
|
|
191
|
+
# Renders one holder's own declared `lifecycle` as a Mermaid
|
|
192
|
+
# `stateDiagram-v2` — its default starting state, and one edge per
|
|
193
|
+
# transition, labeled by the command that causes it.
|
|
194
|
+
#
|
|
195
|
+
# @param bluebook [Bluebook::Chapter] the chapter `holder` belongs to, for the
|
|
196
|
+
# file's re-run banner
|
|
197
|
+
# @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
|
|
198
|
+
# `Bluebook::Entity` subclass) whose `lifecycle` to draw
|
|
199
|
+
# @return [String] the rendered Mermaid `stateDiagram-v2` source
|
|
156
200
|
def lifecycle_diagram(bluebook, holder)
|
|
157
201
|
lifecycle = holder.lifecycle
|
|
158
202
|
edges = lifecycle.transitions.flat_map do |command_name, transition|
|
|
@@ -169,6 +213,10 @@ module Hecks
|
|
|
169
213
|
|
|
170
214
|
# ── relationships -> erDiagram ───────────────────────────────────
|
|
171
215
|
|
|
216
|
+
# Renders the whole domain's declared `has_many`/`has_one`/`belongs_to`/
|
|
217
|
+
# `reference_to` attributes as a Mermaid `erDiagram`, one edge per
|
|
218
|
+
# reference attribute across every holder.
|
|
219
|
+
#
|
|
172
220
|
# Standard crow's-foot reading, the same convention every ORM's own
|
|
173
221
|
# ERD generator (Rails' erd gem included) already uses:
|
|
174
222
|
# `has_many`/`has_one` are read from the owning side — one Holder
|
|
@@ -181,6 +229,10 @@ module Hecks
|
|
|
181
229
|
# absent (a nilable reference, an empty has_one) — never the
|
|
182
230
|
# crow's-foot "many" marker, which is a structural fact independent
|
|
183
231
|
# of any one instance's optionality.
|
|
232
|
+
#
|
|
233
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
234
|
+
# @return [String, nil] the rendered Mermaid `erDiagram` source, or `nil` if the
|
|
235
|
+
# domain declares no reference attribute at all
|
|
184
236
|
def relationship_diagram(bluebook)
|
|
185
237
|
edges = holders(bluebook).flat_map do |holder|
|
|
186
238
|
holder.attributes.select(&:reference?).map { |attribute| relationship_edge(holder, attribute) }
|
|
@@ -191,6 +243,14 @@ module Hecks
|
|
|
191
243
|
"#{header(bluebook.name, subject)}erDiagram\n#{edges.join("\n")}\n"
|
|
192
244
|
end
|
|
193
245
|
|
|
246
|
+
# One erDiagram edge for one holder's own declared reference attribute,
|
|
247
|
+
# in the DSL word's own crow's-foot reading (see `relationship_diagram`).
|
|
248
|
+
#
|
|
249
|
+
# @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
|
|
250
|
+
# `Bluebook::Entity` subclass) that declares `attribute`
|
|
251
|
+
# @param attribute [Bluebook::Attribute] a reference attribute (`attribute.reference?`
|
|
252
|
+
# is true); its `relationship` names which DSL word minted it
|
|
253
|
+
# @return [String] the rendered `erDiagram` edge line
|
|
194
254
|
def relationship_edge(holder, attribute)
|
|
195
255
|
target = attribute.type.target_name
|
|
196
256
|
case attribute.relationship
|
|
@@ -213,6 +273,10 @@ module Hecks
|
|
|
213
273
|
# aggregates may share a command name; event ids are bare
|
|
214
274
|
# (`evt_PizzaCreated`) since an event is this domain's own
|
|
215
275
|
# addressing key, the same way `policy.on_event` reaches it.
|
|
276
|
+
#
|
|
277
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
278
|
+
# @return [String, nil] the rendered Mermaid flowchart source, or `nil` if the
|
|
279
|
+
# domain declares no command `emits` and no policy at all
|
|
216
280
|
def dispatch_diagram(bluebook)
|
|
217
281
|
lines = []
|
|
218
282
|
|
|
@@ -231,6 +295,14 @@ module Hecks
|
|
|
231
295
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
232
296
|
end
|
|
233
297
|
|
|
298
|
+
# One flowchart edge from a command to one event it declares it `emits`.
|
|
299
|
+
#
|
|
300
|
+
# @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
|
|
301
|
+
# `Bluebook::Entity` subclass) that declares `command`
|
|
302
|
+
# @param command [Class] the command class (a `Bluebook::Command` subclass) that
|
|
303
|
+
# emits `event`
|
|
304
|
+
# @param event [String] the emitted event's name
|
|
305
|
+
# @return [String] the rendered `-->|emits|` edge line
|
|
234
306
|
def emits_edge(holder, command, event)
|
|
235
307
|
%( #{command_node(holder.hecks_name, command.hecks_name)} -->|emits| #{event_node(event)})
|
|
236
308
|
end
|
|
@@ -246,6 +318,10 @@ module Hecks
|
|
|
246
318
|
# edge of its own here, which honestly shows "dispatch continues
|
|
247
319
|
# elsewhere" rather than silently dropping the edge. The label
|
|
248
320
|
# names which domain, so that's not a dead end on the page either.
|
|
321
|
+
#
|
|
322
|
+
# @param policy [Bluebook::Policy] the policy to draw as a trigger edge
|
|
323
|
+
# @return [String] the rendered `-->|triggers|` (or `-->|triggers in <domain>|`)
|
|
324
|
+
# edge line
|
|
249
325
|
def trigger_edge(policy)
|
|
250
326
|
bare_event = policy.on_event.to_s.split(".").last
|
|
251
327
|
aggregate_name, command_name = policy.trigger_command.to_s.split(".", 2)
|
|
@@ -253,14 +329,28 @@ module Hecks
|
|
|
253
329
|
%( #{event_node(bare_event)} -->|#{label}| #{command_node(aggregate_name, command_name)})
|
|
254
330
|
end
|
|
255
331
|
|
|
332
|
+
# Mermaid stadium-shaped node id and label for one command, qualified by
|
|
333
|
+
# its owning aggregate since two aggregates may share a command name.
|
|
334
|
+
#
|
|
335
|
+
# @param aggregate_name [String] the command's owning aggregate/entity name
|
|
336
|
+
# @param command_name [String] the command's own name
|
|
337
|
+
# @return [String] the rendered `cmd_<aggregate>_<command>(["..."])` node
|
|
256
338
|
def command_node(aggregate_name, command_name)
|
|
257
339
|
%(cmd_#{aggregate_name}_#{command_name}(["#{aggregate_name}.#{command_name}"]))
|
|
258
340
|
end
|
|
259
341
|
|
|
342
|
+
# Mermaid hexagonal node id and label for one event, addressed bare since
|
|
343
|
+
# an event is this domain's own addressing key.
|
|
344
|
+
#
|
|
345
|
+
# @param event_name [String] the event's own name
|
|
346
|
+
# @return [String] the rendered `evt_<event>{{"..."}}` node
|
|
260
347
|
def event_node(event_name) = %(evt_#{event_name}{{"#{event_name}"}})
|
|
261
348
|
|
|
262
349
|
# ── roles -> flowchart ────────────────────────────────────────────
|
|
263
350
|
|
|
351
|
+
# Renders every command's declared `role` as a Mermaid flowchart, one
|
|
352
|
+
# edge per role issuing a command.
|
|
353
|
+
#
|
|
264
354
|
# Who issues what, across the whole domain — data no existing
|
|
265
355
|
# projection draws at all today (the reference pages' own
|
|
266
356
|
# `command_entry` only ever prints a command's role as a single
|
|
@@ -268,6 +358,10 @@ module Hecks
|
|
|
268
358
|
# no declared `role` draws nothing — there is no fact to state.
|
|
269
359
|
# Circle-shaped so a role reads as "who" beside `dispatch.mmd`'s
|
|
270
360
|
# stadium ("what someone does") and hexagon ("what happened").
|
|
361
|
+
#
|
|
362
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
363
|
+
# @return [String, nil] the rendered Mermaid flowchart source, or `nil` if no
|
|
364
|
+
# command in the domain declares a `role`
|
|
271
365
|
def roles_diagram(bluebook)
|
|
272
366
|
lines = holders(bluebook).flat_map do |holder|
|
|
273
367
|
holder.commands.select(&:role).map { |command| role_edge(holder, command) }
|
|
@@ -278,17 +372,36 @@ module Hecks
|
|
|
278
372
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
279
373
|
end
|
|
280
374
|
|
|
375
|
+
# One flowchart edge from a command's declared role to the command
|
|
376
|
+
# itself.
|
|
377
|
+
#
|
|
378
|
+
# @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
|
|
379
|
+
# `Bluebook::Entity` subclass) that declares `command`
|
|
380
|
+
# @param command [Class] the command class (a `Bluebook::Command` subclass); its
|
|
381
|
+
# `role` is drawn
|
|
382
|
+
# @return [String] the rendered `-->|issues|` edge line
|
|
281
383
|
def role_edge(holder, command)
|
|
282
384
|
%( #{role_node(command.role)} -->|issues| #{command_node(holder.hecks_name, command.hecks_name)})
|
|
283
385
|
end
|
|
284
386
|
|
|
387
|
+
# Mermaid circular node id and label for one role, keeping the real
|
|
388
|
+
# free-text role string as the displayed label (see `role_id`).
|
|
389
|
+
#
|
|
390
|
+
# @param role_name [String] the command's declared `role` text
|
|
391
|
+
# @return [String] the rendered `role_<id>((<role>))` node
|
|
285
392
|
def role_node(role_name) = %(#{role_id(role_name)}((#{role_name})))
|
|
286
393
|
|
|
394
|
+
# Sanitizes a free-text role name into a legal Mermaid node id.
|
|
395
|
+
#
|
|
287
396
|
# A role name is free text ("Back office", "Vault officer") —
|
|
288
397
|
# unlike every other name this file uses as a bare id, this one
|
|
289
398
|
# has to be sanitized to become a legal Mermaid identifier. The
|
|
290
399
|
# real string still appears as the node's own label
|
|
291
400
|
# (`role_node`); only the id is mangled.
|
|
401
|
+
#
|
|
402
|
+
# @param role_name [String] the command's declared `role` text
|
|
403
|
+
# @return [String] `role_` followed by `role_name` with every run of
|
|
404
|
+
# non-alphanumeric characters collapsed to a single underscore
|
|
292
405
|
def role_id(role_name) = "role_#{role_name.to_s.gsub(/[^A-Za-z0-9]+/, '_')}"
|
|
293
406
|
|
|
294
407
|
# ── ports -> flowchart ───────────────────────────────────────────
|
|
@@ -306,8 +419,8 @@ module Hecks
|
|
|
306
419
|
# aggregate the port hangs off (always present — a port always
|
|
307
420
|
# belongs to exactly one aggregate), and a solid "to:" edge to
|
|
308
421
|
# whichever aggregate the operation itself names as its receiver
|
|
309
|
-
# (present only when `to:` is declared —
|
|
310
|
-
#
|
|
422
|
+
# (present only when `to:` is declared — an operation with no
|
|
423
|
+
# declared target has no destination of its own to draw).
|
|
311
424
|
# `emits` reuses `dispatch.mmd`'s own `event_node` unchanged — the
|
|
312
425
|
# same fact, reached from a different direction.
|
|
313
426
|
#
|
|
@@ -317,6 +430,10 @@ module Hecks
|
|
|
317
430
|
# it raises, it isn't just always empty), so walking entities here
|
|
318
431
|
# the way every other diagram in this file does would crash on
|
|
319
432
|
# the first entity-bearing domain.
|
|
433
|
+
#
|
|
434
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
435
|
+
# @return [String, nil] the rendered Mermaid flowchart source, or `nil` if the
|
|
436
|
+
# domain declares no port operation at all
|
|
320
437
|
def ports_diagram(bluebook)
|
|
321
438
|
lines = bluebook.aggregates.flat_map do |holder|
|
|
322
439
|
holder.ports.flat_map { |port| port.operations.map { |operation| port_edges(holder, port, operation) } }
|
|
@@ -328,6 +445,14 @@ module Hecks
|
|
|
328
445
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
329
446
|
end
|
|
330
447
|
|
|
448
|
+
# All of one port operation's own edges: the aggregate that exposes it,
|
|
449
|
+
# its optional `to:` routing target, and one edge per event it `emits`.
|
|
450
|
+
#
|
|
451
|
+
# @param holder [Bluebook::Aggregate] the aggregate `port` is attached to
|
|
452
|
+
# @param port [Bluebook::DomainPort] the port `operation` belongs to
|
|
453
|
+
# @param operation [Bluebook::PortOperation] the operation to draw
|
|
454
|
+
# @return [Array<String>] the rendered edge lines: one "exposes" edge, plus a
|
|
455
|
+
# "to:" edge when `operation.to` is declared, plus one "emits" edge per event
|
|
331
456
|
def port_edges(holder, port, operation)
|
|
332
457
|
op = port_operation_node(holder.hecks_name, port.name, operation.hecks_name)
|
|
333
458
|
edges = [" #{holder.hecks_name}[(#{holder.hecks_name})] -.->|exposes| #{op}"]
|
|
@@ -336,6 +461,13 @@ module Hecks
|
|
|
336
461
|
edges
|
|
337
462
|
end
|
|
338
463
|
|
|
464
|
+
# Mermaid trapezoidal node id and label for one port operation, qualified
|
|
465
|
+
# by both its owning aggregate and its owning port.
|
|
466
|
+
#
|
|
467
|
+
# @param aggregate_name [String] the operation's owning aggregate name
|
|
468
|
+
# @param port_name [String] the operation's owning port name
|
|
469
|
+
# @param operation_name [String] the operation's own name
|
|
470
|
+
# @return [String] the rendered `op_<aggregate>_<port>_<operation>[/"..."/]` node
|
|
339
471
|
def port_operation_node(aggregate_name, port_name, operation_name)
|
|
340
472
|
id = "op_#{aggregate_name}_#{port_name}_#{operation_name}"
|
|
341
473
|
%(#{id}[/"#{port_name}.#{operation_name}"/])
|
|
@@ -343,6 +475,9 @@ module Hecks
|
|
|
343
475
|
|
|
344
476
|
# ── read models -> flowchart ─────────────────────────────────────
|
|
345
477
|
|
|
478
|
+
# Renders the whole domain's declared `read_models` as a Mermaid
|
|
479
|
+
# flowchart, one edge per aggregate a read model is assembled from.
|
|
480
|
+
#
|
|
346
481
|
# The read-side complement to `relationships.mmd` — that diagram
|
|
347
482
|
# shows how aggregates reference each other for writes
|
|
348
483
|
# (`has_many`/`belongs_to`/`reference_to`); this shows how a
|
|
@@ -369,6 +504,10 @@ module Hecks
|
|
|
369
504
|
# read_model. Still MVP scope: `where`/`group_by`/`order_by`
|
|
370
505
|
# aren't drawn at all yet — real facts, not invented, just not
|
|
371
506
|
# this diagram's job yet.
|
|
507
|
+
#
|
|
508
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
509
|
+
# @return [String, nil] the rendered Mermaid flowchart source, or `nil` if the
|
|
510
|
+
# domain declares no `read_model` at all
|
|
372
511
|
def read_model_diagram(bluebook)
|
|
373
512
|
lines = bluebook.read_models.flat_map { |read_model| read_model_edges(read_model) }
|
|
374
513
|
return nil if lines.empty?
|
|
@@ -377,6 +516,12 @@ module Hecks
|
|
|
377
516
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
378
517
|
end
|
|
379
518
|
|
|
519
|
+
# One edge per aggregate one read model is assembled from, into that
|
|
520
|
+
# read model's own subroutine-shaped node.
|
|
521
|
+
#
|
|
522
|
+
# @param read_model [Bluebook::ReadModel] the read model to draw
|
|
523
|
+
# @return [Array<String>] the rendered edge lines, one per entry in
|
|
524
|
+
# `read_model.to_h[:aggregate_heads]`
|
|
380
525
|
def read_model_edges(read_model)
|
|
381
526
|
shape = read_model.to_h
|
|
382
527
|
node = %(rm_#{shape[:name]}[["#{read_model_label(shape)}"]])
|
|
@@ -394,6 +539,13 @@ module Hecks
|
|
|
394
539
|
end
|
|
395
540
|
end
|
|
396
541
|
|
|
542
|
+
# The read model's own subroutine node label — its name, with
|
|
543
|
+
# `(count)`/`(median: field)` appended for the two aggregations this
|
|
544
|
+
# diagram states.
|
|
545
|
+
#
|
|
546
|
+
# @param shape [Hash{Symbol => Object}] a `Bluebook::ReadModel#to_h` result;
|
|
547
|
+
# reads `:name`, `:count` and `:median_field`
|
|
548
|
+
# @return [String] the node's display label
|
|
397
549
|
def read_model_label(shape)
|
|
398
550
|
return "#{shape[:name]} (count)" if shape[:count]
|
|
399
551
|
return "#{shape[:name]} (median: #{shape[:median_field]})" if shape[:median_field]
|
|
@@ -403,6 +555,9 @@ module Hecks
|
|
|
403
555
|
|
|
404
556
|
# ── surface -> flowchart ─────────────────────────────────────────
|
|
405
557
|
|
|
558
|
+
# Renders one holder's own commands (and what each writes) and queries
|
|
559
|
+
# as a Mermaid flowchart.
|
|
560
|
+
#
|
|
406
561
|
# "What can I do to this, what can I ask about it" — one file per
|
|
407
562
|
# holder, unlike every other diagram here: `dispatch.mmd` already
|
|
408
563
|
# shows a command's own onward reaction chain, but never an
|
|
@@ -447,6 +602,12 @@ module Hecks
|
|
|
447
602
|
# every other fact in this file. `append`'s own fields carry no
|
|
448
603
|
# single source at all — its own field names are the fact worth
|
|
449
604
|
# stating (real: `Order.AddTopping` appends `name, amount`).
|
|
605
|
+
#
|
|
606
|
+
# @param bluebook [Bluebook::Chapter] the chapter `holder` belongs to, for the
|
|
607
|
+
# file's re-run banner
|
|
608
|
+
# @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
|
|
609
|
+
# `Bluebook::Entity` subclass) whose commands and queries to draw
|
|
610
|
+
# @return [String] the rendered Mermaid flowchart source
|
|
450
611
|
def surface_diagram(bluebook, holder)
|
|
451
612
|
lines = holder.commands.map do |command|
|
|
452
613
|
" #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}"
|
|
@@ -464,10 +625,25 @@ module Hecks
|
|
|
464
625
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
465
626
|
end
|
|
466
627
|
|
|
628
|
+
# Mermaid diamond-shaped node id and label for one query, qualified by
|
|
629
|
+
# its owning aggregate.
|
|
630
|
+
#
|
|
631
|
+
# @param aggregate_name [String] the query's owning aggregate/entity name
|
|
632
|
+
# @param query_name [String] the query's own name
|
|
633
|
+
# @return [String] the rendered `qry_<aggregate>_<query>{"..."}` node
|
|
467
634
|
def query_node(aggregate_name, query_name)
|
|
468
635
|
%(qry_#{aggregate_name}_#{query_name}{"#{aggregate_name}.#{query_name}"})
|
|
469
636
|
end
|
|
470
637
|
|
|
638
|
+
# One flowchart edge from a command to one attribute it mutates,
|
|
639
|
+
# labeled with what the mutation writes (see `mutation_label`).
|
|
640
|
+
#
|
|
641
|
+
# @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
|
|
642
|
+
# `Bluebook::Entity` subclass) that declares `command` and the mutated attribute
|
|
643
|
+
# @param command [Class] the command class (a `Bluebook::Command` subclass) that
|
|
644
|
+
# declares `mutation`
|
|
645
|
+
# @param mutation [Bluebook::Mutation] the mutation to draw
|
|
646
|
+
# @return [String] the rendered `-->|"..."|` edge line
|
|
471
647
|
def mutation_edge(holder, command, mutation)
|
|
472
648
|
shape = mutation.to_h
|
|
473
649
|
label = mutation_label(shape)
|
|
@@ -475,6 +651,13 @@ module Hecks
|
|
|
475
651
|
%( #{command_node(holder.hecks_name, command.hecks_name)} -->|"#{label}"| #{target})
|
|
476
652
|
end
|
|
477
653
|
|
|
654
|
+
# The edge label naming what one mutation writes — the op, and either
|
|
655
|
+
# its bound field names (an append/delegate/corrects) or its value
|
|
656
|
+
# source (see `mutation_source_detail`).
|
|
657
|
+
#
|
|
658
|
+
# @param shape [Hash{Symbol => Object}] a `Bluebook::Mutation#to_h` result;
|
|
659
|
+
# reads `:op`, and either `:fields` or `:source`
|
|
660
|
+
# @return [String] the rendered `"<op>s: <detail>"` label text
|
|
478
661
|
def mutation_label(shape)
|
|
479
662
|
verb = "#{shape[:op]}s"
|
|
480
663
|
# `fields:` (not `source:`) is the multi-binding shape
|
|
@@ -501,14 +684,27 @@ module Hecks
|
|
|
501
684
|
# was caught). Swapped for a single quote here rather than
|
|
502
685
|
# escaped, the same "state it, don't invent it, just make it
|
|
503
686
|
# legal Mermaid" trade `read_models.mmd`'s own quoting fix made.
|
|
687
|
+
#
|
|
688
|
+
# @param source [Hash{Symbol => Object}] a classified mutation source: `{kind:
|
|
689
|
+
# "literal", value:}`, `{kind: "argument", name:}`, or `{kind: "state", name:}`
|
|
690
|
+
# @return [String] `source[:value]` quoted for a literal, `source[:name]` for an
|
|
691
|
+
# argument, or `source[:kind]` itself for any other kind
|
|
504
692
|
def mutation_source_detail(source)
|
|
505
693
|
case source[:kind]
|
|
506
694
|
when "literal" then "'#{source[:value].to_s.tr('"', "'")}'"
|
|
507
695
|
when "argument" then source[:name]
|
|
508
|
-
else
|
|
696
|
+
else
|
|
697
|
+
# A source kind this file has no real corpus example of yet — named, not hidden.
|
|
698
|
+
source[:kind]
|
|
509
699
|
end
|
|
510
700
|
end
|
|
511
701
|
|
|
702
|
+
# Mermaid plain-rectangle node id and label for one attribute a
|
|
703
|
+
# mutation writes, qualified by its owning holder.
|
|
704
|
+
#
|
|
705
|
+
# @param holder_name [String] the attribute's owning aggregate/entity name
|
|
706
|
+
# @param attribute_name [Symbol, String] the mutated attribute's own name
|
|
707
|
+
# @return [String] the rendered `attr_<holder>_<attribute>[<attribute>]` node
|
|
512
708
|
def attribute_node(holder_name, attribute_name)
|
|
513
709
|
%(attr_#{holder_name}_#{attribute_name}[#{attribute_name}])
|
|
514
710
|
end
|
|
@@ -544,6 +740,12 @@ module Hecks
|
|
|
544
740
|
# dispatch declined is exactly as real a cause of a state
|
|
545
741
|
# transition as an event announced, and the diagram states it
|
|
546
742
|
# exactly as verbatim as every other edge here does.
|
|
743
|
+
#
|
|
744
|
+
# @param bluebook [Bluebook::Chapter] the chapter `saga` belongs to, for the
|
|
745
|
+
# file's re-run banner
|
|
746
|
+
# @param saga [Bluebook::ProcessManager] the process manager whose states and
|
|
747
|
+
# dispatches to draw
|
|
748
|
+
# @return [String] the rendered Mermaid `stateDiagram-v2` source
|
|
547
749
|
def saga_diagram(bluebook, saga)
|
|
548
750
|
edges = saga.handlers.map { |handler| saga_edge(handler, saga) }
|
|
549
751
|
|
|
@@ -556,7 +758,10 @@ module Hecks
|
|
|
556
758
|
MERMAID
|
|
557
759
|
end
|
|
558
760
|
|
|
559
|
-
#
|
|
761
|
+
# One stateDiagram-v2 edge for one saga handler, labeled by the event
|
|
762
|
+
# that causes it and what it dispatches.
|
|
763
|
+
#
|
|
764
|
+
# The `REFUSED` edge's own dispatch list is partly derived now —
|
|
560
765
|
# per-dispatch saga compensation (`compensates`) moved a saga's own
|
|
561
766
|
# compensating dispatches off the hand-written `on :refused` leg
|
|
562
767
|
# and onto whichever forward dispatch each one undoes, so
|
|
@@ -564,9 +769,15 @@ module Hecks
|
|
|
564
769
|
# edge for any saga using it — accurate to the declaration, wrong
|
|
565
770
|
# about what the runtime actually does at refusal (it derives and
|
|
566
771
|
# fires every declared `compensates`, newest first). `saga` is
|
|
567
|
-
# passed through for exactly this — only the REFUSED handler needs
|
|
772
|
+
# passed through for exactly this — only the `REFUSED` handler needs
|
|
568
773
|
# it, every other edge's own `handler.dispatches` already says
|
|
569
774
|
# everything real about it.
|
|
775
|
+
#
|
|
776
|
+
# @param handler [Bluebook::ProcessManagerHandler] the handler row to draw as
|
|
777
|
+
# an edge
|
|
778
|
+
# @param saga [Bluebook::ProcessManager] the process manager `handler` belongs
|
|
779
|
+
# to, needed only to derive the `REFUSED` edge's compensating dispatches
|
|
780
|
+
# @return [String] the rendered `-->` stateDiagram-v2 edge line
|
|
570
781
|
def saga_edge(handler, saga)
|
|
571
782
|
label = handler.event_type
|
|
572
783
|
# Derived first, then the hand-written body — the same order
|
|
@@ -585,12 +796,20 @@ module Hecks
|
|
|
585
796
|
# derives and fires (newest-first, at actual refusal time; this
|
|
586
797
|
# diagram states them in declaration order, since it draws the
|
|
587
798
|
# saga's own shape, not one instance's own runtime history).
|
|
799
|
+
#
|
|
800
|
+
# @param saga [Bluebook::ProcessManager] the process manager whose declared
|
|
801
|
+
# compensations to list
|
|
802
|
+
# @return [Array<String>] the name of each compensating command declared anywhere
|
|
803
|
+
# in `saga`, in declaration order
|
|
588
804
|
def derived_compensations(saga)
|
|
589
805
|
saga.handlers.flat_map { |handler| handler.dispatches.filter_map { |dispatch| dispatch.compensates&.command_name } }
|
|
590
806
|
end
|
|
591
807
|
|
|
592
808
|
# ── frameworks -> flowchart ─────────────────────────────────────
|
|
593
809
|
|
|
810
|
+
# Renders the whole domain's declared `uses_framework` attachments and
|
|
811
|
+
# cross-domain policy targets as a Mermaid flowchart.
|
|
812
|
+
#
|
|
594
813
|
# Every other diagram in this file stays inside one domain's own
|
|
595
814
|
# boundary — this is the one that steps outside it. A real domain
|
|
596
815
|
# depends on another domain's own aggregates in exactly two ways:
|
|
@@ -629,6 +848,13 @@ module Hecks
|
|
|
629
848
|
# caller, or a spec that doesn't care) just means no frameworks.mmd
|
|
630
849
|
# — same "nothing to state" skip every other diagram here already
|
|
631
850
|
# takes when its own underlying data is empty.
|
|
851
|
+
#
|
|
852
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project
|
|
853
|
+
# @param hecksagon [Bluebook::Hecksagon, nil] the chapter's `.hecksagon`, holding
|
|
854
|
+
# its `framework_members`, or `nil` if the caller has none to hand in
|
|
855
|
+
# @return [String, nil] the rendered Mermaid flowchart source, or `nil` if
|
|
856
|
+
# `hecksagon` is `nil`, or the domain declares no `uses_framework` and no
|
|
857
|
+
# cross-domain policy target
|
|
632
858
|
def frameworks_diagram(bluebook, hecksagon)
|
|
633
859
|
return nil unless hecksagon
|
|
634
860
|
|
|
@@ -641,6 +867,16 @@ module Hecks
|
|
|
641
867
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
|
642
868
|
end
|
|
643
869
|
|
|
870
|
+
# One flowchart edge from this domain to a dependency, dotted for a
|
|
871
|
+
# standing `attaches` and solid for a `reaches across` that only fires
|
|
872
|
+
# when a policy does.
|
|
873
|
+
#
|
|
874
|
+
# @param from [String] this domain's own name
|
|
875
|
+
# @param label [String] the edge label, `"attaches"` or `"reaches across"`
|
|
876
|
+
# @param to [String, Symbol] the dependency domain's name
|
|
877
|
+
# @param dotted [Boolean] whether to render a dotted (`true`) or solid (`false`)
|
|
878
|
+
# arrow
|
|
879
|
+
# @return [String] the rendered edge line
|
|
644
880
|
def domain_edge(from, label, to, dotted:)
|
|
645
881
|
arrow = dotted ? "-.->" : "-->"
|
|
646
882
|
%( #{from}[(#{from})] #{arrow}|#{label}| #{to}[(#{to})])
|