foundries 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12575eccffc55ee4735cc1fe6cb981ed1109d252d45b6435ab4b8333862f2663
4
- data.tar.gz: 5ecb9d8707258542b76ec9e72caf4daa3619d72c0dff8f4778163a880302aa3b
3
+ metadata.gz: 26171ce16e8737361c8ac3dcfbed573f17ec11d274eade9a26b465b6a687bfd2
4
+ data.tar.gz: 384a4c0d4f68ed3bb706262a3cea621666549d96e825b53884797c56f906cf8d
5
5
  SHA512:
6
- metadata.gz: bb1eba53555fdf59b449b06cef7e973516341d2806ceed56d42d2e26a43e58be877bf6459cb551b71610acb6790a7f001a3761b5098b25245bb8d3c58be6805e
7
- data.tar.gz: ef7bd963c7c76e978a96ffed6562358a47cbfdb0da52e873f9613b4aecb3bcc357a4143282b7ff743ddada31e142fcdb8bd395035fa99291dbef4b890d06f419
6
+ metadata.gz: 53632c7bc3809bb486bad25597d4044d75c3b923a4657bca554d4ca57b58de2524bcd9d054fc86fdf1c3dae71920ab93c873cd72252b70e447a6b8af6b74da68
7
+ data.tar.gz: f73d427438035744750939c0a1f75491a3c930b1943bce3b97e007868a8e06ff0daeffaea0acd2e7937a322759991f074259a3d9a886d1371df8a6d78db93ce4
data/CHANGELOG.md CHANGED
@@ -5,21 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
- ## [0.1.6] - 2026-08-25
8
+ ## [0.1.7] - 2026-09-10
9
9
 
10
- ### Fixed
10
+ ### Added
11
11
 
12
- - Snapshot files are read and written in binary, so captured data with non-ASCII bytes round-trips (6a7d3a3)
13
- - Restoring a snapshot no longer rewinds a sequence below its current value (df66836)
12
+ - Foundries::Snapshot.with_lock for application-owned snapshot caches (fe91e06)
14
13
 
15
- ## [0.1.5] - 2026-08-12
14
+ ### Fixed
16
15
 
17
- ### Changed
16
+ - Serialize snapshot publication and restoration across workers (fe91e06)
17
+ - Reject incomplete snapshots before restoring rows (fe91e06)
18
+ - Clean staging directories after failed snapshot captures (fe91e06)
19
+ - Restore parent context when a nested blueprint block raises (7fd6e4b)
18
20
 
19
- - Development dependencies are locked (1129826)
21
+ ## [0.1.6] - 2026-08-25
20
22
 
21
23
  ### Fixed
22
24
 
23
- - Snapshot no longer caches a partial tree when a preset writes to a table that already held rows (2e9f863)
24
- - Lint under standard 1.56.0 (52be9eb)
25
- - Releases write a SHA512 checksum again (813a2ae)
25
+ - Snapshot files are read and written in binary, so captured data with non-ASCII bytes round-trips (6a7d3a3)
26
+ - Restoring a snapshot no longer rewinds a sequence below its current value (df66836)
@@ -187,7 +187,9 @@ module Foundries
187
187
 
188
188
  def execute_and_restore_state
189
189
  initial_state = @current.dup
190
- yield.tap { @current = initial_state }
190
+ yield
191
+ ensure
192
+ @current = initial_state
191
193
  end
192
194
 
193
195
  def load_state(object)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "tmpdir"
4
5
 
5
6
  module Foundries
6
7
  module Snapshot
@@ -18,9 +19,7 @@ module Foundries
18
19
  end
19
20
 
20
21
  def cached?
21
- cache_dir.exist? &&
22
- cache_dir.join(".fingerprint").exist? &&
23
- cache_dir.join(".fingerprint").read.strip == @fingerprint.current
22
+ with_lock { valid_cache? }
24
23
  end
25
24
 
26
25
  # Record which tables are empty before the preset block runs.
@@ -55,8 +54,8 @@ module Foundries
55
54
 
56
55
  tables = @capturable_tables || @adapter.table_names
57
56
 
