syntropy 0.41.0 → 0.42.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 +10 -0
- data/TODO.md +334 -31
- data/cmd/new/template/app/_lib/storage.rb +2 -2
- data/cmd/new/template/config/test.rb +1 -1
- data/examples/agent/config/test.rb +1 -1
- data/examples/blog/app/_lib/posts.rb +10 -6
- data/examples/blog/app/_lib/storage.rb +2 -2
- data/examples/blog/app/posts/[id]/index.rb +2 -2
- data/examples/blog/config/test.rb +1 -1
- data/lib/syntropy/collection.rb +206 -0
- data/lib/syntropy/connection_pool.rb +118 -0
- data/lib/syntropy/controller_extensions.rb +12 -0
- data/lib/syntropy/http/server_connection.rb +15 -28
- data/lib/syntropy/json_api.rb +3 -12
- data/lib/syntropy/kv_store.rb +57 -0
- data/lib/syntropy/logger.rb +24 -47
- data/lib/syntropy/markdown.rb +1 -1
- data/lib/syntropy/module_loader.rb +53 -14
- data/lib/syntropy/schema.rb +95 -0
- data/lib/syntropy/storage.rb +42 -4
- data/lib/syntropy/test.rb +1 -1
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +2 -1
- data/syntropy.gemspec +2 -2
- data/test/fixtures/app/_lib/dep_invalidate.rb +5 -0
- data/test/fixtures/collection/basic/_articles/2026-06-30-foo.md +6 -0
- data/test/fixtures/collection/basic/_articles/2026-07-13-bar.md +6 -0
- data/test/fixtures/collection/basic/_articles/2026-08-07-baz.md +6 -0
- data/test/fixtures/collection/basic/index+.rb +23 -0
- data/test/fixtures/collection/docs/_pages/01-foo/01-foo.md +4 -0
- data/test/fixtures/collection/docs/_pages/01-foo/02-bar.md +4 -0
- data/test/fixtures/collection/docs/_pages/01-foo/_index.yml +1 -0
- data/test/fixtures/collection/docs/_pages/02-bar/01-bar.md +4 -0
- data/test/fixtures/collection/docs/_pages/02-bar/02-baz.md +4 -0
- data/test/fixtures/collection/docs/_pages/02-bar/_index.yml +1 -0
- data/test/fixtures/collection/docs/index+.rb +16 -0
- data/test/test_app.rb +7 -7
- data/test/test_cmd_new.rb +38 -0
- data/test/test_collection.rb +164 -0
- data/test/test_connection_pool.rb +79 -3
- data/test/test_http_server_connection.rb +6 -6
- data/test/test_json_api.rb +4 -4
- data/test/test_kv_store.rb +6 -31
- data/test/test_markdown.rb +3 -3
- data/test/test_module_loader.rb +15 -0
- data/test/test_schema.rb +4 -4
- data/test/test_storage.rb +103 -0
- metadata +24 -11
- data/lib/syntropy/storage/connection_pool.rb +0 -106
- data/lib/syntropy/storage/kv_store.rb +0 -68
- data/lib/syntropy/storage/prepared_query.rb +0 -36
- data/lib/syntropy/storage/schema.rb +0 -92
- data/lib/syntropy/storage/store.rb +0 -33
- 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
|