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
|
@@ -14,18 +14,33 @@ module Hecks
|
|
|
14
14
|
include QuerySpecification::Common::DSL
|
|
15
15
|
include WordGate
|
|
16
16
|
|
|
17
|
+
# @param name [String] the read model's own name, as written after `read_model`
|
|
17
18
|
def initialize(name)
|
|
18
19
|
@name = name
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
# Sets the human-readable description shown for this read model.
|
|
23
|
+
#
|
|
24
|
+
# @param value [String] the description text
|
|
25
|
+
# @return [String] the description as stored
|
|
21
26
|
def description(value)
|
|
22
27
|
# moved to the language: ProjectionText / purpose, on Projection.Declare
|
|
23
28
|
@description = value
|
|
24
29
|
end
|
|
25
30
|
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
31
|
+
# Declares the read model's own "one" side — the aggregate head every row projects
|
|
32
|
+
# around, keyed by its identity.
|
|
33
|
+
#
|
|
34
|
+
# Reached through `calls: "reference_to_impl"`. Bootstrap-reachable — the self-hosted
|
|
35
|
+
# `Bluebook.WholeBluebook` read model declares one — so it's exercised while
|
|
36
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` is still in play, not only afterwards.
|
|
37
|
+
#
|
|
38
|
+
# @param type [Module, Symbol, String] the referenced aggregate, written as a bare
|
|
39
|
+
# constant
|
|
40
|
+
# @param as [Symbol, nil] the field name to project the reference under; `nil` derives it
|
|
41
|
+
# from the target's own name
|
|
42
|
+
# @return [void]
|
|
43
|
+
# @raise [Bluebook::DSL::Malformed] if `reference_to` was already declared
|
|
29
44
|
def reference_to_impl(type, as: nil)
|
|
30
45
|
raise Malformed, "#{@name} already has a projection reference" if @reference_target
|
|
31
46
|
|
|
@@ -33,22 +48,28 @@ module Hecks
|
|
|
33
48
|
@reference_name = (as || Naming.snake(@reference_target)).to_sym
|
|
34
49
|
end
|
|
35
50
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# groups). The class-level
|
|
50
|
-
# class body uses is
|
|
51
|
-
#
|
|
51
|
+
# Adds one aggregate head this read model projects.
|
|
52
|
+
#
|
|
53
|
+
# Order-independent: `many:` is decided by comparing the included type
|
|
54
|
+
# against the reference target, resolved at build time (when the
|
|
55
|
+
# reference is known), not at the moment `include` itself runs — so
|
|
56
|
+
# an include declared before `reference_to` resolves exactly like one
|
|
57
|
+
# declared after it.
|
|
58
|
+
#
|
|
59
|
+
# Reached through `calls: "include_impl"`. Bootstrap-reachable —
|
|
60
|
+
# every core chapter's own `read_model` names which aggregates it
|
|
61
|
+
# includes with it — so it's exercised while
|
|
62
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` is still in play.
|
|
63
|
+
# `group_by_impl`, below, is table-routed the same way but is not
|
|
64
|
+
# bootstrap-reachable (no core read_model groups). The class-level
|
|
65
|
+
# `include WordGate` this file's own class body uses is
|
|
66
|
+
# `Module#include`, a different receiver, unrelated to this
|
|
67
|
+
# instance method sharing its name.
|
|
68
|
+
#
|
|
69
|
+
# @param type [Module, Symbol, String] the included aggregate, written as a bare constant
|
|
70
|
+
# @param as [Symbol, nil] the field name to project this head's rows under; `nil` derives
|
|
71
|
+
# it from the target's own name (pluralized for a many-side head)
|
|
72
|
+
# @return [void]
|
|
52
73
|
def include_impl(type, as: nil)
|
|
53
74
|
@includes ||= []
|
|
54
75
|
@includes << [Naming.demodulise(type), as]
|
|
@@ -96,6 +117,21 @@ module Hecks
|
|
|
96
117
|
# not this `Struct`) — found directly by reproducing "undefined
|
|
97
118
|
# method `new' for module WhereClause" against a real corpus load,
|
|
98
119
|
# not guessed.
|
|
120
|
+
#
|
|
121
|
+
# Records one `WhereClause` per `field => value` pair, targeted at a named many-side
|
|
122
|
+
# include when this read model has more than one.
|
|
123
|
+
#
|
|
124
|
+
# @param positional [Array<Hash{Symbol => Object}>] at most one bare Hash of where
|
|
125
|
+
# clauses; in practice always empty, since Ruby routes an ordinary
|
|
126
|
+
# `where(field: value)` call entirely into `rest` (see the comment above)
|
|
127
|
+
# @param on [Module, Symbol, String, nil] the many-side included aggregate these clauses
|
|
128
|
+
# apply to; `nil` when this read model has at most one many-side head
|
|
129
|
+
# @param rest [Hash{Symbol => Object}] `field => value` where clauses; a value is either
|
|
130
|
+
# a literal, a Symbol naming a query argument, or a one-pair Hash `{ comparator =>
|
|
131
|
+
# operand }` such as `{ gte: :minimum }` — a bare value means `eq`
|
|
132
|
+
# @return [void]
|
|
133
|
+
# @raise [ArgumentError] if more than one positional argument is given, or a Hash value
|
|
134
|
+
# does not have exactly one pair, or names a comparator outside `COMPARATORS`
|
|
99
135
|
def where_impl(*positional, on: nil, **rest)
|
|
100
136
|
raise ArgumentError, "wrong number of arguments (given #{positional.size}, expected 1)" if positional.size > 1
|
|
101
137
|
|
|
@@ -108,14 +144,36 @@ module Hecks
|
|
|
108
144
|
end
|
|
109
145
|
end
|
|
110
146
|
|
|
147
|
+
# Records the read model's single ordering, replacing any declared earlier.
|
|
148
|
+
#
|
|
149
|
+
# @param field [Symbol, String] the field to order by; a dotted path such as
|
|
150
|
+
# `:"order.value"` reaches a value object's member
|
|
151
|
+
# @param direction [Symbol, String] `:asc` or `:desc`
|
|
152
|
+
# @param on [Module, Symbol, String, nil] the many-side included aggregate this ordering
|
|
153
|
+
# applies to; `nil` when this read model has at most one many-side head
|
|
154
|
+
# @return [QuerySpecification::Common::OrderBy] the ordering just recorded
|
|
111
155
|
def order_by_impl(field, direction = :asc, on: nil)
|
|
112
156
|
@order_by = QuerySpecification::Common::OrderBy.new(field: field, direction: direction, target: resolve_target(on))
|
|
113
157
|
end
|
|
114
158
|
|
|
159
|
+
# Records the most rows the targeted collection returns.
|
|
160
|
+
#
|
|
161
|
+
# @param value [Integer, Symbol] a literal row count, or a Symbol naming the query
|
|
162
|
+
# argument that supplies it
|
|
163
|
+
# @param on [Module, Symbol, String, nil] the many-side included aggregate this limit
|
|
164
|
+
# applies to; `nil` when this read model has at most one many-side head
|
|
165
|
+
# @return [QuerySpecification::Common::LimitSpec] the limit just recorded
|
|
115
166
|
def limit_impl(value, on: nil)
|
|
116
167
|
@limit = QuerySpecification::Common::LimitSpec.new(value: value, target: resolve_target(on))
|
|
117
168
|
end
|
|
118
169
|
|
|
170
|
+
# Records how many matched rows the targeted collection skips before the limit applies.
|
|
171
|
+
#
|
|
172
|
+
# @param value [Integer, Symbol] a literal row count, or a Symbol naming the query
|
|
173
|
+
# argument that supplies it
|
|
174
|
+
# @param on [Module, Symbol, String, nil] the many-side included aggregate this offset
|
|
175
|
+
# applies to; `nil` when this read model has at most one many-side head
|
|
176
|
+
# @return [QuerySpecification::Common::OffsetSpec] the offset just recorded
|
|
119
177
|
def offset_impl(value, on: nil)
|
|
120
178
|
@offset = QuerySpecification::Common::OffsetSpec.new(value: value, target: resolve_target(on))
|
|
121
179
|
end
|
|
@@ -127,6 +185,9 @@ module Hecks
|
|
|
127
185
|
# `seal_query_options` already enforces for where/order_by/etc
|
|
128
186
|
# applies here too (`seal_group_by`) — grouping is a question
|
|
129
187
|
# about one collection's own rows, same as those are.
|
|
188
|
+
# @param fields [Array<Symbol>] the eligible many-side head's own fields to nest its rows
|
|
189
|
+
# under, one level per field
|
|
190
|
+
# @return [void]
|
|
130
191
|
def group_by_impl(*fields)
|
|
131
192
|
# Hash rows, `{field:}`, not bare symbols — same shape
|
|
132
193
|
# `aggregate_heads` already uses for exactly the reason it
|
|
@@ -168,16 +229,24 @@ module Hecks
|
|
|
168
229
|
# same shape as `count`, above (a bare, kind-driven coerce-and-
|
|
169
230
|
# assign).
|
|
170
231
|
|
|
171
|
-
#
|
|
232
|
+
# Assembles the declared references, includes and clauses into a `ReadModel`, after
|
|
233
|
+
# validating them.
|
|
234
|
+
#
|
|
235
|
+
# `reference_to` is optional — a read model with no root is a
|
|
172
236
|
# bulk one: every `include`d head reads its own aggregate whole
|
|
173
237
|
# (no FK match against a root that doesn't exist), and dispatch
|
|
174
|
-
# takes no id argument at all.
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
238
|
+
# takes no id argument at all. Requiring one would refuse a real,
|
|
239
|
+
# legitimate shape: `group_by`'s own real use (nesting an
|
|
240
|
+
# aggregate's own whole table by its own field values) has no root
|
|
241
|
+
# to speak of. Still needs to describe something — zero includes
|
|
242
|
+
# and no reference is refused.
|
|
243
|
+
#
|
|
244
|
+
# @return [Bluebook::ReadModel] the built read model
|
|
245
|
+
# @raise [Bluebook::DSL::Malformed] if neither `reference_to` nor any `include` is
|
|
246
|
+
# declared, if `where`/`order_by`/`limit`/`offset`/`group_by`/`count`/`median` name an
|
|
247
|
+
# `on:` that isn't a many-side include or are left untargeted with more than one
|
|
248
|
+
# many-side head, if `count` and `median` are both declared or combined with
|
|
249
|
+
# `group_by`, or if `cursor` is declared
|
|
181
250
|
def build
|
|
182
251
|
if !@reference_target && Array(@includes).empty?
|
|
183
252
|
raise Malformed,
|
|
@@ -200,6 +269,12 @@ module Hecks
|
|
|
200
269
|
count: @count, median_field: @median_field)
|
|
201
270
|
end
|
|
202
271
|
|
|
272
|
+
# Evaluates a `read_model` block against a fresh builder and returns what it built.
|
|
273
|
+
#
|
|
274
|
+
# @param name [String] the read model's own name
|
|
275
|
+
# @yield the read model's body, `instance_eval`'d against a new builder
|
|
276
|
+
# @return [Bluebook::ReadModel] the built read model
|
|
277
|
+
# @raise [Bluebook::DSL::Malformed] see `#build`
|
|
203
278
|
def self.build(name, &block)
|
|
204
279
|
builder = new(name)
|
|
205
280
|
builder.instance_eval(&block) if block
|
|
@@ -15,9 +15,10 @@ module Hecks
|
|
|
15
15
|
# reuses one of these three shapes instead of a fourth hand-written
|
|
16
16
|
# near-duplicate resolver.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
18
|
+
# ## Not one unified algorithm
|
|
19
|
+
#
|
|
20
|
+
# A real design question this file answers directly: the three existing
|
|
21
|
+
# resolvers are not superficially different, they are structurally different (a
|
|
21
22
|
# multi-pool fallback chain; one pool keyed by declaring owner,
|
|
22
23
|
# needing disambiguation; a live scan over already-built sibling
|
|
23
24
|
# objects with no separate pool at all) — forcing them into one
|
|
@@ -28,18 +29,21 @@ module Hecks
|
|
|
28
29
|
# `invariant`×3, `ensures`×1) before this file existed — extract
|
|
29
30
|
# predicate source, refuse if extraction failed, build the struct.
|
|
30
31
|
#
|
|
32
|
+
# ## Resolution rules read off the grammar table, not a Ruby-only mirror
|
|
33
|
+
#
|
|
31
34
|
# `#lookup`/`#verify_resolves_via!` read which construct uses which
|
|
32
35
|
# primitive off the self-hosted grammar table itself
|
|
33
|
-
# (`Keyword#resolves_via`, `syntax.bluebook`)
|
|
34
|
-
#
|
|
35
|
-
# round ago) — so a real domain's own boot, not just `bundle exec
|
|
36
|
-
# rspec`, fails loudly the moment the table and this file's own
|
|
36
|
+
# (`Keyword#resolves_via`, `syntax.bluebook`), so a real domain's own boot, not just
|
|
37
|
+
# `bundle exec rspec`, fails loudly the moment the table and this file's own
|
|
37
38
|
# hand-written resolution methods disagree.
|
|
38
39
|
module RuleReference
|
|
39
40
|
module_function
|
|
40
41
|
|
|
42
|
+
# Extracts a predicate block's source and builds the rule struct that holds it,
|
|
43
|
+
# refusing when the source could not be read.
|
|
44
|
+
#
|
|
41
45
|
# `struct_class` is `Given` or `Invariant` (both `Struct.new(
|
|
42
|
-
# :description, :canonical, :predicate, keyword_init: true)` —
|
|
46
|
+
# :description, :canonical, :predicate, :ast, keyword_init: true)` —
|
|
43
47
|
# `Given` lives in command.rb, `Invariant` in value_object.rb).
|
|
44
48
|
# `owner_name`/`word` are only for the refusal message's own
|
|
45
49
|
# wording. `extraction_failure` is the tail of that same
|
|
@@ -52,6 +56,22 @@ module Hecks
|
|
|
52
56
|
# this method existed; unifying them into one generic sentence
|
|
53
57
|
# would be a real (if small) behavior change this refactor is
|
|
54
58
|
# not making.
|
|
59
|
+
#
|
|
60
|
+
# @param struct_class [Class] the rule struct to build — `Bluebook::Given` or
|
|
61
|
+
# `Bluebook::Invariant`
|
|
62
|
+
# @param description [String] the rule's own description, as declared by the caller's
|
|
63
|
+
# own `given`/`invariant`/`ensures` word
|
|
64
|
+
# @param predicate [Proc] the rule's own body block, never called here — only its
|
|
65
|
+
# extracted source is used
|
|
66
|
+
# @param owner_name [String] the declaring construct's own name, for the refusal message
|
|
67
|
+
# @param word [String] the declaring word (`"given"`, `"invariant"`, or `"ensures"`),
|
|
68
|
+
# for the refusal message
|
|
69
|
+
# @param extraction_failure [String] the refusal message's own tail, naming what an
|
|
70
|
+
# unreadable predicate would mean for this particular word
|
|
71
|
+
# @return [Bluebook::Given, Bluebook::Invariant] the built rule struct, an instance of
|
|
72
|
+
# `struct_class`
|
|
73
|
+
# @raise [Bluebook::DSL::Malformed] if `predicate`'s source could not be extracted, or
|
|
74
|
+
# it matches against a pattern construct `Expression::AstJson::PatternSubset` refuses
|
|
55
75
|
def build_rule(struct_class, description, predicate, owner_name:, word:, extraction_failure:)
|
|
56
76
|
canonical = Ports::Extraction.canonical(predicate)
|
|
57
77
|
|
|
@@ -72,6 +92,11 @@ module Hecks
|
|
|
72
92
|
# piece's entity-wide pool) is this with a 2-element chain — a
|
|
73
93
|
# future single-pool bare reference is the same primitive with a
|
|
74
94
|
# 1-element chain, not a separate "just look in one hash" method.
|
|
95
|
+
# @param pools [Array<Hash{String => Bluebook::Given, Bluebook::Invariant}>] pools to
|
|
96
|
+
# search in order; the first pool holding `description` wins
|
|
97
|
+
# @param description [String] the rule's own description to find
|
|
98
|
+
# @return [Bluebook::Given, Bluebook::Invariant, nil] the matching rule, or `nil` if no
|
|
99
|
+
# pool has one
|
|
75
100
|
def resolve_hash_chain(pools, description)
|
|
76
101
|
pools.each { |pool| return pool[description] if pool.key?(description) }
|
|
77
102
|
nil
|
|
@@ -87,6 +112,11 @@ module Hecks
|
|
|
87
112
|
# wording for "none," "ambiguous," and "declared_by: named the
|
|
88
113
|
# wrong owner" rather than one generic message papering over all
|
|
89
114
|
# three.
|
|
115
|
+
# @param pool [Hash{String => Hash{String => Bluebook::Given, Bluebook::Invariant}}]
|
|
116
|
+
# descriptions mapped to their own candidates, each keyed by declaring owner
|
|
117
|
+
# @param description [String] the rule's own description to find
|
|
118
|
+
# @return [Hash{String => Bluebook::Given, Bluebook::Invariant}] the candidates for
|
|
119
|
+
# `description`, keyed by declaring owner; empty when none exist
|
|
90
120
|
def resolve_owner_keyed(pool, description)
|
|
91
121
|
pool[description] || {}
|
|
92
122
|
end
|
|
@@ -102,6 +132,13 @@ module Hecks
|
|
|
102
132
|
# sibling (`:invariants` today; kept a parameter, not hardcoded,
|
|
103
133
|
# since a future sibling-scan scope might reference a different
|
|
104
134
|
# collection).
|
|
135
|
+
# @param siblings [Array<Object>] the already-built sibling objects to scan; each must
|
|
136
|
+
# respond to `reader`
|
|
137
|
+
# @param description [String] the rule's own description to find
|
|
138
|
+
# @param reader [Symbol] the method to call on each sibling to read its own rule
|
|
139
|
+
# collection, such as `:invariants`
|
|
140
|
+
# @return [Bluebook::Given, Bluebook::Invariant, nil] the matching rule, or `nil` if no
|
|
141
|
+
# sibling declares one
|
|
105
142
|
def resolve_sibling_scan(siblings, description, reader:)
|
|
106
143
|
siblings.flat_map { |sibling| sibling.public_send(reader) }
|
|
107
144
|
.find { |rule| rule.description == description }
|
|
@@ -138,6 +175,14 @@ module Hecks
|
|
|
138
175
|
# (bin/project_bootstrap_table, pinned by spec/bootstrap_table_spec.rb).
|
|
139
176
|
BOOTSTRAP_FALLBACK = BootstrapTable::RESOLVES
|
|
140
177
|
|
|
178
|
+
# Reads how a (word, context) pair resolves its rule references, off the self-hosted
|
|
179
|
+
# grammar table itself (or, while that table is still booting, the projected fallback).
|
|
180
|
+
#
|
|
181
|
+
# @param word [String] the DSL word, such as `"given"` or `"invariant"`
|
|
182
|
+
# @param context [String] the grammar context, such as `"Aggregate"`
|
|
183
|
+
# @return [Hash{Symbol => String, nil}] `:resolves_via` and `:disambiguator` for this
|
|
184
|
+
# (word, context) pair, `nil`-valued when the row leaves either blank, or `{}` if no row
|
|
185
|
+
# matches at all
|
|
141
186
|
def lookup(word, context)
|
|
142
187
|
if MetaValidator.bootstrapping?
|
|
143
188
|
BOOTSTRAP_FALLBACK[[word, context]] || {}
|
|
@@ -162,6 +207,13 @@ module Hecks
|
|
|
162
207
|
# real drift between the language's own self-description and
|
|
163
208
|
# its own implementation, caught at the next boot of anything,
|
|
164
209
|
# not just the next `rspec` run.
|
|
210
|
+
# @param word [String] the DSL word, such as `"given"` or `"invariant"`
|
|
211
|
+
# @param context [String] the grammar context, such as `"Aggregate"`
|
|
212
|
+
# @param expected_primitive [String] the resolution primitive's own name the caller is
|
|
213
|
+
# about to use, such as `"hash_chain"`, `"owner_keyed"`, or `"sibling_scan"`
|
|
214
|
+
# @return [void]
|
|
215
|
+
# @raise [RuntimeError] if `syntax.bluebook`'s own `resolves_via` for this (word, context)
|
|
216
|
+
# pair names something other than `expected_primitive`
|
|
165
217
|
def verify_resolves_via!(word, context, expected_primitive)
|
|
166
218
|
actual = lookup(word, context)[:resolves_via]
|
|
167
219
|
return if actual == expected_primitive
|
|
@@ -13,6 +13,9 @@ module Hecks
|
|
|
13
13
|
|
|
14
14
|
include WordGate
|
|
15
15
|
|
|
16
|
+
# @param name [String, Symbol] the aggregate's name in the destination era
|
|
17
|
+
# @param was [String, Symbol, nil] the aggregate's earlier name, when renamed
|
|
18
|
+
# @raise [Bluebook::DSL::Malformed] if `name` is empty
|
|
16
19
|
def initialize(name, was: nil)
|
|
17
20
|
raise Malformed, "an aggregate translation needs a name" if name.to_s.empty?
|
|
18
21
|
|
|
@@ -28,14 +31,23 @@ module Hecks
|
|
|
28
31
|
@backfills = []
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
34
|
+
# Declares a field rename with no other change: same path, new name.
|
|
35
|
+
#
|
|
36
|
+
# Answers the `rename` word (and, via the same table rows, its
|
|
37
|
+
# siblings `move`/`convert`/`retype`/`compute`/`rekey`/`backfill`
|
|
38
|
+
# below) through the table's `calls:` column — item #13's full
|
|
39
|
+
# metaprogrammed dispatch (slice 4c). Each is carried in
|
|
40
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
|
|
41
|
+
# `calls:`-routed word, but none of their rows are ever consulted
|
|
42
|
+
# during real bootstrap: `translation.bluebook` describes its own
|
|
43
|
+
# structure with aggregate/entity/attribute, never with these —
|
|
44
|
+
# they're words for real, user-authored `.translation` files
|
|
45
|
+
# only, loaded after the grammar table already exists.
|
|
46
|
+
#
|
|
47
|
+
# @param old_name [Symbol, String] the field's name in the held era
|
|
48
|
+
# @param to [Symbol, String] the field's name in the destination era
|
|
49
|
+
# @return [void]
|
|
50
|
+
# @raise [Bluebook::DSL::Malformed] if `old_name` or `to` is empty
|
|
39
51
|
def rename_impl(old_name, to:)
|
|
40
52
|
raise Malformed, "a rename needs a source name" if old_name.to_s.empty?
|
|
41
53
|
raise Malformed, "a rename needs a destination name (to:)" if to.to_s.empty?
|
|
@@ -43,6 +55,13 @@ module Hecks
|
|
|
43
55
|
@renames[old_name.to_sym] = to.to_sym
|
|
44
56
|
end
|
|
45
57
|
|
|
58
|
+
# Declares a field moved to a different path, name unchanged.
|
|
59
|
+
#
|
|
60
|
+
# @param old_path [String, Symbol] the field's path in the held era; dotted reaches a
|
|
61
|
+
# value-object member
|
|
62
|
+
# @param to [String, Symbol] the field's path in the destination era
|
|
63
|
+
# @return [Array<Bluebook::TranslationMove>] every move declared so far, this one last
|
|
64
|
+
# @raise [Bluebook::DSL::Malformed] if `old_path` or `to` is empty
|
|
46
65
|
def move_impl(old_path, to:)
|
|
47
66
|
raise Malformed, "a move needs a destination path (to:)" if to.to_s.empty?
|
|
48
67
|
raise Malformed, "a move needs a source path" if old_path.to_s.empty?
|
|
@@ -50,10 +69,21 @@ module Hecks
|
|
|
50
69
|
@moves << TranslationMove.new(old_path.to_s, to.to_s)
|
|
51
70
|
end
|
|
52
71
|
|
|
72
|
+
# Declares an exhaustive value-to-value mapping for a field with nothing structural in
|
|
73
|
+
# common with its replacement.
|
|
74
|
+
#
|
|
53
75
|
# A value with nothing structural in common with its replacement
|
|
54
76
|
# — declared as an exhaustive table, not computed, so every value
|
|
55
77
|
# that can appear in old data has a named destination. Paths
|
|
56
78
|
# follow `move`'s convention: dotted reaches a value-object member.
|
|
79
|
+
#
|
|
80
|
+
# @param old_path [String, Symbol] the field's path in the held era
|
|
81
|
+
# @param to [String, Symbol] the field's path in the destination era
|
|
82
|
+
# @param values [Hash] every old value mapped to its destination value
|
|
83
|
+
# @return [Array<Bluebook::TranslationConvert>] every convert declared so far, this one
|
|
84
|
+
# last
|
|
85
|
+
# @raise [Bluebook::DSL::Malformed] if `old_path` or `to` is empty, or `values` is nil
|
|
86
|
+
# or empty
|
|
57
87
|
def convert_impl(old_path, to:, values:)
|
|
58
88
|
raise Malformed, "a convert needs a destination path (to:)" if to.to_s.empty?
|
|
59
89
|
raise Malformed, "a convert needs a source path" if old_path.to_s.empty?
|
|
@@ -70,10 +100,18 @@ module Hecks
|
|
|
70
100
|
# coerce-and-append with nothing else, now executed by
|
|
71
101
|
# `GenericDispatch`.
|
|
72
102
|
|
|
103
|
+
# Declares that a type's name changed while its member structure stayed the same.
|
|
104
|
+
#
|
|
73
105
|
# A value object's or entity's own type name changed, member
|
|
74
106
|
# structure unchanged. The stored data never carries the type
|
|
75
107
|
# name, so nothing moves — this declares that the pair of names
|
|
76
108
|
# means the same shape, which is what lets the era diff accept it.
|
|
109
|
+
#
|
|
110
|
+
# @param old_type [String, Symbol] the type's name in the held era
|
|
111
|
+
# @param to [String, Symbol] the type's name in the destination era
|
|
112
|
+
# @return [Array<Bluebook::TranslationRetype>] every retype declared so far, this one
|
|
113
|
+
# last
|
|
114
|
+
# @raise [Bluebook::DSL::Malformed] if `old_type` or `to` is empty
|
|
77
115
|
def retype_impl(old_type, to:)
|
|
78
116
|
raise Malformed, "a retype needs a source type name" if old_type.to_s.empty?
|
|
79
117
|
raise Malformed, "a retype needs a destination type name (to:)" if to.to_s.empty?
|
|
@@ -81,10 +119,19 @@ module Hecks
|
|
|
81
119
|
@retypes << TranslationRetype.new(old_type.to_s, to.to_s)
|
|
82
120
|
end
|
|
83
121
|
|
|
122
|
+
# Declares a field computed by a hand-written Postgres SQL expression.
|
|
123
|
+
#
|
|
84
124
|
# A computed transform whose only implementation is the SQL
|
|
85
125
|
# expression itself — Postgres-only by construction. The scaffold
|
|
86
126
|
# never proposes one; a human writes it, and the audit's
|
|
87
127
|
# human-sampled review is its only verification.
|
|
128
|
+
#
|
|
129
|
+
# @param old_path [String, Symbol] the source field's path in the held era
|
|
130
|
+
# @param to [String, Symbol] the field's path in the destination era
|
|
131
|
+
# @param sql [String] the Postgres SQL expression computing the destination value
|
|
132
|
+
# @return [Array<Bluebook::TranslationCompute>] every compute declared so far, this one
|
|
133
|
+
# last
|
|
134
|
+
# @raise [Bluebook::DSL::Malformed] if `old_path`, `to`, or `sql` is empty
|
|
88
135
|
def compute_impl(old_path, to:, sql:)
|
|
89
136
|
raise Malformed, "a compute needs a destination path (to:)" if to.to_s.empty?
|
|
90
137
|
raise Malformed, "a compute needs a source path" if old_path.to_s.empty?
|
|
@@ -93,6 +140,9 @@ module Hecks
|
|
|
93
140
|
@computes << TranslationCompute.new(old_path.to_s, to.to_s, sql.to_s)
|
|
94
141
|
end
|
|
95
142
|
|
|
143
|
+
# Declares a hand-written Postgres SQL expression that recomputes the aggregate's own
|
|
144
|
+
# identity.
|
|
145
|
+
#
|
|
96
146
|
# The aggregate's own identity, changing what it's computed from —
|
|
97
147
|
# not a field crossing a boundary (`move`), not a value's own
|
|
98
148
|
# transform (`compute`): the record's key. No path arguments,
|
|
@@ -101,12 +151,19 @@ module Hecks
|
|
|
101
151
|
# SQL-only, Postgres-only, human-reviewed-sample-is-the-only-
|
|
102
152
|
# verification shape `compute` already has, and for the same
|
|
103
153
|
# reason: there is nothing in-process to check this against.
|
|
154
|
+
#
|
|
155
|
+
# @param sql [String] the Postgres SQL expression computing the destination identity
|
|
156
|
+
# @return [Array<Bluebook::TranslationRekey>] every rekey declared so far, this one last
|
|
157
|
+
# @raise [Bluebook::DSL::Malformed] if `sql` is empty
|
|
104
158
|
def rekey_impl(sql:)
|
|
105
159
|
raise Malformed, "a rekey needs its sql: expression" if sql.to_s.empty?
|
|
106
160
|
|
|
107
161
|
@rekeys << TranslationRekey.new(sql.to_s)
|
|
108
162
|
end
|
|
109
163
|
|
|
164
|
+
# Declares a newly added, required attribute and the default existing records read
|
|
165
|
+
# until a real value is written.
|
|
166
|
+
#
|
|
110
167
|
# A newly added, required attribute — the addition-side sibling of
|
|
111
168
|
# `drop`. Nothing to rename, move, or convert from, since old data
|
|
112
169
|
# never held this field at all; `default` is what an existing
|
|
@@ -118,6 +175,12 @@ module Hecks
|
|
|
118
175
|
# `EraGuard.refuse_unsafe_addition!` asks for when a non-optional
|
|
119
176
|
# attribute with no default: could leave an existing record with
|
|
120
177
|
# the field genuinely absent.
|
|
178
|
+
#
|
|
179
|
+
# @param name [String, Symbol] the new attribute's name
|
|
180
|
+
# @param default [Object] the value an existing record reads until it is written for real
|
|
181
|
+
# @return [Array<Bluebook::TranslationBackfill>] every backfill declared so far, this
|
|
182
|
+
# one last
|
|
183
|
+
# @raise [Bluebook::DSL::Malformed] if `name` is empty or `default` is nil
|
|
121
184
|
def backfill_impl(name, default:)
|
|
122
185
|
raise Malformed, "a backfill needs a name" if name.to_s.empty?
|
|
123
186
|
raise Malformed, "a backfill needs a default: value" if default.nil?
|
|
@@ -125,33 +188,46 @@ module Hecks
|
|
|
125
188
|
@backfills << TranslationBackfill.new(name.to_sym, default)
|
|
126
189
|
end
|
|
127
190
|
|
|
191
|
+
# Always refuses to boot: marks a field the scaffold could not decide a rule for.
|
|
192
|
+
#
|
|
128
193
|
# The scaffold writes this where it cannot decide; a file carrying
|
|
129
194
|
# one can only boot into this refusal — never a guess.
|
|
130
195
|
#
|
|
131
|
-
#
|
|
196
|
+
# Answers the `unresolved` word through the table's `calls:`
|
|
197
|
+
# column — item #13's full metaprogrammed
|
|
132
198
|
# dispatch (slice 4). Builds its own message with real branching
|
|
133
199
|
# (empty vs. named candidates, a special :identity case), not a
|
|
134
|
-
# fixed string a boolean `refuses:` flag could express
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# bootstrap
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
200
|
+
# fixed string a boolean `refuses:` flag could express. Carried in
|
|
201
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
|
|
202
|
+
# `calls:`-routed word, but that row is never consulted during
|
|
203
|
+
# real bootstrap (checked directly, not assumed):
|
|
204
|
+
# translation.bluebook (loaded during bootstrap, to describe the
|
|
205
|
+
# translation DSL itself) never writes `unresolved` — that word is
|
|
206
|
+
# only ever used by real, user-authored `.translation` files,
|
|
207
|
+
# loaded well after the grammar table already exists.
|
|
208
|
+
#
|
|
209
|
+
# @param name [String, Symbol] the unresolved field's name, or `:identity` for an
|
|
210
|
+
# unresolved identity change
|
|
211
|
+
# @param candidates [Array<String, Symbol>] paths the scaffold considered but could not
|
|
212
|
+
# choose between; empty when it found none
|
|
213
|
+
# @return [void]
|
|
214
|
+
# @raise [Bluebook::DSL::Malformed] always
|
|
142
215
|
def unresolved_impl(name, candidates: [])
|
|
143
216
|
raise Malformed, unresolved_message(name, candidates)
|
|
144
217
|
end
|
|
145
218
|
|
|
146
|
-
# `method_missing`/`respond_to_missing?`
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# elsewhere in the grammar but not in this context still gets
|
|
153
|
-
# richer
|
|
219
|
+
# `method_missing`/`respond_to_missing?` answer off the self-hosted
|
|
220
|
+
# grammar table (via the `include`d `WordGate`, above), giving a
|
|
221
|
+
# richer, table-driven "must be rename, move, convert, ..." refusal
|
|
222
|
+
# on a genuinely undefined call than a hand-typed list could —
|
|
223
|
+
# the exact "hardcoded legal-word list" this whole arc's item #13
|
|
224
|
+
# exists to close. A word admitted
|
|
225
|
+
# elsewhere in the grammar but not in this context still gets that
|
|
226
|
+
# richer refusal.
|
|
154
227
|
|
|
228
|
+
# Assembles the declared rules into a `TranslationAggregate`.
|
|
229
|
+
#
|
|
230
|
+
# @return [Bluebook::TranslationAggregate] the built per-aggregate translation
|
|
155
231
|
def build
|
|
156
232
|
TranslationAggregate.new(
|
|
157
233
|
name: @name, was: @was, renames: @renames, moves: @moves, converts: @converts,
|
|
@@ -199,6 +275,10 @@ module Hecks
|
|
|
199
275
|
|
|
200
276
|
include WordGate
|
|
201
277
|
|
|
278
|
+
# @param domain [String, Symbol] the domain this translation carries forward
|
|
279
|
+
# @param from [String, Symbol] the origin era
|
|
280
|
+
# @param to [String, Symbol] the destination era
|
|
281
|
+
# @raise [Bluebook::DSL::Malformed] if `domain`, `from`, or `to` is empty
|
|
202
282
|
def initialize(domain, from:, to:)
|
|
203
283
|
raise Malformed, "a translation names no domain" if domain.to_s.empty?
|
|
204
284
|
raise Malformed, "#{domain}'s translation says nothing about its origin era (from:)" if from.to_s.empty?
|
|
@@ -211,38 +291,64 @@ module Hecks
|
|
|
211
291
|
@retired = []
|
|
212
292
|
end
|
|
213
293
|
|
|
214
|
-
#
|
|
294
|
+
# Declares one aggregate's own translation rules.
|
|
295
|
+
#
|
|
296
|
+
# Answers the `aggregate` word through the table's `calls:`
|
|
297
|
+
# column — item #13's full metaprogrammed
|
|
215
298
|
# dispatch (slice 4c). Not bootstrap-reachable — this "Translation"
|
|
216
299
|
# -context `aggregate` (opens a TranslationAggregateBuilder) is a
|
|
217
300
|
# different (context, word) pair than "Bluebook"-context
|
|
218
301
|
# `aggregate` (the one translation.bluebook itself is described
|
|
219
|
-
# with), so it
|
|
302
|
+
# with), so it never describes the language's own
|
|
220
303
|
# translation chapter.
|
|
304
|
+
#
|
|
305
|
+
# @param name [String, Symbol] the aggregate's name in the destination era
|
|
306
|
+
# @param was [String, Symbol, nil] the aggregate's earlier name, when renamed
|
|
307
|
+
# @yield the aggregate's own translation body, evaluated against a
|
|
308
|
+
# `TranslationAggregateBuilder`
|
|
309
|
+
# @return [Array<Bluebook::TranslationAggregate>] every aggregate translation declared
|
|
310
|
+
# so far, this one last
|
|
311
|
+
# @raise [Bluebook::DSL::Malformed] if `name` is empty, or any rule in the body fails
|
|
312
|
+
# its own checks
|
|
221
313
|
def aggregate_impl(name, was: nil, &block)
|
|
222
314
|
builder = TranslationAggregateBuilder.new(name, was: was)
|
|
223
315
|
builder.instance_eval(&block) if block
|
|
224
316
|
@aggregates << builder.build
|
|
225
317
|
end
|
|
226
318
|
|
|
227
|
-
# `retired`
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
# `method_missing`/`respond_to_missing?`
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# retired aggregates" message.
|
|
239
|
-
|
|
319
|
+
# `retired` (an aggregate that is gone outright, not renamed — the
|
|
320
|
+
# deliberate alternative to a bogus `was:` claim on an unrelated
|
|
321
|
+
# aggregate, the same shape `TranslationAggregateBuilder#drop` is)
|
|
322
|
+
# is executed straight off the grammar table by `GenericDispatch` —
|
|
323
|
+
# item #13's full metaprogrammed dispatch, slice 2 (whole-project
|
|
324
|
+
# table-unification survey) — so no hand-written method answers it here.
|
|
325
|
+
|
|
326
|
+
# `method_missing`/`respond_to_missing?` answer off the self-hosted
|
|
327
|
+
# grammar table (via the `include`d `WordGate`, above, the same
|
|
328
|
+
# mechanism `TranslationAggregateBuilder`'s own comment describes
|
|
329
|
+
# one level up) instead of a hand-typed "it declares aggregate
|
|
330
|
+
# blocks and retired aggregates" message.
|
|
331
|
+
|
|
332
|
+
# Assembles the declared era pair, aggregates and retirements, judged by the translation
|
|
333
|
+
# language.
|
|
334
|
+
#
|
|
335
|
+
# @return [Bluebook::Translation] the translation, returned once the language accepts it
|
|
336
|
+
# @raise [Bluebook::DSL::Malformed] if the translation language refuses the declaration
|
|
240
337
|
def build
|
|
241
338
|
MetaValidator.call_translation(
|
|
242
339
|
Translation.new(domain: @domain, from: @from, to: @to, aggregates: @aggregates, retired: @retired)
|
|
243
340
|
)
|
|
244
341
|
end
|
|
245
342
|
|
|
343
|
+
# Evaluates a `.translation` file's top-level block against a fresh builder.
|
|
344
|
+
#
|
|
345
|
+
# @param domain [String, Symbol] the domain this translation carries forward
|
|
346
|
+
# @param from [String, Symbol] the origin era
|
|
347
|
+
# @param to [String, Symbol] the destination era
|
|
348
|
+
# @yield the translation body, evaluated with the builder as `self`; may be omitted
|
|
349
|
+
# @return [Bluebook::Translation] the judged translation
|
|
350
|
+
# @raise [Bluebook::DSL::Malformed] if the body fails any check, or the translation
|
|
351
|
+
# language refuses the declaration
|
|
246
352
|
def self.build(domain, from:, to:, &block)
|
|
247
353
|
builder = new(domain, from: from, to: to)
|
|
248
354
|
builder.instance_eval(&block) if block
|