mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Merge pull request #133 from jetzig-framework/jetkv-valkey
Valkey backend for JetKV
This commit is contained in:
commit
475ed26952
@ -7,12 +7,12 @@
|
|||||||
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
||||||
},
|
},
|
||||||
.zmpl = .{
|
.zmpl = .{
|
||||||
.url = "https://github.com/jetzig-framework/zmpl/archive/ef1930b08e1f174ddb02a3a0a01b35aa8a4af235.tar.gz",
|
.url = "https://github.com/jetzig-framework/zmpl/archive/7b5e0309ee49c06b99c242fecd218d3f3d15cd40.tar.gz",
|
||||||
.hash = "1220a7bacb828f12cd013b0906da61a17fac6819ab8cee81e00d9ae1aa0faa992720",
|
.hash = "12204d61eb58ee860f748e5817ef9300ad56c9d5efef84864ae590c87baf2e0380a1",
|
||||||
},
|
},
|
||||||
.jetkv = .{
|
.jetkv = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetkv/archive/acaa30db281f1c331d20c48cfe6539186549ad45.tar.gz",
|
||||||
.hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af",
|
.hash = "1220b260b20cb65d801a00a39dc6506387f5faa1a225f85160e011bd2aabd2ce6e0b",
|
||||||
},
|
},
|
||||||
.jetquery = .{
|
.jetquery = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetquery/archive/52e1cf900c94f3c103727ade6ba2dab3057c8663.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetquery/archive/52e1cf900c94f3c103727ade6ba2dab3057c8663.tar.gz",
|
||||||
|
@ -94,43 +94,54 @@ pub const jetzig_options = struct {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Key-value store options. Set backend to `.file` to use a file-based store.
|
/// Key-value store options.
|
||||||
/// When using `.file` backend, you must also set `.file_options`.
|
/// Available backends:
|
||||||
/// The key-value store is exposed as `request.store` in views and is also available in as
|
/// * memory: Simple, in-memory hashmap-backed store.
|
||||||
/// `env.store` in all jobs/mailers.
|
/// * file: Rudimentary file-backed store.
|
||||||
pub const store: jetzig.kv.Store.KVOptions = .{
|
/// * 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 = .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
|
/// Job queue options. Identical to `store` options, but allows using different
|
||||||
/// backends (e.g. `.memory` for key-value store, `.file` for jobs queue.
|
/// backends (e.g. `.memory` for key-value store, `.file` for jobs queue.
|
||||||
/// The job queue is managed internally by Jetzig.
|
/// 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 = .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
|
/// Cache options. Identical to `store` options, but allows using different
|
||||||
/// backends (e.g. `.memory` for key-value store, `.file` for cache.
|
/// 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 = .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,
|
/// SMTP configuration for Jetzig Mail. It is recommended to use a local SMTP relay,
|
||||||
|
@ -48,22 +48,13 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
self.allocator.free(custom_route.template);
|
self.allocator.free(custom_route.template);
|
||||||
};
|
};
|
||||||
|
|
||||||
var store = try jetzig.kv.Store.init(
|
var store = try jetzig.kv.Store.GeneralStore.init(self.allocator, self.env.logger, .general);
|
||||||
self.allocator,
|
|
||||||
jetzig.config.get(jetzig.kv.Store.KVOptions, "store"),
|
|
||||||
);
|
|
||||||
defer store.deinit();
|
defer store.deinit();
|
||||||
|
|
||||||
var job_queue = try jetzig.kv.Store.init(
|
var job_queue = try jetzig.kv.Store.JobQueueStore.init(self.allocator, self.env.logger, .jobs);
|
||||||
self.allocator,
|
|
||||||
jetzig.config.get(jetzig.kv.Store.KVOptions, "job_queue"),
|
|
||||||
);
|
|
||||||
defer job_queue.deinit();
|
defer job_queue.deinit();
|
||||||
|
|
||||||
var cache = try jetzig.kv.Store.init(
|
var cache = try jetzig.kv.Store.CacheStore.init(self.allocator, self.env.logger, .cache);
|
||||||
self.allocator,
|
|
||||||
jetzig.config.get(jetzig.kv.Store.KVOptions, "cache"),
|
|
||||||
);
|
|
||||||
defer cache.deinit();
|
defer cache.deinit();
|
||||||
|
|
||||||
var repo = try jetzig.database.repo(self.allocator, self);
|
var repo = try jetzig.database.repo(self.allocator, self);
|
||||||
|
@ -107,38 +107,38 @@ pub const Schema: type = struct {};
|
|||||||
/// When using `.file` backend, you must also set `.file_options`.
|
/// 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
|
/// The key-value store is exposed as `request.store` in views and is also available in as
|
||||||
/// `env.store` in all jobs/mailers.
|
/// `env.store` in all jobs/mailers.
|
||||||
pub const store: kv.Store.KVOptions = .{
|
pub const store: kv.Store.Options = .{
|
||||||
.backend = .memory,
|
.backend = .memory,
|
||||||
// .backend = .file,
|
// .backend = .file,
|
||||||
// .file_options = .{
|
// .file_options = .{
|
||||||
// .path = "/path/to/jetkv-store.db",
|
// .path = "/path/to/jetkv-store.db",
|
||||||
// .truncate = false, // Set to `true` to clear the store on each server launch.
|
// .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
|
/// Job queue options. Identical to `store` options, but allows using different
|
||||||
/// backends (e.g. `.memory` for key-value store, `.file` for jobs queue.
|
/// backends (e.g. `.memory` for key-value store, `.file` for jobs queue.
|
||||||
/// The job queue is managed internally by Jetzig.
|
/// 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 = .memory,
|
||||||
// .backend = .file,
|
// .backend = .file,
|
||||||
// .file_options = .{
|
// .file_options = .{
|
||||||
// .path = "/path/to/jetkv-queue.db",
|
// .path = "/path/to/jetkv-queue.db",
|
||||||
// .truncate = false, // Set to `true` to clear the store on each server launch.
|
// .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
|
/// Cache. Identical to `store` options, but allows using different
|
||||||
/// backends (e.g. `.memory` for key-value store, `.file` for cache.
|
/// 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 = .memory,
|
||||||
// .backend = .file,
|
// .backend = .file,
|
||||||
// .file_options = .{
|
// .file_options = .{
|
||||||
// .path = "/path/to/jetkv-cache.db",
|
// .path = "/path/to/jetkv-cache.db",
|
||||||
// .truncate = false, // Set to `true` to clear the store on each server launch.
|
// .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),
|
||||||
// },
|
// },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ pub fn parse(self: *Query) !void {
|
|||||||
else => return error.JetzigQueryParseError,
|
else => return error.JetzigQueryParseError,
|
||||||
}
|
}
|
||||||
} else {
|
} 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 array.append(self.dataValue(item.value));
|
||||||
try params.put(key, array);
|
try params.put(key, array);
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ pub fn parse(self: *Query) !void {
|
|||||||
else => return error.JetzigQueryParseError,
|
else => return error.JetzigQueryParseError,
|
||||||
}
|
}
|
||||||
} else {
|
} 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 object.put(mapping.field, self.dataValue(item.value));
|
||||||
try params.put(mapping.key, object);
|
try params.put(mapping.key, object);
|
||||||
}
|
}
|
||||||
@ -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 {
|
fn dataValue(self: Query, value: ?[]const u8) *jetzig.data.Data.Value {
|
||||||
if (value) |item_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));
|
return self.data.string(uriDecode(duped));
|
||||||
} else {
|
} else {
|
||||||
return jetzig.zmpl.Data._null(self.data.allocator());
|
return jetzig.zmpl.Data._null(self.data.allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,65 +50,75 @@ middleware_data: jetzig.http.middleware.MiddlewareData = undefined,
|
|||||||
rendered_multiple: bool = false,
|
rendered_multiple: bool = false,
|
||||||
rendered_view: ?jetzig.views.View = null,
|
rendered_view: ?jetzig.views.View = null,
|
||||||
start_time: i128,
|
start_time: i128,
|
||||||
store: RequestStore,
|
store: RequestStore(jetzig.kv.Store.GeneralStore),
|
||||||
cache: RequestStore,
|
cache: RequestStore(jetzig.kv.Store.CacheStore),
|
||||||
repo: *jetzig.database.Repo,
|
repo: *jetzig.database.Repo,
|
||||||
global: *jetzig.Global,
|
global: *jetzig.Global,
|
||||||
|
|
||||||
/// Wrapper for KV store that uses the request's arena allocator for fetching values.
|
/// Wrapper for KV store that uses the request's arena allocator for fetching values.
|
||||||
pub const RequestStore = struct {
|
pub fn RequestStore(T: type) type {
|
||||||
allocator: std.mem.Allocator,
|
return struct {
|
||||||
store: *jetzig.kv.Store,
|
allocator: std.mem.Allocator,
|
||||||
|
store: *T,
|
||||||
|
|
||||||
/// Put a String or into the key-value store.
|
const Self = @This();
|
||||||
pub fn get(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
|
|
||||||
return try self.store.get(try self.data(), key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a String from the store.
|
/// Get a Value from the store.
|
||||||
pub fn put(self: RequestStore, key: []const u8, value: anytype) !void {
|
pub fn get(self: Self, key: []const u8) !?*jetzig.data.Value {
|
||||||
const alloc = (try self.data()).allocator();
|
return try self.store.get(try self.data(), key);
|
||||||
try self.store.put(key, try jetzig.Data.zmplValue(value, alloc));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a String to from the key-value store and return it if found.
|
/// Store a Value in the key-value store.
|
||||||
pub fn fetchRemove(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
|
pub fn put(self: Self, key: []const u8, value: anytype) !void {
|
||||||
return try self.store.fetchRemove(try self.data(), key);
|
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.
|
/// Store a Value in the key-value store with an expiration time in seconds.
|
||||||
pub fn remove(self: RequestStore, key: []const u8) !void {
|
pub fn putExpire(self: Self, key: []const u8, value: anytype, expiration: i32) !void {
|
||||||
try self.store.remove(key);
|
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.
|
/// Remove a String to from the key-value store and return it if found.
|
||||||
pub fn append(self: RequestStore, key: []const u8, value: anytype) !void {
|
pub fn fetchRemove(self: Self, key: []const u8) !?*jetzig.data.Value {
|
||||||
const alloc = (try self.data()).allocator();
|
return try self.store.fetchRemove(try self.data(), key);
|
||||||
try self.store.append(key, try jetzig.Data.zmplValue(value, alloc));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepend a Value to the start of an Array in the key-value store.
|
/// Remove a String to from the key-value store.
|
||||||
pub fn prepend(self: RequestStore, key: []const u8, value: anytype) !void {
|
pub fn remove(self: Self, key: []const u8) !void {
|
||||||
const alloc = (try self.data()).allocator();
|
try self.store.remove(key);
|
||||||
try self.store.prepend(key, try jetzig.Data.zmplValue(value, alloc));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Pop a String from an Array in the key-value store.
|
/// Append a Value to the end of an Array in the key-value store.
|
||||||
pub fn pop(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
|
pub fn append(self: Self, key: []const u8, value: anytype) !void {
|
||||||
return try self.store.pop(try self.data(), key);
|
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.
|
/// Prepend a Value to the start of an Array in the key-value store.
|
||||||
pub fn popFirst(self: RequestStore, key: []const u8) !?*jetzig.data.Value {
|
pub fn prepend(self: Self, key: []const u8, value: anytype) !void {
|
||||||
return try self.store.popFirst(try self.data(), key);
|
const alloc = (try self.data()).allocator;
|
||||||
}
|
try self.store.prepend(key, try jetzig.Data.zmplValue(value, alloc));
|
||||||
|
}
|
||||||
|
|
||||||
fn data(self: RequestStore) !*jetzig.data.Data {
|
/// Pop a String from an Array in the key-value store.
|
||||||
const arena_data = try self.allocator.create(jetzig.data.Data);
|
pub fn pop(self: Self, key: []const u8) !?*jetzig.data.Value {
|
||||||
arena_data.* = jetzig.data.Data.init(self.allocator);
|
return try self.store.pop(try self.data(), key);
|
||||||
return arena_data;
|
}
|
||||||
}
|
|
||||||
};
|
/// 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(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
@ -15,9 +15,9 @@ job_definitions: []const jetzig.JobDefinition,
|
|||||||
mailer_definitions: []const jetzig.MailerDefinition,
|
mailer_definitions: []const jetzig.MailerDefinition,
|
||||||
mime_map: *jetzig.http.mime.MimeMap,
|
mime_map: *jetzig.http.mime.MimeMap,
|
||||||
initialized: bool = false,
|
initialized: bool = false,
|
||||||
store: *jetzig.kv.Store,
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
cache: *jetzig.kv.Store,
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
repo: *jetzig.database.Repo,
|
repo: *jetzig.database.Repo,
|
||||||
global: *anyopaque,
|
global: *anyopaque,
|
||||||
decoded_static_route_params: []*jetzig.data.Value = &.{},
|
decoded_static_route_params: []*jetzig.data.Value = &.{},
|
||||||
@ -33,9 +33,9 @@ pub fn init(
|
|||||||
job_definitions: []const jetzig.JobDefinition,
|
job_definitions: []const jetzig.JobDefinition,
|
||||||
mailer_definitions: []const jetzig.MailerDefinition,
|
mailer_definitions: []const jetzig.MailerDefinition,
|
||||||
mime_map: *jetzig.http.mime.MimeMap,
|
mime_map: *jetzig.http.mime.MimeMap,
|
||||||
store: *jetzig.kv.Store,
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
cache: *jetzig.kv.Store,
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
repo: *jetzig.database.Repo,
|
repo: *jetzig.database.Repo,
|
||||||
global: *anyopaque,
|
global: *anyopaque,
|
||||||
) Server {
|
) Server {
|
||||||
|
@ -22,9 +22,9 @@ pub const JobEnv = struct {
|
|||||||
/// All jobs detected by Jetzig on startup
|
/// All jobs detected by Jetzig on startup
|
||||||
jobs: []const jetzig.JobDefinition,
|
jobs: []const jetzig.JobDefinition,
|
||||||
/// Global key-value store
|
/// Global key-value store
|
||||||
store: *jetzig.kv.Store,
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
/// Global cache
|
/// Global cache
|
||||||
cache: *jetzig.kv.Store,
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
/// Database repo
|
/// Database repo
|
||||||
repo: *jetzig.database.Repo,
|
repo: *jetzig.database.Repo,
|
||||||
/// Global mutex - use with caution if it is necessary to guarantee thread safety/consistency
|
/// Global mutex - use with caution if it is necessary to guarantee thread safety/consistency
|
||||||
@ -33,9 +33,9 @@ pub const JobEnv = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
store: *jetzig.kv.Store,
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
cache: *jetzig.kv.Store,
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
definition: ?JobDefinition,
|
definition: ?JobDefinition,
|
||||||
@ -47,9 +47,9 @@ const Job = @This();
|
|||||||
/// Initialize a new Job
|
/// Initialize a new Job
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
store: *jetzig.kv.Store,
|
store: *jetzig.kv.Store.GeneralStore,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
cache: *jetzig.kv.Store,
|
cache: *jetzig.kv.Store.CacheStore,
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
jobs: []const JobDefinition,
|
jobs: []const JobDefinition,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
@ -5,7 +5,7 @@ const jetzig = @import("../../jetzig.zig");
|
|||||||
const Pool = @This();
|
const Pool = @This();
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
job_env: jetzig.jobs.JobEnv,
|
job_env: jetzig.jobs.JobEnv,
|
||||||
pool: std.Thread.Pool = undefined,
|
pool: std.Thread.Pool = undefined,
|
||||||
workers: std.ArrayList(*jetzig.jobs.Worker),
|
workers: std.ArrayList(*jetzig.jobs.Worker),
|
||||||
@ -13,7 +13,7 @@ workers: std.ArrayList(*jetzig.jobs.Worker),
|
|||||||
/// Initialize a new worker thread pool.
|
/// Initialize a new worker thread pool.
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
job_env: jetzig.jobs.JobEnv,
|
job_env: jetzig.jobs.JobEnv,
|
||||||
) Pool {
|
) Pool {
|
||||||
return .{
|
return .{
|
||||||
|
@ -6,14 +6,14 @@ const Worker = @This();
|
|||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
job_env: jetzig.jobs.JobEnv,
|
job_env: jetzig.jobs.JobEnv,
|
||||||
id: usize,
|
id: usize,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
interval: usize,
|
interval: usize,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
job_env: jetzig.jobs.JobEnv,
|
job_env: jetzig.jobs.JobEnv,
|
||||||
id: usize,
|
id: usize,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store.JobQueueStore,
|
||||||
interval: usize,
|
interval: usize,
|
||||||
) Worker {
|
) Worker {
|
||||||
return .{
|
return .{
|
||||||
|
@ -1,3 +1,36 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Store = @import("kv/Store.zig");
|
const config = @import("config.zig");
|
||||||
|
|
||||||
|
pub const Store = struct {
|
||||||
|
/// Configuration for JetKV. Encompasses all backends:
|
||||||
|
/// * valkey
|
||||||
|
/// * memory
|
||||||
|
/// * file
|
||||||
|
///
|
||||||
|
/// The Valkey backend is recommended for production deployment. `memory` and `file` can be
|
||||||
|
/// used in local development for convenience. All backends have a unified interface, i.e.
|
||||||
|
/// they can be swapped out without any code changes.
|
||||||
|
pub const Options = @import("kv/Store.zig").KVOptions;
|
||||||
|
|
||||||
|
// For backward compatibility - `jetzig.kv.Options` is preferred.
|
||||||
|
pub const KVOptions = Options;
|
||||||
|
|
||||||
|
/// General-purpose store. Use for storing data with no expiry.
|
||||||
|
pub const GeneralStore = @import("kv/Store.zig").Store(config.get(Store.Options, "store"));
|
||||||
|
|
||||||
|
/// Store ephemeral data.
|
||||||
|
pub const CacheStore = @import("kv/Store.zig").Store(config.get(Store.Options, "cache"));
|
||||||
|
|
||||||
|
/// Background job storage.
|
||||||
|
pub const JobQueueStore = @import("kv/Store.zig").Store(config.get(Store.Options, "job_queue"));
|
||||||
|
|
||||||
|
/// Generic store type. Create a custom store by passing `Options`, e.g.:
|
||||||
|
/// ```zig
|
||||||
|
/// var store = Generic(.{ .backend = .memory }).init(allocator, logger, .custom);
|
||||||
|
/// ```
|
||||||
|
pub const Generic = @import("kv/Store.zig").Store;
|
||||||
|
|
||||||
|
/// Role a given store fills. Used in log outputs.
|
||||||
|
pub const Role = @import("kv/Store.zig").Role;
|
||||||
|
};
|
||||||
|
@ -1,87 +1,156 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
const Store = @This();
|
|
||||||
|
|
||||||
store: jetzig.jetkv.JetKV,
|
|
||||||
options: KVOptions,
|
|
||||||
|
|
||||||
pub const KVOptions = struct {
|
pub const KVOptions = struct {
|
||||||
backend: enum { memory, file } = .memory,
|
backend: enum { memory, file, valkey } = .memory,
|
||||||
file_options: struct {
|
file_options: struct {
|
||||||
path: ?[]const u8 = null,
|
path: ?[]const u8 = null,
|
||||||
address_space_size: u32 = jetzig.jetkv.JetKV.FileBackend.addressSpace(4096),
|
address_space_size: u32 = jetzig.jetkv.FileBackend.addressSpace(4096),
|
||||||
truncate: bool = false,
|
truncate: bool = false,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
valkey_options: struct {
|
||||||
|
host: []const u8 = "localhost",
|
||||||
|
port: u16 = 6379,
|
||||||
|
connect_timeout: u64 = 1000, // (ms)
|
||||||
|
read_timeout: u64 = 1000, // (ms)
|
||||||
|
connect: enum { auto, manual, lazy } = .lazy,
|
||||||
|
buffer_size: u32 = 8192,
|
||||||
|
pool_size: u16 = 8,
|
||||||
|
} = .{},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ValueType = enum { string, array };
|
const ValueType = enum { string, array };
|
||||||
|
|
||||||
/// Initialize a new memory or file store.
|
fn jetKVOptions(options: KVOptions) jetzig.jetkv.Options {
|
||||||
pub fn init(allocator: std.mem.Allocator, options: KVOptions) !Store {
|
return switch (options.backend) {
|
||||||
const store = try jetzig.jetkv.JetKV.init(
|
.file => .{
|
||||||
allocator,
|
.backend = .file,
|
||||||
switch (options.backend) {
|
.file_backend_options = .{
|
||||||
.file => .{
|
.path = options.file_options.path,
|
||||||
.backend = .file,
|
.address_space_size = options.file_options.address_space_size,
|
||||||
.file_backend_options = .{
|
.truncate = options.file_options.truncate,
|
||||||
.path = options.file_options.path,
|
|
||||||
.address_space_size = options.file_options.address_space_size,
|
|
||||||
.truncate = options.file_options.truncate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.memory => .{
|
|
||||||
.backend = .memory,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
.memory => .{
|
||||||
|
.backend = .memory,
|
||||||
return .{ .store = store, .options = options };
|
},
|
||||||
|
.valkey => .{
|
||||||
|
.backend = .valkey,
|
||||||
|
.valkey_backend_options = .{
|
||||||
|
.host = options.valkey_options.host,
|
||||||
|
.port = options.valkey_options.port,
|
||||||
|
.connect_timeout = options.valkey_options.connect_timeout * std.time.ms_per_s,
|
||||||
|
.read_timeout = options.valkey_options.read_timeout * std.time.ms_per_s,
|
||||||
|
.connect = std.enums.nameCast(
|
||||||
|
jetzig.jetkv.ValkeyBackendOptions.ConnectMode,
|
||||||
|
options.valkey_options.connect,
|
||||||
|
),
|
||||||
|
.buffer_size = options.valkey_options.buffer_size,
|
||||||
|
.pool_size = options.valkey_options.pool_size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Free allocated resources/close database file.
|
/// Role a given store fills. Used in log outputs.
|
||||||
pub fn deinit(self: *Store) void {
|
pub const Role = enum { jobs, cache, general, custom };
|
||||||
self.store.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Put a or into the key-value store.
|
pub fn Store(comptime options: KVOptions) type {
|
||||||
pub fn put(self: *Store, key: []const u8, value: *jetzig.data.Value) !void {
|
return struct {
|
||||||
try self.store.put(key, try value.toJson());
|
const Self = @This();
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a Value from the store.
|
store: jetzig.jetkv.JetKV(jetKVOptions(options)),
|
||||||
pub fn get(self: *Store, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
logger: jetzig.loggers.Logger,
|
||||||
return try parseValue(data, try self.store.get(data.allocator(), key));
|
options: KVOptions,
|
||||||
}
|
role: Role,
|
||||||
|
|
||||||
/// Remove a Value to from the key-value store and return it if found.
|
/// Initialize a new memory or file store.
|
||||||
pub fn fetchRemove(self: *Store, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
pub fn init(allocator: std.mem.Allocator, logger: jetzig.loggers.Logger, role: Role) !Self {
|
||||||
return try parseValue(data, try self.store.fetchRemove(data.allocator(), key));
|
const store = try jetzig.jetkv.JetKV(jetKVOptions(options)).init(allocator);
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a Value to from the key-value store.
|
return .{ .store = store, .role = role, .logger = logger, .options = options };
|
||||||
pub fn remove(self: *Store, key: []const u8) !void {
|
}
|
||||||
try self.store.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a Value to the end of an Array in the key-value store.
|
/// Free allocated resources/close database file.
|
||||||
pub fn append(self: *Store, key: []const u8, value: *const jetzig.data.Value) !void {
|
pub fn deinit(self: *Self) void {
|
||||||
try self.store.append(key, try value.toJson());
|
self.store.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepend a Value to the start of an Array in the key-value store.
|
/// Put a or into the key-value store.
|
||||||
pub fn prepend(self: *Store, key: []const u8, value: *const jetzig.data.Value) !void {
|
pub fn put(self: *Self, key: []const u8, value: *jetzig.data.Value) !void {
|
||||||
try self.store.prepend(key, try value.toJson());
|
try self.store.put(key, try value.toJson());
|
||||||
}
|
if (self.role == .cache) {
|
||||||
|
try self.logger.DEBUG(
|
||||||
|
"[cache:{s}:store] {s}",
|
||||||
|
.{ @tagName(self.store.backend), key },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pop a Value from an Array in the key-value store.
|
/// Put a or into the key-value store with an expiration in seconds.
|
||||||
pub fn pop(self: *Store, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
pub fn putExpire(self: *Self, key: []const u8, value: *jetzig.data.Value, expiration: i32) !void {
|
||||||
return try parseValue(data, try self.store.pop(data.allocator(), key));
|
try self.store.putExpire(key, try value.toJson(), expiration);
|
||||||
}
|
if (self.role == .cache) {
|
||||||
|
try self.logger.DEBUG(
|
||||||
|
"[cache:{s}:store:expire:{d}s] {s}",
|
||||||
|
.{ @tagName(self.store.backend), expiration, key },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Left-pop a Value from an Array in the key-value store.
|
/// Get a Value from the store.
|
||||||
pub fn popFirst(self: *Store, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
pub fn get(self: *Self, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
||||||
return try parseValue(data, try self.store.popFirst(data.allocator(), key));
|
const start = std.time.nanoTimestamp();
|
||||||
|
const json = try self.store.get(data.allocator, key);
|
||||||
|
const value = try parseValue(data, json);
|
||||||
|
const end = std.time.nanoTimestamp();
|
||||||
|
if (self.role == .cache) {
|
||||||
|
if (value == null) {
|
||||||
|
try self.logger.DEBUG("[cache:miss] {s}", .{key});
|
||||||
|
} else {
|
||||||
|
try self.logger.DEBUG(
|
||||||
|
"[cache:{s}:hit:{}] {s}",
|
||||||
|
.{
|
||||||
|
@tagName(self.store.backend),
|
||||||
|
std.fmt.fmtDuration(@intCast(end - start)),
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a Value to from the key-value store and return it if found.
|
||||||
|
pub fn fetchRemove(self: *Self, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
||||||
|
return try parseValue(data, try self.store.fetchRemove(data.allocator, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a Value to from the key-value store.
|
||||||
|
pub fn remove(self: *Self, key: []const u8) !void {
|
||||||
|
try self.store.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a Value to the end of an Array in the key-value store.
|
||||||
|
pub fn append(self: *Self, key: []const u8, value: *const jetzig.data.Value) !void {
|
||||||
|
try self.store.append(key, try value.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepend a Value to the start of an Array in the key-value store.
|
||||||
|
pub fn prepend(self: *Self, key: []const u8, value: *const jetzig.data.Value) !void {
|
||||||
|
try self.store.prepend(key, try value.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop a Value from an Array in the key-value store.
|
||||||
|
pub fn pop(self: *Self, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
||||||
|
return try parseValue(data, try self.store.pop(data.allocator, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Left-pop a Value from an Array in the key-value store.
|
||||||
|
pub fn popFirst(self: *Self, data: *jetzig.data.Data, key: []const u8) !?*jetzig.data.Value {
|
||||||
|
return try parseValue(data, try self.store.popFirst(data.allocator, key));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseValue(data: *jetzig.data.Data, maybe_json: ?[]const u8) !?*jetzig.data.Value {
|
fn parseValue(data: *jetzig.data.Data, maybe_json: ?[]const u8) !?*jetzig.data.Value {
|
||||||
|
@ -130,7 +130,7 @@ fn defaultHtml(
|
|||||||
data.value = if (params.get("params")) |capture|
|
data.value = if (params.get("params")) |capture|
|
||||||
capture
|
capture
|
||||||
else
|
else
|
||||||
try jetzig.zmpl.Data.createObject(data.allocator());
|
try jetzig.zmpl.Data.createObject(data.allocator);
|
||||||
try data.addConst("jetzig_view", data.string(""));
|
try data.addConst("jetzig_view", data.string(""));
|
||||||
try data.addConst("jetzig_action", data.string(""));
|
try data.addConst("jetzig_action", data.string(""));
|
||||||
return if (jetzig.zmpl.findPrefixed("mailers", mailer.html_template)) |template|
|
return if (jetzig.zmpl.findPrefixed("mailers", mailer.html_template)) |template|
|
||||||
@ -148,7 +148,7 @@ fn defaultText(
|
|||||||
data.value = if (params.get("params")) |capture|
|
data.value = if (params.get("params")) |capture|
|
||||||
capture
|
capture
|
||||||
else
|
else
|
||||||
try jetzig.zmpl.Data.createObject(data.allocator());
|
try jetzig.zmpl.Data.createObject(data.allocator);
|
||||||
try data.addConst("jetzig_view", data.string(""));
|
try data.addConst("jetzig_view", data.string(""));
|
||||||
try data.addConst("jetzig_action", data.string(""));
|
try data.addConst("jetzig_action", data.string(""));
|
||||||
return if (jetzig.zmpl.findPrefixed("mailers", mailer.text_template)) |template|
|
return if (jetzig.zmpl.findPrefixed("mailers", mailer.text_template)) |template|
|
||||||
|
@ -4,13 +4,14 @@ const jetzig = @import("../../jetzig.zig");
|
|||||||
const httpz = @import("httpz");
|
const httpz = @import("httpz");
|
||||||
|
|
||||||
const App = @This();
|
const App = @This();
|
||||||
|
const MemoryStore = jetzig.kv.Store.Generic(.{ .backend = .memory });
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
routes: []const jetzig.views.Route,
|
routes: []const jetzig.views.Route,
|
||||||
arena: *std.heap.ArenaAllocator,
|
arena: *std.heap.ArenaAllocator,
|
||||||
store: *jetzig.kv.Store,
|
store: *MemoryStore,
|
||||||
cache: *jetzig.kv.Store,
|
cache: *MemoryStore,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *MemoryStore,
|
||||||
multipart_boundary: ?[]const u8 = null,
|
multipart_boundary: ?[]const u8 = null,
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
server: Server,
|
server: Server,
|
||||||
@ -60,9 +61,9 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {
|
|||||||
.arena = arena,
|
.arena = arena,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.routes = &routes_module.routes,
|
.routes = &routes_module.routes,
|
||||||
.store = try createStore(arena.allocator()),
|
.store = try createStore(arena.allocator(), logger, .general),
|
||||||
.cache = try createStore(arena.allocator()),
|
.cache = try createStore(arena.allocator(), logger, .cache),
|
||||||
.job_queue = try createStore(arena.allocator()),
|
.job_queue = try createStore(arena.allocator(), logger, .jobs),
|
||||||
.logger = logger,
|
.logger = logger,
|
||||||
.server = .{ .logger = logger },
|
.server = .{ .logger = logger },
|
||||||
.repo = repo,
|
.repo = repo,
|
||||||
@ -391,9 +392,17 @@ fn multiFormKeyValue(allocator: std.mem.Allocator, max: usize) !*httpz.key_value
|
|||||||
return key_value;
|
return key_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createStore(allocator: std.mem.Allocator) !*jetzig.kv.Store {
|
fn createStore(
|
||||||
const store = try allocator.create(jetzig.kv.Store);
|
allocator: std.mem.Allocator,
|
||||||
store.* = try jetzig.kv.Store.init(allocator, .{});
|
logger: jetzig.loggers.Logger,
|
||||||
|
role: jetzig.kv.Store.Role,
|
||||||
|
) !*MemoryStore {
|
||||||
|
const store = try allocator.create(MemoryStore);
|
||||||
|
store.* = try MemoryStore.init(
|
||||||
|
allocator,
|
||||||
|
logger,
|
||||||
|
role,
|
||||||
|
);
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user