Skip to content

Commit

Permalink
Merge pull request #133 from jetzig-framework/jetkv-valkey
Browse files Browse the repository at this point in the history
Valkey backend for JetKV
  • Loading branch information
bobf authored Dec 6, 2024
2 parents 31f8a4a + 35adaf3 commit 475ed26
Show file tree
Hide file tree
Showing 14 changed files with 317 additions and 194 deletions.
8 changes: 4 additions & 4 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
},
.zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/ef1930b08e1f174ddb02a3a0a01b35aa8a4af235.tar.gz",
.hash = "1220a7bacb828f12cd013b0906da61a17fac6819ab8cee81e00d9ae1aa0faa992720",
.url = "https://github.com/jetzig-framework/zmpl/archive/7b5e0309ee49c06b99c242fecd218d3f3d15cd40.tar.gz",
.hash = "12204d61eb58ee860f748e5817ef9300ad56c9d5efef84864ae590c87baf2e0380a1",
},
.jetkv = .{
.url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz",
.hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af",
.url = "https://github.com/jetzig-framework/jetkv/archive/acaa30db281f1c331d20c48cfe6539186549ad45.tar.gz",
.hash = "1220b260b20cb65d801a00a39dc6506387f5faa1a225f85160e011bd2aabd2ce6e0b",
},
.jetquery = .{
.url = "https://github.com/jetzig-framework/jetquery/archive/52e1cf900c94f3c103727ade6ba2dab3057c8663.tar.gz",
Expand Down
61 changes: 36 additions & 25 deletions demo/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -94,43 +94,54 @@ pub const jetzig_options = struct {
},
};

