hecks 1.0.2 → 1.2.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 +3 -3
- data/lib/hecks/adapters/driven/d1.rb +48 -28
- data/lib/hecks/adapters/driven/folder.rb +10 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +2 -4
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +3 -5
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +4 -1
- data/lib/hecks/adapters/driven/lambda.rb +2 -2
- data/lib/hecks/adapters/driven/local_storage.adapter +3 -0
- data/lib/hecks/adapters/driven/local_storage.rb +120 -0
- data/lib/hecks/adapters/driven/memory.rb +48 -3
- data/lib/hecks/adapters/driven/postgres/codec.rb +3 -1
- data/lib/hecks/adapters/driven/postgres/outbox.rb +100 -0
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +57 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +22 -2
- data/lib/hecks/adapters/driven/postgres.rb +45 -31
- data/lib/hecks/adapters/driven/prism.rb +11 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +45 -20
- data/lib/hecks/adapters/driven/sqlite/codec.rb +3 -1
- data/lib/hecks/adapters/driven/sqlite/projection.rb +22 -3
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +23 -1
- data/lib/hecks/adapters/driven/sqlite.rb +87 -8
- data/lib/hecks/adapters/driven.rb +4 -0
- data/lib/hecks/adapters.rb +4 -0
- data/lib/hecks/behaviors/dsl.rb +7 -0
- data/lib/hecks/behaviors/expectations.rb +32 -4
- data/lib/hecks/behaviors/rspec.rb +2 -0
- data/lib/hecks/behaviors/runner.rb +4 -0
- data/lib/hecks/bluebook/aggregate.rb +6 -5
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +4 -4
- data/lib/hecks/bluebook/assembly/build.rb +6 -3
- data/lib/hecks/bluebook/assembly/contract.rb +5 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +4 -0
- data/lib/hecks/bluebook/assembly/marks.rb +15 -4
- data/lib/hecks/bluebook/assembly.rb +1 -3
- data/lib/hecks/bluebook/attribute.rb +6 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +18 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +17 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +53 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +1 -1
- data/lib/hecks/bluebook/command.rb +6 -5
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +4 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +524 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +29 -477
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +14 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +5 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +848 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +25 -784
- data/lib/hecks/bluebook/dsl/command_builder.rb +53 -13
- data/lib/hecks/bluebook/dsl/const_shim.rb +6 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +20 -7
- data/lib/hecks/bluebook/dsl/entity_builder.rb +27 -6
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +12 -2
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +8 -1
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +24 -12
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +40 -1
- data/lib/hecks/bluebook/dsl/policy_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/port_builder.rb +6 -1
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +33 -12
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +56 -12
- data/lib/hecks/bluebook/dsl/query_builder.rb +7 -1
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +142 -19
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -1
- data/lib/hecks/bluebook/dsl/translation_builder.rb +13 -2
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +8 -1
- data/lib/hecks/bluebook/dsl/word_gate.rb +5 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +13 -3
- data/lib/hecks/bluebook/entity.rb +5 -3
- data/lib/hecks/bluebook/expression/ast_json.rb +88 -9
- data/lib/hecks/bluebook/expression/ast_reader.rb +107 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +7 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +64 -11
- data/lib/hecks/bluebook/expression/projection.json +12 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +4 -3
- data/lib/hecks/bluebook/expression/resolver.rb +83 -11
- data/lib/hecks/bluebook/expression.rb +1 -0
- data/lib/hecks/bluebook/hexagon.rb +10 -0
- data/lib/hecks/bluebook/lifecycle.rb +9 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +10 -4
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +7 -4
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +42 -3
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +5 -5
- data/lib/hecks/bluebook/model_check.rb +119 -82
- data/lib/hecks/bluebook/pattern_subset.rb +9 -0
- data/lib/hecks/bluebook/policy.rb +2 -1
- data/lib/hecks/bluebook/process_manager.rb +6 -0
- data/lib/hecks/bluebook/project_register.rb +45 -25
- data/lib/hecks/bluebook/query.rb +4 -0
- data/lib/hecks/bluebook/read_model.rb +2 -2
- data/lib/hecks/bluebook/smoke_test.rb +5 -0
- data/lib/hecks/bluebook/synthesizer.rb +2 -1
- data/lib/hecks/bluebook/translation.rb +3 -0
- data/lib/hecks/bluebook/value_object.rb +2 -2
- data/lib/hecks/codemod.rb +13 -0
- data/lib/hecks/doc/reference.rb +17 -11
- data/lib/hecks/facade/cli_runner.rb +2 -2
- data/lib/hecks/facade/command_request.rb +11 -11
- data/lib/hecks/facade/handle.rb +3 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +52 -38
- data/lib/hecks/facade.rb +4 -0
- data/lib/hecks/forms/app.rb +10 -6
- data/lib/hecks/forms/command_form_renderer.rb +3 -3
- data/lib/hecks/forms/field_renderer.rb +14 -9
- data/lib/hecks/forms/field_shape.rb +7 -2
- data/lib/hecks/forms/html.rb +5 -2
- data/lib/hecks/forms/index_renderer.rb +1 -1
- data/lib/hecks/forms/page.rb +7 -3
- data/lib/hecks/forms/query_form_renderer.rb +3 -2
- data/lib/hecks/forms/record_renderer.rb +22 -16
- data/lib/hecks/forms.rb +4 -0
- data/lib/hecks/fqn.rb +21 -9
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +30 -8
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -1
- data/lib/hecks/fuzzing/isolated_boot.rb +14 -6
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +264 -0
- data/lib/hecks/fuzzing/properties/guards.rb +251 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +351 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +99 -0
- data/lib/hecks/fuzzing/properties/querying.rb +206 -0
- data/lib/hecks/fuzzing/properties.rb +41 -1065
- data/lib/hecks/fuzzing/replay.rb +60 -16
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +6 -2
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -1
- data/lib/hecks/fuzzing/value_generator.rb +2 -2
- data/lib/hecks/grammar/evolve.rb +34 -10
- data/lib/hecks/grammar/expression_operators.json +124 -0
- data/lib/hecks/grammar.rb +15 -5
- data/lib/hecks/ir.rb +9 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +4 -2
- data/lib/hecks/language/bluebook/command.bluebook +2 -1
- data/lib/hecks/language/bluebook/entity.bluebook +2 -1
- data/lib/hecks/language/bluebook/projection.bluebook +2 -1
- data/lib/hecks/language/bluebook/query.bluebook +4 -2
- data/lib/hecks/language/bluebook/shape.bluebook +2 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -0
- data/lib/hecks/literal.rb +18 -8
- data/lib/hecks/naming.rb +6 -3
- data/lib/hecks/ports/access_control.rb +1 -1
- data/lib/hecks/ports/agent.rb +5 -3
- data/lib/hecks/ports/authentication.rb +3 -2
- data/lib/hecks/ports/authorization.rb +1 -1
- data/lib/hecks/ports/clock.rb +1 -1
- data/lib/hecks/ports/extraction.rb +5 -1
- data/lib/hecks/ports/identity_assignment.rb +1 -1
- data/lib/hecks/ports/identity_generation.rb +1 -1
- data/lib/hecks/ports/identity_resolution.rb +1 -1
- data/lib/hecks/ports/loading.rb +4 -1
- data/lib/hecks/ports/persistence/append_only.rb +51 -4
- data/lib/hecks/ports/persistence/binding_policy.rb +1 -1
- data/lib/hecks/ports/persistence/plugin.rb +3 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -1
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +14 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +4 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +80 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +23 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +43 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +25 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +49 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +10 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +49 -38
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +77 -18
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +19 -21
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +27 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +9 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +25 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +2 -6
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -1
- data/lib/hecks/ports/persistence/remote_runtime.rb +1 -1
- data/lib/hecks/ports/persistence.rb +7 -3
- data/lib/hecks/ports/projection.rb +13 -5
- data/lib/hecks/ports/query/in_memory.rb +8 -2
- data/lib/hecks/ports/query.rb +1 -3
- data/lib/hecks/ports.rb +2 -0
- data/lib/hecks/projections/diagrams.rb +11 -3
- data/lib/hecks/projections/glossary.rb +374 -0
- data/lib/hecks/projections/ir.rb +2 -0
- data/lib/hecks/projections/model/deviations.rb +2 -1
- data/lib/hecks/projections/model.rb +15 -10
- data/lib/hecks/projections/vocabulary.rb +1 -3
- data/lib/hecks/projections.rb +1 -0
- data/lib/hecks/projector/cli_projector.rb +78 -42
- data/lib/hecks/projector/docs_projector.rb +21 -12
- data/lib/hecks/projector/exporter.rb +34 -1
- data/lib/hecks/projector/narrate_projector.rb +83 -25
- data/lib/hecks/projector.rb +2 -2
- data/lib/hecks/query_ir.rb +60 -36
- data/lib/hecks/query_specification/common/authorization_spec.rb +1 -1
- data/lib/hecks/query_specification/common/comparators.rb +4 -0
- data/lib/hecks/query_specification/common/comparison.rb +8 -1
- data/lib/hecks/query_specification/common/dsl.rb +5 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -2
- data/lib/hecks/query_specification/common/null_policy.rb +6 -2
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -2
- data/lib/hecks/query_specification/common/options.rb +17 -5
- data/lib/hecks/query_specification/common/order_by.rb +9 -2
- data/lib/hecks/query_specification/common/where_clause.rb +15 -2
- data/lib/hecks/query_specification/field_path.rb +4 -4
- data/lib/hecks/query_specification/read_model/specification.rb +5 -2
- data/lib/hecks/router/namespace_installer.rb +22 -10
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +57 -22
- data/lib/hecks/runtime/command_interpreter.rb +104 -65
- data/lib/hecks/runtime/command_rules/admissibility.rb +26 -8
- data/lib/hecks/runtime/command_rules/arithmetic.rb +35 -14
- data/lib/hecks/runtime/dependency_planning.rb +33 -21
- data/lib/hecks/runtime/dispatcher.rb +41 -7
- data/lib/hecks/runtime/entity_element.rb +25 -6
- data/lib/hecks/runtime/entity_interpreter.rb +28 -15
- data/lib/hecks/runtime/errors.rb +28 -2
- data/lib/hecks/runtime/instance.rb +9 -3
- data/lib/hecks/runtime/interpreting.rb +45 -9
- data/lib/hecks/runtime/loader.rb +21 -0
- data/lib/hecks/runtime/outbox.rb +365 -0
- data/lib/hecks/runtime/policy_interpreter.rb +32 -17
- data/lib/hecks/runtime/query_interpreter.rb +36 -16
- data/lib/hecks/runtime/reaction_invocation.rb +35 -22
- data/lib/hecks/runtime/read_model_interpreter.rb +31 -7
- data/lib/hecks/runtime/reference_hop.rb +1 -1
- data/lib/hecks/runtime/refusal_wording.rb +55 -49
- data/lib/hecks/runtime/registry/saga_persistence.rb +3 -1
- data/lib/hecks/runtime/registry/verification.rb +39 -2
- data/lib/hecks/runtime/registry.rb +21 -11
- data/lib/hecks/runtime/remote_dispatcher.rb +3 -1
- data/lib/hecks/runtime/routing.rb +52 -39
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +7 -7
- data/lib/hecks/runtime/saga_interpreter.rb +119 -90
- data/lib/hecks/runtime/saga_pending_dispatch.rb +6 -3
- data/lib/hecks/runtime/tenant_scope.rb +7 -0
- data/lib/hecks/runtime/value/coercion.rb +271 -49
- data/lib/hecks/runtime/value.rb +1 -1
- data/lib/hecks/runtime.rb +7 -2
- data/lib/hecks/storehouse.rb +15 -8
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +1 -0
- data/lib/hecks.rb +17 -13
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +1 -1
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +1 -1
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
- metadata +16 -2
|
@@ -24,6 +24,26 @@ module Hecks
|
|
|
24
24
|
def layer_two!(violations, aggregate, declared, before, after)
|
|
25
25
|
rekeyed = declared && !declared.rekeys.empty?
|
|
26
26
|
|
|
27
|
+
check_id_conservation!(violations, aggregate, before, after, rekeyed: rekeyed)
|
|
28
|
+
|
|
29
|
+
return unless declared
|
|
30
|
+
# No correspondence between an old id and its rekeyed row exists
|
|
31
|
+
# in Ruby (the rekey's SQL is its only implementation, same as
|
|
32
|
+
# compute's) — exempt from the per-record equivalence gate for
|
|
33
|
+
# the same reason compute paths already are, extended to the
|
|
34
|
+
# whole record since an old-id lookup into `after` can never
|
|
35
|
+
# succeed once the id itself has changed.
|
|
36
|
+
return if rekeyed
|
|
37
|
+
|
|
38
|
+
check_value_preservation!(violations, aggregate, declared, before, after)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Record-count / id-set conservation — one of the two properties
|
|
42
|
+
# this module's own header names, and independent of the other
|
|
43
|
+
# (per-rule value preservation, below): it needs only `before`,
|
|
44
|
+
# `after`, and whether the edge rekeyed, never the declared rules
|
|
45
|
+
# themselves.
|
|
46
|
+
def check_id_conservation!(violations, aggregate, before, after, rekeyed:)
|
|
27
47
|
# A rekey legitimately changes the id SET (that's the entire
|
|
28
48
|
# point) — set-equality would flag every honest rekey as data
|
|
29
49
|
# loss. What must still hold is RECORD COUNT: a botched rekey
|
|
@@ -43,15 +63,13 @@ module Hecks
|
|
|
43
63
|
violations << "#{aggregate.name}: the id set changed across the edge " \
|
|
44
64
|
"(lost #{lost.sort.inspect}, gained #{gained.sort.inspect})"
|
|
45
65
|
end
|
|
46
|
-
|
|
47
|
-
# No correspondence between an old id and its rekeyed row exists
|
|
48
|
-
# in Ruby (the rekey's SQL is its only implementation, same as
|
|
49
|
-
# compute's) — exempt from the per-record equivalence gate for
|
|
50
|
-
# the same reason compute paths already are, extended to the
|
|
51
|
-
# whole record since an old-id lookup into `after` can never
|
|
52
|
-
# succeed once the id itself has changed.
|
|
53
|
-
return if rekeyed
|
|
66
|
+
end
|
|
54
67
|
|
|
68
|
+
# Per-rule value preservation — the other property this module's
|
|
69
|
+
# header names. Only reached once `layer_two!` has already ruled
|
|
70
|
+
# out "no declared edge" and "rekeyed" (see its own comment on
|
|
71
|
+
# that second guard); this method assumes both are false.
|
|
72
|
+
def check_value_preservation!(violations, aggregate, declared, before, after)
|
|
55
73
|
rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
|
|
56
74
|
compute_paths = declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
|
|
57
75
|
|
|
@@ -63,7 +81,7 @@ module Hecks
|
|
|
63
81
|
actual = strip_compute_paths(normalize(after[id]), compute_paths)
|
|
64
82
|
next if expected == actual
|
|
65
83
|
|
|
66
|
-
diverged = (expected.keys | actual.keys).
|
|
84
|
+
diverged = (expected.keys | actual.keys).reject { |key| expected[key] == actual[key] }
|
|
67
85
|
violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
|
|
68
86
|
"transform at #{diverged.sort.join(', ')}"
|
|
69
87
|
end
|
|
@@ -7,6 +7,15 @@ module Hecks
|
|
|
7
7
|
# attribute, and the loud refusal for a REQUIRED one comes from
|
|
8
8
|
# Layer 1 the moment an invariant reads it.
|
|
9
9
|
module UnfedReport
|
|
10
|
+
# `fed` build-up plus one ordered guard chain per attribute
|
|
11
|
+
# (already fed, has a default, actually present in some record, no
|
|
12
|
+
# records to check at all) answering a single question — "is
|
|
13
|
+
# anything, ever, going to feed this field" — where each `next`
|
|
14
|
+
# rules out one way the answer is "not unfed after all" before the
|
|
15
|
+
# next is even worth asking. Splitting the guards out would just
|
|
16
|
+
# turn each into a same-shaped one-line predicate call.
|
|
17
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
18
|
+
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
10
19
|
def unfed(aggregate, declared, after)
|
|
11
20
|
fed = if declared
|
|
12
21
|
(declared.renames.values.map(&:to_s) +
|
|
@@ -74,27 +74,43 @@ module Hecks
|
|
|
74
74
|
held_attrs = (held_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
|
|
75
75
|
current_attrs = (current_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
rules.concat(retype_rules(held_attrs, current_attrs))
|
|
78
|
+
retyped = rules.filter_map { |rule| rule[:from] if rule[:kind] == :retype }
|
|
79
|
+
|
|
80
|
+
vanished = vanished_paths(held_attrs, current_attrs, retyped)
|
|
81
|
+
appeared = appeared_paths(held_attrs, current_attrs, retyped)
|
|
82
|
+
|
|
83
|
+
# Mutates `rules` in place (not build-and-concat, like the two
|
|
84
|
+
# passes above) because it must READ its own earlier writes:
|
|
85
|
+
# `taken`, below, is recomputed from `rules` at the top of every
|
|
86
|
+
# iteration, so a target this same loop already claimed for an
|
|
87
|
+
# earlier vanished path is excluded from a later one. Passing a
|
|
88
|
+
# snapshot instead of the live array would let two vanished
|
|
89
|
+
# paths both match the same appeared target.
|
|
90
|
+
resolve_vanished_rules!(rules, vanished, appeared)
|
|
91
|
+
rules
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# retype pass: same attribute name, same member structure, the
|
|
95
|
+
# TYPE's own name changed
|
|
96
|
+
def retype_rules(held_attrs, current_attrs)
|
|
97
|
+
(held_attrs.keys & current_attrs.keys).filter_map do |name|
|
|
80
98
|
held = held_attrs[name]
|
|
81
99
|
current = current_attrs[name]
|
|
82
100
|
next if held == current
|
|
83
101
|
next unless container?(held) && container?(current)
|
|
84
102
|
next unless held["type"]["members"] == current["type"]["members"] && held["list"] == current["list"]
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
{ kind: :retype, from: held["type"]["type"], to: current["type"]["type"] }
|
|
87
105
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
vanished = vanished_paths(held_attrs, current_attrs, retyped)
|
|
91
|
-
appeared = appeared_paths(held_attrs, current_attrs, retyped)
|
|
106
|
+
end
|
|
92
107
|
|
|
108
|
+
def resolve_vanished_rules!(rules, vanished, appeared)
|
|
93
109
|
vanished.each do |path, signature|
|
|
94
110
|
matches = appeared.select { |_, candidate| candidate == signature }.keys
|
|
95
111
|
taken = rules.filter_map { |rule| rule[:to] if %i[rename move].include?(rule[:kind]) }
|
|
96
112
|
matches -= taken
|
|
97
|
-
if matches.size == 1 && vanished.
|
|
113
|
+
if matches.size == 1 && vanished.one? { |_, other| other == signature }
|
|
98
114
|
target = matches.first
|
|
99
115
|
kind = path.include?(".") || target.include?(".") ? :move : :rename
|
|
100
116
|
rules << { kind: kind, from: path, to: target }
|
|
@@ -103,7 +119,6 @@ module Hecks
|
|
|
103
119
|
rules << { kind: :unresolved, from: path, candidates: candidates }
|
|
104
120
|
end
|
|
105
121
|
end
|
|
106
|
-
rules
|
|
107
122
|
end
|
|
108
123
|
|
|
109
124
|
# Held paths that need explaining: whole attributes that vanished,
|
|
@@ -10,12 +10,8 @@ module Hecks
|
|
|
10
10
|
edge.aggregates.each do |aggregate|
|
|
11
11
|
header = " aggregate #{aggregate.name.inspect}"
|
|
12
12
|
header += ", was: #{aggregate.was.inspect}" if aggregate.was
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
else
|
|
16
|
-
lines << "#{header} do"
|
|
17
|
-
aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" }
|
|
18
|
-
end
|
|
13
|
+
lines << "#{header} do"
|
|
14
|
+
aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" } unless aggregate.rules.empty?
|
|
19
15
|
lines << " end"
|
|
20
16
|
end
|
|
21
17
|
edge.retired.each { |name| lines << " retired #{name.inspect}" }
|
|
@@ -35,7 +35,9 @@ module Hecks
|
|
|
35
35
|
# unchanged: registered only when this registry has an aggregate
|
|
36
36
|
# actually bound to a lineage-capable adapter.
|
|
37
37
|
def contribute_boot_gates(registry, gates)
|
|
38
|
-
gates.register(:era_compute_rules,
|
|
38
|
+
gates.register(:era_compute_rules, lambda { |reg, _dir|
|
|
39
|
+
Runtime::EraCheck.check_compute_rules_for_registry!(reg)
|
|
40
|
+
}, phase: :pre_verify)
|
|
39
41
|
gates.register(:era_check, Runtime::EraCheck.method(:check_lineage!), phase: :pre_verify) if
|
|
40
42
|
Runtime::EraCheck.lineage_capable_registry?(registry)
|
|
41
43
|
end
|
|
@@ -33,7 +33,7 @@ module Hecks
|
|
|
33
33
|
"#{self.class.name} is a remote-runtime delegate — dispatch through " \
|
|
34
34
|
"Runtime::RemoteDispatcher instead of writing through the repository directly"
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
alias project append
|
|
37
37
|
|
|
38
38
|
def entries = []
|
|
39
39
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Ports
|
|
3
3
|
module Persistence
|
|
4
|
-
NAME = "persistence"
|
|
5
|
-
VERB = "persisted_by"
|
|
6
|
-
DEFAULT_ADAPTER = "Memory"
|
|
4
|
+
NAME = "persistence".freeze
|
|
5
|
+
VERB = "persisted_by".freeze
|
|
6
|
+
DEFAULT_ADAPTER = "Memory".freeze
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -18,6 +18,10 @@ require_relative "persistence/null_saga_store"
|
|
|
18
18
|
|
|
19
19
|
module Hecks
|
|
20
20
|
module Ports
|
|
21
|
+
# Reopened once `BindingPolicy`/`RepositoryFactory` (required above) are
|
|
22
|
+
# loaded, to add the persistence port's actual call surface: resolving
|
|
23
|
+
# which adapter authoritatively binds an aggregate and building the
|
|
24
|
+
# repository that reads/writes it.
|
|
21
25
|
module Persistence
|
|
22
26
|
module_function
|
|
23
27
|
|
|
@@ -7,8 +7,8 @@ module Hecks
|
|
|
7
7
|
# Projection is a read-side port. Its stores are rebuildable consumers of
|
|
8
8
|
# authoritative entries and never participate in command decisions.
|
|
9
9
|
module Projection
|
|
10
|
-
NAME = "projection"
|
|
11
|
-
VERB = "projected_by"
|
|
10
|
+
NAME = "projection".freeze
|
|
11
|
+
VERB = "projected_by".freeze
|
|
12
12
|
|
|
13
13
|
module_function
|
|
14
14
|
|
|
@@ -26,6 +26,11 @@ module Hecks
|
|
|
26
26
|
Worker.new(authoritative, target, policy: policy)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Catches one projection's store up to its authoritative journal:
|
|
30
|
+
# replays whatever entries the store hasn't yet appended/projected.
|
|
31
|
+
# Constructed via Projection.worker; #catch_up! runs from a
|
|
32
|
+
# separate process or scheduler, never from the command-side write
|
|
33
|
+
# path.
|
|
29
34
|
class Worker
|
|
30
35
|
attr_reader :projection
|
|
31
36
|
|
|
@@ -67,7 +72,10 @@ module Hecks
|
|
|
67
72
|
unless consistent?(entries, present)
|
|
68
73
|
raise Runtime::WiringError, "projection history does not match its authoritative history" if @policy == :strict
|
|
69
74
|
end
|
|
70
|
-
entries.drop(present.length).each
|
|
75
|
+
entries.drop(present.length).each do |entry|
|
|
76
|
+
@projection.append(entry)
|
|
77
|
+
@projection.project(entry)
|
|
78
|
+
end
|
|
71
79
|
@projection
|
|
72
80
|
end
|
|
73
81
|
|
|
@@ -78,9 +86,9 @@ module Hecks
|
|
|
78
86
|
def same_entry?(left, right) = left.operation == right.operation && left.id == right.id && left.state == right.state
|
|
79
87
|
|
|
80
88
|
def consistent?(entries, present)
|
|
81
|
-
present.length <= entries.length && entries.first(present.length).zip(present).all?
|
|
89
|
+
present.length <= entries.length && entries.first(present.length).zip(present).all? do |left, right|
|
|
82
90
|
same_entry?(left, right)
|
|
83
|
-
|
|
91
|
+
end
|
|
84
92
|
end
|
|
85
93
|
end
|
|
86
94
|
|
|
@@ -6,6 +6,12 @@ require_relative "../../query_specification/common/comparison"
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Ports
|
|
8
8
|
module Query
|
|
9
|
+
# In-memory implementation of a declared query specification:
|
|
10
|
+
# applies wheres/order_by/offset/limit directly to an Array of
|
|
11
|
+
# records instead of compiling SQL. Deliberately kept in exact
|
|
12
|
+
# LIMIT/OFFSET-order agreement with SqlQueryBuilder (see #execute's
|
|
13
|
+
# own comment) so a query answers the same page regardless of
|
|
14
|
+
# which adapter backs it.
|
|
9
15
|
module InMemory
|
|
10
16
|
FieldPath = QuerySpecification::FieldPath
|
|
11
17
|
Comparison = QuerySpecification::Common::Comparison
|
|
@@ -14,9 +20,9 @@ module Hecks
|
|
|
14
20
|
|
|
15
21
|
def execute(records, declared, args = {}, registry: nil)
|
|
16
22
|
matched = records.select do |record|
|
|
17
|
-
declared.wheres.all?
|
|
23
|
+
declared.wheres.all? do |clause|
|
|
18
24
|
holds?(clause, comparable(FieldPath.dig(record, clause.field)), args, registry: registry)
|
|
19
|
-
|
|
25
|
+
end
|
|
20
26
|
end
|
|
21
27
|
field = declared.order_by&.field
|
|
22
28
|
matched = Ordering.apply(matched, declared.order_by, declared.null_semantics,
|
data/lib/hecks/ports/query.rb
CHANGED
|
@@ -19,9 +19,7 @@ module Hecks
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def validate!(specification, adapter)
|
|
22
|
-
if specification.cursor && specification.offset
|
|
23
|
-
raise Unsupported, "a query cannot combine cursor and offset pagination"
|
|
24
|
-
end
|
|
22
|
+
raise Unsupported, "a query cannot combine cursor and offset pagination" if specification.cursor && specification.offset
|
|
25
23
|
|
|
26
24
|
return if specification.inspection.nil? || adapter.respond_to?(:inspect_query)
|
|
27
25
|
return if specification.inspection.mode.to_s == "sql" && adapter.respond_to?(:query)
|
data/lib/hecks/ports.rb
CHANGED
|
@@ -448,9 +448,17 @@ module Hecks
|
|
|
448
448
|
# single source at all — its own field NAMES are the fact worth
|
|
449
449
|
# stating (real: `Order.AddTopping` appends `name, amount`).
|
|
450
450
|
def surface_diagram(bluebook, holder)
|
|
451
|
-
lines = holder.commands.map
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
lines = holder.commands.map do |command|
|
|
452
|
+
" #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}"
|
|
453
|
+
end
|
|
454
|
+
lines += holder.commands.flat_map do |command|
|
|
455
|
+
command.mutations.map do |mutation|
|
|
456
|
+
mutation_edge(holder, command, mutation)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
lines += holder.queries.map do |query|
|
|
460
|
+
" #{holder.hecks_name}[(#{holder.hecks_name})] -.->|asks| #{query_node(holder.hecks_name, query.hecks_name)}"
|
|
461
|
+
end
|
|
454
462
|
|
|
455
463
|
subject = "#{holder.hecks_name}'s own declared commands (and what each writes) and queries"
|
|
456
464
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|