ruggy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +315 -0
- package/assets/icon.jpeg +0 -0
- package/index.d.ts +288 -0
- package/lib/ruggy.dll +0 -0
- package/package.json +42 -0
- package/src/Collection.js +176 -0
- package/src/Database.js +217 -0
- package/src/Pool.js +251 -0
- package/src/bindings.js +34 -0
- package/src/config.js +124 -0
- package/src/index.js +58 -0
- package/src/utils.js +73 -0
package/src/bindings.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const koffi = require('koffi');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Load DLL
|
|
5
|
+
const dllPath = path.join(__dirname, '..', 'lib', 'ruggy.dll');
|
|
6
|
+
const lib = koffi.load(dllPath);
|
|
7
|
+
|
|
8
|
+
// Database operations
|
|
9
|
+
const ruggy_open = lib.func('ruggy_open', 'void *', ['string']);
|
|
10
|
+
const ruggy_db_free = lib.func('ruggy_db_free', 'void', ['void *']);
|
|
11
|
+
|
|
12
|
+
// Collection operations
|
|
13
|
+
const ruggy_get_collection = lib.func('ruggy_get_collection', 'void *', ['void *', 'string']);
|
|
14
|
+
const ruggy_col_free = lib.func('ruggy_col_free', 'void', ['void *']);
|
|
15
|
+
|
|
16
|
+
// Data operations
|
|
17
|
+
const ruggy_insert = lib.func('ruggy_insert', 'void *', ['void *', 'string']);
|
|
18
|
+
const ruggy_find_all = lib.func('ruggy_find_all', 'void *', ['void *']);
|
|
19
|
+
const ruggy_find = lib.func('ruggy_find', 'void *', ['void *', 'string', 'string']);
|
|
20
|
+
|
|
21
|
+
// Memory management
|
|
22
|
+
const ruggy_str_free = lib.func('ruggy_str_free', 'void', ['void *']);
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
koffi,
|
|
26
|
+
ruggy_open,
|
|
27
|
+
ruggy_db_free,
|
|
28
|
+
ruggy_get_collection,
|
|
29
|
+
ruggy_col_free,
|
|
30
|
+
ruggy_insert,
|
|
31
|
+
ruggy_find_all,
|
|
32
|
+
ruggy_find,
|
|
33
|
+
ruggy_str_free
|
|
34
|
+
};
|
package/src/config.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const yaml = require('js-yaml');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cached configuration to avoid repeated file system lookups
|
|
7
|
+
* @type {Object|null}
|
|
8
|
+
*/
|
|
9
|
+
let cachedConfig = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration values
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
dataPath: './data'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Searches for ruggy.yaml starting from current directory up to root
|
|
20
|
+
* @param {string} startDir - Directory to start searching from
|
|
21
|
+
* @returns {string|null} - Path to ruggy.yaml or null if not found
|
|
22
|
+
*/
|
|
23
|
+
function findConfigFile(startDir = process.cwd()) {
|
|
24
|
+
let currentDir = path.resolve(startDir);
|
|
25
|
+
const root = path.parse(currentDir).root;
|
|
26
|
+
|
|
27
|
+
while (true) {
|
|
28
|
+
const configPath = path.join(currentDir, 'ruggy.yaml');
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(configPath)) {
|
|
31
|
+
return configPath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check for .yml variant
|
|
35
|
+
const altConfigPath = path.join(currentDir, 'ruggy.yml');
|
|
36
|
+
if (fs.existsSync(altConfigPath)) {
|
|
37
|
+
return altConfigPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Reached root without finding config
|
|
41
|
+
if (currentDir === root) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Move up one directory
|
|
46
|
+
currentDir = path.dirname(currentDir);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Loads and parses ruggy.yaml configuration file
|
|
52
|
+
* @param {string} configPath - Path to configuration file
|
|
53
|
+
* @returns {Object} - Parsed configuration
|
|
54
|
+
* @throws {Error} If file cannot be read or parsed
|
|
55
|
+
*/
|
|
56
|
+
function parseConfigFile(configPath) {
|
|
57
|
+
try {
|
|
58
|
+
const fileContents = fs.readFileSync(configPath, 'utf8');
|
|
59
|
+
const config = yaml.load(fileContents);
|
|
60
|
+
|
|
61
|
+
if (!config || typeof config !== 'object') {
|
|
62
|
+
throw new Error('Configuration must be a valid YAML object');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return config;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`Failed to parse configuration file at '${configPath}': ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Loads Ruggy configuration from ruggy.yaml or returns defaults
|
|
73
|
+
* Configuration is cached after first load
|
|
74
|
+
* @param {Object} options - Options
|
|
75
|
+
* @param {boolean} options.reload - Force reload configuration (bypass cache)
|
|
76
|
+
* @param {string} options.searchFrom - Directory to start searching from (default: process.cwd())
|
|
77
|
+
* @returns {Object} - Configuration object
|
|
78
|
+
*/
|
|
79
|
+
function loadConfig(options = {}) {
|
|
80
|
+
const { reload = false, searchFrom = process.cwd() } = options;
|
|
81
|
+
|
|
82
|
+
// Return cached config if available and not forcing reload
|
|
83
|
+
if (cachedConfig && !reload) {
|
|
84
|
+
return cachedConfig;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const configPath = findConfigFile(searchFrom);
|
|
88
|
+
|
|
89
|
+
if (!configPath) {
|
|
90
|
+
// No config file found, use defaults
|
|
91
|
+
cachedConfig = { ...DEFAULT_CONFIG };
|
|
92
|
+
return cachedConfig;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const userConfig = parseConfigFile(configPath);
|
|
96
|
+
|
|
97
|
+
// Merge with defaults
|
|
98
|
+
cachedConfig = {
|
|
99
|
+
...DEFAULT_CONFIG,
|
|
100
|
+
...userConfig
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Resolve relative paths to absolute based on config file location
|
|
104
|
+
if (cachedConfig.dataPath && !path.isAbsolute(cachedConfig.dataPath)) {
|
|
105
|
+
const configDir = path.dirname(configPath);
|
|
106
|
+
cachedConfig.dataPath = path.resolve(configDir, cachedConfig.dataPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return cachedConfig;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Clears the cached configuration
|
|
114
|
+
* Useful for testing or when configuration changes at runtime
|
|
115
|
+
*/
|
|
116
|
+
function clearCache() {
|
|
117
|
+
cachedConfig = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
loadConfig,
|
|
122
|
+
clearCache,
|
|
123
|
+
DEFAULT_CONFIG
|
|
124
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruggy - A simple, fast embedded database for Node.js
|
|
3
|
+
*
|
|
4
|
+
* @module ruggy
|
|
5
|
+
* @example
|
|
6
|
+
* // Quick usage with auto-cleanup
|
|
7
|
+
* const { RuggyDatabase } = require('ruggy');
|
|
8
|
+
*
|
|
9
|
+
* await RuggyDatabase.withDatabase('./data', async (db) => {
|
|
10
|
+
* await db.withCollection('users', col => {
|
|
11
|
+
* col.insert({ name: 'Alice', age: 30 });
|
|
12
|
+
* console.log(col.findAll());
|
|
13
|
+
* });
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Manual connection management
|
|
18
|
+
* const { RuggyDatabase } = require('ruggy');
|
|
19
|
+
*
|
|
20
|
+
* const db = new RuggyDatabase('./data');
|
|
21
|
+
* const users = db.collection('users');
|
|
22
|
+
* users.insert({ name: 'Bob' });
|
|
23
|
+
* users.close();
|
|
24
|
+
* db.close();
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Connection pooling for servers
|
|
28
|
+
* const { RuggyPool } = require('ruggy');
|
|
29
|
+
*
|
|
30
|
+
* const pool = new RuggyPool('./data');
|
|
31
|
+
*
|
|
32
|
+
* app.get('/users', async (req, res) => {
|
|
33
|
+
* await pool.withCollection('users', col => {
|
|
34
|
+
* res.json(col.findAll());
|
|
35
|
+
* });
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* process.on('SIGTERM', () => pool.close());
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const RuggyDatabase = require('./Database');
|
|
42
|
+
// const RuggyCollection = require('./Collection');
|
|
43
|
+
const RuggyPool = require('./Pool');
|
|
44
|
+
|
|
45
|
+
// Export classes
|
|
46
|
+
module.exports = {
|
|
47
|
+
RuggyDatabase,
|
|
48
|
+
// RuggyCollection,
|
|
49
|
+
RuggyPool,
|
|
50
|
+
|
|
51
|
+
// Aliases for convenience
|
|
52
|
+
Ruggy: RuggyDatabase,
|
|
53
|
+
Database: RuggyDatabase,
|
|
54
|
+
// Collection: RuggyCollection,
|
|
55
|
+
Pool: RuggyPool
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// module.exports.default = RuggyDatabase;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { koffi } = require('./bindings');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads a null-terminated C string from a memory pointer
|
|
5
|
+
* Optimized to read in chunks instead of byte-by-byte
|
|
6
|
+
* @param {*} ptr - Memory pointer from Koffi
|
|
7
|
+
* @returns {string|null} - UTF-8 decoded string or null
|
|
8
|
+
*/
|
|
9
|
+
function readCString(ptr) {
|
|
10
|
+
if (!ptr || koffi.address(ptr) === 0n) return null;
|
|
11
|
+
|
|
12
|
+
const CHUNK_SIZE = 256;
|
|
13
|
+
const MAX_SIZE = 1024 * 1024; // 1MB safety limit
|
|
14
|
+
const chunks = [];
|
|
15
|
+
let offset = 0;
|
|
16
|
+
|
|
17
|
+
while (offset < MAX_SIZE) {
|
|
18
|
+
const buffer = koffi.decode(ptr, offset, koffi.types.uint8, CHUNK_SIZE);
|
|
19
|
+
|
|
20
|
+
// Find null terminator
|
|
21
|
+
let nullIndex = -1;
|
|
22
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
23
|
+
if (buffer[i] === 0) {
|
|
24
|
+
nullIndex = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (nullIndex !== -1) {
|
|
30
|
+
// Found null terminator
|
|
31
|
+
chunks.push(buffer.slice(0, nullIndex));
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
chunks.push(buffer);
|
|
36
|
+
offset += CHUNK_SIZE;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Concatenate all chunks and decode UTF-8
|
|
40
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
41
|
+
const fullBuffer = new Uint8Array(totalLength);
|
|
42
|
+
let pos = 0;
|
|
43
|
+
for (const chunk of chunks) {
|
|
44
|
+
fullBuffer.set(chunk, pos);
|
|
45
|
+
pos += chunk.length;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Buffer.from(fullBuffer).toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Converts a JavaScript string to a null-terminated UTF-8 buffer
|
|
53
|
+
* @param {string} str - String to convert
|
|
54
|
+
* @returns {Buffer} - Buffer with null terminator
|
|
55
|
+
*/
|
|
56
|
+
function toCString(str) {
|
|
57
|
+
return Buffer.from(str + '\0', 'utf8');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validates if a pointer is valid (not null)
|
|
62
|
+
* @param {*} ptr - Pointer to validate
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
function isValidPointer(ptr) {
|
|
66
|
+
return ptr && koffi.address(ptr) !== 0n;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
readCString,
|
|
71
|
+
toCString,
|
|
72
|
+
isValidPointer
|
|
73
|
+
};
|