/// Key-value store options. Set backend to `.file` to use a file-based store.
/// When using `.file` backend, you must also set `.file_options`.
/// The key-value store is exposed as `request.store` in views and is also available in as
/// `env.store` in all jobs/mailers.
pub const store: jetzig.kv.Store.KVOptions = .{
/// Key-value store options.
/// Available backends:
/// * memory: Simple, in-memory hashmap-backed store.
/// * file: Rudimentary file-backed store.
/// * valkey: Valkey-backed store with connection pool.
///
/// When using `.file` or `.valkey` backend, you must also set `.file_options` or
/// `.valkey_options` respectively.
///
/// ## File backend:
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-store.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// },
//
// ## Valkey backend
// .backend = .valkey,
// .valkey_options = .{
// .host = "localhost",
// .port = 6379,
// .timeout = 1000, // in milliseconds, i.e. 1 second.
// .connect = .lazy, // Connect on first use, or `auto` to connect on server startup.
// .buffer_size = 8192,
// .pool_size = 8,
// },
/// Available configuration options for `store`, `job_queue`, and `cache` are identical.
///
/// For production deployment, the `valkey` backend is recommended for all use cases.
///
/// The general-purpose key-value store is exposed as `request.store` in views and is also
/// available in as `env.store` in all jobs/mailers.
pub const store: jetzig.kv.Store.Options = .{
.backend = .memory,
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-store.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// },
};

/// Job queue options. Identical to `store` options, but allows using different
/// backends (e.g. `.memory` for key-value store, `.file` for jobs queue.
/// The job queue is managed internally by Jetzig.
pub const job_queue: jetzig.kv.Store.KVOptions = .{
pub const job_queue: jetzig.kv.Store.Options = .{
.backend = .memory,
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-queue.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// },
};

/// Cache options. Identical to `store` options, but allows using different
/// backends (e.g. `.memory` for key-value store, `.file` for cache.
pub const cache: jetzig.kv.Store.KVOptions = .{
pub const cache: jetzig.kv.Store.Options = .{
.backend = .memory,
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-cache.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// },
};

/// SMTP configuration for Jetzig Mail. It is recommended to use a local SMTP relay,
Expand Down
15 changes: 3 additions & 12 deletions src/jetzig/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,13 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
self.allocator.free(custom_route.template);
};

var store = try jetzig.kv.Store.init(
self.allocator,
jetzig.config.get(jetzig.kv.Store.KVOptions, "store"),
);
var store = try jetzig.kv.Store.GeneralStore.init(self.allocator, self.env.logger, .general);
defer store.deinit();

var job_queue = try jetzig.kv.Store.init(
self.allocator,
jetzig.config.get(jetzig.kv.Store.KVOptions, "job_queue"),
);
var job_queue = try jetzig.kv.Store.JobQueueStore.init(self.allocator, self.env.logger, .jobs);
defer job_queue.deinit();

var cache = try jetzig.kv.Store.init(
self.allocator,
jetzig.config.get(jetzig.kv.Store.KVOptions, "cache"),
);
var cache = try jetzig.kv.Store.CacheStore.init(self.allocator, self.env.logger, .cache);
defer cache.deinit();

var repo = try jetzig.database.repo(self.allocator, self);
Expand Down
12 changes: 6 additions & 6 deletions src/jetzig/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -107,38 +107,38 @@ pub const Schema: type = struct {};
/// When using `.file` backend, you must also set `.file_options`.
/// The key-value store is exposed as `request.store` in views and is also available in as
/// `env.store` in all jobs/mailers.
pub const store: kv.Store.KVOptions = .{
pub const store: kv.Store.Options = .{
.backend = .memory,
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-store.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// .address_space_size = jetzig.jetkv.FileBackend.addressSpace(4096),
// },
};

/// Job queue options. Identical to `store` options, but allows using different
/// backends (e.g. `.memory` for key-value store, `.file` for jobs queue.
/// The job queue is managed internally by Jetzig.
pub const job_queue: kv.Store.KVOptions = .{
pub const job_queue: kv.Store.Options = .{
.backend = .memory,
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-queue.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// .address_space_size = jetzig.jetkv.JetKV.addressSpace(4096),
// },
};

/// Cache. Identical to `store` options, but allows using different
/// backends (e.g. `.memory` for key-value store, `.file` for cache.
pub const cache: kv.Store.KVOptions = .{
pub const cache: kv.Store.Options = .{
.backend = .memory,
// .backend = .file,
// .file_options = .{
// .path = "/path/to/jetkv-cache.db",
// .truncate = false, // Set to `true` to clear the store on each server launch.
// .address_space_size = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
// .address_space_size = jetzig.jetkv.JetKV.addressSpace(4096),
// },
};

Expand Down
8 changes: 4 additions & 4 deletions src/jetzig/http/Query.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub fn parse(self: *Query) !void {
else => return error.JetzigQueryParseError,
}
} else {
var array = try jetzig.zmpl.Data.createArray(self.data.allocator());
var array = try jetzig.zmpl.Data.createArray(self.data.allocator);
try array.append(self.dataValue(item.value));
try params.put(key, array);
}
Expand All @@ -72,7 +72,7 @@ pub fn parse(self: *Query) !void {
else => return error.JetzigQueryParseError,
}
} else {
var object = try jetzig.zmpl.Data.createObject(self.data.allocator());
var object = try jetzig.zmpl.Data.createObject(self.data.allocator);
try object.put(mapping.field, self.dataValue(item.value));
try params.put(mapping.key, object);
}
Expand Down Expand Up @@ -109,10 +109,10 @@ fn mappingParam(input: []const u8) ?struct { key: []const u8, field: []const u8

fn dataValue(self: Query, value: ?[]const u8) *jetzig.data.Data.Value {
if (value) |item_value| {
const duped = self.data.allocator().dupe(u8, item_value) catch @panic("OOM");
const duped = self.data.allocator.dupe(u8, item_value) catch @panic("OOM");
return self.data.string(uriDecode(duped));
} else {
return jetzig.zmpl.Data._null(self.data.allocator());
return jetzig.zmpl.Data._null(self.data.allocator);
}
}

Expand Down
102 changes: 56 additions & 46 deletions src/jetzig/http/Request.zig
Original file line number Diff line number Diff line change
Expand Up @@ -50,65 +50,75 @@ middleware_data: jetzig.http.middleware.MiddlewareData = undefined,
rendered_multiple: bool = false,
rendered_view: ?jetzig.views.View = null,
start_time: i128,
store: RequestStore,
cache: RequestStore,
store: RequestStore(jetzig.kv.Store.GeneralStore),
cache: RequestStore(jetzig.kv.Store.CacheStore),
repo: *jetzig.database.Repo,
global: *jetzig.Global,

/// Wrapper for KV store that uses the request's arena allocator for fetching values.
pub const RequestStore = struct {
allocator: std.mem.Allocator,
store: *jetzig.kv.Store,
pub fn RequestStore(T: type) type {
return struct {
allocator: std.mem.Allocator,
store: *T,

/// Put a String or into the key-value store.
pub fn get(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
return try self.store.get(try self.data(), key);
}
const Self = @This();

/// Get a String from the store.
pub fn put(self: RequestStore, key: []const u8, value: anytype) !void {
const alloc = (try self.data()).allocator();
try self.store.put(key, try jetzig.Data.zmplValue(value, alloc));
}
/// Get a Value from the store.
pub fn get(self: Self, key: []const u8) !?*jetzig.data.Value {
return try self.store.get(try self.data(), key);
}

/// Remove a String to from the key-value store and return it if found.
pub fn fetchRemove(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
return try self.store.fetchRemove(try self.data(), key);
}
/// Store a Value in the key-value store.
pub fn put(self: Self, key: []const u8, value: anytype) !void {
const alloc = (try self.data()).allocator;
try self.store.put(key, try jetzig.Data.zmplValue(value, alloc));
}

/// Remove a String to from the key-value store.
pub fn remove(self: RequestStore, key: []const u8) !void {
try self.store.remove(key);
}
/// Store a Value in the key-value store with an expiration time in seconds.
pub fn putExpire(self: Self, key: []const u8, value: anytype, expiration: i32) !void {
const alloc = (try self.data()).allocator;
try self.store.putExpire(key, try jetzig.Data.zmplValue(value, alloc), expiration);
}

/// Append a Value to the end of an Array in the key-value store.
pub fn append(self: RequestStore, key: []const u8, value: anytype) !void {
const alloc = (try self.data()).allocator();
try self.store.append(key, try jetzig.Data.zmplValue(value, alloc));
}
/// Remove a String to from the key-value store and return it if found.
pub fn fetchRemove(self: Self, key: []const u8) !?*jetzig.data.Value {
return try self.store.fetchRemove(try self.data(), key);
}

/// Prepend a Value to the start of an Array in the key-value store.
pub fn prepend(self: RequestStore, key: []const u8, value: anytype) !void {
const alloc = (try self.data()).allocator();
try self.store.prepend(key, try jetzig.Data.zmplValue(value, alloc));
}
/// Remove a String to from the key-value store.
pub fn remove(self: Self, key: []const u8) !void {
try self.store.remove(key);
}

/// Pop a String from an Array in the key-value store.
pub fn pop(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
return try self.store.pop(try self.data(), key);
}
/// Append a Value to the end of an Array in the key-value store.
pub fn append(self: Self, key: []const u8, value: anytype) !void {
const alloc = (try self.data()).allocator;
try self.store.append(key, try jetzig.Data.zmplValue(value, alloc));
}

/// Left-pop a String from an Array in the key-value store.
pub fn popFirst(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
return try self.store.popFirst(try self.data(), key);
}
/// Prepend a Value to the start of an Array in the key-value store.
pub fn prepend(self: Self, key: []const u8, value: anytype) !void {
const alloc = (try self.data()).allocator;
try self.store.prepend(key, try jetzig.Data.zmplValue(value, alloc));
}

fn data(self: RequestStore) !*jetzig.data.Data {
const arena_data = try self.allocator.create(jetzig.data.Data);
arena_data.* = jetzig.data.Data.init(self.allocator);
return arena_data;
}
};
/// Pop a String from an Array in the key-value store.
pub fn pop(self: Self, key: []const u8) !?*jetzig.data.Value {
return try self.store.pop(try self.data(), key);
}

/// Left-pop a String from an Array in the key-value store.
pub fn popFirst(self: Self, key: []const u8) !?*jetzig.data.Value {
return try self.store.popFirst(try self.data(), key);
}

fn data(self: Self) !*jetzig.data.Data {
const arena_data = try self.allocator.create(jetzig.data.Data);
arena_data.* = jetzig.data.Data.init(self.allocator);
return arena_data;
}
};
}

pub fn init(
allocator: std.mem.Allocator,
Expand Down
12 changes: 6 additions & 6 deletions src/jetzig/http/Server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ job_definitions: []const jetzig.JobDefinition,
mailer_definitions: []const jetzig.MailerDefinition,
mime_map: *jetzig.http.mime.MimeMap,
initialized: bool = false,
store: *jetzig.kv.Store,
job_queue: *jetzig.kv.Store,
cache: *jetzig.kv.Store,
store: *jetzig.kv.Store.GeneralStore,
job_queue: *jetzig.kv.Store.JobQueueStore,
cache: *jetzig.kv.Store.CacheStore,
repo: *jetzig.database.Repo,
global: *anyopaque,
decoded_static_route_params: []*jetzig.data.Value = &.{},
Expand All @@ -33,9 +33,9 @@ pub fn init(
job_definitions: []const jetzig.JobDefinition,
mailer_definitions: []const jetzig.MailerDefinition,
mime_map: *jetzig.http.mime.MimeMap,
store: *jetzig.kv.Store,
job_queue: *jetzig.kv.Store,
cache: *jetzig.kv.Store,
store: *jetzig.kv.Store.GeneralStore,
job_queue: *jetzig.kv.Store.JobQueueStore,
cache: *jetzig.kv.Store.CacheStore,
repo: *jetzig.database.Repo,
global: *anyopaque,
) Server {
Expand Down
16 changes: 8 additions & 8 deletions src/jetzig/jobs/Job.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ pub const JobEnv = struct {
/// All jobs detected by Jetzig on startup
jobs: []const jetzig.JobDefinition,
/// Global key-value store
store: *jetzig.kv.Store,
store: *jetzig.kv.Store.GeneralStore,
/// Global cache
cache: *jetzig.kv.Store,
cache: *jetzig.kv.Store.CacheStore,
/// Database repo
repo: *jetzig.database.Repo,
/// Global mutex - use with caution if it is necessary to guarantee thread safety/consistency
Expand All @@ -33,9 +33,9 @@ pub const JobEnv = struct {
};

allocator: std.mem.Allocator,
store: *jetzig.kv.Store,
job_queue: *jetzig.kv.Store,
cache: *jetzig.kv.Store,
store: *jetzig.kv.Store.GeneralStore,
job_queue: *jetzig.kv.Store.JobQueueStore,
cache: *jetzig.kv.Store.CacheStore,
logger: jetzig.loggers.Logger,
name: []const u8,
definition: ?JobDefinition,
Expand All @@ -47,9 +47,9 @@ const Job = @This();
/// Initialize a new Job
pub fn init(
allocator: std.mem.Allocator,
store: *jetzig.kv.Store,
job_queue: *jetzig.kv.Store,
cache: *jetzig.kv.Store,
store: *jetzig.kv.Store.GeneralStore,
job_queue: *jetzig.kv.Store.JobQueueStore,
cache: *jetzig.kv.Store.CacheStore,
logger: jetzig.loggers.Logger,
jobs: []const JobDefinition,
name: []const u8,
Expand Down
Loading

0 comments on commit 475ed26

Please sign in to comment.