textus 0.55.2 → 0.56.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/CHANGELOG.md +175 -2768
- data/README.md +8 -9
- data/lib/textus/artifact.rb +8 -0
- data/lib/textus/errors.rb +0 -39
- data/lib/textus/human.rb +7 -0
- data/lib/textus/infra/adapters/concurrency_adapter.rb +15 -0
- data/lib/textus/infra/clock.rb +7 -0
- data/lib/textus/infra/database.rb +127 -0
- data/lib/textus/infra/file_system.rb +54 -0
- data/lib/textus/infra/locks.rb +50 -0
- data/lib/textus/infra/port/clock.rb +18 -0
- data/lib/textus/infra/port/publisher.rb +71 -0
- data/lib/textus/infra/port/sentinel.rb +25 -0
- data/lib/textus/infra/port/sentinel_store.rb +93 -0
- data/lib/textus/infra/store/audit_log.rb +42 -0
- data/lib/textus/infra/store/base.rb +25 -0
- data/lib/textus/infra/store/cursor_store.rb +27 -0
- data/lib/textus/infra/store/entry_index.rb +31 -0
- data/lib/textus/infra/store/envelope/meta.rb +63 -0
- data/lib/textus/infra/store/file_store.rb +23 -0
- data/lib/textus/infra/store/index/builder.rb +28 -0
- data/lib/textus/infra/store/index/lookup.rb +23 -0
- data/lib/textus/infra/store/link_store.rb +43 -0
- data/lib/textus/infra/store/workflow_queue.rb +54 -0
- data/lib/textus/infra/store.rb +29 -0
- data/lib/textus/ingest.rb +8 -0
- data/lib/textus/lanes/artifact/cache.rb +35 -0
- data/lib/textus/lanes/artifact/handlers.rb +29 -0
- data/lib/textus/lanes/artifact/lifecycle.rb +23 -0
- data/lib/textus/lanes/artifact/verbs.rb +18 -0
- data/lib/textus/lanes/ingest/dedup.rb +17 -0
- data/lib/textus/lanes/ingest/entry_types/asset.rb +45 -0
- data/lib/textus/lanes/ingest/entry_types/link.rb +16 -0
- data/lib/textus/lanes/ingest/entry_types/text.rb +17 -0
- data/lib/textus/lanes/ingest/entry_types.rb +25 -0
- data/lib/textus/lanes/ingest/handlers.rb +54 -0
- data/lib/textus/lanes/ingest/index_rebuilder.rb +13 -0
- data/lib/textus/lanes/ingest/key_builder.rb +33 -0
- data/lib/textus/lanes/ingest/resolver.rb +21 -0
- data/lib/textus/lanes/knowledge/freeform.rb +15 -0
- data/lib/textus/lanes/knowledge/handlers.rb +53 -0
- data/lib/textus/lanes/knowledge/runner.rb +45 -0
- data/lib/textus/lanes/knowledge/verbs.rb +16 -0
- data/lib/textus/lanes/scratchpad/handlers.rb +164 -0
- data/lib/textus/lanes/scratchpad/verbs.rb +73 -0
- data/lib/textus/{dispatch → protocol}/binder.rb +1 -7
- data/lib/textus/protocol/boot/catalog.rb +34 -0
- data/lib/textus/protocol/boot/context.rb +124 -0
- data/lib/textus/protocol/boot/protocol.rb +33 -0
- data/lib/textus/protocol/boot.rb +42 -0
- data/lib/textus/protocol/builder/domain.rb +17 -0
- data/lib/textus/protocol/builder/infrastructure.rb +17 -0
- data/lib/textus/protocol/builder/runtime.rb +46 -0
- data/lib/textus/protocol/builder.rb +11 -0
- data/lib/textus/protocol/container.rb +22 -0
- data/lib/textus/protocol/diff.rb +170 -0
- data/lib/textus/protocol/entry_constraint.rb +54 -0
- data/lib/textus/protocol/envelope.rb +70 -0
- data/lib/textus/protocol/format/base.rb +80 -0
- data/lib/textus/protocol/format/bash.rb +10 -0
- data/lib/textus/protocol/format/javascript.rb +10 -0
- data/lib/textus/protocol/format/json.rb +82 -0
- data/lib/textus/protocol/format/markdown.rb +53 -0
- data/lib/textus/protocol/format/python.rb +10 -0
- data/lib/textus/protocol/format/ruby.rb +10 -0
- data/lib/textus/protocol/format/script.rb +55 -0
- data/lib/textus/protocol/format/text.rb +48 -0
- data/lib/textus/protocol/format/yaml.rb +105 -0
- data/lib/textus/protocol/format.rb +73 -0
- data/lib/textus/{store → protocol}/freshness/ttl_evaluator.rb +7 -3
- data/lib/textus/protocol/gate.rb +61 -0
- data/lib/textus/protocol/handlers/command_context.rb +19 -0
- data/lib/textus/protocol/handlers/concern.rb +15 -0
- data/lib/textus/protocol/handlers/ops/audit_filter.rb +37 -0
- data/lib/textus/protocol/handlers/ops/bulk_planner.rb +38 -0
- data/lib/textus/protocol/handlers/ops/data_mv.rb +49 -0
- data/lib/textus/protocol/handlers/ops/git_blame.rb +65 -0
- data/lib/textus/protocol/handlers/ops/pulse_coordinator.rb +37 -0
- data/lib/textus/protocol/handlers/ops/source_expander.rb +41 -0
- data/lib/textus/protocol/handlers/query_context.rb +41 -0
- data/lib/textus/protocol/handlers/read.rb +67 -0
- data/lib/textus/protocol/handlers/system.rb +93 -0
- data/lib/textus/protocol/handlers/write.rb +57 -0
- data/lib/textus/protocol/init.rb +143 -0
- data/lib/textus/protocol/key/grammar.rb +35 -0
- data/lib/textus/protocol/key/matching.rb +23 -0
- data/lib/textus/protocol/key/path.rb +23 -0
- data/lib/textus/protocol/key.rb +25 -0
- data/lib/textus/protocol/lane.rb +12 -0
- data/lib/textus/{store → protocol}/layout.rb +16 -22
- data/lib/textus/protocol/links/resolver.rb +33 -0
- data/lib/textus/protocol/links/uri_rewriter.rb +30 -0
- data/lib/textus/protocol/links.rb +10 -0
- data/lib/textus/protocol/manifest/data.rb +90 -0
- data/lib/textus/protocol/manifest/entry/base.rb +95 -0
- data/lib/textus/protocol/manifest/entry/leaf.rb +19 -0
- data/lib/textus/protocol/manifest/entry/nested.rb +19 -0
- data/lib/textus/protocol/manifest/entry/parser.rb +71 -0
- data/lib/textus/protocol/manifest/entry/publish.rb +209 -0
- data/lib/textus/protocol/manifest/entry.rb +10 -0
- data/lib/textus/protocol/manifest/policy/publish_target.rb +36 -0
- data/lib/textus/protocol/manifest/policy.rb +69 -0
- data/lib/textus/protocol/manifest/resolver.rb +156 -0
- data/lib/textus/protocol/manifest/schema/contract.rb +53 -0
- data/lib/textus/protocol/manifest/schema/validator.rb +52 -0
- data/lib/textus/protocol/manifest/schema.rb +40 -0
- data/lib/textus/protocol/manifest.rb +62 -0
- data/lib/textus/protocol/produce/render/context.rb +43 -0
- data/lib/textus/protocol/produce/render.rb +44 -0
- data/lib/textus/protocol/schema/registry.rb +44 -0
- data/lib/textus/protocol/schema/tools.rb +104 -0
- data/lib/textus/protocol/schema.rb +117 -0
- data/lib/textus/protocol/session/bridge.rb +11 -0
- data/lib/textus/protocol/session/context.rb +5 -0
- data/lib/textus/protocol/session.rb +87 -0
- data/lib/textus/protocol/store_engine/delete.rb +37 -0
- data/lib/textus/protocol/store_engine/move.rb +109 -0
- data/lib/textus/protocol/store_engine/pipeline.rb +61 -0
- data/lib/textus/protocol/store_engine/put.rb +104 -0
- data/lib/textus/protocol/store_engine.rb +86 -0
- data/lib/textus/protocol/v4.rb +25 -0
- data/lib/textus/protocol/verb_registry/arg_spec.rb +19 -0
- data/lib/textus/protocol/verb_registry/core_verbs.rb +164 -0
- data/lib/textus/protocol/verb_registry/verb_spec.rb +42 -0
- data/lib/textus/protocol/verb_registry.rb +42 -0
- data/lib/textus/surface/cli/group/{rule.rb → web.rb} +2 -2
- data/lib/textus/surface/cli/runner.rb +22 -21
- data/lib/textus/surface/cli/sources.rb +1 -3
- data/lib/textus/surface/cli/verb/get.rb +2 -7
- data/lib/textus/surface/cli/verb/init.rb +1 -1
- data/lib/textus/surface/cli/verb/put.rb +7 -7
- data/lib/textus/surface/cli/verb/web_serve.rb +22 -0
- data/lib/textus/surface/cli.rb +25 -6
- data/lib/textus/surface/dispatch.rb +17 -0
- data/lib/textus/surface/mcp/adapters/mcp_adapter.rb +28 -0
- data/lib/textus/surface/mcp/catalog.rb +12 -12
- data/lib/textus/surface/mcp/cursor_manager.rb +22 -0
- data/lib/textus/surface/mcp/drift_annotation.rb +19 -0
- data/lib/textus/surface/mcp/server.rb +35 -24
- data/lib/textus/surface/watcher.rb +18 -12
- data/lib/textus/surface/web.rb +50 -0
- data/lib/textus/value/call.rb +1 -1
- data/lib/textus/value/etag.rb +7 -7
- data/lib/textus/version.rb +1 -1
- data/lib/textus/workflow/consumer.rb +39 -0
- data/lib/textus/workflow/dsl/lifecycle.rb +32 -0
- data/lib/textus/workflow/dsl/pattern_matcher.rb +20 -0
- data/lib/textus/workflow/dsl/step_builder.rb +56 -0
- data/lib/textus/workflow/dsl.rb +111 -21
- data/lib/textus/workflow/event_emitter.rb +21 -0
- data/lib/textus/workflow/publisher.rb +59 -0
- data/lib/textus/workflow/registry.rb +31 -8
- data/lib/textus/workflow/runner.rb +25 -53
- data/lib/textus/workflow/scheduler.rb +45 -0
- data/lib/textus/workflow/step_executor.rb +78 -0
- data/lib/textus/workflow/step_helpers.rb +102 -0
- data/lib/textus/workflow.rb +2 -5
- data/lib/textus.rb +16 -16
- metadata +169 -185
- data/SPEC.md +0 -909
- data/docs/architecture/README.md +0 -273
- data/docs/reference/conventions.md +0 -141
- data/lib/textus/boot.rb +0 -181
- data/lib/textus/dispatch/contracts.rb +0 -63
- data/lib/textus/dispatch/handler_registry.rb +0 -21
- data/lib/textus/dispatch/middleware/audit_index.rb +0 -51
- data/lib/textus/dispatch/middleware/auth.rb +0 -40
- data/lib/textus/dispatch/middleware/base.rb +0 -26
- data/lib/textus/dispatch/middleware/binder.rb +0 -20
- data/lib/textus/dispatch/middleware/cascade.rb +0 -53
- data/lib/textus/dispatch/pipeline.rb +0 -35
- data/lib/textus/doctor/check/audit_log.rb +0 -34
- data/lib/textus/doctor/check/generator_drift.rb +0 -29
- data/lib/textus/doctor/check/illegal_keys.rb +0 -60
- data/lib/textus/doctor/check/manifest_files.rb +0 -25
- data/lib/textus/doctor/check/orphaned_publish_targets.rb +0 -35
- data/lib/textus/doctor/check/proposal_targets.rb +0 -45
- data/lib/textus/doctor/check/protocol_version.rb +0 -47
- data/lib/textus/doctor/check/publish_tree_index_overlap.rb +0 -48
- data/lib/textus/doctor/check/raw_asset_paths.rb +0 -50
- data/lib/textus/doctor/check/rule_ambiguity.rb +0 -49
- data/lib/textus/doctor/check/schema_parse_error.rb +0 -28
- data/lib/textus/doctor/check/schema_violations.rb +0 -28
- data/lib/textus/doctor/check/schemas.rb +0 -26
- data/lib/textus/doctor/check/scratchpad_sources.rb +0 -55
- data/lib/textus/doctor/check/sentinels.rb +0 -60
- data/lib/textus/doctor/check/stale_reviewed_stamp.rb +0 -54
- data/lib/textus/doctor/check/templates.rb +0 -29
- data/lib/textus/doctor/check/unowned_schema_fields.rb +0 -40
- data/lib/textus/doctor/check.rb +0 -43
- data/lib/textus/doctor/validator.rb +0 -93
- data/lib/textus/doctor.rb +0 -54
- data/lib/textus/format/base.rb +0 -77
- data/lib/textus/format/json.rb +0 -81
- data/lib/textus/format/markdown.rb +0 -59
- data/lib/textus/format/text.rb +0 -55
- data/lib/textus/format/yaml.rb +0 -104
- data/lib/textus/format.rb +0 -61
- data/lib/textus/handlers/maintenance/boot_store.rb +0 -15
- data/lib/textus/handlers/maintenance/doctor_store.rb +0 -15
- data/lib/textus/handlers/maintenance/drain_store.rb +0 -21
- data/lib/textus/handlers/maintenance/ingest_entry.rb +0 -159
- data/lib/textus/handlers/maintenance/jobs_action.rb +0 -21
- data/lib/textus/handlers/maintenance/published_entries.rb +0 -17
- data/lib/textus/handlers/maintenance/rule_explain.rb +0 -77
- data/lib/textus/handlers/maintenance/rule_lint.rb +0 -54
- data/lib/textus/handlers/maintenance/rule_list.rb +0 -32
- data/lib/textus/handlers/maintenance/schema_envelope.rb +0 -19
- data/lib/textus/handlers/read/audit_entries.rb +0 -48
- data/lib/textus/handlers/read/blame_entry.rb +0 -71
- data/lib/textus/handlers/read/deps_entry.rb +0 -17
- data/lib/textus/handlers/read/get_entry.rb +0 -68
- data/lib/textus/handlers/read/list_keys.rb +0 -36
- data/lib/textus/handlers/read/pulse_entries.rb +0 -66
- data/lib/textus/handlers/read/rdeps_entry.rb +0 -21
- data/lib/textus/handlers/read/uid_entry.rb +0 -18
- data/lib/textus/handlers/read/where_entry.rb +0 -18
- data/lib/textus/handlers/write/accept_proposal.rb +0 -39
- data/lib/textus/handlers/write/data_mv.rb +0 -55
- data/lib/textus/handlers/write/delete_key.rb +0 -17
- data/lib/textus/handlers/write/enqueue_job.rb +0 -27
- data/lib/textus/handlers/write/key_delete_prefix.rb +0 -32
- data/lib/textus/handlers/write/key_mv_prefix.rb +0 -45
- data/lib/textus/handlers/write/move_key.rb +0 -80
- data/lib/textus/handlers/write/propose_entry.rb +0 -29
- data/lib/textus/handlers/write/put_entry.rb +0 -29
- data/lib/textus/handlers/write/reject_proposal.rb +0 -29
- data/lib/textus/init.rb +0 -157
- data/lib/textus/jobs.rb +0 -9
- data/lib/textus/key/distance.rb +0 -55
- data/lib/textus/key/grammar.rb +0 -33
- data/lib/textus/key/matching.rb +0 -24
- data/lib/textus/key/path.rb +0 -28
- data/lib/textus/manifest/capabilities.rb +0 -29
- data/lib/textus/manifest/data.rb +0 -102
- data/lib/textus/manifest/entry/base.rb +0 -105
- data/lib/textus/manifest/entry/ignore_matcher.rb +0 -46
- data/lib/textus/manifest/entry/leaf.rb +0 -17
- data/lib/textus/manifest/entry/nested.rb +0 -37
- data/lib/textus/manifest/entry/parser.rb +0 -99
- data/lib/textus/manifest/entry/produced.rb +0 -36
- data/lib/textus/manifest/entry/publish/mode.rb +0 -45
- data/lib/textus/manifest/entry/publish/none.rb +0 -14
- data/lib/textus/manifest/entry/publish/subtree_mirror.rb +0 -73
- data/lib/textus/manifest/entry/publish/template.rb +0 -16
- data/lib/textus/manifest/entry/publish/to_paths.rb +0 -76
- data/lib/textus/manifest/entry/publish/tree.rb +0 -50
- data/lib/textus/manifest/entry/publish.rb +0 -45
- data/lib/textus/manifest/entry/validators/format_matrix.rb +0 -21
- data/lib/textus/manifest/entry/validators/ignore.rb +0 -28
- data/lib/textus/manifest/entry/validators/publish.rb +0 -30
- data/lib/textus/manifest/entry/validators.rb +0 -18
- data/lib/textus/manifest/entry.rb +0 -8
- data/lib/textus/manifest/policy/matcher.rb +0 -51
- data/lib/textus/manifest/policy/predicates/author_held.rb +0 -22
- data/lib/textus/manifest/policy/predicates/etag_match.rb +0 -18
- data/lib/textus/manifest/policy/predicates/fresh_within.rb +0 -13
- data/lib/textus/manifest/policy/predicates/lane_deletable_by.rb +0 -31
- data/lib/textus/manifest/policy/predicates/lane_writable_by.rb +0 -23
- data/lib/textus/manifest/policy/predicates/raw_lane_ingest_only.rb +0 -25
- data/lib/textus/manifest/policy/predicates/raw_write_once.rb +0 -24
- data/lib/textus/manifest/policy/predicates/schema_valid.rb +0 -41
- data/lib/textus/manifest/policy/predicates/target_is_canon.rb +0 -20
- data/lib/textus/manifest/policy/predicates.rb +0 -54
- data/lib/textus/manifest/policy/publish_target.rb +0 -34
- data/lib/textus/manifest/policy/react.rb +0 -30
- data/lib/textus/manifest/policy/retention.rb +0 -29
- data/lib/textus/manifest/policy/source.rb +0 -30
- data/lib/textus/manifest/policy.rb +0 -95
- data/lib/textus/manifest/resolver.rb +0 -118
- data/lib/textus/manifest/rules.rb +0 -86
- data/lib/textus/manifest/schema/contract.rb +0 -61
- data/lib/textus/manifest/schema/keys.rb +0 -90
- data/lib/textus/manifest/schema/semantics/cross_field.rb +0 -53
- data/lib/textus/manifest/schema/semantics/invariants.rb +0 -125
- data/lib/textus/manifest/schema/semantics/migration.rb +0 -83
- data/lib/textus/manifest/schema/semantics.rb +0 -27
- data/lib/textus/manifest/schema/validator.rb +0 -48
- data/lib/textus/manifest/schema/vocabulary.rb +0 -25
- data/lib/textus/manifest/schema.rb +0 -30
- data/lib/textus/manifest.rb +0 -65
- data/lib/textus/orchestration.rb +0 -55
- data/lib/textus/port/audit_log.rb +0 -268
- data/lib/textus/port/build_lock.rb +0 -64
- data/lib/textus/port/clock.rb +0 -10
- data/lib/textus/port/publisher.rb +0 -60
- data/lib/textus/port/sentinel.rb +0 -25
- data/lib/textus/port/sentinel_store.rb +0 -83
- data/lib/textus/port/storage/file_stat.rb +0 -19
- data/lib/textus/port/storage/file_store.rb +0 -49
- data/lib/textus/port/storage/interface.rb +0 -17
- data/lib/textus/port/store.rb +0 -149
- data/lib/textus/port/watcher_lock.rb +0 -48
- data/lib/textus/produce/engine.rb +0 -40
- data/lib/textus/produce/publisher.rb +0 -21
- data/lib/textus/produce/render.rb +0 -20
- data/lib/textus/schema/registry.rb +0 -42
- data/lib/textus/schema/tools.rb +0 -108
- data/lib/textus/schema.rb +0 -100
- data/lib/textus/store/container.rb +0 -173
- data/lib/textus/store/cursor.rb +0 -26
- data/lib/textus/store/entry/reader.rb +0 -47
- data/lib/textus/store/entry/writer.rb +0 -219
- data/lib/textus/store/envelope/meta.rb +0 -61
- data/lib/textus/store/freshness/drift_detector.rb +0 -93
- data/lib/textus/store/freshness/evaluator.rb +0 -20
- data/lib/textus/store/freshness/verdict.rb +0 -24
- data/lib/textus/store/freshness.rb +0 -8
- data/lib/textus/store/index/builder.rb +0 -76
- data/lib/textus/store/index/lookup.rb +0 -60
- data/lib/textus/store/jobs/base.rb +0 -13
- data/lib/textus/store/jobs/index.rb +0 -15
- data/lib/textus/store/jobs/materialize.rb +0 -15
- data/lib/textus/store/jobs/plan.rb +0 -11
- data/lib/textus/store/jobs/planner.rb +0 -124
- data/lib/textus/store/jobs/queue.rb +0 -162
- data/lib/textus/store/jobs/registry.rb +0 -19
- data/lib/textus/store/jobs/retention/base.rb +0 -52
- data/lib/textus/store/jobs/retention/sweep.rb +0 -55
- data/lib/textus/store/jobs/retention.rb +0 -8
- data/lib/textus/store/jobs/sweep.rb +0 -21
- data/lib/textus/store/jobs/worker.rb +0 -64
- data/lib/textus/store.rb +0 -122
- data/lib/textus/surface/cli/verb/doctor.rb +0 -20
- data/lib/textus/surface/cli/verb/schema_diff.rb +0 -17
- data/lib/textus/surface/cli/verb/schema_init.rb +0 -21
- data/lib/textus/surface/cli/verb/schema_migrate.rb +0 -21
- data/lib/textus/surface/mcp/projector.rb +0 -20
- data/lib/textus/surface/mcp.rb +0 -8
- data/lib/textus/value/command.rb +0 -16
- data/lib/textus/value/envelope.rb +0 -90
- data/lib/textus/value/result.rb +0 -46
- data/lib/textus/verb_registry.rb +0 -417
- data/lib/textus/workflow/collector.rb +0 -27
- data/lib/textus/workflow/context.rb +0 -5
- data/lib/textus/workflow/loader.rb +0 -17
- data/lib/textus/workflow/pattern.rb +0 -18
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "time"
|
|
4
|
-
|
|
5
|
-
module Textus
|
|
6
|
-
class Store
|
|
7
|
-
module Freshness
|
|
8
|
-
class DriftDetector
|
|
9
|
-
def initialize(manifest:, file_stat:, clock:)
|
|
10
|
-
@manifest = manifest
|
|
11
|
-
@file_stat = file_stat
|
|
12
|
-
@clock = clock
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def drift_rows(mentry)
|
|
16
|
-
return [] unless mentry.external?
|
|
17
|
-
|
|
18
|
-
path = Textus::Key::Path.resolve(@manifest.data, mentry)
|
|
19
|
-
reason = drift_reason(mentry, path)
|
|
20
|
-
reason ? [drift_row(mentry, path, reason)] : []
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def drift_reason(mentry, path)
|
|
26
|
-
return "derived entry has never been generated" unless @file_stat.exists?(path)
|
|
27
|
-
|
|
28
|
-
generated_at = generated_at_of(mentry, path)
|
|
29
|
-
return "missing generated.at frontmatter" unless generated_at
|
|
30
|
-
|
|
31
|
-
gen_time = parse_time(generated_at)
|
|
32
|
-
return "unparseable generated.at: #{generated_at.inspect}" unless gen_time
|
|
33
|
-
|
|
34
|
-
offender = newest_source_after(mentry.source, gen_time)
|
|
35
|
-
"source '#{offender}' modified after generated.at" if offender
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def generated_at_of(mentry, path)
|
|
39
|
-
Textus::Format.for(mentry.format).parse(@file_stat.read(path), path: path)["_meta"]
|
|
40
|
-
.dig("generated", "at")
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def parse_time(str)
|
|
44
|
-
Time.parse(str.to_s)
|
|
45
|
-
rescue StandardError
|
|
46
|
-
nil
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def newest_source_after(external_src, gen_time)
|
|
50
|
-
Array(external_src.sources).each do |src|
|
|
51
|
-
offender = check_source(src, gen_time)
|
|
52
|
-
return offender if offender
|
|
53
|
-
end
|
|
54
|
-
nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def check_source(src, gen_time)
|
|
58
|
-
if src.match?(/\A[a-z0-9.][a-z0-9._-]*\z/) && !src.include?("/")
|
|
59
|
-
@manifest.resolver.enumerate(prefix: src).each do |row|
|
|
60
|
-
return src if @file_stat.mtime(row[:path]) > gen_time
|
|
61
|
-
end
|
|
62
|
-
nil
|
|
63
|
-
else
|
|
64
|
-
check_filesystem_source(src, gen_time)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def check_filesystem_source(src, gen_time)
|
|
69
|
-
abs = absolutize_source(src)
|
|
70
|
-
if @file_stat.directory?(abs)
|
|
71
|
-
dir_has_newer_file?(abs, gen_time) ? src : nil
|
|
72
|
-
elsif @file_stat.exists?(abs) && @file_stat.mtime(abs) > gen_time
|
|
73
|
-
src
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def absolutize_source(src)
|
|
78
|
-
File.absolute_path?(src) ? src : File.join(File.dirname(@manifest.data.root), src)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def dir_has_newer_file?(abs, gen_time)
|
|
82
|
-
@file_stat.glob(File.join(abs, "**", "*")).any? do |fpath|
|
|
83
|
-
!@file_stat.directory?(fpath) && @file_stat.exists?(fpath) && @file_stat.mtime(fpath) > gen_time
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def drift_row(mentry, path, reason)
|
|
88
|
-
{ "key" => mentry.key, "path" => path, "generator" => mentry.source.command, "reason" => reason }
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
class Store
|
|
5
|
-
module Freshness
|
|
6
|
-
# Thin facade delegating to the focused TtlEvaluator and DriftDetector.
|
|
7
|
-
# Prefer using TtlEvaluator or DriftDetector directly.
|
|
8
|
-
class Evaluator
|
|
9
|
-
def initialize(manifest:, file_stat:, clock:)
|
|
10
|
-
@ttl = TtlEvaluator.new(manifest: manifest, file_stat: file_stat, clock: clock)
|
|
11
|
-
@drift = DriftDetector.new(manifest: manifest, file_stat: file_stat, clock: clock)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def verdict(mentry) = @ttl.verdict(mentry)
|
|
15
|
-
def stale_keys(**) = @ttl.stale_keys(**)
|
|
16
|
-
def drift_rows(mentry) = @drift.drift_rows(mentry)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Store
|
|
3
|
-
module Freshness
|
|
4
|
-
Verdict = Data.define(
|
|
5
|
-
:stale, :fetching, :reason, :fetch_error, :checked_at, :ttl_remaining_ms
|
|
6
|
-
) do
|
|
7
|
-
def self.build(stale:, fetching: false, reason: nil, fetch_error: nil,
|
|
8
|
-
checked_at: nil, ttl_remaining_ms: nil)
|
|
9
|
-
new(
|
|
10
|
-
stale: stale, fetching: fetching, reason: reason,
|
|
11
|
-
fetch_error: fetch_error, checked_at: checked_at,
|
|
12
|
-
ttl_remaining_ms: ttl_remaining_ms
|
|
13
|
-
)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def to_h_for_wire
|
|
17
|
-
h = { "stale" => stale, "stale_reason" => reason, "fetching" => fetching }
|
|
18
|
-
h["fetch_error"] = fetch_error unless fetch_error.nil?
|
|
19
|
-
h
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require "time"
|
|
5
|
-
|
|
6
|
-
module Textus
|
|
7
|
-
class Store
|
|
8
|
-
module Index
|
|
9
|
-
class Builder
|
|
10
|
-
def initialize(store:)
|
|
11
|
-
@store = store
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def rebuild!(resolver:)
|
|
15
|
-
rows = resolver.enumerate.filter_map { |row| build_row(row) }
|
|
16
|
-
now_iso = Time.now.utc.iso8601
|
|
17
|
-
|
|
18
|
-
@store.transaction do
|
|
19
|
-
@store.execute("DELETE FROM entries")
|
|
20
|
-
rows.each do |data|
|
|
21
|
-
@store.execute(
|
|
22
|
-
"INSERT INTO entries (key, lane, format, etag, content, extra, indexed_at, schema_ref)
|
|
23
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
24
|
-
[data[:key], data[:lane], data[:format], data[:etag],
|
|
25
|
-
data[:content], data[:extra], now_iso, data[:schema_ref]],
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
@store.execute("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')")
|
|
29
|
-
end
|
|
30
|
-
{ indexed: rows.size }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def build_row(row)
|
|
36
|
-
key = row.fetch(:key)
|
|
37
|
-
path = row.fetch(:path)
|
|
38
|
-
entry = row.fetch(:manifest_entry)
|
|
39
|
-
return nil unless path && File.file?(path)
|
|
40
|
-
|
|
41
|
-
raw = File.read(path)
|
|
42
|
-
parsed = Textus::Format.for(entry.format).parse(raw, path: path)
|
|
43
|
-
{
|
|
44
|
-
key: key,
|
|
45
|
-
lane: entry.lane,
|
|
46
|
-
format: entry.format.to_s,
|
|
47
|
-
etag: Textus::Value::Etag.for_bytes(raw),
|
|
48
|
-
content: content_text(parsed),
|
|
49
|
-
extra: extra_json(parsed),
|
|
50
|
-
schema_ref: entry.schema,
|
|
51
|
-
}
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def content_text(parsed)
|
|
55
|
-
content = parsed["content"]
|
|
56
|
-
body = parsed["body"]
|
|
57
|
-
parts = []
|
|
58
|
-
parts << body if body
|
|
59
|
-
parts << JSON.dump(content) if content
|
|
60
|
-
parts.join("\n")
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def extra_json(parsed)
|
|
64
|
-
content = parsed["content"]
|
|
65
|
-
extra = {}
|
|
66
|
-
if content.is_a?(Hash)
|
|
67
|
-
extra["content_hash"] = content["content_hash"] if content["content_hash"]
|
|
68
|
-
url = content.dig("source", "url")
|
|
69
|
-
extra["url"] = url if url
|
|
70
|
-
end
|
|
71
|
-
JSON.dump(extra)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Textus
|
|
6
|
-
class Store
|
|
7
|
-
module Index
|
|
8
|
-
class Lookup
|
|
9
|
-
def initialize(store:)
|
|
10
|
-
@store = store
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def search(query, lane: nil)
|
|
14
|
-
return [] if query.to_s.strip.empty?
|
|
15
|
-
|
|
16
|
-
clauses = ["entries_fts MATCH ?"]
|
|
17
|
-
params = [query]
|
|
18
|
-
if lane
|
|
19
|
-
clauses << "entries.lane = ?"
|
|
20
|
-
params << lane
|
|
21
|
-
end
|
|
22
|
-
conditions = "WHERE #{clauses.join(" AND ")}"
|
|
23
|
-
@store.execute(
|
|
24
|
-
"SELECT entries.key, entries.lane, entries.format, entries.etag, bm25(entries_fts) AS rank
|
|
25
|
-
FROM entries_fts JOIN entries ON entries_fts.rowid = entries.rowid
|
|
26
|
-
#{conditions}
|
|
27
|
-
ORDER BY rank",
|
|
28
|
-
params,
|
|
29
|
-
)
|
|
30
|
-
rescue SQLite3::SQLException
|
|
31
|
-
[]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def find_by_hash(content_hash)
|
|
35
|
-
return nil if content_hash.to_s.empty?
|
|
36
|
-
|
|
37
|
-
find_extra("content_hash", content_hash)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def find_by_url(url)
|
|
41
|
-
return nil if url.to_s.empty?
|
|
42
|
-
|
|
43
|
-
find_extra("url", url)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def find_extra(name, value)
|
|
49
|
-
@store.execute("SELECT key, extra FROM entries ORDER BY indexed_at DESC").each do |row|
|
|
50
|
-
extra = JSON.parse(row["extra"] || "{}")
|
|
51
|
-
return row["key"] if extra[name] == value
|
|
52
|
-
end
|
|
53
|
-
nil
|
|
54
|
-
rescue SQLite3::SQLException
|
|
55
|
-
nil
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
class Store
|
|
5
|
-
module Jobs
|
|
6
|
-
class Index < Base
|
|
7
|
-
TYPE = "index"
|
|
8
|
-
|
|
9
|
-
def self.call(container:, call:) # rubocop:disable Lint/UnusedMethodArgument
|
|
10
|
-
Textus::Store::Index::Builder.new(store: container.job_store).rebuild!(resolver: container.manifest.resolver)
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
class Store
|
|
5
|
-
module Jobs
|
|
6
|
-
class Materialize < Base
|
|
7
|
-
TYPE = "materialize"
|
|
8
|
-
|
|
9
|
-
def self.call(container:, call:, key:)
|
|
10
|
-
Textus::Produce::Engine.converge(container: container, call: call, keys: [key])
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
class Store
|
|
5
|
-
module Jobs
|
|
6
|
-
class Planner
|
|
7
|
-
ACTIONS_BY_TRIGGER = {
|
|
8
|
-
"convergence" => %w[materialize sweep index],
|
|
9
|
-
"entry.written" => %w[materialize],
|
|
10
|
-
"entry.deleted" => %w[materialize],
|
|
11
|
-
"entry.moved" => %w[materialize],
|
|
12
|
-
"proposal.accepted" => %w[materialize],
|
|
13
|
-
"proposal.rejected" => %w[materialize],
|
|
14
|
-
}.freeze
|
|
15
|
-
|
|
16
|
-
ENTRY_LEVEL_TRIGGERS = %w[
|
|
17
|
-
entry.written entry.deleted entry.moved
|
|
18
|
-
proposal.accepted proposal.rejected
|
|
19
|
-
].freeze
|
|
20
|
-
|
|
21
|
-
SCOPE_RESOLVERS = {
|
|
22
|
-
"materialize" => :producible_keys,
|
|
23
|
-
"sweep" => :lane_keys,
|
|
24
|
-
}.freeze
|
|
25
|
-
|
|
26
|
-
GLOBAL_ACTIONS = {
|
|
27
|
-
"index" => {},
|
|
28
|
-
"sweep" => { "scope" => {} },
|
|
29
|
-
}.freeze
|
|
30
|
-
|
|
31
|
-
def self.seed(container:, queue:, role:)
|
|
32
|
-
jobs = new(container: container).plan(
|
|
33
|
-
trigger: { "type" => "convergence" },
|
|
34
|
-
role: role,
|
|
35
|
-
)
|
|
36
|
-
jobs.each { |j| queue.enqueue(j) }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def initialize(container:)
|
|
40
|
-
@container = container
|
|
41
|
-
@manifest = container.manifest
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def plan(trigger:, role:)
|
|
45
|
-
type = trigger["type"] || trigger[:type]
|
|
46
|
-
target = trigger["target"] || trigger[:target]
|
|
47
|
-
return [] if type.nil?
|
|
48
|
-
|
|
49
|
-
blocks_with_react = @manifest.rules.blocks.select(&:react)
|
|
50
|
-
if blocks_with_react.any?
|
|
51
|
-
plan_from_rules(blocks_with_react, type, role, target: target)
|
|
52
|
-
else
|
|
53
|
-
plan_from_defaults(type, role, target: target)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def plan_from_rules(blocks, type, role, target: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
60
|
-
jobs = []
|
|
61
|
-
blocks
|
|
62
|
-
.select { |b| matches_trigger?(b.react, type) }
|
|
63
|
-
.each do |block|
|
|
64
|
-
do_action = block.react.raw["do"]
|
|
65
|
-
Array(do_action).each do |action|
|
|
66
|
-
if (global_args = GLOBAL_ACTIONS[action])
|
|
67
|
-
jobs << Textus::Store::Jobs::Queue::Job.new(type: action, args: global_args, role: role)
|
|
68
|
-
else
|
|
69
|
-
resolver = SCOPE_RESOLVERS.fetch(action, :producible_keys)
|
|
70
|
-
keys = send(resolver, nil)
|
|
71
|
-
keys.each { |key| jobs << job(action, key, role) }
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
jobs
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def plan_from_defaults(type, role, target: nil)
|
|
79
|
-
actions = ACTIONS_BY_TRIGGER.fetch(type, [])
|
|
80
|
-
jobs = []
|
|
81
|
-
if actions.include?("materialize")
|
|
82
|
-
keys = target && ENTRY_LEVEL_TRIGGERS.include?(type) ? dependent_keys(target) : producible_keys(nil)
|
|
83
|
-
keys.each { |k| jobs << job("materialize", k, role) }
|
|
84
|
-
end
|
|
85
|
-
GLOBAL_ACTIONS.each do |action, args|
|
|
86
|
-
jobs << Textus::Store::Jobs::Queue::Job.new(type: action, args: args, role: role) if actions.include?(action)
|
|
87
|
-
end
|
|
88
|
-
jobs
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def matches_trigger?(react, type)
|
|
92
|
-
on = react.raw["on"]
|
|
93
|
-
Array(on).include?(type)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def job(type, key, role)
|
|
97
|
-
Textus::Store::Jobs::Queue::Job.new(type: type, args: { "key" => key }, role: role)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def producible_keys(_target)
|
|
101
|
-
@manifest.data.entries
|
|
102
|
-
.select { |e| !e.publish_tree.nil? || !e.publish_to.empty? }
|
|
103
|
-
.map(&:key)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def lane_keys(_target)
|
|
107
|
-
@manifest.data.entries.map(&:key)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def dependent_keys(target_key)
|
|
111
|
-
@manifest.data.entries
|
|
112
|
-
.select(&:external?)
|
|
113
|
-
.select do |e|
|
|
114
|
-
Array(e.source&.sources).compact.any? do |s|
|
|
115
|
-
target_key == s || target_key.start_with?("#{s}.")
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
.select { |e| !e.publish_tree.nil? || !e.publish_to.empty? }
|
|
119
|
-
.map(&:key)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "digest"
|
|
4
|
-
require "json"
|
|
5
|
-
require "securerandom"
|
|
6
|
-
require "time"
|
|
7
|
-
|
|
8
|
-
module Textus
|
|
9
|
-
class Store
|
|
10
|
-
module Jobs
|
|
11
|
-
class Queue
|
|
12
|
-
VALID_STATES = %w[ready leased done failed].freeze
|
|
13
|
-
|
|
14
|
-
Leased = Data.define(:job)
|
|
15
|
-
|
|
16
|
-
class Job
|
|
17
|
-
DIGEST_BYTES = 16
|
|
18
|
-
|
|
19
|
-
attr_reader :type, :args, :role, :attempts, :max_attempts, :errors
|
|
20
|
-
|
|
21
|
-
def initialize(type:, args:, role:, attempts: 0, max_attempts: 3, errors: [])
|
|
22
|
-
@type = type.to_s
|
|
23
|
-
@args = stringify(args)
|
|
24
|
-
@role = role.to_s
|
|
25
|
-
@attempts = attempts.to_i
|
|
26
|
-
@max_attempts = max_attempts.to_i
|
|
27
|
-
@errors = Array(errors)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def id
|
|
31
|
-
"#{type}:#{Digest::SHA256.hexdigest(JSON.dump(args.sort.to_h))[0, DIGEST_BYTES]}"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def stringify(hash)
|
|
37
|
-
hash.to_h.transform_keys(&:to_s)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def initialize(store:)
|
|
42
|
-
@store = store
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def enqueue(job)
|
|
46
|
-
now = iso_now
|
|
47
|
-
inserted = @store.execute(
|
|
48
|
-
"INSERT OR IGNORE INTO jobs (id, type, args, state, role, attempts, max_attempts, errors, lease, created_at, updated_at)
|
|
49
|
-
VALUES (?, ?, ?, 'ready', ?, ?, ?, ?, NULL, ?, ?)",
|
|
50
|
-
[job.id, job.type, JSON.dump(job.args), job.role, job.attempts, job.max_attempts, JSON.dump(job.errors), now, now],
|
|
51
|
-
)
|
|
52
|
-
return inserted if @store.query_value("SELECT changes()")&.to_i&.positive?
|
|
53
|
-
|
|
54
|
-
@store.execute(
|
|
55
|
-
"UPDATE jobs
|
|
56
|
-
SET state = 'ready', role = ?, args = ?, attempts = 0, errors = ?, lease = NULL, max_attempts = ?, updated_at = ?
|
|
57
|
-
WHERE id = ? AND state IN ('done', 'failed')",
|
|
58
|
-
[job.role, JSON.dump(job.args), JSON.dump([]), job.max_attempts, now, job.id],
|
|
59
|
-
)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def ready_ids
|
|
63
|
-
list(:ready)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def lease(worker_id:, lease_ttl:)
|
|
67
|
-
now = Time.now.utc
|
|
68
|
-
expires_at = now + lease_ttl
|
|
69
|
-
token = SecureRandom.hex(8)
|
|
70
|
-
marked_lease = JSON.dump({ "worker_id" => worker_id, "expires_at" => expires_at.iso8601, "token" => token })
|
|
71
|
-
|
|
72
|
-
@store.execute(
|
|
73
|
-
"UPDATE jobs
|
|
74
|
-
SET state = 'leased', lease = ?, updated_at = ?
|
|
75
|
-
WHERE id = (
|
|
76
|
-
SELECT id FROM jobs WHERE state = 'ready' ORDER BY created_at, id LIMIT 1
|
|
77
|
-
)",
|
|
78
|
-
[marked_lease, now.iso8601],
|
|
79
|
-
)
|
|
80
|
-
row = @store.execute("SELECT * FROM jobs WHERE state = 'leased' AND lease = ? LIMIT 1", [marked_lease]).first
|
|
81
|
-
return nil unless row
|
|
82
|
-
|
|
83
|
-
Leased.new(job_from_row(row))
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def ack(leased)
|
|
87
|
-
@store.execute(
|
|
88
|
-
"UPDATE jobs SET state = 'done', lease = NULL, updated_at = ? WHERE id = ? AND state = 'leased'",
|
|
89
|
-
[iso_now, leased.job.id],
|
|
90
|
-
)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def fail(leased, error:)
|
|
94
|
-
job = leased.job
|
|
95
|
-
attempts = job.attempts + 1
|
|
96
|
-
errors = job.errors + [{ "attempt" => attempts, "error" => error, "at" => iso_now }]
|
|
97
|
-
dead = attempts >= job.max_attempts
|
|
98
|
-
state = dead ? "failed" : "ready"
|
|
99
|
-
@store.execute(
|
|
100
|
-
"UPDATE jobs SET state = ?, attempts = ?, errors = ?, lease = NULL, updated_at = ? WHERE id = ?",
|
|
101
|
-
[state, attempts, JSON.dump(errors), iso_now, job.id],
|
|
102
|
-
)
|
|
103
|
-
dead ? :dead_lettered : :requeued
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def reclaim(now:)
|
|
107
|
-
rows = @store.execute("SELECT id, lease FROM jobs WHERE state = 'leased'")
|
|
108
|
-
expired = rows.select do |row|
|
|
109
|
-
lease = JSON.parse(row["lease"] || "{}")
|
|
110
|
-
expires_at = lease["expires_at"]
|
|
111
|
-
expires_at.nil? || Time.parse(expires_at) <= now
|
|
112
|
-
end
|
|
113
|
-
expired.each do |row|
|
|
114
|
-
@store.execute(
|
|
115
|
-
"UPDATE jobs SET state = 'ready', lease = NULL, updated_at = ? WHERE id = ?",
|
|
116
|
-
[now.utc.iso8601, row["id"]],
|
|
117
|
-
)
|
|
118
|
-
end
|
|
119
|
-
expired.size
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def list(state)
|
|
123
|
-
state = state.to_s
|
|
124
|
-
raise Textus::UsageError.new("unknown job state: #{state}") unless VALID_STATES.include?(state)
|
|
125
|
-
|
|
126
|
-
@store.execute("SELECT id FROM jobs WHERE state = ? ORDER BY created_at, id", [state]).map { |row| row["id"] }
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def retry_failed(job_id)
|
|
130
|
-
@store.execute(
|
|
131
|
-
"UPDATE jobs SET state = 'ready', attempts = 0, errors = ?, lease = NULL, updated_at = ? WHERE id = ? AND state = 'failed'",
|
|
132
|
-
[JSON.dump([]), iso_now, job_id],
|
|
133
|
-
)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def purge(state)
|
|
137
|
-
state = state.to_s
|
|
138
|
-
raise Textus::UsageError.new("unknown job state: #{state}") unless VALID_STATES.include?(state)
|
|
139
|
-
|
|
140
|
-
@store.execute("DELETE FROM jobs WHERE state = ?", [state])
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
private
|
|
144
|
-
|
|
145
|
-
def job_from_row(row)
|
|
146
|
-
Job.new(
|
|
147
|
-
type: row["type"],
|
|
148
|
-
args: JSON.parse(row["args"] || "{}"),
|
|
149
|
-
role: row["role"],
|
|
150
|
-
attempts: row["attempts"],
|
|
151
|
-
max_attempts: row["max_attempts"],
|
|
152
|
-
errors: JSON.parse(row["errors"] || "[]"),
|
|
153
|
-
)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def iso_now
|
|
157
|
-
Time.now.utc.iso8601
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Store
|
|
3
|
-
module Jobs
|
|
4
|
-
module Registry
|
|
5
|
-
class UnknownJob < KeyError; end
|
|
6
|
-
|
|
7
|
-
JOBS = {
|
|
8
|
-
"index" => Store::Jobs::Index,
|
|
9
|
-
"materialize" => Store::Jobs::Materialize,
|
|
10
|
-
"sweep" => Store::Jobs::Sweep,
|
|
11
|
-
}.freeze
|
|
12
|
-
|
|
13
|
-
def self.fetch(type)
|
|
14
|
-
JOBS.fetch(type.to_s) { raise UnknownJob.new("Unknown job type: #{type}") }
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|