hecks 1.3.0 → 1.5.0
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 +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- 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 +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- 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 +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- 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 +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -16,17 +16,33 @@ module Hecks
|
|
|
16
16
|
# model is simple enough for one; otherwise runs the whole join
|
|
17
17
|
# in-process against loaded records.
|
|
18
18
|
class ReadModelInterpreter
|
|
19
|
+
# @param registry [Runtime::Registry] the booted registry whose repositories
|
|
20
|
+
# this interpreter reads
|
|
19
21
|
def initialize(registry) = @registry = registry
|
|
20
22
|
|
|
23
|
+
# Runs one declared read model and returns its projected rows.
|
|
24
|
+
#
|
|
25
|
+
# @param domain [String, Symbol] the domain the read model is declared in
|
|
26
|
+
# @param model [Bluebook::ReadModel] the read model to run
|
|
27
|
+
# @param args [Hash{Symbol => Object}] the query's declared arguments
|
|
28
|
+
# @return [Array<Hash>] a one-element Array holding a Hash of head name to
|
|
29
|
+
# projected rows (or a single row, for a non-`:many` head)
|
|
30
|
+
# @raise [Runtime::TypeMismatch] if the reference argument is offered as a whole
|
|
31
|
+
# object rather than a plain identity, or a `median` field is not numeric
|
|
32
|
+
# @raise [Runtime::NotFound] if the reference argument names no record
|
|
33
|
+
# @raise [KeyError] if a rooted read model is asked without its reference argument
|
|
34
|
+
# @raise [ArgumentError] if `group_by` or `median` names a field its target
|
|
35
|
+
# aggregate does not declare
|
|
36
|
+
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
21
37
|
def call(domain, model, args)
|
|
22
38
|
project(domain, model, args)
|
|
23
39
|
end
|
|
24
40
|
|
|
25
41
|
private
|
|
26
42
|
|
|
27
|
-
#
|
|
28
|
-
# each step's own comment names a real,
|
|
29
|
-
# current
|
|
43
|
+
# Root-first, then the SQLite escape hatch, then the join loop —
|
|
44
|
+
# each step's own comment names a real, already-shipped bug the
|
|
45
|
+
# current order fixes (the reference/TenantScope refusal ordering
|
|
30
46
|
# above, the root-first head processing below). Splitting this
|
|
31
47
|
# into smaller methods would scatter that ordering across method
|
|
32
48
|
# boundaries where a future editor could silently break it, and
|
|
@@ -40,7 +56,7 @@ module Hecks
|
|
|
40
56
|
def project(domain, model, args)
|
|
41
57
|
bluebook = @registry.bluebook(domain)
|
|
42
58
|
rootless = model.reference_target.nil?
|
|
43
|
-
#
|
|
59
|
+
# Before the adapter early-return below, so the SQLite path inherits it.
|
|
44
60
|
# Without this a stale caller passing a wrapped reference gets a
|
|
45
61
|
# path-dependent answer — an adapter could quietly open the wrapped
|
|
46
62
|
# reference while the in-process path reads it whole and finds nothing.
|
|
@@ -49,16 +65,16 @@ module Hecks
|
|
|
49
65
|
# wrong at all.
|
|
50
66
|
refuse_object_reference(model, args) unless rootless
|
|
51
67
|
reference_id = reference(args.fetch(model.reference_name)) unless rootless
|
|
52
|
-
# Computed off the
|
|
68
|
+
# Computed off the original model, before TenantScope wraps it — the
|
|
53
69
|
# "which head(s) do options apply to" question is about what the
|
|
54
70
|
# bluebook author declared, not about the synthetic tenant clause
|
|
55
71
|
# the wrapper adds underneath. Plural (ADR 0055) — `on:` lets more
|
|
56
72
|
# than one many-side head be eligible at once.
|
|
57
73
|
eligible = model.filtered_head_names
|
|
58
74
|
model = TenantScope.apply(model, args)
|
|
59
|
-
# A
|
|
75
|
+
# A rootless, `group_by`-declared, or `count`/`median`-declared
|
|
60
76
|
# model skips the SQLite native escape hatch entirely (there is
|
|
61
|
-
# no root aggregate to look up a repository
|
|
77
|
+
# no root aggregate to look up a repository for when rootless,
|
|
62
78
|
# and `query_read_model` knows nothing about grouping or
|
|
63
79
|
# reducing) — always runs the in-process loop below instead.
|
|
64
80
|
# Correct everywhere ; not SQL-pushed-down for a SQLite-backed
|
|
@@ -73,17 +89,17 @@ module Hecks
|
|
|
73
89
|
end
|
|
74
90
|
end
|
|
75
91
|
|
|
76
|
-
#
|
|
92
|
+
# **Root first, always** — regardless of `include` order in the
|
|
77
93
|
# bluebook. `read_model_builder.rb`'s own `include` is
|
|
78
94
|
# documented "Order-independent" (the `:many` flag is resolved
|
|
79
95
|
# at build time, once `@reference_target` is known), but that
|
|
80
|
-
# promise
|
|
81
|
-
# their literal declared order and
|
|
82
|
-
# against whatever was
|
|
83
|
-
# very first time through, if a many-side head happened
|
|
84
|
-
# declared before the root. A real, live bug (not a guess):
|
|
96
|
+
# promise is not kept without this: running heads in
|
|
97
|
+
# their literal declared order and matching each "many" head
|
|
98
|
+
# against whatever was already in `projected` would leave it
|
|
99
|
+
# empty, the very first time through, if a many-side head happened
|
|
100
|
+
# to be declared before the root. A real, live bug (not a guess):
|
|
85
101
|
# `include Promotion` before `include Item` on a read model
|
|
86
|
-
# whose root
|
|
102
|
+
# whose root is Item silently returned an empty array for
|
|
87
103
|
# Promotion — no error, just a wrong, too-small answer — while
|
|
88
104
|
# the reverse order worked purely by accident. `partition`,
|
|
89
105
|
# not `sort_by`: Ruby's `sort_by` is not guaranteed stable,
|
|
@@ -97,14 +113,14 @@ module Hecks
|
|
|
97
113
|
[fetch(bluebook, domain, head[:aggregate], reference_id)]
|
|
98
114
|
elsif rootless
|
|
99
115
|
# No root to FK-match against — a rootless model reads
|
|
100
|
-
# each of its own heads
|
|
101
|
-
# heads on one rootless model are
|
|
116
|
+
# each of its own heads whole, independently. Multiple
|
|
117
|
+
# heads on one rootless model are never cross-joined
|
|
102
118
|
# against each other, and there is no DSL to declare
|
|
103
119
|
# one if you wanted to — `ReadModelBuilder#include_impl`
|
|
104
120
|
# takes only `type`/`as:` (checked directly, not
|
|
105
121
|
# assumed), and `group_by` groups this bulk read's own
|
|
106
122
|
# output, it names no predicate between two heads.
|
|
107
|
-
# Building a cross-join here would mean
|
|
123
|
+
# Building a cross-join here would mean choosing a join
|
|
108
124
|
# semantics (equality on which fields?) nobody has
|
|
109
125
|
# declared — a real, deliberate scope limit pending a
|
|
110
126
|
# future `include ..., joins: ...`-shaped grammar
|
|
@@ -124,7 +140,7 @@ module Hecks
|
|
|
124
140
|
projected << { aggregate: head[:aggregate], rows: rows }
|
|
125
141
|
rows_by_as[head[:as]] = head[:many] ? rows : rows.first
|
|
126
142
|
end
|
|
127
|
-
# Declared order preserved in the
|
|
143
|
+
# Declared order preserved in the output — only the
|
|
128
144
|
# computation above needed reordering, not what a caller sees
|
|
129
145
|
# back.
|
|
130
146
|
heads = model.aggregate_heads.to_h { |head| [head[:as], rows_by_as[head[:as]]] }
|
|
@@ -145,9 +161,9 @@ module Hecks
|
|
|
145
161
|
end]
|
|
146
162
|
end
|
|
147
163
|
|
|
148
|
-
#
|
|
164
|
+
# **The root-first fix's own fix** — root-first alone only reaches one
|
|
149
165
|
# level: it guarantees the root is in `projected` before any other
|
|
150
|
-
# head is matched, but a
|
|
166
|
+
# head is matched, but a chain of non-root heads (a head that
|
|
151
167
|
# references another non-root head, not the root) is still
|
|
152
168
|
# matched against whatever declaration order happened to put in
|
|
153
169
|
# `projected` so far. `include Coupon` before `include Promotion`
|
|
@@ -160,9 +176,9 @@ module Hecks
|
|
|
160
176
|
# to declare `include` in dependency order (the same promise
|
|
161
177
|
# `read_model_builder.rb` already makes and this file is the one
|
|
162
178
|
# place obligated to keep), but by topologically sorting the
|
|
163
|
-
# non-root heads on their
|
|
179
|
+
# non-root heads on their own declared reference fields before
|
|
164
180
|
# this method's runtime matching ever runs — Kahn's algorithm,
|
|
165
|
-
# picking ready heads in
|
|
181
|
+
# picking ready heads in declared order at each step so declaring
|
|
166
182
|
# order still governs whenever there is no dependency to break a
|
|
167
183
|
# tie. This generalizes root-first (a chain of length 1) to a
|
|
168
184
|
# chain of any depth, and to a head depending on more than one
|
|
@@ -192,13 +208,13 @@ module Hecks
|
|
|
192
208
|
ordered
|
|
193
209
|
end
|
|
194
210
|
|
|
195
|
-
# Which
|
|
211
|
+
# Which other declared (non-root) heads a head's own aggregate
|
|
196
212
|
# holds a reference field toward — the same relationship this
|
|
197
213
|
# file's runtime matching checks record-by-record, asked here
|
|
198
214
|
# statically, once, to order heads before any record is read.
|
|
199
215
|
#
|
|
200
216
|
# `head[:aggregate]` names whatever `include` was given — and
|
|
201
|
-
# `include` accepts a nested
|
|
217
|
+
# `include` accepts a nested entity (Member, nested under
|
|
202
218
|
# ValueObject ; Handler and Dispatch, nested under ProcessManager
|
|
203
219
|
# — bluebook.bluebook's own `WholeBluebook` read model includes
|
|
204
220
|
# all three) just as readily as a top-level aggregate.
|
|
@@ -220,7 +236,7 @@ module Hecks
|
|
|
220
236
|
.map { |other| other[:aggregate] }
|
|
221
237
|
end
|
|
222
238
|
|
|
223
|
-
# `group_by`'s own declared fields, checked against the
|
|
239
|
+
# `group_by`'s own declared fields, checked against the one
|
|
224
240
|
# many-side head they apply to (`seal_group_by` already refuses
|
|
225
241
|
# zero or several) — resolved here, once, rather than re-derived
|
|
226
242
|
# per row. Raises loudly on a typo'd field name rather than
|
|
@@ -232,15 +248,15 @@ module Hecks
|
|
|
232
248
|
aggregate = bluebook.aggregate(target[:aggregate])
|
|
233
249
|
model.group_by_fields.each do |field|
|
|
234
250
|
next if aggregate.attribute(field)
|
|
235
|
-
#
|
|
251
|
+
# The lifecycle field is a field, and refusing it here was a drift
|
|
236
252
|
# between two halves of the same language: `where(status: "logged")`
|
|
237
253
|
# has always been legal on the same aggregate, because a lifecycle
|
|
238
254
|
# state is stored on the record like anything else — it is simply
|
|
239
255
|
# declared by `lifecycle :status` rather than by `attribute`.
|
|
240
256
|
#
|
|
241
257
|
# It is also the grouping anybody actually wants. "How are we doing"
|
|
242
|
-
# over a bug ledger
|
|
243
|
-
# group by every field
|
|
258
|
+
# over a bug ledger is the count per status, and a report that could
|
|
259
|
+
# group by every field except that one could not answer the question
|
|
244
260
|
# reports exist for.
|
|
245
261
|
next if aggregate.lifecycle && aggregate.lifecycle.field.to_sym == field.to_sym
|
|
246
262
|
|
|
@@ -253,7 +269,7 @@ module Hecks
|
|
|
253
269
|
|
|
254
270
|
# One level of nesting per field, in `group_by`'s own declared
|
|
255
271
|
# order — the leaf is the row with every grouped field removed
|
|
256
|
-
# (already spent, as the keys that reached it).
|
|
272
|
+
# (already spent, as the keys that reached it). Assumes the full
|
|
257
273
|
# `group_by` path uniquely identifies one row (true for grouping by
|
|
258
274
|
# an aggregate's own full identity, ConsoleSettings' own real use)
|
|
259
275
|
# — `leaves.first` silently keeps only the first row when several
|
|
@@ -263,9 +279,9 @@ module Hecks
|
|
|
263
279
|
def nest(rows, fields)
|
|
264
280
|
field, *rest = fields
|
|
265
281
|
rows.group_by { |row| row[field] }.transform_values do |group|
|
|
266
|
-
# Strip
|
|
282
|
+
# Strip only the field just grouped by, not the whole remaining
|
|
267
283
|
# list — `rest`'s own fields have to survive into the recursive
|
|
268
|
-
# call below, or the
|
|
284
|
+
# call below, or the next level groups by a key that's already
|
|
269
285
|
# gone (found by trying it: a two-field group_by's own second
|
|
270
286
|
# level came back keyed `nil` for every group, every time).
|
|
271
287
|
stripped = group.map { |row| row.reject { |key, _| key == field } }
|
|
@@ -273,7 +289,7 @@ module Hecks
|
|
|
273
289
|
end
|
|
274
290
|
end
|
|
275
291
|
|
|
276
|
-
# `count`/`median`'s own declared target — the
|
|
292
|
+
# `count`/`median`'s own declared target — the same single
|
|
277
293
|
# many-side head `group_by_target` resolves, for the same reason
|
|
278
294
|
# (`seal_aggregation` already refuses zero or several many-side
|
|
279
295
|
# heads, and refuses count/median declared alongside group_by, so
|
|
@@ -303,12 +319,12 @@ module Hecks
|
|
|
303
319
|
target
|
|
304
320
|
end
|
|
305
321
|
|
|
306
|
-
#
|
|
307
|
-
# middle value, sorted ; an
|
|
322
|
+
# **The standard definition**. An odd count's median is its one true
|
|
323
|
+
# middle value, sorted ; an even count's median is the average of
|
|
308
324
|
# its two middle values — the common convention (as opposed to,
|
|
309
325
|
# say, always taking the lower of the two), and the one this
|
|
310
326
|
# session's own task named explicitly as the deliberate choice.
|
|
311
|
-
# An
|
|
327
|
+
# An empty collection has no median: nil, not zero, so a caller
|
|
312
328
|
# cannot mistake "nothing to average" for "the values averaged to
|
|
313
329
|
# zero". Reuses `Ports::Query::InMemory`'s own field reading
|
|
314
330
|
# (`FieldPath.dig` + `comparable`) — the same unwrap `where`/
|
|
@@ -326,8 +342,8 @@ module Hecks
|
|
|
326
342
|
|
|
327
343
|
def fetch(bluebook, domain, aggregate_name, id)
|
|
328
344
|
@registry.read_repository(domain, bluebook.aggregate(aggregate_name)).find(id) ||
|
|
329
|
-
raise(NotFound, RefusalWording.
|
|
330
|
-
|
|
345
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "read_model_reference_missing",
|
|
346
|
+
aggregate: aggregate_name, offered: Rendering.describe(id)))
|
|
331
347
|
end
|
|
332
348
|
|
|
333
349
|
def records(bluebook, domain, aggregate_name)
|
|
@@ -335,27 +351,27 @@ module Hecks
|
|
|
335
351
|
aggregate ? @registry.read_repository(domain, aggregate).all : []
|
|
336
352
|
end
|
|
337
353
|
|
|
338
|
-
# A stored reference holds the target's id inside the
|
|
339
|
-
#
|
|
354
|
+
# A stored reference holds the target's id inside the reference
|
|
355
|
+
# attribute's own declared shape, so reading it is reading that shape —
|
|
340
356
|
# a different thing from the identity unwrap that was removed. An
|
|
341
|
-
#
|
|
357
|
+
# identity is declared as a path and followed (Runtime::Identity) ; a
|
|
342
358
|
# reference has no path of its own, and `Value.scalar` refuses a
|
|
343
359
|
# composite rather than guessing which field was meant.
|
|
344
360
|
#
|
|
345
361
|
# Storing the scalar itself would remove this reading altogether. That
|
|
346
|
-
# is a change to how references are
|
|
362
|
+
# is a change to how references are stored, not to how identities are
|
|
347
363
|
# declared, so it is not made here.
|
|
348
364
|
# An ask names itself where a command would name itself, and says the
|
|
349
365
|
# same thing about the same shape. `Value.refuse_object_reference` is
|
|
350
|
-
# not reused because it speaks of a
|
|
366
|
+
# not reused because it speaks of a command and its attribute ; a read
|
|
351
367
|
# model has a query name and one declared reference.
|
|
352
368
|
def refuse_object_reference(model, args)
|
|
353
369
|
offered = args.fetch(model.reference_name, nil)
|
|
354
370
|
return unless offered.is_a?(Hash) || offered.is_a?(Value)
|
|
355
371
|
|
|
356
372
|
raise TypeMismatch,
|
|
357
|
-
RefusalWording.
|
|
358
|
-
|
|
373
|
+
RefusalWording.render_site("TypeMismatch", "read_model_object_reference",
|
|
374
|
+
query: model.query_name, field: model.reference_name)
|
|
359
375
|
end
|
|
360
376
|
|
|
361
377
|
# A reference is the id, in the argument and in the stored row alike.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Runtime
|
|
3
|
-
#
|
|
3
|
+
# The out-of-band half of `projects` (S12, ADR 0025 — "Consistency
|
|
4
4
|
# across aggregate boundaries"). A projected field is never written
|
|
5
5
|
# by the command that reads it — nothing at dispatch time takes a
|
|
6
6
|
# live cross-aggregate read the way `CommandRules::References
|
|
7
7
|
# #dereference` still does — so this is the one place a projected
|
|
8
8
|
# field's value actually gets copied over: walk every record of the
|
|
9
|
-
#
|
|
9
|
+
# owning aggregate, resolve each of its own `projected_fields`
|
|
10
10
|
# through the reference it names, and `save` the local copy.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# **Explicit and callable, not automatic** — no on-boot detection of a
|
|
13
13
|
# freshly-declared `projects` with no held-era precedent, no
|
|
14
14
|
# generated `Policy#for_each` reaction keeping it live in real
|
|
15
15
|
# time as the target changes. Both are real extensions this same
|
|
@@ -19,7 +19,7 @@ module Hecks
|
|
|
19
19
|
# needs — proven to work end to end before either automatic
|
|
20
20
|
# trigger is built on top of it.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
22
|
+
# **Needs no new adapter capability**. `find`/`all`/`save` are the same
|
|
23
23
|
# three primitives every real adapter already answers identically
|
|
24
24
|
# (`Ports::Persistence::AppendOnly#save` — append, then project,
|
|
25
25
|
# the same for Memory/Postgres/SQLite/D1/Heki) — confirmed by
|
|
@@ -32,6 +32,12 @@ module Hecks
|
|
|
32
32
|
# `save` only runs when a projected value would differ from what
|
|
33
33
|
# is already stored, so re-running a sweep with nothing having
|
|
34
34
|
# moved on the target side touches the append log not at all.
|
|
35
|
+
#
|
|
36
|
+
# @param registry [Runtime::Registry] the booted registry to read repositories from
|
|
37
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
38
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate whose `projected_fields` to
|
|
39
|
+
# refresh
|
|
40
|
+
# @return [Integer] the number of records actually changed and saved
|
|
35
41
|
def call(registry, domain, aggregate)
|
|
36
42
|
return 0 if aggregate.projected_fields.empty?
|
|
37
43
|
|
|
@@ -39,6 +45,16 @@ module Hecks
|
|
|
39
45
|
repository.all.count { |record| refresh(registry, domain, aggregate, record, repository) }
|
|
40
46
|
end
|
|
41
47
|
|
|
48
|
+
# Refreshes one record's own projected fields in place, saving it if any changed.
|
|
49
|
+
#
|
|
50
|
+
# @param registry [Runtime::Registry] the booted registry to read the target's
|
|
51
|
+
# repository from
|
|
52
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
53
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `record` is an instance of
|
|
54
|
+
# @param record [Runtime::Instance] the record to refresh, mutated in place
|
|
55
|
+
# @param repository [Ports::Persistence::AppendOnly] the repository to save `record`
|
|
56
|
+
# through when it changes
|
|
57
|
+
# @return [Boolean] true if any projected field's value changed and `record` was saved
|
|
42
58
|
def refresh(registry, domain, aggregate, record, repository)
|
|
43
59
|
changed = false
|
|
44
60
|
|
|
@@ -55,10 +71,22 @@ module Hecks
|
|
|
55
71
|
changed
|
|
56
72
|
end
|
|
57
73
|
|
|
74
|
+
# Reads the current value of one projected field's remote target field.
|
|
75
|
+
#
|
|
58
76
|
# `nil` when the reference itself does not resolve in this
|
|
59
77
|
# chapter (a cross-domain target left "unfollowed" the same way
|
|
60
78
|
# References#dereference already leaves one) or when the record
|
|
61
79
|
# names no target at all — an optional reference nobody set.
|
|
80
|
+
#
|
|
81
|
+
# @param registry [Runtime::Registry] the booted registry to read the target's
|
|
82
|
+
# repository from
|
|
83
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
84
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate declaring `field`
|
|
85
|
+
# @param record [Runtime::Instance] the record holding the reference to follow
|
|
86
|
+
# @param field [Bluebook::ProjectedField] the projected field to resolve
|
|
87
|
+
# @return [Object, nil] the target record's own `field.remote_field` value; nil if the
|
|
88
|
+
# reference type does not resolve, the record names no target, or the target record
|
|
89
|
+
# cannot be found
|
|
62
90
|
def remote_value(registry, domain, aggregate, record, field)
|
|
63
91
|
target = aggregate.attribute(field.reference)&.type&.resolve
|
|
64
92
|
return nil unless target
|
|
@@ -13,12 +13,12 @@ module Hecks
|
|
|
13
13
|
# answers a hop for free, with no per-engine code and no way for
|
|
14
14
|
# one engine to forget it.
|
|
15
15
|
#
|
|
16
|
-
# A hop's own filtering never happens here —
|
|
16
|
+
# A hop's own filtering never happens here — resolving one hop
|
|
17
17
|
# means running one ordinary, adapter-agnostic query against the
|
|
18
|
-
# hop's
|
|
18
|
+
# hop's target aggregate (through the same Ports::Query boundary
|
|
19
19
|
# any other query goes through), and folding the ids it answers
|
|
20
20
|
# back in as a local membership check. A multi-hop chain resolves
|
|
21
|
-
# from the
|
|
21
|
+
# from the far end inward: `fold` only ever peels off the head hop
|
|
22
22
|
# (QuerySpecification::HopPath.next_hop, the one-step primitive),
|
|
23
23
|
# and hands everything still left in the tail to a recursive
|
|
24
24
|
# `apply` call — so hop 2, hop 3, and so on each get resolved by
|
|
@@ -31,6 +31,21 @@ module Hecks
|
|
|
31
31
|
module ReferenceHop
|
|
32
32
|
module_function
|
|
33
33
|
|
|
34
|
+
# Folds every hop clause in `declared.wheres` into a synthetic local `in` clause.
|
|
35
|
+
#
|
|
36
|
+
# @param declared [Bluebook::Query, Runtime::TenantScope::Scoped,
|
|
37
|
+
# QuerySpecification::Common::Options] the declared query specification to fold hop
|
|
38
|
+
# clauses of
|
|
39
|
+
# @param args [Hash] the query's arguments, read when resolving each hop's own query
|
|
40
|
+
# @param registry [Runtime::Registry] the booted registry to resolve each hop's target
|
|
41
|
+
# repository from
|
|
42
|
+
# @param domain [String] the domain `aggregate` belongs to
|
|
43
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `declared` queries
|
|
44
|
+
# @return [Bluebook::Query, Runtime::TenantScope::Scoped, QuerySpecification::Common::
|
|
45
|
+
# Options, Hecks::Runtime::ReferenceHop::Folded] `declared` unchanged when it has no
|
|
46
|
+
# hop clauses; otherwise a `Folded` wrapper whose `#wheres` replaces each hop clause
|
|
47
|
+
# with its folded `in` clause
|
|
48
|
+
# @raise [Runtime::WiringError] if a hop's target no longer resolves (see `fold`)
|
|
34
49
|
def apply(declared, args, registry:, domain:, aggregate:)
|
|
35
50
|
hopped, local = declared.wheres.partition { |clause| QuerySpecification::HopPath.hop_head?(clause.field, aggregate.attributes) }
|
|
36
51
|
return declared if hopped.empty?
|
|
@@ -39,6 +54,18 @@ module Hecks
|
|
|
39
54
|
Folded.new(declared, local + folded)
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
# Folds one hop clause into a synthetic `in` clause over the hop attribute's own ids.
|
|
58
|
+
#
|
|
59
|
+
# @param clause [QuerySpecification::Common::WhereClause] the hop clause to fold; its
|
|
60
|
+
# `field` names the hop path, dotted past the first segment
|
|
61
|
+
# @param args [Hash] the query's arguments, read when resolving the inner query
|
|
62
|
+
# @param registry [Runtime::Registry] the booted registry to resolve the hop's target
|
|
63
|
+
# repository from
|
|
64
|
+
# @param domain [String] the domain `aggregate` belongs to
|
|
65
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `clause` is declared against
|
|
66
|
+
# @return [QuerySpecification::Common::WhereClause] a synthetic `in` clause on the hop
|
|
67
|
+
# attribute's name, whose value is every id the inner clause admits on the target
|
|
68
|
+
# @raise [Runtime::WiringError] if the hop's target aggregate no longer resolves
|
|
42
69
|
def fold(clause, args, registry:, domain:, aggregate:)
|
|
43
70
|
step = QuerySpecification::HopPath.next_hop(clause.field, aggregate.attributes)
|
|
44
71
|
hop, rest = step
|
|
@@ -63,13 +90,23 @@ module Hecks
|
|
|
63
90
|
QuerySpecification::Common::WhereClause.new(field: hop.attribute.name, op: "in", value: ids)
|
|
64
91
|
end
|
|
65
92
|
|
|
66
|
-
# Every id the inner clause admits on the hop's
|
|
93
|
+
# Every id the inner clause admits on the hop's target — one
|
|
67
94
|
# whole, ordinary query against the target's own repository,
|
|
68
95
|
# through the very same Ports::Query boundary the outer ask
|
|
69
96
|
# uses, so a hop is answered by whatever engine the target
|
|
70
97
|
# aggregate is actually bound to (which may not be the engine
|
|
71
|
-
# the
|
|
98
|
+
# the outer aggregate is bound to at all) rather than by a
|
|
72
99
|
# second reading of the comparators.
|
|
100
|
+
#
|
|
101
|
+
# @param domain [String] the domain `target` belongs to
|
|
102
|
+
# @param target [Bluebook::Aggregate] the hop's target aggregate to query
|
|
103
|
+
# @param wheres [Array<QuerySpecification::Common::WhereClause>] the inner clause(s) to
|
|
104
|
+
# run against `target`
|
|
105
|
+
# @param args [Hash] the outer query's arguments, read when resolving the inner query
|
|
106
|
+
# @param registry [Runtime::Registry] the booted registry to resolve `target`'s
|
|
107
|
+
# repository from
|
|
108
|
+
# @return [Array<String>] every distinct id the inner clause(s) admit on `target`
|
|
109
|
+
# @raise [Runtime::WiringError] if a hop nested inside `wheres` no longer resolves
|
|
73
110
|
def matching_ids(domain, target, wheres, args, registry:)
|
|
74
111
|
spec = apply(QuerySpecification::Common::Options.new(wheres: wheres), args,
|
|
75
112
|
registry: registry, domain: domain, aggregate: target)
|
|
@@ -81,12 +118,17 @@ module Hecks
|
|
|
81
118
|
end
|
|
82
119
|
|
|
83
120
|
# Never returned to a caller that might call an IR-level method
|
|
84
|
-
# (`to_h`, …) whose
|
|
121
|
+
# (`to_h`, …) whose own internal `wheres` read would resolve
|
|
85
122
|
# against the original object, not this override — the exact
|
|
86
123
|
# caution TenantScope::Scoped's own comment gives, for the exact
|
|
87
124
|
# same reason: SimpleDelegator only intercepts calls made
|
|
88
125
|
# directly on the wrapper.
|
|
89
126
|
class Folded < SimpleDelegator
|
|
127
|
+
# @param declared [Bluebook::Query, Runtime::TenantScope::Scoped,
|
|
128
|
+
# QuerySpecification::Common::Options] the wrapped query specification, delegated to
|
|
129
|
+
# for everything but `#wheres`
|
|
130
|
+
# @param wheres [Array<QuerySpecification::Common::WhereClause>] the replacement
|
|
131
|
+
# where-clauses, hop clauses folded to synthetic `in` clauses
|
|
90
132
|
def initialize(declared, wheres)
|
|
91
133
|
super(declared)
|
|
92
134
|
@wheres = wheres
|