syntropy 0.41.0 → 0.43.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/TODO.md +334 -31
  4. data/cmd/new/template/app/_lib/storage.rb +2 -2
  5. data/cmd/new/template/config/test.rb +1 -1
  6. data/examples/agent/config/test.rb +1 -1
  7. data/examples/blog/app/_lib/posts.rb +10 -6
  8. data/examples/blog/app/_lib/storage.rb +2 -2
  9. data/examples/blog/app/posts/[id]/index.rb +2 -2
  10. data/examples/blog/config/test.rb +1 -1
  11. data/lib/syntropy/collection.rb +206 -0
  12. data/lib/syntropy/connection_pool.rb +118 -0
  13. data/lib/syntropy/controller_extensions.rb +12 -0
  14. data/lib/syntropy/http/server_connection.rb +15 -28
  15. data/lib/syntropy/json_api.rb +3 -12
  16. data/lib/syntropy/kv_store.rb +57 -0
  17. data/lib/syntropy/logger.rb +28 -48
  18. data/lib/syntropy/markdown.rb +1 -1
  19. data/lib/syntropy/module_loader.rb +53 -14
  20. data/lib/syntropy/schema.rb +95 -0
  21. data/lib/syntropy/storage.rb +42 -4
  22. data/lib/syntropy/test.rb +1 -1
  23. data/lib/syntropy/version.rb +1 -1
  24. data/lib/syntropy.rb +2 -1
  25. data/syntropy.gemspec +2 -2
  26. data/test/fixtures/app/_lib/dep_invalidate.rb +5 -0
  27. data/test/fixtures/collection/basic/_articles/2026-06-30-foo.md +6 -0
  28. data/test/fixtures/collection/basic/_articles/2026-07-13-bar.md +6 -0
  29. data/test/fixtures/collection/basic/_articles/2026-08-07-baz.md +6 -0
  30. data/test/fixtures/collection/basic/index+.rb +23 -0
  31. data/test/fixtures/collection/docs/_pages/01-foo/01-foo.md +4 -0
  32. data/test/fixtures/collection/docs/_pages/01-foo/02-bar.md +4 -0
  33. data/test/fixtures/collection/docs/_pages/01-foo/_index.yml +1 -0
  34. data/test/fixtures/collection/docs/_pages/02-bar/01-bar.md +4 -0
  35. data/test/fixtures/collection/docs/_pages/02-bar/02-baz.md +4 -0
  36. data/test/fixtures/collection/docs/_pages/02-bar/_index.yml +1 -0
  37. data/test/fixtures/collection/docs/index+.rb +16 -0
  38. data/test/test_app.rb +7 -7
  39. data/test/test_cmd_new.rb +38 -0
  40. data/test/test_collection.rb +164 -0
  41. data/test/test_connection_pool.rb +79 -3
  42. data/test/test_http_server_connection.rb +6 -6
  43. data/test/test_json_api.rb +4 -4
  44. data/test/test_kv_store.rb +6 -31
  45. data/test/test_markdown.rb +3 -3
  46. data/test/test_module_loader.rb +15 -0
  47. data/test/test_schema.rb +4 -4
  48. data/test/test_storage.rb +103 -0
  49. metadata +24 -11
  50. data/lib/syntropy/storage/connection_pool.rb +0 -106
  51. data/lib/syntropy/storage/kv_store.rb +0 -68
  52. data/lib/syntropy/storage/prepared_query.rb +0 -36
  53. data/lib/syntropy/storage/schema.rb +0 -92
  54. data/lib/syntropy/storage/store.rb +0 -33
  55. data/test/test_store.rb +0 -24
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'syntropy/storage/store'
4
-
5
- module Syntropy
6
- module Storage
7
- # The KVStore class implements an SQLite-backed key-value store
8
- class KVStore < Store
9
- attr_reader :q_get, :q_set
10
-
11
- def self.apply_schema(db, table_name)
12
- db.execute <<~SQL
13
- create table if not exists #{table_name} (key text primary key, value, expires float);
14
- create index if not exists idx_#{table_name}_expires on #{table_name} (expires) where expires is not null;
15
- SQL
16
- end
17
-
18
- def initialize(connection_pool, table_name)
19
- super(connection_pool)
20
- @table_name = table_name
21
-
22
- setup_queries
23
- end
24
-
25
- def get(db, key)
26
- db[@q_get].bind(key).next
27
- end
28
-
29
- def set(db, key, value)
30
- db[@q_set].execute(key, value)
31
- end
32
-
33
- def setex(db, key, value, ttl)
34
- db[@q_setex].execute(key, value, ttl ? Time.now.to_f + ttl : nil)
35
- end
36
-
37
- def sweep(db)
38
- db[@q_sweep].execute(Time.now.to_f)
39
- end
40
-
41
- private
42
-
43
- def setup_queries
44
- @q_get = Storage.prepare_splat <<~SQL
45
- select value from #{@table_name}
46
- where key = ?
47
- SQL
48
-
49
- @q_set = Storage.prepare <<~SQL
50
- insert into #{@table_name} (key, value)
51
- values($1, $2)
52
- on conflict (key) do update set value = $2, expires = null
53
- SQL
54
-
55
- @q_setex = Storage.prepare <<~SQL
56
- insert into #{@table_name} (key, value, expires)
57
- values($1, $2, $3)
58
- on conflict (key) do update set value = $2, expires = $3
59
- SQL
60
-
61
- @q_sweep = Storage.prepare <<~SQL
62
- delete from #{@table_name}
63
- where expires < ?
64
- SQL
65
- end
66
- end
67
- end
68
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'extralite'
4
-
5
- module Syntropy
6
- module Storage
7
- class << self
8
- def prepare(sql)
9
- PreparedQuery.new(sql)
10
- end
11
-
12
- def prepare_splat(sql)
13
- PreparedQuery.new(sql, :prepare_splat)
14
- end
15
- end
16
-
17
- # Represents information about a prepared query
18
- class PreparedQuery
19
- attr_reader :sql, :mode
20
-
21
- def initialize(sql, mode = :prepare)
22
- @sql = sql
23
- @mode = mode
24
- end
25
- end
26
-
27
- # Extensions for Extralite::Database
28
- module ExtraliteDatabaseExtensions
29
- def [](pq)
30
- (@prepared_queries ||= {})[pq] ||= send(pq.mode, pq.sql)
31
- end
32
- end
33
-
34
- ::Extralite::Database.include(ExtraliteDatabaseExtensions)
35
- end
36
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Syntropy
4
- module Storage
5
- class Schema
6
- def initialize(module_loader: nil, schema_root: '_schema', &)
7
- @migrations = {}
8
- @module_loader = module_loader
9
- @schema_root = schema_root
10
- load_schema_from_modules if @module_loader
11
- run_schema_block(&) if block_given?
12
- end
13
-
14
- def apply(connection_pool)
15
- execute_migrations(connection_pool)
16
- end
17
-
18
- def current_version(connection_pool)
19
- connection_pool.with_db do |db|
20
- get_schema_version(db)
21
- end
22
- end
23
-
24
- private
25
-
26
- def load_schema_from_modules
27
- modules = @module_loader.list(@schema_root)
28
- modules.each do |name|
29
- @migrations[File.basename(name)] = @module_loader.load(name)
30
- end
31
- end
32
-
33
- class SchemaBlockRunner
34
- def initialize(migrations, &)
35
- @migrations = migrations
36
- instance_eval(&)
37
- end
38
-
39
- def initial(&block)
40
- @migrations['0000'] = block
41
- end
42
-
43
- def version(key, &block)
44
- @migrations[key] = block
45
- end
46
- end
47
-
48
- def run_schema_block(&)
49
- SchemaBlockRunner.new(@migrations, &)
50
- end
51
-
52
- def execute_migrations(connection_pool)
53
- connection_pool.with_db do |db|
54
- current_version = get_schema_version(db)
55
- migrations_keys = @migrations.keys.sort
56
- migrations_keys.select! { it > current_version } if current_version
57
-
58
- migrations_keys.each do |key|
59
- db.transaction do
60
- @migrations[key].(db)
61
- set_schema_version(db, key)
62
- current_version = key
63
- end
64
- end
65
-
66
- current_version
67
- end
68
- end
69
-
70
- def get_schema_version(db)
71
- db.execute <<~SQL
72
- create table if not exists __syntropy_schema__(
73
- k text primary key,
74
- v text
75
- );
76
- SQL
77
- db.query_single_splat <<~SQL
78
- select v from __syntropy_schema__
79
- where k = 'version'
80
- SQL
81
- end
82
-
83
- def set_schema_version(db, version)
84
- db.execute <<~SQL, version
85
- insert into __syntropy_schema__ (k, v)
86
- values ('version', $1)
87
- on conflict(k) do update set v = $1
88
- SQL
89
- end
90
- end
91
- end
92
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Syntropy
4
- module Storage
5
- # The Store class represents a data store based on one or more tables, and
6
- # connected to a database connection pool.
7
- class Store
8
- def initialize(connection_pool)
9
- @connection_pool = connection_pool
10
- end
11
-
12
- def query(sql, *, **)
13
- @connection_pool.with_db { it.query(sql, *, **) }
14
- end
15
-
16
- def query_single_row(sql, *, **)
17
- @connection_pool.with_db { it.query_single(sql, *, **) }
18
- end
19
-
20
- def query_single_value(sql, *, **)
21
- @connection_pool.with_db { it.query_single_splat(sql, *, **) }
22
- end
23
-
24
- def execute(sql, *, **)
25
- @connection_pool.with_db { it.execute(sql, *, **) }
26
- end
27
-
28
- def transaction(&)
29
- @connection_pool.with_db(&)
30
- end
31
- end
32
- end
33
- end
data/test/test_store.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'helper'
4
-
5
- class StoreTest < Minitest::Test
6
- def setup
7
- @machine = UM.new
8
- @fn = "/tmp/#{rand(100000)}.db"
9
- FileUtils.rm(@fn) rescue nil
10
- @cp = Syntropy::Storage::ConnectionPool.new(@machine, @fn, 4)
11
- end
12
-
13
- def teardown
14
- @cp.close
15
- end
16
-
17
- def test_db_store
18
- store = Syntropy::Storage::Store.new(@cp)
19
-
20
- assert_equal [{a: 42}], store.query("select ? as a", 42)
21
- assert_equal({a: 42}, store.query_single_row("select ? as a", 42))
22
- assert_equal 42, store.query_single_value("select ?", 42)
23
- end
24
- end