58
- tmp_dir = Pathname.new("#{cache_dir}.#{$$}.tmp")
59
- tmp_dir.mkpath
57
+ FileUtils.mkdir_p(@storage_path)
58
+ tmp_dir = Pathname.new(Dir.mktmpdir("#{@preset_name}.", @storage_path))
60
59
 
61
60
  tables.each do |table|
62
61
  tmp_dir.join("#{table}.dat").open("wb") do |f|
@@ -64,29 +63,50 @@ module Foundries
64
63
  end
65
64
  end
66
65
 
66
+ tmp_dir.join(".tables").write(tables.join("\n"))
67
67
  tmp_dir.join(".fingerprint").write(@fingerprint.current)
68
68
 
69
- # Atomic swap
70
- FileUtils.rm_rf(cache_dir) if cache_dir.exist?
71
- FileUtils.mv(tmp_dir, cache_dir)
69
+ with_lock do
70
+ FileUtils.rm_rf(cache_dir)
71
+ FileUtils.mv(tmp_dir, cache_dir)
72
+ end
73
+ ensure
74
+ FileUtils.rm_rf(tmp_dir) if tmp_dir
72
75
  end
73
76
 
74
77
  def restore
75
- @adapter.disable_referential_integrity do
76
- cache_dir.glob("*.dat").each do |file|
77
- next unless file.size > 0
78
+ with_lock do
79
+ raise "Invalid or incomplete snapshot: #{@preset_name}" unless valid_cache?
80
+
81
+ @adapter.disable_referential_integrity do
82
+ captured_tables.each do |table|
83
+ file = cache_dir.join("#{table}.dat")
84
+ next unless file.size > 0
78
85
 
79
- table = file.basename(".dat").to_s
80
- file.open("rb") do |f|
81
- @adapter.restore(table, f)
86
+ file.open("rb") { |io| @adapter.restore(table, io) }
87
+ @adapter.reset_sequence(table)
82
88
  end
83
- @adapter.reset_sequence(table)
84
89
  end
85
90
  end
86
91
  end
87
92
 
88
93
  private
89
94
 
95
+ def with_lock(&block)
96
+ Snapshot.with_lock(@preset_name, storage_path: @storage_path, &block)
97
+ end
98
+
99
+ def captured_tables
100
+ cache_dir.join(".tables").read.lines.map(&:chomp)
101
+ end
102
+
103
+ def valid_cache?
104
+ stamp = cache_dir.join(".fingerprint")
105
+ stamp.file? && stamp.read.strip == @fingerprint.current &&
106
+ cache_dir.join(".tables").file? &&
107
+ captured_tables.all? { |table| cache_dir.join("#{table}.dat").file? }
108
+ end
109
+
90
110
  # Pre-populated tables whose row count moved while the preset ran. Their
91
111
  # new rows can't be told apart from the ones that were already there, so
92
112
  # the snapshot would silently omit them.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
+ require "fileutils"
4
5
  require_relative "snapshot/fingerprint"
5
6
  require_relative "snapshot/adapter"
6
7
  require_relative "snapshot/adapters/postgres_adapter"
@@ -12,6 +13,19 @@ module Foundries
12
13
  class << self
13
14
  attr_writer :storage_path, :connection, :enabled, :source_paths
14
15
 
16
+ # Keep the lock outside the replaceable cache directory. Readers and
17
+ # publishers must hold it for the entire filesystem operation.
18
+ def with_lock(name, storage_path: self.storage_path)
19
+ FileUtils.mkdir_p(storage_path)
20
+ lock_path = File.join(storage_path, "#{name}.lock")
21
+ File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock|
22
+ lock.flock(File::LOCK_EX)
23
+ yield
24
+ ensure
25
+ lock.flock(File::LOCK_UN)
26
+ end
27
+ end
28
+
15
29
  def storage_path
16
30
  @storage_path || "tmp/foundries"
17
31
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Foundries
4
- VERSION = "0.1.6"
5
- RELEASE_DATE = "2026-08-25"
4
+ VERSION = "0.1.7"
5
+ RELEASE_DATE = "2026-09-10"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foundries
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Dowd