@rcrsr/rill 0.8.0 → 0.8.2

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 (43) hide show
  1. package/README.md +28 -0
  2. package/dist/error-registry.d.ts.map +1 -1
  3. package/dist/error-registry.js +164 -0
  4. package/dist/error-registry.js.map +1 -1
  5. package/dist/ext/crypto/index.d.ts +32 -0
  6. package/dist/ext/crypto/index.d.ts.map +1 -0
  7. package/dist/ext/crypto/index.js +143 -0
  8. package/dist/ext/crypto/index.js.map +1 -0
  9. package/dist/ext/exec/index.d.ts +45 -0
  10. package/dist/ext/exec/index.d.ts.map +1 -0
  11. package/dist/ext/exec/index.js +168 -0
  12. package/dist/ext/exec/index.js.map +1 -0
  13. package/dist/ext/exec/runner.d.ts +62 -0
  14. package/dist/ext/exec/runner.d.ts.map +1 -0
  15. package/dist/ext/exec/runner.js +168 -0
  16. package/dist/ext/exec/runner.js.map +1 -0
  17. package/dist/ext/fetch/index.d.ts +68 -0
  18. package/dist/ext/fetch/index.d.ts.map +1 -0
  19. package/dist/ext/fetch/index.js +259 -0
  20. package/dist/ext/fetch/index.js.map +1 -0
  21. package/dist/ext/fetch/request.d.ts +90 -0
  22. package/dist/ext/fetch/request.d.ts.map +1 -0
  23. package/dist/ext/fetch/request.js +413 -0
  24. package/dist/ext/fetch/request.js.map +1 -0
  25. package/dist/ext/fs/index.d.ts +39 -0
  26. package/dist/ext/fs/index.d.ts.map +1 -0
  27. package/dist/ext/fs/index.js +560 -0
  28. package/dist/ext/fs/index.js.map +1 -0
  29. package/dist/ext/fs/sandbox.d.ts +78 -0
  30. package/dist/ext/fs/sandbox.d.ts.map +1 -0
  31. package/dist/ext/fs/sandbox.js +208 -0
  32. package/dist/ext/fs/sandbox.js.map +1 -0
  33. package/dist/ext/kv/index.d.ts +46 -0
  34. package/dist/ext/kv/index.d.ts.map +1 -0
  35. package/dist/ext/kv/index.js +215 -0
  36. package/dist/ext/kv/index.js.map +1 -0
  37. package/dist/ext/kv/store.d.ts +46 -0
  38. package/dist/ext/kv/store.d.ts.map +1 -0
  39. package/dist/ext/kv/store.js +256 -0
  40. package/dist/ext/kv/store.js.map +1 -0
  41. package/dist/generated/version-data.d.ts +1 -1
  42. package/dist/generated/version-data.js +2 -2
  43. package/package.json +37 -11
