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
|
@@ -8,11 +8,11 @@ module Hecks
|
|
|
8
8
|
# What a construct needs that the language cannot say, and how a claim that a
|
|
9
9
|
# field needs no assembling is checked.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
11
|
+
# Naming a field derived is a claim, and a claim needs a kind, not just a name
|
|
12
|
+
# in a list. A bare list of names is a promise with nobody holding it: the
|
|
13
|
+
# coverage gate only asks whether a field is accounted for, so `derived:
|
|
14
|
+
# %i[version]` would satisfy it while dropping a chapter's version in
|
|
15
|
+
# silence — the exact shape of every defect this arc has found.
|
|
16
16
|
#
|
|
17
17
|
# :parent the containment tree supplies it — a `*_id`, or one of
|
|
18
18
|
# the named pointers below. Checked against the name.
|
|
@@ -32,6 +32,10 @@ module Hecks
|
|
|
32
32
|
# shape, because a list needs a reader per element and a folded field is
|
|
33
33
|
# gathered rather than fetched. Same pattern as `rows`, in the other
|
|
34
34
|
# direction: declare the exceptions, default the rest.
|
|
35
|
+
#
|
|
36
|
+
# @param key [String, Symbol] the field's declaration key, as `fields` names it
|
|
37
|
+
# @return [Symbol, Array, nil] the reader named in `reads` for `key`, or `nil`
|
|
38
|
+
# when the field reads through the default single-cell reader
|
|
35
39
|
def reader(key) = Hash(reads)[key.to_sym]
|
|
36
40
|
|
|
37
41
|
# How an appendable list becomes rows the walk can offer. A list absent from
|
|
@@ -39,10 +43,24 @@ module Hecks
|
|
|
39
43
|
# because the IR keeps a shape the language does not — a transition whose
|
|
40
44
|
# `from` is a list is several rows, an append binds several fields at once,
|
|
41
45
|
# an open map is one row per entry.
|
|
46
|
+
#
|
|
47
|
+
# @param list [String, Symbol] the appendable list's field name
|
|
48
|
+
# @return [Symbol, nil] the shaper method named in `rows` for `list`, or `nil`
|
|
49
|
+
# when the list reads straight off the node instead
|
|
42
50
|
def shaper(list) = Hash(rows)[list.to_sym]
|
|
43
51
|
|
|
52
|
+
# Whether `field` is one this contract accounts for, stored or derived.
|
|
53
|
+
#
|
|
54
|
+
# @param field [Symbol] the field name to check
|
|
55
|
+
# @return [Boolean] whether `field` is a key of `fields` or of `derived`
|
|
44
56
|
def declares?(field) = fields.key?(field) || derived.key?(field)
|
|
45
57
|
|
|
58
|
+
# Names the kind of derived claim `field` makes, if any.
|
|
59
|
+
#
|
|
60
|
+
# @param field [Symbol] the derived field name to check
|
|
61
|
+
# @return [Symbol, Array, nil] the kind `derived` names for `field` — `:parent`,
|
|
62
|
+
# `:children`, `:elsewhere`, `:walk`, an `[:computed, method]` or `[:folded,
|
|
63
|
+
# object, member]` pair — or `nil` when `field` is not derived
|
|
46
64
|
def kind_of(field) = derived[field]
|
|
47
65
|
|
|
48
66
|
# The fields the walk supplies — every `derived: { field => :walk }` claim.
|
|
@@ -50,20 +68,27 @@ module Hecks
|
|
|
50
68
|
# judge can order siblings, but no constructor takes one. This is the one
|
|
51
69
|
# place that fact is stated ; `Specializer` and `Model::Deviations` read it
|
|
52
70
|
# here rather than each keeping their own `%i[position]`.
|
|
71
|
+
#
|
|
72
|
+
# @return [Array<Symbol>] every field name whose derived kind is `:walk`
|
|
53
73
|
def walked = derived.select { |_field, kind| kind == :walk }.keys
|
|
54
74
|
|
|
55
75
|
# Where a folded field actually lives, as [object, member].
|
|
56
76
|
#
|
|
57
77
|
# `[:folded, :lifecycle, :field]` says the language's `state_field` is the
|
|
58
|
-
# `field` of the IR's one Lifecycle.
|
|
59
|
-
#
|
|
60
|
-
# drives both directions: the walk reads the member on the way in, and
|
|
61
|
-
# reconstruction gathers the members back into the object on the way out.
|
|
78
|
+
# `field` of the IR's one Lifecycle. `Readings` would otherwise have to state
|
|
79
|
+
# that fact a second time as `node.lifecycle&.field` — saying it once here
|
|
80
|
+
# drives both directions instead: the walk reads the member on the way in, and
|
|
81
|
+
# the reconstruction gathers the members back into the object on the way out.
|
|
62
82
|
#
|
|
63
83
|
# A nil member means the fold has no single member to name — `rows` is a
|
|
64
84
|
# count of what `closed_set` and `members` hold between them, and `options`
|
|
65
85
|
# spreads across eight keys. Those keep their own code, and the gate still
|
|
66
86
|
# checks the object they name is real.
|
|
87
|
+
#
|
|
88
|
+
# @param field [Symbol] the derived field name to check
|
|
89
|
+
# @return [Array(Symbol, Symbol), Array(Array<Symbol>, nil), nil] the `[object,
|
|
90
|
+
# member]` pair the field folds into — `member` is `nil` when the fold has
|
|
91
|
+
# no single member to name — or `nil` when `field` does not fold at all
|
|
67
92
|
def folded(field)
|
|
68
93
|
kind = derived[field]
|
|
69
94
|
return nil unless kind.is_a?(Array) && kind.first == :folded
|
|
@@ -81,6 +106,10 @@ module Hecks
|
|
|
81
106
|
#
|
|
82
107
|
# So a computed field is one the holder answers and the constructor does not
|
|
83
108
|
# accept. `query_name` qualifies (`Naming.snake(name)`) ; `version` cannot.
|
|
109
|
+
#
|
|
110
|
+
# @param method [Symbol] the field name to check
|
|
111
|
+
# @return [Boolean] whether `holder` answers `method` but its constructor does
|
|
112
|
+
# not accept it as a keyword
|
|
84
113
|
def computes?(method)
|
|
85
114
|
return false unless holder
|
|
86
115
|
return false unless answers?(method)
|
|
@@ -88,10 +117,22 @@ module Hecks
|
|
|
88
117
|
!accepts?(method)
|
|
89
118
|
end
|
|
90
119
|
|
|
120
|
+
# Whether `holder` answers `method`, checked the way that fits how it is
|
|
121
|
+
# built: `respond_to?` for a `:declare` holder (a class, queried live) and
|
|
122
|
+
# `method_defined?` for a `:new` holder (an instance method, checked without
|
|
123
|
+
# building one).
|
|
124
|
+
#
|
|
125
|
+
# @param method [Symbol] the method name to check
|
|
126
|
+
# @return [Boolean] whether `holder` answers `method`
|
|
91
127
|
def answers?(method)
|
|
92
128
|
make == :declare ? holder.respond_to?(method) : holder.method_defined?(method)
|
|
93
129
|
end
|
|
94
130
|
|
|
131
|
+
# Whether the holder's constructor takes `keyword` as a keyword argument.
|
|
132
|
+
#
|
|
133
|
+
# @param keyword [Symbol] the keyword to check
|
|
134
|
+
# @return [Boolean] whether the holder's constructor (`.declare` or
|
|
135
|
+
# `#initialize`) accepts `keyword` as an optional or required keyword
|
|
95
136
|
def accepts?(keyword)
|
|
96
137
|
builder = make == :declare ? holder.method(:declare) : holder.instance_method(:initialize)
|
|
97
138
|
|
|
@@ -109,13 +150,18 @@ module Hecks
|
|
|
109
150
|
# owner Entity's own text twin of that link, which is why
|
|
110
151
|
# Entity's contract claims `owner: :parent`
|
|
111
152
|
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# and
|
|
116
|
-
#
|
|
153
|
+
# `shape` and `handler` are not on this list — S17 (ADR 0026) made Member and
|
|
154
|
+
# Dispatch nested entities, so neither is a parent field any more the way
|
|
155
|
+
# `owner` is. `spec/assembly_spec` derives this set from `Plan` and the
|
|
156
|
+
# contracts directly and fails on any drift, rather than trusting this list to
|
|
157
|
+
# stay in sync with a second hand-written copy kept by the model generator.
|
|
117
158
|
PARENT_POINTERS = %i[aggregate bluebook owner].freeze
|
|
118
159
|
|
|
160
|
+
# Whether `field` is a parent pointer that does not end in `_id`.
|
|
161
|
+
#
|
|
162
|
+
# @param field [String, Symbol] the field name to check
|
|
163
|
+
# @return [Boolean] whether `field` ends with `_id`, or is one of
|
|
164
|
+
# `PARENT_POINTERS`
|
|
119
165
|
def self.parent_pointer?(field)
|
|
120
166
|
field.to_s.end_with?("_id") || PARENT_POINTERS.include?(field.to_sym)
|
|
121
167
|
end
|
|
@@ -131,6 +177,12 @@ module Hecks
|
|
|
131
177
|
Bluebook: %i[normalisations]
|
|
132
178
|
}.freeze
|
|
133
179
|
|
|
180
|
+
# Whether `field` is allow-listed as describing something other than
|
|
181
|
+
# `category`'s own construct — see `ELSEWHERE`.
|
|
182
|
+
#
|
|
183
|
+
# @param category [String, Symbol] the construct category name
|
|
184
|
+
# @param field [Symbol] the field name to check
|
|
185
|
+
# @return [Boolean] whether `field` is on `category`'s `ELSEWHERE` entry
|
|
134
186
|
def self.elsewhere?(category, field)
|
|
135
187
|
Array(ELSEWHERE[category.to_sym]).include?(field)
|
|
136
188
|
end
|
|
@@ -24,18 +24,23 @@ module Hecks
|
|
|
24
24
|
# about whether a declaration is admissible — the language settled that on the
|
|
25
25
|
# way in. This is only how a spelling becomes an object again.
|
|
26
26
|
#
|
|
27
|
-
# Why a table and not a method per category
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
27
|
+
# Why a table and not a method per category: a hand-written branch per
|
|
28
|
+
# category can decorate a verb the language declares without the walk ever
|
|
29
|
+
# offering it, and nothing goes red, because a branch that does not exist
|
|
30
|
+
# cannot fail. An assembler with a method per category is the same shape.
|
|
31
|
+
# The table avoids it because spec/assembly_spec checks it against the
|
|
32
|
+
# language: a field the language declares that no contract consumes is a
|
|
33
|
+
# failure, not a silence.
|
|
34
34
|
#
|
|
35
35
|
# The `derived` list is how a field says it needs no assembling — a parent
|
|
36
36
|
# pointer the containment tree already knows, or something computed from what
|
|
37
37
|
# is here (`query_name` is `Naming.snake(name)`, `creates?` is whether a verb
|
|
38
38
|
# names a root). Naming one is a claim, and the coverage gate holds it.
|
|
39
|
+
#
|
|
40
|
+
# @param category [String, Symbol] the construct category name, such as
|
|
41
|
+
# `"Aggregate"` or `:Command`
|
|
42
|
+
# @return [Bluebook::Assembly::Contract] the category's field contract
|
|
43
|
+
# @raise [KeyError] if no contract is registered for `category`
|
|
39
44
|
def self.contract(category) = CONTRACTS.fetch(category.to_s)
|
|
40
45
|
|
|
41
46
|
CONTRACTS = {
|
|
@@ -163,13 +168,13 @@ module Hecks
|
|
|
163
168
|
fields: {
|
|
164
169
|
name: [:name, :plain],
|
|
165
170
|
description: [:description, :plain],
|
|
166
|
-
# A list of paths, exactly as an aggregate's is
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
171
|
+
# A list of paths, exactly as an aggregate's is, with no room left for
|
|
172
|
+
# the two to differ. A Symbol here and a String there would look
|
|
173
|
+
# identical to `to_h`'s byte-equality check, since both render as a
|
|
174
|
+
# string, while diverging at runtime: the assembled graph would carry a
|
|
175
|
+
# String, and `element_of`'s lookup of `args["sequence"]` in a
|
|
176
|
+
# symbol-keyed payload would find nothing — refusing "Reverse acts on
|
|
177
|
+
# one LedgerEntry — pass sequence:" while sequence was in fact passed.
|
|
173
178
|
identified_by: [:identified_by, :plain],
|
|
174
179
|
attributes: [:attributes, [:each, :shape_field]],
|
|
175
180
|
# ADR 0028 — the same shape Aggregate's own `preconditions`
|
|
@@ -263,11 +268,11 @@ module Hecks
|
|
|
263
268
|
# S17, ADR 0026 — Dispatch is a genuine entity now, nested under
|
|
264
269
|
# Handler (`entity "Dispatch"`, process_manager.bluebook) — two
|
|
265
270
|
# levels deep, "no life outside its Handler" (the ADR's own
|
|
266
|
-
# words). `command_name` alone
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
# real handler
|
|
270
|
-
# `position`
|
|
271
|
+
# words). `command_name` alone is not Dispatch's own identity —
|
|
272
|
+
# items #151/#152 (`process_manager.bluebook`'s own
|
|
273
|
+
# `DispatchPosition` comment) found it collides the instant a
|
|
274
|
+
# real handler fans the same command out more than once, so
|
|
275
|
+
# `position` joins it, walk-minted the same way every other
|
|
271
276
|
# category's own `position` is (`derived: { position: :walk }`,
|
|
272
277
|
# the same entry ProcessManager's own contract carries above) —
|
|
273
278
|
# never a stored field on `DispatchSpec` itself, exactly like
|
|
@@ -9,7 +9,7 @@ module Hecks
|
|
|
9
9
|
# is that Shapes rebuilds hashes and this rebuilds objects, so it has to
|
|
10
10
|
# recover types rather than just strings.
|
|
11
11
|
#
|
|
12
|
-
# Encoding losses are the
|
|
12
|
+
# Encoding losses are the largest family of bug in this codebase, and every
|
|
13
13
|
# member has the same shape: reading an object where `to_h` holds a spelling.
|
|
14
14
|
# So each method below names the spelling it inverts.
|
|
15
15
|
module Marks
|
|
@@ -18,6 +18,14 @@ module Hecks
|
|
|
18
18
|
# `Attribute#to_h` spells a type with `to_s`, so a reference arrives as
|
|
19
19
|
# "Reference<Customer>" and has to become an edge again. Everything else is
|
|
20
20
|
# a name and stays one.
|
|
21
|
+
#
|
|
22
|
+
# @param field [Hash{Symbol => Object}] one attribute's declared row, keyed
|
|
23
|
+
# `:type` (String, a plain type name or `"Reference<Target>"`), `:name`
|
|
24
|
+
# (String), `:list` and `:optional` (read as truthy), `:default` (Object,
|
|
25
|
+
# nil), `:pattern` (String, nil), `:admits` (String, nil) and
|
|
26
|
+
# `:relationship` (String, nil), each the raw declared value
|
|
27
|
+
# @return [Bluebook::Attribute] the attribute, with `type` resolved to a
|
|
28
|
+
# `Bluebook::Reference` when the row spelled one
|
|
21
29
|
def attribute(field)
|
|
22
30
|
type = field[:type].to_s
|
|
23
31
|
target = type[/\AReference<(.+)>\z/, 1]
|
|
@@ -50,11 +58,18 @@ module Hecks
|
|
|
50
58
|
# The same shape. Two names because a head's field and a verb's argument are
|
|
51
59
|
# different things in the language even though the IR keeps one class for
|
|
52
60
|
# both — `Aggregate.Attribute` and `Command.Argument` are separate verbs.
|
|
61
|
+
#
|
|
62
|
+
# @param field [Hash{Symbol => Object}] see `attribute`
|
|
63
|
+
# @return [Bluebook::Attribute] see `attribute`
|
|
53
64
|
def shape_field(field) = attribute(field)
|
|
54
65
|
|
|
55
66
|
# One part of an identity. It goes in as a row so the language can hold an
|
|
56
67
|
# ordered list of them, and comes back out as the path it always was —
|
|
57
68
|
# a String, because `identity_paths` splits paths and never symbols.
|
|
69
|
+
#
|
|
70
|
+
# @param part [Hash{Symbol => Object}] one identity-path row, keyed `:value`
|
|
71
|
+
# (Object, read via `to_s`)
|
|
72
|
+
# @return [String] the path segment
|
|
58
73
|
def identity_path(part) = part[:value].to_s
|
|
59
74
|
|
|
60
75
|
# A member's fields — an open map, which is why Member is its own root in
|
|
@@ -64,6 +79,11 @@ module Hecks
|
|
|
64
79
|
# and the language stores them as text: `member code: "JPY", minor_units: 0`
|
|
65
80
|
# came back with a minor_units of "0", and a closed set that admits the string
|
|
66
81
|
# would refuse the number the caller passes.
|
|
82
|
+
#
|
|
83
|
+
# @param pairs [Array<Array(String, Object)>] one member's own field pairs,
|
|
84
|
+
# each `[field_name, raw_value]` as declared
|
|
85
|
+
# @return [Hash{Symbol => Object}] the member's fields, keyed by field name,
|
|
86
|
+
# each value read back to its native type through `unmark_scalar`
|
|
67
87
|
def member(pairs)
|
|
68
88
|
pairs.to_h { |key, value| [key.to_sym, unmark_scalar(value)] }
|
|
69
89
|
end
|
|
@@ -71,6 +91,12 @@ module Hecks
|
|
|
71
91
|
# A read model's gathered head. The keys must be symbols whichever way the
|
|
72
92
|
# declaration arrived, and `as` must be one too: it names the reader the
|
|
73
93
|
# projection answers to, and `ReadModel#to_h` spells it `to_s`.
|
|
94
|
+
#
|
|
95
|
+
# @param row [Hash{Symbol => Object}] one aggregate-head row, keyed
|
|
96
|
+
# `:aggregate` (String), `:as` (String, the reader name) and `:many`
|
|
97
|
+
# (Boolean)
|
|
98
|
+
# @return [Hash{Symbol => Object}] the head, with every key a Symbol and
|
|
99
|
+
# `:as` read back to a Symbol
|
|
74
100
|
def head(row)
|
|
75
101
|
row.to_h { |key, value| [key.to_sym, key.to_sym == :as ? value.to_sym : value] }
|
|
76
102
|
end
|
|
@@ -78,12 +104,20 @@ module Hecks
|
|
|
78
104
|
# A group_by field's own name — the builder's native shape is already
|
|
79
105
|
# `{field: :symbol}`, so this matches it rather than leaving `field`
|
|
80
106
|
# as the String `Shapes#group_by_field` reads back.
|
|
107
|
+
#
|
|
108
|
+
# @param row [Hash{Symbol => String}] one group-by row, keyed `:field`
|
|
109
|
+
# @return [Hash{field: Symbol}] the row with `:field` read back to a Symbol
|
|
81
110
|
def group_by_field(row) = { field: row[:field].to_sym }
|
|
82
111
|
|
|
83
112
|
# A scalar that was written as itself rather than inspected — a member's
|
|
84
113
|
# value, where the language holds text and the type has to be read back from
|
|
85
114
|
# the shape of it. Unlike `read`, a bare word stays a String here, because
|
|
86
115
|
# a closed set admits words far more often than symbols.
|
|
116
|
+
#
|
|
117
|
+
# @param value [Object] the raw declared scalar, read via `to_s`
|
|
118
|
+
# @return [true, false, Integer, Float, String] `true`/`false` for those
|
|
119
|
+
# literal words, a number when the text matches a bare integer or decimal
|
|
120
|
+
# pattern, or the string itself otherwise
|
|
87
121
|
def unmark_scalar(value)
|
|
88
122
|
text = value.to_s
|
|
89
123
|
return true if text == "true"
|
|
@@ -97,12 +131,32 @@ module Hecks
|
|
|
97
131
|
# A saga's argument bindings. Each value rides Literal's spelling, which
|
|
98
132
|
# marks a Symbol with a leading colon — lose it and an argument reads as a
|
|
99
133
|
# string of the same name.
|
|
134
|
+
#
|
|
135
|
+
# @param with [Array<Array(String, String)>, nil] the declared bindings,
|
|
136
|
+
# each `[argument_name, literal_spelling]` pair, or `nil` for none
|
|
137
|
+
# @return [Hash{Symbol => Object}] the bindings, keyed by argument name,
|
|
138
|
+
# each value read back through `Literal.read`
|
|
100
139
|
def bindings(with) = Array(with).to_h { |key, value| [key.to_sym, read(value)] }
|
|
101
140
|
|
|
141
|
+
# Builds one aggregate or value object invariant from its declared row.
|
|
142
|
+
#
|
|
143
|
+
# @param rule [Hash{Symbol => String}] one invariant's declared row, keyed
|
|
144
|
+
# `:description` and `:canonical` (the canonical-form rendering of the
|
|
145
|
+
# rule's expression)
|
|
146
|
+
# @return [Bluebook::Invariant] the invariant, with `predicate`/`ast` left
|
|
147
|
+
# `nil` — `Expression::Evaluator` parses `canonical` on demand when
|
|
148
|
+
# neither is set
|
|
102
149
|
def invariant(rule)
|
|
103
150
|
Invariant.new(description: rule[:description], canonical: rule[:canonical])
|
|
104
151
|
end
|
|
105
152
|
|
|
153
|
+
# Builds one command precondition from its declared row.
|
|
154
|
+
#
|
|
155
|
+
# @param rule [Hash{Symbol => String}] one given's declared row, keyed
|
|
156
|
+
# `:description` and `:canonical` (the canonical-form rendering of the
|
|
157
|
+
# rule's expression)
|
|
158
|
+
# @return [Bluebook::Given] the given, with `predicate`/`ast` left `nil` —
|
|
159
|
+
# `Expression::Evaluator` parses `canonical` on demand when neither is set
|
|
106
160
|
def given(rule)
|
|
107
161
|
Given.new(description: rule[:description], canonical: rule[:canonical])
|
|
108
162
|
end
|
|
@@ -111,6 +165,11 @@ module Hecks
|
|
|
111
165
|
# All three fields are identifiers, unlike Invariant/Given's own
|
|
112
166
|
# free text, so — like `attribute`'s own `name`/`type` below —
|
|
113
167
|
# they come back as Symbols.
|
|
168
|
+
#
|
|
169
|
+
# @param row [Hash{Symbol => String}] one projected-field's declared row,
|
|
170
|
+
# keyed `:name`, `:reference` and `:remote_field` (each an identifier)
|
|
171
|
+
# @return [Bluebook::ProjectedField] the projected field, with each
|
|
172
|
+
# identifier read back to a Symbol
|
|
114
173
|
def projected_field(row)
|
|
115
174
|
ProjectedField.new(name: row[:name].to_sym, reference: row[:reference].to_sym,
|
|
116
175
|
remote_field: row[:remote_field].to_sym)
|
|
@@ -122,6 +181,14 @@ module Hecks
|
|
|
122
181
|
# Symbol, wearing its colon) or a literal — the distinction that is the
|
|
123
182
|
# whole reason `append: { direction: "out" }` was once indistinguishable
|
|
124
183
|
# from an argument named `out`.
|
|
184
|
+
#
|
|
185
|
+
# @param change [Hash{Symbol => Object}] one mutation's declared row, keyed
|
|
186
|
+
# `:target` (String), `:op` (String, such as `"set"`, `"append"`,
|
|
187
|
+
# `"delegate"` or `"corrects"`), `:fields` (Array, for the append-shaped
|
|
188
|
+
# ops) and `:source` (Hash, for any other op)
|
|
189
|
+
# @return [Bluebook::Mutation] the mutation, with `source` a
|
|
190
|
+
# `Hash{Symbol => Object}` of field bindings for an append-shaped op, or
|
|
191
|
+
# `classified`'s own result for any other
|
|
125
192
|
def mutation(change)
|
|
126
193
|
target = change[:target].to_sym
|
|
127
194
|
op = change[:op].to_sym
|
|
@@ -135,12 +202,27 @@ module Hecks
|
|
|
135
202
|
Mutation.new(target: target, op: op, source: classified(change[:source]))
|
|
136
203
|
end
|
|
137
204
|
|
|
205
|
+
# Reads an append mutation's field bindings back off their declared rows.
|
|
206
|
+
#
|
|
207
|
+
# @param fields [Array<Array(String, String)>, nil] the append's declared
|
|
208
|
+
# field bindings, each `[field_name, literal_spelling]` pair, or `nil`
|
|
209
|
+
# for none
|
|
210
|
+
# @return [Hash{Symbol => Object}] the bindings, keyed by field name, each
|
|
211
|
+
# value read back through `Literal.read`
|
|
138
212
|
def appended(fields)
|
|
139
213
|
Array(fields).to_h { |field, source| [field.to_sym, read(source)] }
|
|
140
214
|
end
|
|
141
215
|
|
|
142
216
|
# A set reads one thing, and `classified_source` said which: an argument by
|
|
143
217
|
# name, or a literal by value.
|
|
218
|
+
#
|
|
219
|
+
# @param source [Hash{Symbol => Object}, nil] the mutation's declared
|
|
220
|
+
# source, keyed `:kind` (String, `"argument"`, `"state"` or a literal
|
|
221
|
+
# kind) plus `:name` (String, for `"argument"`/`"state"`) or `:value`
|
|
222
|
+
# (Object, for a literal), or `nil` for no source
|
|
223
|
+
# @return [Symbol, Hecks::StateRef, Object, nil] the command argument name,
|
|
224
|
+
# a state self-reference, the literal value, or `nil` when `source` is
|
|
225
|
+
# `nil`
|
|
144
226
|
def classified(source)
|
|
145
227
|
return nil if source.nil?
|
|
146
228
|
|
|
@@ -155,19 +237,22 @@ module Hecks
|
|
|
155
237
|
#
|
|
156
238
|
# A where-clause value, a saga's argument bindings, an append binding, a
|
|
157
239
|
# limit: all of them ride Literal's self-describing form, so all of them
|
|
158
|
-
# come back through
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
240
|
+
# come back through this one reader, never a second one that might
|
|
241
|
+
# disagree about a quoted string or a number.
|
|
242
|
+
#
|
|
243
|
+
# An object literal is the one that bites hardest. A saga leg binds
|
|
244
|
+
# `narrative: { text: "transfer out" }` — a value object's fields written
|
|
245
|
+
# inline — and reading it as a plain string rather than through `Literal`
|
|
246
|
+
# would reach the runtime as `"{:text=>\"transfer out\"}"`, which coercion
|
|
247
|
+
# refuses: the debit leg is never delivered, and the whole settlement wire
|
|
248
|
+
# stops — banking emits TransferRequested five times and TransferDebited
|
|
249
|
+
# never. A whole-history replay gate is what catches this, because a saga
|
|
250
|
+
# that silently does nothing looks exactly like a saga with nothing to do.
|
|
251
|
+
#
|
|
252
|
+
# @param value [String, Object] wire spelling produced by `Literal.render`,
|
|
253
|
+
# or a bare word
|
|
254
|
+
# @return [nil, true, false, Integer, Float, Symbol, Hecks::StateRef,
|
|
255
|
+
# String, Hash, Array] the value read back — see `Literal.read`
|
|
171
256
|
def read(value) = Literal.read(value)
|
|
172
257
|
|
|
173
258
|
# `target:` (ADR 0055) — read straight off the wire, unconverted:
|
|
@@ -180,12 +265,25 @@ module Hecks
|
|
|
180
265
|
# declared `on:` — `clause[:target]`/`declared[:target]` reads
|
|
181
266
|
# `nil` for a missing key exactly like an explicit `nil` would,
|
|
182
267
|
# so this is additive, not a migration.
|
|
268
|
+
#
|
|
269
|
+
# @param clause [Hash{Symbol => Object}] one where-clause's declared row,
|
|
270
|
+
# keyed `:field` (String), `:op` (String, a comparator name), `:value`
|
|
271
|
+
# (String, Literal's spelling) and `:target` (String, `nil` when absent)
|
|
272
|
+
# @return [QuerySpecification::Common::WhereClause] the clause, with `op`
|
|
273
|
+
# read to a Symbol and `value` read back through `Literal.read`
|
|
183
274
|
def where_clause(clause)
|
|
184
275
|
QuerySpecification::Common::WhereClause.new(
|
|
185
276
|
field: clause[:field], op: clause[:op].to_sym, value: read(clause[:value]), target: clause[:target]
|
|
186
277
|
)
|
|
187
278
|
end
|
|
188
279
|
|
|
280
|
+
# Builds a query's declared ordering, if it declares one.
|
|
281
|
+
#
|
|
282
|
+
# @param declared [Hash{Symbol => Object}, nil] the declared order-by row,
|
|
283
|
+
# keyed `:field` (String), `:direction` (String) and `:target` (String,
|
|
284
|
+
# `nil` when absent), or `nil` when the construct declares no ordering
|
|
285
|
+
# @return [QuerySpecification::Common::OrderBy, nil] the order-by, with
|
|
286
|
+
# `direction` read to a Symbol, or `nil` when `declared` is `nil`
|
|
189
287
|
def order_by(declared)
|
|
190
288
|
return nil unless declared
|
|
191
289
|
|
|
@@ -194,6 +292,14 @@ module Hecks
|
|
|
194
292
|
)
|
|
195
293
|
end
|
|
196
294
|
|
|
295
|
+
# Builds a query's declared limit, if it declares one.
|
|
296
|
+
#
|
|
297
|
+
# @param declared [Hash{Symbol => Object}, nil] the declared limit row,
|
|
298
|
+
# keyed `:value` (String, Literal's spelling) and `:target` (String,
|
|
299
|
+
# `nil` when absent), or `nil` when the construct declares no limit
|
|
300
|
+
# @return [QuerySpecification::Common::LimitSpec, nil] the limit, with
|
|
301
|
+
# `value` read back through `Literal.read`, or `nil` when `declared` is
|
|
302
|
+
# `nil`
|
|
197
303
|
def limit(declared)
|
|
198
304
|
return nil unless declared
|
|
199
305
|
|
|
@@ -219,6 +325,20 @@ module Hecks
|
|
|
219
325
|
# with `to_s`, so the colon is not there to strip.
|
|
220
326
|
SYMBOLIC = %i[mode policy tenant].freeze
|
|
221
327
|
|
|
328
|
+
# Builds one query specification option from its declared row and `OPTIONS`.
|
|
329
|
+
#
|
|
330
|
+
# @param name [Symbol] the option's name, a key of `OPTIONS` (`:offset`,
|
|
331
|
+
# `:cursor`, `:null_semantics`, `:authorization` or `:inspection`)
|
|
332
|
+
# @param declared [Hash{Symbol => Object}, nil] the option's declared row,
|
|
333
|
+
# or `nil` when the construct does not declare this option
|
|
334
|
+
# @return [QuerySpecification::Common::OffsetSpec,
|
|
335
|
+
# QuerySpecification::Common::CursorSpec,
|
|
336
|
+
# QuerySpecification::Common::NullSemantics,
|
|
337
|
+
# QuerySpecification::Common::AuthorizationSpec,
|
|
338
|
+
# QuerySpecification::Common::InspectionSpec, nil] the option built
|
|
339
|
+
# through the holder `OPTIONS` names for `name`, or `nil` when `declared`
|
|
340
|
+
# is `nil`
|
|
341
|
+
# @raise [KeyError] if `name` is not a key of `OPTIONS`
|
|
222
342
|
def option(name, declared)
|
|
223
343
|
return nil if declared.nil?
|
|
224
344
|
|
|
@@ -226,6 +346,15 @@ module Hecks
|
|
|
226
346
|
holder.new(**Hash(declared).to_h { |key, value| [key, option_value(key, value, marked)] })
|
|
227
347
|
end
|
|
228
348
|
|
|
349
|
+
# Reads one option member's declared value back to its native type.
|
|
350
|
+
#
|
|
351
|
+
# @param key [Symbol] the option member's name being read
|
|
352
|
+
# @param value [Object, nil] the declared value for that member
|
|
353
|
+
# @param marked [Array<Symbol>] the members of this option that ride
|
|
354
|
+
# Literal's spelling, from `OPTIONS`
|
|
355
|
+
# @return [Object, nil] `value` read back through `Literal.read` when
|
|
356
|
+
# `key` is marked, a Symbol when `key` is one of `SYMBOLIC`, the raw
|
|
357
|
+
# `value` otherwise, or `nil` when `value` is `nil`
|
|
229
358
|
def option_value(key, value, marked)
|
|
230
359
|
return nil if value.nil?
|
|
231
360
|
return read(value) if marked.include?(key)
|
|
@@ -27,10 +27,23 @@ module Hecks
|
|
|
27
27
|
# (searched recursively, the same reason `Value::Coercion#find_
|
|
28
28
|
# entity` does: a nested entity, like `Dispatch` inside
|
|
29
29
|
# `Handler`, is not a direct child of any aggregate either).
|
|
30
|
+
#
|
|
31
|
+
# @param chapter [Bluebook::Chapter] the chapter to search
|
|
32
|
+
# @param name [String, Symbol] the construct's declared name
|
|
33
|
+
# @return [Bluebook::Aggregate, Class, nil] the aggregate, or the entity class
|
|
34
|
+
# (a `Bluebook::Entity` subclass) nested anywhere under one, or `nil` if
|
|
35
|
+
# `chapter` declares no construct by that name
|
|
30
36
|
def construct_for(chapter, name)
|
|
31
37
|
chapter.aggregate(name) || chapter.aggregates.filter_map { |a| find_entity(a, name) }.first
|
|
32
38
|
end
|
|
33
39
|
|
|
40
|
+
# Searches a construct's own entities, recursively, for one by name.
|
|
41
|
+
#
|
|
42
|
+
# @param construct [Bluebook::Aggregate, Class] the aggregate, or entity class,
|
|
43
|
+
# to search under
|
|
44
|
+
# @param name [String, Symbol] the entity's declared name
|
|
45
|
+
# @return [Class, nil] the entity class (a `Bluebook::Entity` subclass), or `nil`
|
|
46
|
+
# if none of `construct`'s nested entities has that name
|
|
34
47
|
def find_entity(construct, name)
|
|
35
48
|
construct.entities.each do |candidate|
|
|
36
49
|
return candidate if candidate.hecks_name == name
|
|
@@ -52,6 +65,12 @@ module Hecks
|
|
|
52
65
|
# So the skip reads the category's own walk claims (`Contract#walked`)
|
|
53
66
|
# rather than restating `position` here — Handler, which has no
|
|
54
67
|
# walk-minted position, skips nothing.
|
|
68
|
+
#
|
|
69
|
+
# @param category [String, Symbol] the construct category's name, such as
|
|
70
|
+
# `"Aggregate"` or `"Command"`
|
|
71
|
+
# @return [Hash{Symbol => Array(Symbol, Symbol)}] a `contracts.rb`-shaped
|
|
72
|
+
# `fields:` table, one `field => [field, :plain]` entry per scalar,
|
|
73
|
+
# non-reference, non-walked attribute the language declares for `category`
|
|
55
74
|
def fields_for(category)
|
|
56
75
|
language = construct_for(MetaValidator.grammar_registry.bluebook("Bluebook"), category.to_s)
|
|
57
76
|
walked = Assembly.contract(category).walked
|
|
@@ -8,6 +8,8 @@ module Hecks
|
|
|
8
8
|
# into the graph the runtime runs: IR aggregates with their verbs, value
|
|
9
9
|
# objects, entities and asks, owned by the chapter that declares them.
|
|
10
10
|
#
|
|
11
|
+
# ## Why a hash, not a runtime
|
|
12
|
+
#
|
|
11
13
|
# It takes a hash, not a runtime, on purpose. That makes it a pure inverse of
|
|
12
14
|
# `to_h` and testable without the meta-domain in the picture at all:
|
|
13
15
|
#
|
|
@@ -17,23 +19,39 @@ module Hecks
|
|
|
17
19
|
# Feed it the reconstruction instead and the same code assembles what the
|
|
18
20
|
# language holds — the only difference being where the declarations came from.
|
|
19
21
|
#
|
|
22
|
+
# ## One table, not a method per category
|
|
23
|
+
#
|
|
20
24
|
# Every field is read from one table. There is no method per category here:
|
|
21
25
|
# `Contracts` names what the language cannot say about a construct, `Build`
|
|
22
|
-
# reads it, and the coverage gate holds the table to the language.
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
+
# reads it, and the coverage gate holds the table to the language. A method
|
|
27
|
+
# per category can decorate a verb the language declares without ever
|
|
28
|
+
# offering it; the table cannot, because `spec/assembly_spec` checks every
|
|
29
|
+
# field the language declares against it.
|
|
30
|
+
#
|
|
31
|
+
# ## What stays hand-written
|
|
26
32
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
33
|
+
# The containment — which construct holds which — stays hand-written; it is
|
|
34
|
+
# not a field table. The runtime surface is no longer built here at all — the
|
|
35
|
+
# door is a per-boot projection, facade/surface.rb.
|
|
30
36
|
class Assembly
|
|
37
|
+
# Builds the graph the runtime runs from one chapter's declared hash.
|
|
38
|
+
#
|
|
39
|
+
# @param declaration [Hash{Symbol => Object}] a chapter's declarations, in the
|
|
40
|
+
# shape `Chapter#to_h` spells
|
|
41
|
+
# @return [Bluebook::Chapter] the assembled chapter, with its aggregates, read
|
|
42
|
+
# models and process managers built and wired
|
|
31
43
|
def self.call(declaration) = new(declaration).bluebook
|
|
32
44
|
|
|
45
|
+
# @param declaration [Hash{Symbol => Object}] a chapter's declarations, in the
|
|
46
|
+
# shape `Chapter#to_h` spells
|
|
33
47
|
def initialize(declaration)
|
|
34
48
|
@declaration = declaration
|
|
35
49
|
end
|
|
36
50
|
|
|
51
|
+
# Assembles this instance's declared hash into the chapter's runtime graph.
|
|
52
|
+
#
|
|
53
|
+
# @return [Bluebook::Chapter] the assembled chapter, with its aggregates, read
|
|
54
|
+
# models and process managers built and wired
|
|
37
55
|
def bluebook
|
|
38
56
|
aggregates = Array(@declaration[:aggregates]).map { |row| AggregateAssembly.new(row).aggregate }
|
|
39
57
|
models = Array(@declaration[:read_models]).map { |row| Build.call("ReadModel", row) }
|