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
|
@@ -51,6 +51,21 @@ module Hecks
|
|
|
51
51
|
# The wire carries the name, not the members. A reader resolves it
|
|
52
52
|
# against the IR it holds, so the members are declared once and
|
|
53
53
|
# copied nowhere — which is the same reason `admits` exists at all.
|
|
54
|
+
#
|
|
55
|
+
# @param name [Symbol, String] the attribute's name
|
|
56
|
+
# @param type [Module, Bluebook::Reference, String, Symbol] the bare constant naming
|
|
57
|
+
# a primitive or a value object, a `reference_to`-built Reference, or already-spelled
|
|
58
|
+
# text
|
|
59
|
+
# @param list [Boolean] whether this attribute holds a list of values rather than one
|
|
60
|
+
# @param default [Object, nil] the value a new record starts with when none is given
|
|
61
|
+
# @param optional [Boolean] whether a command may omit this attribute
|
|
62
|
+
# @param pattern [String, nil] a regex source the value must match; shared-engine
|
|
63
|
+
# subset only (`PatternSubset`)
|
|
64
|
+
# @param admits [String, nil] an already-declared closed set's aggregate-qualified
|
|
65
|
+
# name, such as `"Vocabulary::QueryComparator"`, that the value must belong to
|
|
66
|
+
# @param relationship [Symbol, nil] the DSL word that minted this attribute
|
|
67
|
+
# (`:reference_to`, `:has_many`, `:has_one` or `:belongs_to`), or `nil` for an
|
|
68
|
+
# ordinary attribute
|
|
54
69
|
def initialize(name:, type:, list: false, default: nil, optional: false, pattern: nil,
|
|
55
70
|
admits: nil, relationship: nil)
|
|
56
71
|
@name = name.to_sym
|
|
@@ -77,7 +92,7 @@ module Hecks
|
|
|
77
92
|
# built against somebody else's aggregate — silently, with no refusal —
|
|
78
93
|
# and the attribute stops meaning what the file plainly says.
|
|
79
94
|
#
|
|
80
|
-
#
|
|
95
|
+
# Demodulised, so both paths spell it the same: `:Target` and
|
|
81
96
|
# `QualityControl::Target` are both "Target". A plain class stays itself —
|
|
82
97
|
# `String` demodulises to "String" — so the ordinary case is untouched.
|
|
83
98
|
# This does not undo the constant leak; it makes the leak unable to change
|
|
@@ -91,8 +106,7 @@ module Hecks
|
|
|
91
106
|
private :spell
|
|
92
107
|
|
|
93
108
|
# Held because a declared vocabulary pins it — spec/vocabulary_conformance
|
|
94
|
-
# holds `Primitive`'s members to this list.
|
|
95
|
-
# used to read it had no caller anywhere and is gone.
|
|
109
|
+
# holds `Primitive`'s members to this list.
|
|
96
110
|
PRIMITIVES = Hecks::Vocabulary.fetch("Primitive")
|
|
97
111
|
|
|
98
112
|
# `type` is spelled, never handed over. A Reference renders as
|
|
@@ -26,11 +26,17 @@ module Hecks
|
|
|
26
26
|
|
|
27
27
|
# An aggregate is a member of its chapter's namespace — "Pizzas::Pizza" —
|
|
28
28
|
# where everything else is declared on its owner and joins with ".".
|
|
29
|
+
#
|
|
30
|
+
# @return [String] the literal string `"::"`
|
|
29
31
|
def hecks_separator = "::"
|
|
30
32
|
|
|
31
33
|
# The hook the generated constructor calls once every declared
|
|
32
34
|
# field is assigned. Nothing here is derivable from the
|
|
33
35
|
# declaration, which is exactly why it is not generated.
|
|
36
|
+
#
|
|
37
|
+
# @return [Bluebook::Aggregate] self, once identity is derived, every
|
|
38
|
+
# declaration is indexed and its commands, value objects, entities and
|
|
39
|
+
# queries are stamped as owned by it
|
|
34
40
|
def settle
|
|
35
41
|
derive_identity
|
|
36
42
|
index_declarations
|
|
@@ -40,6 +46,10 @@ module Hecks
|
|
|
40
46
|
self
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
# Builds every by-name finder index `settle` needs — attributes, value
|
|
50
|
+
# objects, commands, queries, ports and projected fields.
|
|
51
|
+
#
|
|
52
|
+
# @return [void]
|
|
43
53
|
def index_declarations
|
|
44
54
|
index_attributes(@attributes)
|
|
45
55
|
@value_objects_by_name = index_by_hecks_name(@value_objects)
|
|
@@ -53,6 +63,11 @@ module Hecks
|
|
|
53
63
|
@projected_fields_by_name = @projected_fields.to_h { |field| [field.name, field] }
|
|
54
64
|
end
|
|
55
65
|
|
|
66
|
+
# Finds a declared `projects` field by its declared name.
|
|
67
|
+
#
|
|
68
|
+
# @param named [String, Symbol] the projected field's declared name
|
|
69
|
+
# @return [Bluebook::ProjectedField, nil] the field named `named`, or
|
|
70
|
+
# `nil` if none is declared under that name
|
|
56
71
|
def projected_field(named) = @projected_fields_by_name[named.to_sym]
|
|
57
72
|
|
|
58
73
|
# A value object is a class now, so `name` is Ruby's answer (the constant
|
|
@@ -60,7 +75,17 @@ module Hecks
|
|
|
60
75
|
# out — once an attribute's type is the class there is nothing to find —
|
|
61
76
|
# but every consumer still asks by type string, so it stays until they
|
|
62
77
|
# stop.
|
|
78
|
+
#
|
|
79
|
+
# @param named [String, Symbol] the value object's declared name
|
|
80
|
+
# @return [Class, nil] the value object class (a `Bluebook::ValueObject`
|
|
81
|
+
# subclass) named `named`, or `nil` if none is declared under that name
|
|
63
82
|
def value_object(named) = @value_objects_by_name[named.to_s]
|
|
83
|
+
|
|
84
|
+
# Finds a port attached to this aggregate by its declared name.
|
|
85
|
+
#
|
|
86
|
+
# @param named [String, Symbol] the port's declared name
|
|
87
|
+
# @return [Bluebook::DomainPort, nil] the port named `named`, or `nil` if
|
|
88
|
+
# none is attached under that name
|
|
64
89
|
def port(named) = @ports_by_name[named.to_s]
|
|
65
90
|
|
|
66
91
|
# A port is declared in the hecksagon, not the bluebook — the
|
|
@@ -71,11 +96,18 @@ module Hecks
|
|
|
71
96
|
# calls it once per `port` declaration, having already stamped each
|
|
72
97
|
# operation's reference attributes with `declared_in = self`, since
|
|
73
98
|
# nothing upstream of a hecksagon load does that for it.
|
|
99
|
+
#
|
|
100
|
+
# @param port [Bluebook::DomainPort] the aggregate-scoped port to attach
|
|
101
|
+
# @return [void]
|
|
74
102
|
def add_port(port)
|
|
75
103
|
@ports << port
|
|
76
104
|
@ports_by_name[port.name] = port
|
|
77
105
|
end
|
|
78
106
|
|
|
107
|
+
# Names the table, file or key persistence adapters store this aggregate
|
|
108
|
+
# under.
|
|
109
|
+
#
|
|
110
|
+
# @return [String] the aggregate's name, snake-cased
|
|
79
111
|
def storage_name = Naming.snake(@name)
|
|
80
112
|
end
|
|
81
113
|
end
|
|
@@ -6,8 +6,19 @@ module Hecks
|
|
|
6
6
|
# in `aggregate.bluebook`'s own `Field`. These are the questions
|
|
7
7
|
# readers ask about that shape, which no declaration states.
|
|
8
8
|
module Attribute
|
|
9
|
+
# Says whether this attribute was declared `list`.
|
|
10
|
+
#
|
|
11
|
+
# @return [Boolean] whether this attribute holds a list of values rather than one
|
|
9
12
|
def list? = @list
|
|
13
|
+
|
|
14
|
+
# Says whether this attribute holds a single value rather than a list.
|
|
15
|
+
#
|
|
16
|
+
# @return [Boolean] whether this attribute holds a single value rather than a list
|
|
10
17
|
def scalar? = !@list
|
|
18
|
+
|
|
19
|
+
# Says whether this attribute's type is another aggregate reached via `reference_to`.
|
|
20
|
+
#
|
|
21
|
+
# @return [Boolean] whether this attribute's type is a `reference_to` another aggregate
|
|
11
22
|
def reference? = @type.is_a?(Reference)
|
|
12
23
|
|
|
13
24
|
# May this fact be left out?
|
|
@@ -20,6 +31,8 @@ module Hecks
|
|
|
20
31
|
# Only a command enforces this. An aggregate's own attributes are filled
|
|
21
32
|
# by the commands that set them, and a value object's by its
|
|
22
33
|
# constructor ; neither is a payload anyone hands in.
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean] whether a command may omit this attribute from its payload
|
|
23
36
|
def optional? = @optional
|
|
24
37
|
end
|
|
25
38
|
end
|
|
@@ -16,6 +16,9 @@ module Hecks
|
|
|
16
16
|
# means), the ports table — which a `.hecksagon` fills later, so
|
|
17
17
|
# the bluebook cannot declare it — and stamping its own children,
|
|
18
18
|
# the same act an Aggregate performs one level down.
|
|
19
|
+
#
|
|
20
|
+
# @return [Bluebook::Chapter] self, once root marking, the ports table and ownership
|
|
21
|
+
# stamping are all set up
|
|
19
22
|
def settle
|
|
20
23
|
@hecks_root = true
|
|
21
24
|
@ports = []
|
|
@@ -24,23 +27,54 @@ module Hecks
|
|
|
24
27
|
self
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# Finds a declared aggregate by name.
|
|
31
|
+
#
|
|
32
|
+
# @param named [String, Symbol] the aggregate's declared name
|
|
33
|
+
# @return [Bluebook::Aggregate, nil] the aggregate, or `nil` if none is declared
|
|
34
|
+
# by that name
|
|
27
35
|
def aggregate(named) = @aggregates.find { |a| a.name == named.to_s }
|
|
36
|
+
|
|
37
|
+
# Finds a declared read model by its own name or by the query name it answers.
|
|
38
|
+
#
|
|
39
|
+
# @param named [String, Symbol] the read model's declared name, or its query name
|
|
40
|
+
# @return [Bluebook::ReadModel, nil] the read model, or `nil` if none matches
|
|
28
41
|
def read_model(named) = @read_models.find { |model| model.name == named.to_s || model.query_name == named.to_s }
|
|
42
|
+
|
|
43
|
+
# Finds a port declared at this chapter's root by name.
|
|
44
|
+
#
|
|
45
|
+
# @param named [String, Symbol] the port's declared name
|
|
46
|
+
# @return [Bluebook::DomainPort, nil] the port, or `nil` if none is declared
|
|
47
|
+
# by that name
|
|
29
48
|
def port(named) = @ports_by_name[named.to_s]
|
|
30
49
|
|
|
31
50
|
# What this chapter declared it provides — `{ key => local verb }`
|
|
32
51
|
# for one capability, or nil when it declares none. Read by
|
|
33
|
-
# everything that
|
|
34
|
-
# name
|
|
52
|
+
# everything that resolves a capability's provider from what a
|
|
53
|
+
# chapter declares, rather than from the chapter's own name
|
|
54
|
+
# (`Registry#authorization_provider_for`).
|
|
55
|
+
#
|
|
56
|
+
# @param capability [String, Symbol] the capability's name, such as
|
|
57
|
+
# `Bluebook::Capabilities::AUTHORIZATION`
|
|
58
|
+
# @return [Hash{Symbol => String}, nil] each declared key mapped to its local verb,
|
|
59
|
+
# or `nil` if this chapter declares no `provides` row for that capability
|
|
35
60
|
def provision(capability)
|
|
36
61
|
rows = @provides.select { |row| row.capability == capability.to_s }
|
|
37
62
|
rows.empty? ? nil : rows.to_h { |row| [row.key.to_sym, row.verb] }
|
|
38
63
|
end
|
|
39
64
|
|
|
65
|
+
# Says whether this chapter declares that it provides a capability.
|
|
66
|
+
#
|
|
67
|
+
# @param capability [String, Symbol] the capability's name
|
|
68
|
+
# @return [Boolean] whether this chapter declares a `provides` row for that capability
|
|
40
69
|
def provides?(capability) = !provision(capability).nil?
|
|
41
70
|
|
|
42
71
|
# The declared verb for `key`, qualified with this chapter's own
|
|
43
72
|
# name — the spelling `Dispatcher#dispatch`/`#query` take.
|
|
73
|
+
#
|
|
74
|
+
# @param capability [String, Symbol] the capability's name
|
|
75
|
+
# @param key [String, Symbol] the provided key to resolve
|
|
76
|
+
# @return [String, nil] the verb qualified as `"ChapterName::verb"`, or `nil` if this
|
|
77
|
+
# chapter provides no such capability or key
|
|
44
78
|
def provided_verb(capability, key)
|
|
45
79
|
local = provision(capability)&.fetch(key.to_sym, nil)
|
|
46
80
|
local && "#{name}::#{local}"
|
|
@@ -49,11 +83,27 @@ module Hecks
|
|
|
49
83
|
# A port is declared in the hecksagon, not the bluebook — so it
|
|
50
84
|
# attaches after the chapter already exists, the same way an
|
|
51
85
|
# aggregate's own ports do.
|
|
86
|
+
#
|
|
87
|
+
# @param port [Bluebook::DomainPort] the operations-shaped port to attach
|
|
88
|
+
# @return [void]
|
|
52
89
|
def add_port(port)
|
|
53
90
|
@ports << port
|
|
54
91
|
@ports_by_name[port.name] = port
|
|
55
92
|
end
|
|
56
93
|
|
|
94
|
+
# A TRANSLATED REACTION IS ALSO DECLARED IN THE HECKSAGON —
|
|
95
|
+
# `HecksagonBuilder#translates` builds an ordinary `Policy` (same
|
|
96
|
+
# shape, same `PolicyInterpreter` runtime, as one written inside a
|
|
97
|
+
# `policy` block in this chapter's own `.bluebook`) and attaches it
|
|
98
|
+
# here, after the chapter already exists, the same reasoning
|
|
99
|
+
# `add_port` gives one method up. Kept out of the bluebook itself
|
|
100
|
+
# on purpose: which FOREIGN domain's event this chapter reacts to
|
|
101
|
+
# is a wiring/context-mapping decision, not a fact the domain
|
|
102
|
+
# states about its own model.
|
|
103
|
+
def add_policy(policy)
|
|
104
|
+
@policies << policy
|
|
105
|
+
end
|
|
106
|
+
|
|
57
107
|
# Every dispatchable name this chapter answers to, spelled exactly
|
|
58
108
|
# as Dispatcher#dispatch takes it. Derived from the aggregates,
|
|
59
109
|
# never declared — which is why Projections::OIDC can hold its own
|
|
@@ -68,6 +118,9 @@ module Hecks
|
|
|
68
118
|
# already proved out for the meta-domain's own grammar (S17, ADR
|
|
69
119
|
# 0026) — entities nest arbitrarily deep (`Dispatch`, inside
|
|
70
120
|
# `Handler`), so one flat level isn't enough.
|
|
121
|
+
#
|
|
122
|
+
# @return [Array<String>] every command verb reachable on this chapter, spelled
|
|
123
|
+
# `"Domain::Aggregate.command"` or, nested, `"Domain::Aggregate.Entity.command"`
|
|
71
124
|
def verbs
|
|
72
125
|
@aggregates.flat_map { |agg| aggregate_verbs(agg) }
|
|
73
126
|
end
|
|
@@ -10,6 +10,9 @@ module Hecks
|
|
|
10
10
|
|
|
11
11
|
# Indexed once — attributes are final once absorbed, and every
|
|
12
12
|
# dispatch asks this finder by name.
|
|
13
|
+
#
|
|
14
|
+
# @return [Class] self — the command class, once its attributes are
|
|
15
|
+
# indexed
|
|
13
16
|
def settle
|
|
14
17
|
index_attributes(@attributes)
|
|
15
18
|
self
|
|
@@ -26,6 +29,11 @@ module Hecks
|
|
|
26
29
|
#
|
|
27
30
|
# On an aggregate, a creating command acts on no existing root, so nil is
|
|
28
31
|
# the truth: there is nothing there yet.
|
|
32
|
+
#
|
|
33
|
+
# @return [Class, Bluebook::Aggregate, nil] the entity class (a
|
|
34
|
+
# `Bluebook::Entity` subclass) this verb acts on when declared on an
|
|
35
|
+
# entity, the aggregate instance when declared on an aggregate and
|
|
36
|
+
# non-creating, or `nil` for a creating command
|
|
29
37
|
def acts_on
|
|
30
38
|
# Fully qualified, and it has to be: inside `module Behaviour`
|
|
31
39
|
# the bare name `Entity` resolves to Behaviour::Entity — this
|
|
@@ -37,6 +45,10 @@ module Hecks
|
|
|
37
45
|
creates? ? nil : hecks_owner
|
|
38
46
|
end
|
|
39
47
|
|
|
48
|
+
# Whether this command creates a new root rather than acting on one.
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean] whether this command creates a new root — true when
|
|
51
|
+
# it declares no `reference_to` back to its own owner
|
|
40
52
|
def creates? = @references.nil?
|
|
41
53
|
|
|
42
54
|
# Every reason this verb can refuse on a rule — the descriptions of
|
|
@@ -47,6 +59,9 @@ module Hecks
|
|
|
47
59
|
# the language wrote" reads this rather than re-deriving the two
|
|
48
60
|
# collections; `compact` because a rule's description is optional
|
|
49
61
|
# (behavior.bluebook's Rule) and an unnamed one quotes nothing.
|
|
62
|
+
#
|
|
63
|
+
# @return [Array<String>] every named given's and ensure's own
|
|
64
|
+
# description text, skipping unnamed rules
|
|
50
65
|
def guard_descriptions = (@givens + @ensures).map(&:description).compact
|
|
51
66
|
|
|
52
67
|
# The argument name that addresses an instance of `aggregate_name`
|
|
@@ -84,6 +99,11 @@ module Hecks
|
|
|
84
99
|
# aggregate at all. A caller minting a fan-out dispatch is
|
|
85
100
|
# expected to treat `nil` as "this command cannot be addressed by
|
|
86
101
|
# a row of this aggregate," not to fall back on a guess.
|
|
102
|
+
#
|
|
103
|
+
# @param aggregate_name [String, Symbol] the aggregate a fan-out dispatch is
|
|
104
|
+
# addressing an instance of
|
|
105
|
+
# @return [String, nil] the argument name that addresses it, or nil if this command
|
|
106
|
+
# cannot be addressed by a row of that aggregate
|
|
87
107
|
def addressing_key_for(aggregate_name)
|
|
88
108
|
return Naming.reference_key(aggregate_name) if references.to_s == aggregate_name.to_s
|
|
89
109
|
|
|
@@ -96,11 +116,21 @@ module Hecks
|
|
|
96
116
|
# a Struct, so these are instance methods.
|
|
97
117
|
module Mutation
|
|
98
118
|
# An append binds several fields at once, each from either a command
|
|
99
|
-
# argument (a Symbol) or a literal.
|
|
100
|
-
#
|
|
101
|
-
#
|
|
119
|
+
# argument (a Symbol) or a literal. Spelled through `Literal.render`,
|
|
120
|
+
# the same self-describing form a where-clause's own value already
|
|
121
|
+
# uses, rather than a bare Symbol paired with an inspected literal —
|
|
122
|
+
# see Hecks::Literal.
|
|
123
|
+
#
|
|
124
|
+
# @return [Hash{Symbol => String}] the append's field bindings, each
|
|
125
|
+
# value rendered through `Literal.render`
|
|
102
126
|
def appended_fields = source.transform_values { |value| Literal.render(value) }
|
|
103
127
|
|
|
128
|
+
# Classifies this mutation's source for the wire, the counterpart
|
|
129
|
+
# `Assembly::Marks#classified` reads back.
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash{Symbol => Object}] `{kind: "argument", name:}` for a
|
|
132
|
+
# command argument, `{kind: "state", name:}` for a state
|
|
133
|
+
# self-reference, or `{kind: "literal", value:}` for a literal value
|
|
104
134
|
def classified_source
|
|
105
135
|
if source.is_a?(Symbol)
|
|
106
136
|
{ kind: "argument", name: source.to_s }
|
|
@@ -9,6 +9,8 @@ module Hecks
|
|
|
9
9
|
|
|
10
10
|
# An operation declares no reference of its own — the owner is
|
|
11
11
|
# what it acts for, and `identity_attribute` is how that is found.
|
|
12
|
+
#
|
|
13
|
+
# @return [nil] always `nil`
|
|
12
14
|
def references = nil
|
|
13
15
|
|
|
14
16
|
# Never a creating command — a port operation always acts on an
|
|
@@ -21,8 +23,16 @@ module Hecks
|
|
|
21
23
|
# `target.command.creates?` on a port operation the same way it
|
|
22
24
|
# already does on an ordinary command, and correctly lift a
|
|
23
25
|
# same-aggregate policy's own Event.id as the operation's receiver.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean] always `false`
|
|
24
28
|
def creates? = false
|
|
25
29
|
|
|
30
|
+
# Finds the reference-typed attribute through which this operation addresses
|
|
31
|
+
# its owning aggregate.
|
|
32
|
+
#
|
|
33
|
+
# @param owner_name [String, Symbol] the owning aggregate's name
|
|
34
|
+
# @return [Bluebook::Attribute, nil] the reference-typed attribute targeting
|
|
35
|
+
# `owner_name`, or `nil` if this operation declares none
|
|
26
36
|
def identity_attribute(owner_name)
|
|
27
37
|
@attributes.find { |attribute| attribute.reference? && attribute.type.target_name == owner_name.to_s }
|
|
28
38
|
end
|
|
@@ -37,11 +47,21 @@ module Hecks
|
|
|
37
47
|
# reason `Command#addressing_key_for` is: `ReactionInvocation
|
|
38
48
|
# #aggregate_aliases` calls it on whatever `target.command` holds,
|
|
39
49
|
# a `PortOperation` now included since a policy can trigger one.
|
|
50
|
+
#
|
|
51
|
+
# @param aggregate_name [String, Symbol] the name of the aggregate a row of which
|
|
52
|
+
# would address this operation
|
|
53
|
+
# @return [Symbol, nil] the reference-typed attribute's name a caller passes to
|
|
54
|
+
# address that row, or `nil` if this operation cannot be addressed by one
|
|
40
55
|
def addressing_key_for(aggregate_name) = identity_attribute(aggregate_name)&.name
|
|
41
56
|
end
|
|
42
57
|
|
|
43
58
|
# **What a port does** — one finder over its declared operations.
|
|
44
59
|
module DomainPort
|
|
60
|
+
# Finds a declared operation by name.
|
|
61
|
+
#
|
|
62
|
+
# @param named [String, Symbol] the operation's declared name
|
|
63
|
+
# @return [Bluebook::PortOperation, nil] the operation, or `nil` if none is
|
|
64
|
+
# declared by that name
|
|
45
65
|
def operation(named) = @operations.find { |op| op.hecks_name == named.to_s }
|
|
46
66
|
end
|
|
47
67
|
end
|
|
@@ -17,12 +17,19 @@ module Hecks
|
|
|
17
17
|
include Indexed
|
|
18
18
|
include Owns
|
|
19
19
|
|
|
20
|
+
# The hook `absorb` calls once every declared field is assigned.
|
|
21
|
+
#
|
|
22
|
+
# @return [Class] this entity's own class (a `Bluebook::Entity` subclass), self,
|
|
23
|
+
# once identity and indexes are derived
|
|
20
24
|
def settle
|
|
21
25
|
derive_identity
|
|
22
26
|
index_declarations
|
|
23
27
|
self
|
|
24
28
|
end
|
|
25
29
|
|
|
30
|
+
# Builds every by-name index this entity answers finders through.
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
26
33
|
def index_declarations
|
|
27
34
|
index_attributes(@attributes)
|
|
28
35
|
@commands_by_name = index_by_hecks_name(@commands)
|
|
@@ -43,6 +50,9 @@ module Hecks
|
|
|
43
50
|
# `.entities` is walked for its direct children. Entity's own
|
|
44
51
|
# header comment already promises it stays "structurally
|
|
45
52
|
# interchangeable with an aggregate" for exactly this reason.
|
|
53
|
+
#
|
|
54
|
+
# @return [Array<Class>] this entity's own nested entities (each a `Bluebook::Entity`
|
|
55
|
+
# subclass), or `[]` if it declares none
|
|
46
56
|
def entities = @entities || []
|
|
47
57
|
|
|
48
58
|
# A piece owns the verbs declared on it, so they can state an
|
|
@@ -52,6 +62,8 @@ module Hecks
|
|
|
52
62
|
# subclass that will own them exists. `@entities` too now
|
|
53
63
|
# (S17, ADR 0026) — a nested entity states its own owner chain
|
|
54
64
|
# exactly the way a nested command does.
|
|
65
|
+
#
|
|
66
|
+
# @return [void]
|
|
55
67
|
def stamp_children = stamp(@commands, @queries, @entities)
|
|
56
68
|
end
|
|
57
69
|
end
|
|
@@ -8,11 +8,25 @@ module Hecks
|
|
|
8
8
|
# `HecksagonBuilder#method_missing`). Checked directly rather than
|
|
9
9
|
# via `aggregate_name`/`Naming.demodulise`, which would need its own
|
|
10
10
|
# nil-handling for the default row.
|
|
11
|
+
#
|
|
12
|
+
# @param aggregate_name [String, Symbol] the aggregate to find a bind for
|
|
13
|
+
# @param verb [String, Symbol] the port verb name to find a bind for
|
|
14
|
+
# @return [Bluebook::Bind, nil] the aggregate-specific bind when one is
|
|
15
|
+
# declared, the domain-level default bind for `verb` otherwise, or
|
|
16
|
+
# `nil` when neither exists
|
|
11
17
|
def bind_for(aggregate_name, verb)
|
|
12
18
|
@binds.find { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s } ||
|
|
13
19
|
@binds.find { |b| b.aggregate.nil? && b.verb.to_s == verb.to_s }
|
|
14
20
|
end
|
|
15
21
|
|
|
22
|
+
# Every bind for `verb`, aggregate-specific ones winning as a group over
|
|
23
|
+
# the domain-level default.
|
|
24
|
+
#
|
|
25
|
+
# @param aggregate_name [String, Symbol] the aggregate to find binds for
|
|
26
|
+
# @param verb [String, Symbol] the port verb name to find binds for
|
|
27
|
+
# @return [Array<Bluebook::Bind>] every aggregate-specific bind for
|
|
28
|
+
# `verb` when any are declared, otherwise every domain-level default
|
|
29
|
+
# bind for `verb`
|
|
16
30
|
def binds_for(aggregate_name, verb)
|
|
17
31
|
specific = @binds.select { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s }
|
|
18
32
|
return specific if specific.any?
|
|
@@ -24,6 +38,11 @@ module Hecks
|
|
|
24
38
|
# **What a world does** — settings lookup, with the adapter-specific
|
|
25
39
|
# entry falling back to the verb's own.
|
|
26
40
|
module World
|
|
41
|
+
# Finds the generic settings declared for a port verb.
|
|
42
|
+
#
|
|
43
|
+
# @param verb [String, Symbol] the port verb name, such as `"persistence"`
|
|
44
|
+
# @return [Hash{Symbol => Object}] the settings declared for `verb`, or
|
|
45
|
+
# `{}` when none are declared
|
|
27
46
|
def for_verb(verb) = @settings.fetch(verb.to_s, {})
|
|
28
47
|
|
|
29
48
|
# The generic `verb` entry (`persisted_by("Heki") do dir :default
|
|
@@ -37,6 +56,13 @@ module Hecks
|
|
|
37
56
|
# names Heki, not Memory, so the fallback was never actually for
|
|
38
57
|
# this bind. `{}` is exactly right when nothing was configured for
|
|
39
58
|
# this adapter — Memory, which takes no values at all.
|
|
59
|
+
#
|
|
60
|
+
# @param verb [String, Symbol] the port verb name to find settings for
|
|
61
|
+
# @param adapter [String, Symbol] the adapter name, such as `"Heki"`, to
|
|
62
|
+
# find settings for
|
|
63
|
+
# @return [Hash{Symbol => Object}] the adapter-qualified settings when
|
|
64
|
+
# declared, the generic verb settings when they name this same
|
|
65
|
+
# adapter, or `{}` otherwise
|
|
40
66
|
def for_binding(verb, adapter)
|
|
41
67
|
qualified = @settings["#{verb}:#{adapter.to_s.downcase}"]
|
|
42
68
|
return qualified if qualified
|
|
@@ -7,14 +7,35 @@ module Hecks
|
|
|
7
7
|
# declared transition expands into the several rows the emission
|
|
8
8
|
# carries when `from` names more than one source state.
|
|
9
9
|
module Lifecycle
|
|
10
|
+
# Lists every state this lifecycle can be in.
|
|
11
|
+
#
|
|
12
|
+
# @return [Array<String>] every state this lifecycle can be in — the
|
|
13
|
+
# default plus each declared transition's own target, deduplicated
|
|
10
14
|
def states
|
|
11
15
|
([default] + transitions.map { |_command, t| t.target }).uniq
|
|
12
16
|
end
|
|
13
17
|
|
|
18
|
+
# Finds every transition a command declares.
|
|
19
|
+
#
|
|
20
|
+
# @param command [String, Symbol] the command name to find declared
|
|
21
|
+
# transitions for
|
|
22
|
+
# @return [Array<Bluebook::StateTransition>] every transition declared
|
|
23
|
+
# for `command`
|
|
14
24
|
def transitions_for(command)
|
|
15
25
|
transitions.select { |name, _| name == command.to_s }.map { |_, t| t }
|
|
16
26
|
end
|
|
17
27
|
|
|
28
|
+
# Finds the state a command moves the record to.
|
|
29
|
+
#
|
|
30
|
+
# @param command [String, Symbol] the command name to find the target
|
|
31
|
+
# state for
|
|
32
|
+
# @param current_state [String, Symbol, nil] the record's current
|
|
33
|
+
# state, to pick the one declared transition that admits it; `nil`
|
|
34
|
+
# to skip that check and take the first declared transition
|
|
35
|
+
# @return [String, nil] the state `command` moves the record to, or
|
|
36
|
+
# `nil` when `command` declares no transition at all
|
|
37
|
+
# @raise [Runtime::WiringError] if `command` declares transitions but
|
|
38
|
+
# none of them admits `current_state`
|
|
18
39
|
def target_for(command, current_state = nil)
|
|
19
40
|
match_transition(command, current_state)&.target
|
|
20
41
|
end
|
|
@@ -37,10 +58,10 @@ module Hecks
|
|
|
37
58
|
return nil if matches.empty?
|
|
38
59
|
return matches.first unless current_state
|
|
39
60
|
|
|
40
|
-
# Not `|| matches.first` — that
|
|
61
|
+
# Not `|| matches.first` — that would silently hand back an
|
|
41
62
|
# arbitrary declared transition for `command` whenever none of
|
|
42
63
|
# them actually admitted `current_state`, picking a `target`
|
|
43
|
-
# that command dispatch would in fact
|
|
64
|
+
# that command dispatch would in fact refuse (that
|
|
44
65
|
# refusal is `CommandRules::Admissibility#admissible_transition`'s
|
|
45
66
|
# own job, which raises `LifecycleRefused` for exactly this
|
|
46
67
|
# case rather than guessing — this module has no state subject
|
|
@@ -7,21 +7,37 @@ module Hecks
|
|
|
7
7
|
# The bluebook's name for this construct, asked the same way of a class
|
|
8
8
|
# that has crossed over and of an IR object that has not. Collapses into
|
|
9
9
|
# Construct when this one crosses.
|
|
10
|
+
#
|
|
11
|
+
# @return [String] this policy's declared name
|
|
10
12
|
def hecks_name = @name
|
|
11
13
|
|
|
14
|
+
# The domain-and-aggregate prefix of the triggering event's name.
|
|
15
|
+
#
|
|
16
|
+
# @return [String, nil] the part of `on_event` before its first `.`
|
|
17
|
+
# (the `"Domain::Aggregate"` prefix), or `nil` when `on_event`
|
|
18
|
+
# carries no dot
|
|
12
19
|
def event_qualifier = Naming.qualifier(@on_event)
|
|
13
20
|
|
|
21
|
+
# The bare name of the triggering event, without its domain/aggregate
|
|
22
|
+
# prefix.
|
|
23
|
+
#
|
|
24
|
+
# @return [String] the part of `on_event` after its first `.` (the
|
|
25
|
+
# bare event name), or the whole string when it carries no dot
|
|
14
26
|
def event_name = Naming.unqualified(@on_event)
|
|
15
27
|
|
|
16
28
|
# Whether this policy fans out — `for_each` names a query, and a
|
|
17
29
|
# non-empty one turns a single reaction into one dispatch per row
|
|
18
30
|
# the query answers. Read by the interpreter that runs the fan-out
|
|
19
31
|
# and by the property that checks it dispatched once per row.
|
|
32
|
+
#
|
|
33
|
+
# @return [Boolean] whether `for_each` names a query
|
|
20
34
|
def fans_out? = !@for_each.to_s.empty?
|
|
21
35
|
|
|
22
36
|
# Whether this policy is guarded — a non-empty `where` decides
|
|
23
37
|
# whether the policy fires at all, read against the triggering
|
|
24
38
|
# event's own payload.
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean] whether `where` is declared and non-empty
|
|
25
41
|
def guarded? = !@where.to_s.empty?
|
|
26
42
|
|
|
27
43
|
# The structured form of `where`, derived once — the same tree
|
|
@@ -29,6 +45,9 @@ module Hecks
|
|
|
29
45
|
# here because a policy is consulted once per event, not once per
|
|
30
46
|
# boot. Nil when there is no `where`, exactly as the wire carries
|
|
31
47
|
# it.
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash, nil] the JSON-shaped boolean AST for `where`, or
|
|
50
|
+
# `nil` when this policy is not guarded
|
|
32
51
|
def where_ast
|
|
33
52
|
if defined?(@where_ast)
|
|
34
53
|
@where_ast
|
|
@@ -40,6 +59,9 @@ module Hecks
|
|
|
40
59
|
# The rule-shaped reading of the guard, for `Evaluator.call_rule`
|
|
41
60
|
# — a policy's `where` has no description (nothing refuses with
|
|
42
61
|
# it; an unmet where is a silent skip).
|
|
62
|
+
#
|
|
63
|
+
# @return [Bluebook::Given] the guard as a rule, with no description
|
|
64
|
+
# and its `ast` already resolved via `where_ast`
|
|
43
65
|
def where_rule = @where_rule ||= Given.new(description: nil, canonical: @where, ast: where_ast)
|
|
44
66
|
|
|
45
67
|
# The fan-out query's route, split the way the runtime runs it:
|
|
@@ -48,6 +70,11 @@ module Hecks
|
|
|
48
70
|
# names one ("Domain::Aggregate.query"). Deliberately independent
|
|
49
71
|
# of `across`/`target_domain`, which name where `trigger` fires,
|
|
50
72
|
# not where the fan-out's own query runs.
|
|
73
|
+
#
|
|
74
|
+
# @param default_domain [String, Symbol] the triggering event's own
|
|
75
|
+
# domain, used when `for_each` names no domain of its own
|
|
76
|
+
# @return [Array(String, String, String)] `[domain, aggregate_name,
|
|
77
|
+
# query_name]` the fan-out query resolves against
|
|
51
78
|
def for_each_route(default_domain)
|
|
52
79
|
path, query_name = @for_each.to_s.split(".", 2)
|
|
53
80
|
domain, aggregate = path.to_s.include?("::") ? path.split("::", 2) : [default_domain.to_s, path]
|
|
@@ -55,15 +82,13 @@ module Hecks
|
|
|
55
82
|
end
|
|
56
83
|
|
|
57
84
|
# The argument name a fan-out dispatch mints each matched row's id
|
|
58
|
-
# under
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# lives on `Behaviour::Command#addressing_key_for`, asked of the
|
|
66
|
-
# resolved target command by `PolicyInterpreter#addressing_key_for`.
|
|
85
|
+
# under is not minted here — it depends on the target command's own
|
|
86
|
+
# declared shape (a target command declared on the very aggregate it
|
|
87
|
+
# self-references, such as `Account.Freeze`, is addressed by its own
|
|
88
|
+
# identity field's name, not a synthetic foreign key), not on the
|
|
89
|
+
# aggregate name alone, so it lives on
|
|
90
|
+
# `Behaviour::Command#addressing_key_for`, asked of the resolved
|
|
91
|
+
# target command by `PolicyInterpreter#addressing_key_for`.
|
|
67
92
|
end
|
|
68
93
|
end
|
|
69
94
|
end
|