@@ -0,0 +1,208 @@
1
+ /**
2
+ * fs Extension Sandbox Module
3
+ *
4
+ * Path resolution and validation implementing 9-step security sequence.
5
+ * Prevents path traversal and symlink attacks via realpath() defense.
6
+ */
7
+ import fs from 'node:fs/promises';
8
+ import path from 'node:path';
9
+ import { RuntimeError } from '../../error-classes.js';
10
+ // ============================================================
11
+ // PATH RESOLUTION
12
+ // ============================================================
13
+ /**
14
+ * Resolves and validates path within mount boundaries.
15
+ *
16
+ * 9-step path resolution sequence (spec lines 331-342):
17
+ * 1. Resolve mount name to MountConfig
18
+ * 2. Use mount's resolved physical path (from creation time)
19
+ * 3. Join resolved mount base with script's relative path argument
20
+ * 4. Normalize with path.resolve() to collapse .. segments
21
+ * 5. Resolve final path with fs.realpath() (symlink defense)
22
+ * 6. Verify resolved path starts with mount's resolved base (startsWith())
23
+ * 7. If glob set, verify filename matches pattern
24
+ * 8. Check mode permits operation
25
+ * 9. Return validated path for node:fs operation
26
+ *
27
+ * @param mountName - Mount identifier from script
28
+ * @param relativePath - Script-provided path relative to mount
29
+ * @param mounts - Mount configuration map
30
+ * @param operation - Operation type for mode validation
31
+ * @param createMode - For write operations creating new files (checks parent dir)
32
+ * @returns Validated absolute path
33
+ * @throws RuntimeError - EC-1 (unknown mount), EC-2 (path escape), EC-3 (glob), EC-4 (mode), EC-7 (permission)
34
+ */
35
+ export async function resolvePath(mountName, relativePath, mounts, operation, createMode = false) {
36
+ // Step 1: Resolve mount name to MountConfig
37
+ // EC-1: Unknown mount name
38
+ const mount = mounts[mountName];
39
+ if (!mount) {
40
+ throw new RuntimeError('RILL-R017', `mount "${mountName}" not configured`, undefined, { mountName });
41
+ }
42
+ // Step 2: Use mount's resolved physical path (set at creation time)
43
+ const mountBase = mount.resolvedPath;
44
+ if (!mountBase) {
45
+ throw new RuntimeError('RILL-R017', `mount "${mountName}" not initialized (missing resolvedPath)`, undefined, { mountName });
46
+ }
47
+ // Step 3: Join resolved mount base with script's relative path
48
+ const joined = path.join(mountBase, relativePath);
49
+ // Step 4: Normalize with path.resolve() to collapse .. segments
50
+ const normalized = path.resolve(joined);
51
+ // Step 6 (early check): Verify normalized path starts with mount base before realpath
52
+ // This catches path traversal attempts even when the target doesn't exist
53
+ // EC-2: Path escapes boundary
54
+ if (!normalized.startsWith(mountBase + path.sep) &&
55
+ normalized !== mountBase) {
56
+ throw new RuntimeError('RILL-R018', 'path escapes mount boundary', undefined, { mountName, path: relativePath, normalized, mountBase });
57
+ }
58
+ // Step 5: Resolve final path with fs.realpath() (symlink defense)
59
+ // For write operations creating new files, resolve parent directory instead
60
+ let resolvedPath;
61
+ try {
62
+ if (createMode) {
63
+ // New file write: resolve parent directory
64
+ const parentDir = path.dirname(normalized);
65
+ const resolvedParent = await fs.realpath(parentDir);
66
+ const filename = path.basename(normalized);
67
+ resolvedPath = path.join(resolvedParent, filename);
68
+ }
69
+ else {
70
+ // Existing file: resolve full path
71
+ resolvedPath = await fs.realpath(normalized);
72
+ }
73
+ }
74
+ catch (error) {
75
+ // EC-7: Permission denied or ENOENT
76
+ if (error && typeof error === 'object' && 'code' in error) {
77
+ const code = error.code;
78
+ if (code === 'EACCES' || code === 'EPERM') {
79
+ throw new RuntimeError('RILL-R021', `permission denied: ${normalized}`, undefined, { path: normalized, code });
80
+ }
81
+ // For ENOENT on createMode, this is expected (new file)
82
+ // For ENOENT on read mode, propagate as path doesn't exist
83
+ if (code === 'ENOENT') {
84
+ if (createMode) {
85
+ // Parent directory doesn't exist for new file write
86
+ throw new RuntimeError('RILL-R021', `parent directory does not exist: ${path.dirname(normalized)}`, undefined, { path: normalized });
87
+ }
88
+ else {
89
+ // File doesn't exist for read
90
+ throw new RuntimeError('RILL-R021', `file not found: ${normalized}`, undefined, { path: normalized });
91
+ }
92
+ }
93
+ }
94
+ throw error;
95
+ }
96
+ // Step 6 (post-realpath): Verify resolved path still within mount (symlink defense)
97
+ // EC-2: Path escapes boundary via symlink
98
+ if (!resolvedPath.startsWith(mountBase + path.sep) &&
99
+ resolvedPath !== mountBase) {
100
+ throw new RuntimeError('RILL-R018', 'path escapes mount boundary', undefined, { mountName, path: relativePath, resolvedPath, mountBase });
101
+ }
102
+ // Step 7: If glob set, verify filename matches pattern
103
+ // EC-3: Glob mismatch
104
+ if (mount.glob) {
105
+ const filename = path.basename(resolvedPath);
106
+ if (!matchesGlob(filename, mount.glob)) {
107
+ throw new RuntimeError('RILL-R019', `file type not permitted in mount "${mountName}"`, undefined, { mountName, glob: mount.glob, filename });
108
+ }
109
+ }
110
+ // Step 8: Check mode permits operation
111
+ // EC-4: Mode violation
112
+ if (!checkMode(mount.mode, operation)) {
113
+ throw new RuntimeError('RILL-R020', `mount "${mountName}" does not permit ${operation}`, undefined, { mountName, mode: mount.mode, operation });
114
+ }
115
+ // Step 9: Return validated path for node:fs operation
116
+ return resolvedPath;
117
+ }
118
+ // ============================================================
119
+ // GLOB MATCHING
120
+ // ============================================================
121
+ /**
122
+ * Simple glob pattern matching (spec lines 346-354).
123
+ *
124
+ * Supported patterns:
125
+ * - *.csv - Files ending in .csv
126
+ * - *.{json,yaml} - Files ending in .json or .yaml
127
+ * - * - All files (default when omitted)
128
+ * - **\/*.csv - CSV files at any depth (for find() only)
129
+ *
130
+ * No third-party glob library. Uses path.extname() checks.
131
+ *
132
+ * @param filename - Filename to match (basename only)
133
+ * @param pattern - Glob pattern
134
+ * @returns true if filename matches pattern
135
+ */
136
+ export function matchesGlob(filename, pattern) {
137
+ // Pattern: * (all files)
138
+ if (pattern === '*') {
139
+ return true;
140
+ }
141
+ // Pattern: *.ext (single extension)
142
+ if (pattern.startsWith('*.') && !pattern.includes('{')) {
143
+ const ext = pattern.slice(1); // Remove leading *
144
+ return filename.endsWith(ext);
145
+ }
146
+ // Pattern: *.{ext1,ext2} (multiple extensions)
147
+ if (pattern.startsWith('*.{') && pattern.endsWith('}')) {
148
+ const extensionsStr = pattern.slice(3, -1); // Extract between *.{ and }
149
+ const extensions = extensionsStr.split(',').map((e) => `.${e.trim()}`);
150
+ return extensions.some((ext) => filename.endsWith(ext));
151
+ }
152
+ // Pattern: **/*.ext (recursive, any depth)
153
+ if (pattern.startsWith('**/')) {
154
+ const subPattern = pattern.slice(3); // Remove leading **/
155
+ return matchesGlob(filename, subPattern);
156
+ }
157
+ // Unknown pattern: no match (conservative)
158
+ return false;
159
+ }
160
+ // ============================================================
161
+ // MODE VALIDATION
162
+ // ============================================================
163
+ /**
164
+ * Checks if mount mode permits operation.
165
+ *
166
+ * @param mode - Mount access mode
167
+ * @param operation - Operation type
168
+ * @returns true if operation permitted
169
+ */
170
+ export function checkMode(mode, operation) {
171
+ if (mode === 'read-write')
172
+ return true;
173
+ if (mode === 'read' && operation === 'read')
174
+ return true;
175
+ if (mode === 'write' && operation === 'write')
176
+ return true;
177
+ return false;
178
+ }
179
+ // ============================================================
180
+ // MOUNT INITIALIZATION
181
+ // ============================================================
182
+ /**
183
+ * Resolves mount path at creation time (Step 2 of sequence).
184
+ *
185
+ * Mutates MountConfig to set resolvedPath field.
186
+ *
187
+ * @param mount - Mount configuration
188
+ * @throws RuntimeError - If mount path invalid or inaccessible
189
+ */
190
+ export async function initializeMount(mount) {
191
+ try {
192
+ // Resolve mount path with fs.realpath() at creation time (spec line 333)
193
+ mount.resolvedPath = await fs.realpath(mount.path);
194
+ }
195
+ catch (error) {
196
+ if (error && typeof error === 'object' && 'code' in error) {
197
+ const code = error.code;
198
+ if (code === 'ENOENT') {
199
+ throw new RuntimeError('RILL-R017', `mount path does not exist: ${mount.path}`, undefined, { path: mount.path });
200
+ }
201
+ if (code === 'EACCES' || code === 'EPERM') {
202
+ throw new RuntimeError('RILL-R021', `permission denied: ${mount.path}`, undefined, { path: mount.path, code });
203
+ }
204
+ }
205
+ throw error;
206
+ }
207
+ }
208
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../src/ext/fs/sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * KV Extension Factory
3
+ *
4
+ * Provides key-value store operations with JSON persistence and schema validation.
5
+ * Supports both open mode (any key/value) and declared mode (schema-defined keys only).
6
+ */
7
+ import type { ExtensionResult } from '../../runtime/ext/extensions.js';
8
+ import { type SchemaEntry } from './store.js';
9
+ /** KV extension configuration */
10
+ export interface KvConfig {
11
+ /** Path to store file */
12
+ store: string;
13
+ /** Schema definitions (optional, enables declared mode) */
14
+ schema?: Record<string, SchemaEntry> | undefined;
15
+ /** Maximum entries (default: 10000) */
16
+ maxEntries?: number | undefined;
17
+ /** Maximum value size in bytes (default: 102400 = 100KB) */
18
+ maxValueSize?: number | undefined;
19
+ /** Maximum store size in bytes (default: 10485760 = 10MB) */
20
+ maxStoreSize?: number | undefined;
21
+ /** Write policy: 'dispose' (default) or 'immediate' */
22
+ writePolicy?: 'dispose' | 'immediate' | undefined;
23
+ }
24
+ export type { SchemaEntry };
25
+ /**
26
+ * Create KV extension with persistent storage.
27
+ *
28
+ * Initializes store by loading existing file or creating with schema defaults.
29
+ * Returns 8 functions: get, set, delete, keys, has, clear, getAll, schema.
30
+ *
31
+ * @param config - Store configuration
32
+ * @returns ExtensionResult with 8 KV functions and dispose handler
33
+ * @throws RuntimeError if store file is corrupt (EC-25)
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const kvExt = createKvExtension({
38
+ * store: './data/state.json',
39
+ * schema: {
40
+ * count: { type: 'number', default: 0 }
41
+ * }
42
+ * });
43
+ * ```
44
+ */
45
+ export declare function createKvExtension(config: KvConfig): ExtensionResult;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ext/kv/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAM3D,iCAAiC;AACjC,MAAM,WAAW,QAAQ;IACvB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,SAAS,CAAC;IACjD,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,uDAAuD;IACvD,WAAW,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;CACnD;AAGD,YAAY,EAAE,WAAW,EAAE,CAAC;AAM5B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,CA2MnE"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * KV Extension Factory
3
+ *
4
+ * Provides key-value store operations with JSON persistence and schema validation.
5
+ * Supports both open mode (any key/value) and declared mode (schema-defined keys only).
6
+ */
7
+ import { createStore } from './store.js';
8
+ // ============================================================
9
+ // FACTORY
10
+ // ============================================================
11
+ /**
12
+ * Create KV extension with persistent storage.
13
+ *
14
+ * Initializes store by loading existing file or creating with schema defaults.
15
+ * Returns 8 functions: get, set, delete, keys, has, clear, getAll, schema.
16
+ *
17
+ * @param config - Store configuration
18
+ * @returns ExtensionResult with 8 KV functions and dispose handler
19
+ * @throws RuntimeError if store file is corrupt (EC-25)
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const kvExt = createKvExtension({
24
+ * store: './data/state.json',
25
+ * schema: {
26
+ * count: { type: 'number', default: 0 }
27
+ * }
28
+ * });
29
+ * ```
30
+ */
31
+ export function createKvExtension(config) {
32
+ // Apply defaults
33
+ const maxEntries = config.maxEntries ?? 10000;
34
+ const maxValueSize = config.maxValueSize ?? 102400; // 100KB
35
+ const maxStoreSize = config.maxStoreSize ?? 10485760; // 10MB
36
+ const writePolicy = config.writePolicy ?? 'dispose';
37
+ // Store will be initialized lazily on first operation
38
+ let storePromise = null;
39
+ let storeInstance = null;
40
+ // Helper to get or create store
41
+ const getStore = async () => {
42
+ if (storeInstance)
43
+ return storeInstance;
44
+ if (!storePromise) {
45
+ storePromise = createStore({
46
+ store: config.store,
47
+ schema: config.schema,
48
+ maxEntries,
49
+ maxValueSize,
50
+ maxStoreSize,
51
+ writePolicy,
52
+ });
53
+ }
54
+ storeInstance = await storePromise;
55
+ return storeInstance;
56
+ };
57
+ // ============================================================
58
+ // FUNCTIONS
59
+ // ============================================================
60
+ /**
61
+ * Get value or schema default.
62
+ * IR-15, EC-20 (key not in schema)
63
+ * Returns empty string for missing keys in open mode.
64
+ */
65
+ const get = async (args) => {
66
+ const store = await getStore();
67
+ const key = args[0];
68
+ const value = store.get(key);
69
+ // In rill, functions cannot return undefined - return empty string for missing keys in open mode
70
+ return value !== undefined ? value : '';
71
+ };
72
+ /**
73
+ * Set value with validation.
74
+ * IR-16, EC-20-24
75
+ */
76
+ const set = async (args) => {
77
+ const store = await getStore();
78
+ const key = args[0];
79
+ const value = args[1];
80
+ await store.set(key, value);
81
+ return true;
82
+ };
83
+ /**
84
+ * Delete key.
85
+ * IR-17
86
+ */
87
+ const deleteKey = async (args) => {
88
+ const store = await getStore();
89
+ const key = args[0];
90
+ return store.delete(key);
91
+ };
92
+ /**
93
+ * Get all keys.
94
+ * IR-18
95
+ */
96
+ const keys = async () => {
97
+ const store = await getStore();
98
+ return store.keys();
99
+ };
100
+ /**
101
+ * Check key existence.
102
+ * IR-19
103
+ */
104
+ const has = async (args) => {
105
+ const store = await getStore();
106
+ const key = args[0];
107
+ return store.has(key);
108
+ };
109
+ /**
110
+ * Clear all keys (restores schema defaults if declared mode).
111
+ * IR-20
112
+ */
113
+ const clear = async () => {
114
+ const store = await getStore();
115
+ store.clear();
116
+ return true;
117
+ };
118
+ /**
119
+ * Get all entries as dict.
120
+ * IR-21
121
+ */
122
+ const getAll = async () => {
123
+ const store = await getStore();
124
+ return store.getAll();
125
+ };
126
+ /**
127
+ * Get schema information (empty list in open mode).
128
+ * IR-22
129
+ */
130
+ const schema = async () => {
131
+ if (!config.schema) {
132
+ return []; // Open mode - no schema
133
+ }
134
+ // Declared mode - return schema entries as list of dicts
135
+ const result = [];
136
+ for (const [key, entry] of Object.entries(config.schema)) {
137
+ result.push({
138
+ key,
139
+ type: entry.type,
140
+ description: entry.description ?? '',
141
+ });
142
+ }
143
+ return result;
144
+ };
145
+ // ============================================================
146
+ // DISPOSE
147
+ // ============================================================
148
+ /**
149
+ * Flush state to disk on dispose.
150
+ */
151
+ const dispose = async () => {
152
+ if (storeInstance) {
153
+ await storeInstance.flush();
154
+ }
155
+ };
156
+ // ============================================================
157
+ // EXTENSION RESULT
158
+ // ============================================================
159
+ const result = {
160
+ get: {
161
+ params: [{ name: 'key', type: 'string', description: 'Key to retrieve' }],
162
+ fn: get,
163
+ description: 'Get value or schema default',
164
+ returnType: 'any',
165
+ },
166
+ set: {
167
+ params: [
168
+ { name: 'key', type: 'string', description: 'Key to set' },
169
+ { name: 'value', type: 'string', description: 'Value to store' },
170
+ ],
171
+ fn: set,
172
+ description: 'Set value with validation',
173
+ returnType: 'bool',
174
+ },
175
+ delete: {
176
+ params: [{ name: 'key', type: 'string', description: 'Key to delete' }],
177
+ fn: deleteKey,
178
+ description: 'Delete key',
179
+ returnType: 'bool',
180
+ },
181
+ keys: {
182
+ params: [],
183
+ fn: keys,
184
+ description: 'Get all keys',
185
+ returnType: 'list',
186
+ },
187
+ has: {
188
+ params: [{ name: 'key', type: 'string', description: 'Key to check' }],
189
+ fn: has,
190
+ description: 'Check key existence',
191
+ returnType: 'bool',
192
+ },
193
+ clear: {
194
+ params: [],
195
+ fn: clear,
196
+ description: 'Clear all keys',
197
+ returnType: 'bool',
198
+ },
199
+ getAll: {
200
+ params: [],
201
+ fn: getAll,
202
+ description: 'Get all entries as dict',
203
+ returnType: 'dict',
204
+ },
205
+ schema: {
206
+ params: [],
207
+ fn: schema,
208
+ description: 'Get schema information',
209
+ returnType: 'list',
210
+ },
211
+ };
212
+ result.dispose = dispose;
213
+ return result;
214
+ }
215
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ext/kv/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * KV Store Implementation
3
+ *
4
+ * Provides JSON-based key-value persistence with schema validation.
5
+ * Lifecycle: Load (read store file) -> Execute (in-memory operations) -> Flush (atomic write on dispose)
6
+ */
7
+ import type { RillValue } from '../../runtime/core/values.js';
8
+ /** Schema entry defining type and default for a key */
9
+ export interface SchemaEntry {
10
+ type: 'string' | 'number' | 'bool' | 'list' | 'dict';
11
+ default: RillValue;
12
+ description?: string | undefined;
13
+ }
14
+ /** Store configuration */
15
+ export interface StoreConfig {
16
+ /** Path to store file */
17
+ store: string;
18
+ /** Schema definitions (optional, enables declared mode) */
19
+ schema?: Record<string, SchemaEntry> | undefined;
20
+ /** Maximum entries (default: 10000) */
21
+ maxEntries?: number | undefined;
22
+ /** Maximum value size in bytes (default: 102400 = 100KB) */
23
+ maxValueSize?: number | undefined;
24
+ /** Maximum store size in bytes (default: 10485760 = 10MB) */
25
+ maxStoreSize?: number | undefined;
26
+ /** Write policy: 'dispose' (default) or 'immediate' */
27
+ writePolicy?: 'dispose' | 'immediate' | undefined;
28
+ }
29
+ /**
30
+ * Create KV store with JSON persistence.
31
+ *
32
+ * @param config - Store configuration
33
+ * @returns Store operations object
34
+ * @throws RuntimeError if store file is corrupt (EC-25)
35
+ */
36
+ export declare function createStore(config: StoreConfig): Promise<{
37
+ get: (key: string) => RillValue | undefined;
38
+ set: (key: string, value: RillValue) => Promise<void>;
39
+ delete: (key: string) => boolean;
40
+ keys: () => string[];
41
+ has: (key: string) => boolean;
42
+ clear: () => void;
43
+ getAll: () => Record<string, RillValue>;
44
+ flush: () => Promise<void>;
45
+ }>;
46
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/ext/kv/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAM9D,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACrD,OAAO,EAAE,SAAS,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,SAAS,CAAC;IACjD,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,uDAAuD;IACvD,WAAW,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;CACnD;AAMD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC;IAC9D,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,CAAC;IAC5C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,IAAI,EAAE,MAAM,MAAM,EAAE,CAAC;IACrB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAmTD"}