@rcrsr/rill-ext-kv-redis 0.8.5

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andre Bremer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @rcrsr/rill-ext-kv-redis
2
+
3
+ Redis kv backend implementation for rill scripting language.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @rcrsr/rill-ext-kv-redis
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { createRedisKvExtension } from '@rcrsr/rill-ext-kv-redis';
15
+
16
+ const extension = createRedisKvExtension({
17
+ url: 'redis://localhost:6379',
18
+ mounts: {
19
+ // Mount configurations
20
+ },
21
+ maxStoreSize: 1000000,
22
+ writePolicy: 'dispose',
23
+ });
24
+ ```
25
+
26
+ ## Documentation
27
+
28
+ Full documentation available at [rill.run/docs/extensions/kv-redis/](https://rill.run/docs/extensions/kv-redis/)
29
+
30
+ ## License
31
+
32
+ MIT
@@ -0,0 +1,195 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ import { ExtensionResult, SchemaEntry } from '@rcrsr/rill';
4
+
5
+ /**
6
+ * Configuration for a single Redis kv mount.
7
+ *
8
+ * Extends common kv mount configuration with Redis-specific fields.
9
+ * Each mount represents a key prefix in Redis.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const mountConfig: RedisKvMountConfig = {
14
+ * mode: 'read-write',
15
+ * prefix: 'app:user:',
16
+ * schema: {
17
+ * name: { type: 'string', default: '' },
18
+ * count: { type: 'number', default: 0 }
19
+ * },
20
+ * maxEntries: 5000,
21
+ * maxValueSize: 50000,
22
+ * ttl: 3600
23
+ * };
24
+ * ```
25
+ */
26
+ export interface RedisKvMountConfig {
27
+ /**
28
+ * Access mode for this mount.
29
+ *
30
+ * - 'read': Read-only access (set/delete/clear operations throw errors)
31
+ * - 'write': Write-only access (get operations throw errors)
32
+ * - 'read-write': Full access to all operations
33
+ */
34
+ readonly mode: "read" | "write" | "read-write";
35
+ /**
36
+ * Key prefix for this mount.
37
+ *
38
+ * All keys in this mount are prefixed with this string to namespace them.
39
+ * Different mounts can use different prefixes to organize keys.
40
+ *
41
+ * @example 'app:user:'
42
+ */
43
+ readonly prefix: string;
44
+ /**
45
+ * Schema definitions (optional).
46
+ *
47
+ * When provided, enables declared mode:
48
+ * - Only declared keys can be accessed
49
+ * - Type validation enforced on set operations
50
+ * - Missing keys return schema defaults
51
+ *
52
+ * When undefined, enables open mode:
53
+ * - Any key can be accessed
54
+ * - No type validation
55
+ * - Missing keys return empty string
56
+ */
57
+ readonly schema?: Record<string, SchemaEntry> | undefined;
58
+ /**
59
+ * Maximum number of entries allowed in this mount.
60
+ *
61
+ * Defaults to 10000.
62
+ * Set operations throw when limit exceeded.
63
+ */
64
+ readonly maxEntries?: number | undefined;
65
+ /**
66
+ * Maximum value size in bytes (JSON-encoded).
67
+ *
68
+ * Defaults to 102400 bytes (100 KB).
69
+ * Set operations throw when value exceeds limit.
70
+ */
71
+ readonly maxValueSize?: number | undefined;
72
+ /**
73
+ * Time-to-live in seconds for keys in this mount.
74
+ *
75
+ * Optional - when undefined, keys do not expire.
76
+ * When set, keys are automatically deleted after TTL expires.
77
+ *
78
+ * @example 3600 // 1 hour
79
+ */
80
+ readonly ttl?: number | undefined;
81
+ }
82
+ /**
83
+ * Configuration options for Redis kv extension.
84
+ *
85
+ * Defines Redis connection URL, mount configurations, size limits,
86
+ * and write policies for Redis-backed key-value storage.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // Multi-mount configuration
91
+ * const config: RedisKvConfig = {
92
+ * url: 'redis://localhost:6379',
93
+ * mounts: {
94
+ * user: {
95
+ * mode: 'read-write',
96
+ * prefix: 'app:user:',
97
+ * schema: { name: { type: 'string', default: '' } },
98
+ * ttl: 3600
99
+ * },
100
+ * cache: {
101
+ * mode: 'read-write',
102
+ * prefix: 'app:cache:',
103
+ * ttl: 300
104
+ * }
105
+ * },
106
+ * maxStoreSize: 5242880,
107
+ * writePolicy: 'immediate'
108
+ * };
109
+ * ```
110
+ */
111
+ export interface RedisKvConfig {
112
+ /**
113
+ * Redis connection URL.
114
+ *
115
+ * Required - specifies the Redis server connection string.
116
+ * Supports standard Redis URL format.
117
+ *
118
+ * @example 'redis://localhost:6379'
119
+ * @example 'redis://user:pass@host:port/db'
120
+ */
121
+ readonly url: string;
122
+ /**
123
+ * Mount definitions keyed by mount name.
124
+ *
125
+ * Each mount corresponds to a key prefix in Redis.
126
+ * Mount names are used in kv function calls (e.g., `kv::get('user', 'name')`).
127
+ *
128
+ * Required field - at least one mount must be defined.
129
+ */
130
+ readonly mounts: Record<string, RedisKvMountConfig>;
131
+ /**
132
+ * Maximum total store size in bytes across all mounts.
133
+ *
134
+ * Defaults to 10485760 bytes (10 MB).
135
+ * This limit applies to the sum of all values in all mounts.
136
+ * Set operations throw when total size exceeds limit.
137
+ */
138
+ readonly maxStoreSize?: number | undefined;
139
+ /**
140
+ * Write policy for Redis operations.
141
+ *
142
+ * - 'dispose': Changes buffered in memory, flushed on extension dispose (default)
143
+ * - 'immediate': Each set operation writes to Redis immediately
144
+ *
145
+ * Immediate mode trades performance for durability.
146
+ * Dispose mode is faster but risks data loss if process crashes.
147
+ */
148
+ readonly writePolicy?: "dispose" | "immediate" | undefined;
149
+ }
150
+ /**
151
+ * Configuration for Redis kv extension factory.
152
+ */
153
+ export interface RedisKvExtensionConfig {
154
+ /** Redis connection URL (e.g., "redis://localhost:6379") */
155
+ readonly url: string;
156
+ /** Mount point configurations */
157
+ readonly mounts: Record<string, RedisKvMountConfig>;
158
+ /** Maximum store size in bytes (optional) */
159
+ readonly maxStoreSize?: number | undefined;
160
+ /** Write policy: 'dispose' (write on dispose) or 'immediate' (write immediately) */
161
+ readonly writePolicy?: "dispose" | "immediate" | undefined;
162
+ }
163
+ /**
164
+ * Creates a Redis kv backend extension for rill.
165
+ *
166
+ * Connects to Redis server and validates configuration.
167
+ * Throws error for invalid connection string or unreachable server.
168
+ *
169
+ * @param config - Extension configuration
170
+ * @returns Extension result with kv functions and dispose
171
+ * @throws Error for invalid configuration (AC-10)
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * const ext = createRedisKvExtension({
176
+ * url: 'redis://localhost:6379',
177
+ * mounts: {
178
+ * user: {
179
+ * mode: 'read-write',
180
+ * prefix: 'app:user:',
181
+ * schema: { name: { type: 'string', default: '' } }
182
+ * }
183
+ * }
184
+ * });
185
+ * // Use with rill runtime...
186
+ * await ext.dispose();
187
+ * ```
188
+ */
189
+ export declare function createRedisKvExtension(config: RedisKvExtensionConfig): ExtensionResult;
190
+
191
+ export {
192
+ SchemaEntry,
193
+ };
194
+
195
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,425 @@
1
+ // src/factory.ts
2
+ import { Redis } from "ioredis";
3
+ function createRedisKvExtension(config) {
4
+ if (!config.mounts || Object.keys(config.mounts).length === 0) {
5
+ throw new Error(
6
+ "Redis kv extension requires at least one mount in configuration"
7
+ );
8
+ }
9
+ if (!config.url || typeof config.url !== "string") {
10
+ throw new Error("Redis kv extension requires a valid connection URL");
11
+ }
12
+ if (!config.url.startsWith("redis://") && !config.url.startsWith("rediss://")) {
13
+ throw new Error(
14
+ `Invalid Redis connection URL: must start with redis:// or rediss:// (got: ${config.url})`
15
+ );
16
+ }
17
+ const prefixes = Object.entries(config.mounts).map(([name, cfg]) => ({
18
+ name,
19
+ prefix: cfg.prefix
20
+ }));
21
+ for (let i = 0; i < prefixes.length; i++) {
22
+ for (let j = i + 1; j < prefixes.length; j++) {
23
+ const a = prefixes[i];
24
+ const b = prefixes[j];
25
+ if (a.prefix.startsWith(b.prefix) || b.prefix.startsWith(a.prefix)) {
26
+ throw new Error(
27
+ `Mount prefix overlap detected: "${a.name}" (${a.prefix}) and "${b.name}" (${b.prefix})`
28
+ );
29
+ }
30
+ }
31
+ }
32
+ let client;
33
+ try {
34
+ client = new Redis(config.url);
35
+ } catch (error) {
36
+ throw new Error(
37
+ `Failed to create Redis client: ${error instanceof Error ? error.message : String(error)}`
38
+ );
39
+ }
40
+ client.on("error", (err) => {
41
+ console.error("Redis connection error:", err);
42
+ });
43
+ function getMountConfig(mountName) {
44
+ const mountConfig = config.mounts[mountName];
45
+ if (!mountConfig) {
46
+ throw new Error(
47
+ `Mount '${mountName}' not found. Available mounts: ${Object.keys(config.mounts).join(", ")}`
48
+ );
49
+ }
50
+ return mountConfig;
51
+ }
52
+ function buildKey(prefix, key) {
53
+ return `${prefix}${key}`;
54
+ }
55
+ function checkWritePermission(mountName, mode) {
56
+ if (mode === "read") {
57
+ throw new Error(`Mount '${mountName}' is read-only (mode: ${mode})`);
58
+ }
59
+ }
60
+ function calculateValueSize(value) {
61
+ return Buffer.byteLength(JSON.stringify(value), "utf-8");
62
+ }
63
+ function validateType(key, value, expectedType) {
64
+ let actualType;
65
+ if (typeof value === "string") {
66
+ actualType = "string";
67
+ } else if (typeof value === "number") {
68
+ actualType = "number";
69
+ } else if (typeof value === "boolean") {
70
+ actualType = "bool";
71
+ } else if (Array.isArray(value)) {
72
+ actualType = "list";
73
+ } else if (typeof value === "object" && value !== null) {
74
+ actualType = "dict";
75
+ } else {
76
+ actualType = typeof value;
77
+ }
78
+ if (actualType !== expectedType) {
79
+ throw new Error(
80
+ `key "${key}" expects ${expectedType}, got ${actualType}`
81
+ );
82
+ }
83
+ }
84
+ function isDict(value) {
85
+ return typeof value === "object" && value !== null && !Array.isArray(value);
86
+ }
87
+ const get = async (args) => {
88
+ const mountName = args[0];
89
+ const key = args[1];
90
+ const mountConfig = getMountConfig(mountName);
91
+ if (mountConfig.schema && !(key in mountConfig.schema)) {
92
+ throw new Error(`key "${key}" not declared in schema`);
93
+ }
94
+ const redisKey = buildKey(mountConfig.prefix, key);
95
+ const value = await client.get(redisKey);
96
+ if (value !== null) {
97
+ return JSON.parse(value);
98
+ }
99
+ if (mountConfig.schema && key in mountConfig.schema) {
100
+ return mountConfig.schema[key].default;
101
+ }
102
+ return "";
103
+ };
104
+ const get_or = async (args) => {
105
+ const mountName = args[0];
106
+ const key = args[1];
107
+ const fallback = args[2];
108
+ const mountConfig = getMountConfig(mountName);
109
+ const redisKey = buildKey(mountConfig.prefix, key);
110
+ const value = await client.get(redisKey);
111
+ if (value !== null) {
112
+ return JSON.parse(value);
113
+ }
114
+ return fallback;
115
+ };
116
+ const set = async (args) => {
117
+ const mountName = args[0];
118
+ const key = args[1];
119
+ const value = args[2];
120
+ const mountConfig = getMountConfig(mountName);
121
+ checkWritePermission(mountName, mountConfig.mode);
122
+ if (mountConfig.schema && !(key in mountConfig.schema)) {
123
+ throw new Error(`key "${key}" not declared in schema`);
124
+ }
125
+ if (mountConfig.schema && key in mountConfig.schema) {
126
+ validateType(key, value, mountConfig.schema[key].type);
127
+ }
128
+ const maxValueSize = mountConfig.maxValueSize ?? 102400;
129
+ const valueSize = calculateValueSize(value);
130
+ if (valueSize > maxValueSize) {
131
+ throw new Error(
132
+ `value for "${key}" exceeds size limit (${valueSize} > ${maxValueSize})`
133
+ );
134
+ }
135
+ const maxEntries = mountConfig.maxEntries ?? 1e4;
136
+ const redisKey = buildKey(mountConfig.prefix, key);
137
+ const exists = await client.exists(redisKey) === 1;
138
+ if (!exists) {
139
+ const pattern = `${mountConfig.prefix}*`;
140
+ const keys2 = await scanKeys(pattern);
141
+ if (keys2.length >= maxEntries) {
142
+ throw new Error(
143
+ `store exceeds entry limit (${keys2.length + 1} > ${maxEntries})`
144
+ );
145
+ }
146
+ }
147
+ const serialized = JSON.stringify(value);
148
+ if (mountConfig.ttl) {
149
+ await client.setex(redisKey, mountConfig.ttl, serialized);
150
+ } else {
151
+ await client.set(redisKey, serialized);
152
+ }
153
+ return true;
154
+ };
155
+ const merge = async (args) => {
156
+ const mountName = args[0];
157
+ const key = args[1];
158
+ const partial = args[2];
159
+ const mountConfig = getMountConfig(mountName);
160
+ checkWritePermission(mountName, mountConfig.mode);
161
+ const redisKey = buildKey(mountConfig.prefix, key);
162
+ let retries = 0;
163
+ const MAX_RETRIES = 10;
164
+ while (retries < MAX_RETRIES) {
165
+ await client.watch(redisKey);
166
+ try {
167
+ const currentValueStr = await client.get(redisKey);
168
+ let currentValue;
169
+ if (currentValueStr !== null) {
170
+ currentValue = JSON.parse(currentValueStr);
171
+ if (!isDict(currentValue)) {
172
+ await client.unwatch();
173
+ throw new Error(`Cannot merge into non-dict value at key "${key}"`);
174
+ }
175
+ }
176
+ const mergedValue = {
177
+ ...currentValue,
178
+ ...partial
179
+ };
180
+ if (mountConfig.schema && key in mountConfig.schema) {
181
+ validateType(key, mergedValue, mountConfig.schema[key].type);
182
+ }
183
+ const maxValueSize = mountConfig.maxValueSize ?? 102400;
184
+ const valueSize = calculateValueSize(mergedValue);
185
+ if (valueSize > maxValueSize) {
186
+ await client.unwatch();
187
+ throw new Error(
188
+ `merged value for "${key}" exceeds size limit (${valueSize} > ${maxValueSize})`
189
+ );
190
+ }
191
+ const serialized = JSON.stringify(mergedValue);
192
+ const result2 = await client.multi().set(redisKey, serialized).exec();
193
+ if (result2 !== null) {
194
+ if (mountConfig.ttl) {
195
+ await client.expire(redisKey, mountConfig.ttl);
196
+ }
197
+ return true;
198
+ }
199
+ retries++;
200
+ } catch (error) {
201
+ await client.unwatch();
202
+ throw error;
203
+ }
204
+ }
205
+ throw new Error(
206
+ `Failed to merge after ${MAX_RETRIES} retries due to concurrent modifications`
207
+ );
208
+ };
209
+ const deleteKey = async (args) => {
210
+ const mountName = args[0];
211
+ const key = args[1];
212
+ const mountConfig = getMountConfig(mountName);
213
+ checkWritePermission(mountName, mountConfig.mode);
214
+ const redisKey = buildKey(mountConfig.prefix, key);
215
+ const result2 = await client.del(redisKey);
216
+ return result2 > 0;
217
+ };
218
+ async function scanKeys(pattern) {
219
+ const keys2 = [];
220
+ let cursor = "0";
221
+ do {
222
+ const [nextCursor, foundKeys] = await client.scan(
223
+ cursor,
224
+ "MATCH",
225
+ pattern,
226
+ "COUNT",
227
+ 100
228
+ );
229
+ cursor = nextCursor;
230
+ keys2.push(...foundKeys);
231
+ } while (cursor !== "0");
232
+ return keys2;
233
+ }
234
+ const keys = async (args) => {
235
+ const mountName = args[0];
236
+ const mountConfig = getMountConfig(mountName);
237
+ const pattern = `${mountConfig.prefix}*`;
238
+ const redisKeys = await scanKeys(pattern);
239
+ const prefixLen = mountConfig.prefix.length;
240
+ return redisKeys.map((k) => k.substring(prefixLen));
241
+ };
242
+ const has = async (args) => {
243
+ const mountName = args[0];
244
+ const key = args[1];
245
+ const mountConfig = getMountConfig(mountName);
246
+ const redisKey = buildKey(mountConfig.prefix, key);
247
+ const exists = await client.exists(redisKey);
248
+ return exists === 1;
249
+ };
250
+ const clear = async (args) => {
251
+ const mountName = args[0];
252
+ const mountConfig = getMountConfig(mountName);
253
+ checkWritePermission(mountName, mountConfig.mode);
254
+ const pattern = `${mountConfig.prefix}*`;
255
+ const redisKeys = await scanKeys(pattern);
256
+ if (redisKeys.length > 0) {
257
+ await client.del(...redisKeys);
258
+ }
259
+ if (mountConfig.schema) {
260
+ const pipeline = client.pipeline();
261
+ for (const [key, entry] of Object.entries(mountConfig.schema)) {
262
+ const redisKey = buildKey(mountConfig.prefix, key);
263
+ const serialized = JSON.stringify(entry.default);
264
+ if (mountConfig.ttl) {
265
+ pipeline.setex(redisKey, mountConfig.ttl, serialized);
266
+ } else {
267
+ pipeline.set(redisKey, serialized);
268
+ }
269
+ }
270
+ await pipeline.exec();
271
+ }
272
+ return true;
273
+ };
274
+ const getAll = async (args) => {
275
+ const mountName = args[0];
276
+ const mountConfig = getMountConfig(mountName);
277
+ const pattern = `${mountConfig.prefix}*`;
278
+ const redisKeys = await scanKeys(pattern);
279
+ if (redisKeys.length === 0) {
280
+ return {};
281
+ }
282
+ const values = await client.mget(...redisKeys);
283
+ const result2 = {};
284
+ const prefixLen = mountConfig.prefix.length;
285
+ for (let i = 0; i < redisKeys.length; i++) {
286
+ const key = redisKeys[i].substring(prefixLen);
287
+ const value = values[i];
288
+ if (value !== null && value !== void 0) {
289
+ result2[key] = JSON.parse(value);
290
+ }
291
+ }
292
+ return result2;
293
+ };
294
+ const schema = (args) => {
295
+ const mountName = args[0];
296
+ const mountConfig = getMountConfig(mountName);
297
+ if (!mountConfig.schema) {
298
+ return [];
299
+ }
300
+ const result2 = [];
301
+ for (const [key, entry] of Object.entries(mountConfig.schema)) {
302
+ result2.push({
303
+ key,
304
+ type: entry.type,
305
+ description: entry.description ?? ""
306
+ });
307
+ }
308
+ return result2;
309
+ };
310
+ const mountsList = () => {
311
+ const result2 = [];
312
+ for (const [name, mountConfig] of Object.entries(config.mounts)) {
313
+ result2.push({
314
+ name,
315
+ mode: mountConfig.mode,
316
+ schema: mountConfig.schema ? "declared" : "open",
317
+ maxEntries: mountConfig.maxEntries ?? 1e4,
318
+ maxValueSize: mountConfig.maxValueSize ?? 102400,
319
+ prefix: mountConfig.prefix,
320
+ ttl: mountConfig.ttl ?? 0
321
+ });
322
+ }
323
+ return result2;
324
+ };
325
+ const result = {
326
+ get: {
327
+ params: [
328
+ { name: "mount", type: "string", description: "Mount name" },
329
+ { name: "key", type: "string", description: "Key to retrieve" }
330
+ ],
331
+ fn: get,
332
+ description: "Get value or schema default",
333
+ returnType: "any"
334
+ },
335
+ get_or: {
336
+ params: [
337
+ { name: "mount", type: "string", description: "Mount name" },
338
+ { name: "key", type: "string", description: "Key to retrieve" },
339
+ {
340
+ name: "fallback",
341
+ type: "dict",
342
+ description: "Fallback value if key missing"
343
+ }
344
+ ],
345
+ fn: get_or,
346
+ description: "Get value or return fallback if key missing",
347
+ returnType: "any"
348
+ },
349
+ set: {
350
+ params: [
351
+ { name: "mount", type: "string", description: "Mount name" },
352
+ { name: "key", type: "string", description: "Key to set" },
353
+ { name: "value", type: "string", description: "Value to store" }
354
+ ],
355
+ fn: set,
356
+ description: "Set value with validation",
357
+ returnType: "bool"
358
+ },
359
+ merge: {
360
+ params: [
361
+ { name: "mount", type: "string", description: "Mount name" },
362
+ { name: "key", type: "string", description: "Key to merge into" },
363
+ { name: "partial", type: "dict", description: "Partial dict to merge" }
364
+ ],
365
+ fn: merge,
366
+ description: "Merge partial dict into existing dict value",
367
+ returnType: "bool"
368
+ },
369
+ delete: {
370
+ params: [
371
+ { name: "mount", type: "string", description: "Mount name" },
372
+ { name: "key", type: "string", description: "Key to delete" }
373
+ ],
374
+ fn: deleteKey,
375
+ description: "Delete key",
376
+ returnType: "bool"
377
+ },
378
+ keys: {
379
+ params: [{ name: "mount", type: "string", description: "Mount name" }],
380
+ fn: keys,
381
+ description: "Get all keys in mount",
382
+ returnType: "list"
383
+ },
384
+ has: {
385
+ params: [
386
+ { name: "mount", type: "string", description: "Mount name" },
387
+ { name: "key", type: "string", description: "Key to check" }
388
+ ],
389
+ fn: has,
390
+ description: "Check key existence",
391
+ returnType: "bool"
392
+ },
393
+ clear: {
394
+ params: [{ name: "mount", type: "string", description: "Mount name" }],
395
+ fn: clear,
396
+ description: "Clear all keys in mount",
397
+ returnType: "bool"
398
+ },
399
+ getAll: {
400
+ params: [{ name: "mount", type: "string", description: "Mount name" }],
401
+ fn: getAll,
402
+ description: "Get all entries as dict",
403
+ returnType: "dict"
404
+ },
405
+ schema: {
406
+ params: [{ name: "mount", type: "string", description: "Mount name" }],
407
+ fn: schema,
408
+ description: "Get schema information",
409
+ returnType: "list"
410
+ },
411
+ mounts: {
412
+ params: [],
413
+ fn: mountsList,
414
+ description: "Get list of mount metadata",
415
+ returnType: "list"
416
+ }
417
+ };
418
+ result.dispose = async () => {
419
+ await client.quit();
420
+ };
421
+ return result;
422
+ }
423
+ export {
424
+ createRedisKvExtension
425
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@rcrsr/rill-ext-kv-redis",
3
+ "version": "0.8.5",
4
+ "description": "rill extension for Redis kv backend implementation",
5
+ "license": "MIT",
6
+ "author": "Andre Bremer",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "keywords": [
11
+ "rill",
12
+ "redis",
13
+ "kv",
14
+ "key-value",
15
+ "storage",
16
+ "extension",
17
+ "scripting"
18
+ ],
19
+ "peerDependencies": {
20
+ "@rcrsr/rill": "^0.8.5"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.2.3",
24
+ "dts-bundle-generator": "^9.5.1",
25
+ "tsup": "^8.5.0",
26
+ "undici-types": "^7.21.0",
27
+ "@rcrsr/rill": "^0.8.5"
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/rcrsr/rill.git",
35
+ "directory": "packages/ext/kv-redis"
36
+ },
37
+ "homepage": "https://rill.run/docs/extensions/kv-redis/",
38
+ "bugs": {
39
+ "url": "https://github.com/rcrsr/rill/issues"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "ioredis": "^5.4.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup && dts-bundle-generator --config dts-bundle-generator.config.cjs",
49
+ "test": "vitest run",
50
+ "typecheck": "tsc --noEmit",
51
+ "lint": "eslint --config ../../../eslint.config.js src/",
52
+ "check": "pnpm run build && pnpm run test && pnpm run lint"
53
+ }
54
+ }