Ormyx is a TypeScript-based ORM-like library designed to manage JSON data as a relational-like database with built-in encryption. It simplifies data persistence by providing a structured way to handle tables, rows, and automatic ID generation while keeping your data secure.
I don't recommend using this for large-scale projects; it's best suited for small projects, prototypes, or hobbies. This was created for fun and as a way to expand my ideas and knowledge.
- Relational Structure: Organize your JSON data into tables and records.
- Built-in Encryption: Uses
json-enc-decto keep your database files secure. - Data Filtering & Type Safety: Automatically sanitizes input and enforces column types to prevent unwanted data injection.
- Auto ID Generation: Support for both random 12-character strings and incremental numeric IDs.
- Flexible Data Insertion: Support for both JSON objects and easy-to-use string formats.
- TypeScript Support: Fully typed for an enhanced developer experience.
npm install ormyxTo get started, you need to initialize Ormyx with a secret encryption key. If the database file does not exist, it will be created automatically.
Option A: Providing a Custom Filename
You can specify a name for your database. Filenames are automatically sanitized (only the base name is kept) and the .dat extension is added if missing.
import { ormyx } from 'ormyx';
// This creates/loads 'my-database.dat'
const db = ormyx('your-secret-key', 'my-database');
// 'my.db.json' will be sanitized to 'my.dat'
const db2 = ormyx('your-secret-key', 'my.db.json');Option B: Using the Default Filename (Optional)
If you omit the second parameter, the database will default to data.dat.
import { ormyx } from 'ormyx';
// This creates/loads 'data.dat'
const db = ormyx('your-secret-key');Warning: The encryption key is the only way to access your data. If you lose the key or use a different one later, you will not be able to read the existing database. Always store your keys in environment variables!
You must create a table before you can insert any data. This defines the structure, column types, and ID generation strategy.
Option A: Using an Object (Recommended) Define columns and their types using a JSON object.
// Example: db.create('users', { username: 'string', email: 'string', active: 'boolean' })
db.create('users', {
username: 'string',
email: 'string',
role: 'string',
active: 'boolean'
});Option B: Using a String Format A convenient shorthand for defining columns and types.
// Example: db.create('users', "username = string, email = string, age = int")
db.create('users', "username = string, email = string, role = string, active = boolean");Column Type Rules:
- Supported Types:
string, (numberorint),boolean. - ID Column: By default, an
idcolumn is added as anumberorint(incremental). To use a random string ID, defineid: 'string'. - Autoincrement: The third parameter (default:
true) controls if numeric IDs should automatically increment.
You can insert data into your tables by providing either a JSON object or a formatted string.
This method is recommended for structured data and better type safety.
// Example: db.insert('users', { username: 'ryannkim', active: true })
try {
const result = db.insert('users', {
username: 'ryannkim',
role: 'developer',
active: true
});
console.log('Inserted record:', result);
} catch (error) {
console.error('Failed to insert:', error.message);
}A convenient shorthand for simple data entry.
// Example: db.insert('users', "username = mpop, role = admin")
db.insert('users', "username = mpop, role = admin, active = true");String Format Rules:
- Pairs: Use
key = valueformat. - Types: Numbers and booleans (
true/false) are automatically parsed. - Quotes: Wrap values in
'or"if they contain commas or special characters. - Separators: Separate fields with a comma (
,).
Control how IDs are generated via the options parameter or table structure.
// Incremental ID: 1, 2, 3... (Default if 'id' type is 'number')
db.insert('logs', { event: 'startup' });
// Random 12-character alphanumeric ID (Default if 'id' type is 'string')
db.insert('sessions', { token: 'xyz123' });
// Custom ID length for string-based IDs
db.insert('tokens', { value: 'abc' }, { idLength: 16 });Retrieve a specific record by its ID.
// Example: db.read('users', 1)
const user = db.read('users', 1);
if (user) {
console.log('User data:', user);
} else {
console.error('Record not found');
}Search for data within a table that matches specific criteria.
// Example: db.filter('users', { role: 'admin' })
const admins = db.filter('users', { role: 'admin' });
console.log('Admins found:', admins);
// Using string format
const activeUsers = db.filter('users', "active = true");Modify an existing record. New data is merged with the existing record.
// Example: db.update('users', 1, { active: false })
db.update('users', 1, { active: false });
// Using string format
db.update('users', 1, "role = administrator, active = true");Remove a specific record from a table.
// Example: db.remove('users', 1)
const status = db.remove('users', 1);
console.log(status.message); // "Deleted successfully"Modify the structure of an existing table. You can add new columns with types or remove existing ones.
// Add new columns to the 'users' table
db.alter('users', { age: 'number', gender: 'string' });
// Using string format
db.alter('users', "address = string");
// Remove columns from the 'users' table
db.alter('users', undefined, ['role']);Rename an existing table. This will preserve both the table structure and its data.
// Example: db.rename('users', 'members')
const result = db.rename('users', 'members');
console.log(result.message); // "Table users is now renamed to members"The internal structure of the database follows a hierarchical JSON format:
{
"table_struct": {
"users": {
"username": "string",
"email": "string",
"role": "string",
"active": "boolean",
"id": "number"
}
},
"users": {
"1": {
"username": "ryannkim",
"email": "ryann@example.com",
"role": "developer",
"active": true,
"id": 1
}
}
}type data_structure = Record<string, string | number | boolean | undefined | null>
type json_data = Record<string | number, data_structure>
type main_structure = Record<string, json_data>
interface insertOptions {
increment?: boolean
idLength?: number
}- Initialization:
ormyx()checks for the database file. If missing, it initializes a new encrypted file. - Operations: Every write operation (
insert,update,remove) synchronizes with the encrypted file, performs the operation in-memory, and then flushes the re-encrypted data back to disk. - Encryption: Powered by
json-enc-dec, ensuring data at rest is secure.
Contributions are welcome! To maintain a clean and manageable history, please use Semantic Commit Messages:
-
feat:for new features -
fix:for bug fixes -
rebrand:for rebranding changes -
docs:ordoc:for documentation changes -
style:for formatting, missing semi colons, etc. -
refactor:for refactoring production code -
test:for adding missing tests -
chore:for updating build tasks, package manager configs, etc.
Example: feat: add support for custom primary keys
- Environment Variables: Never hardcode your encryption key. Use
.envfiles or secrets managers. - Key Consistency: Using a different key on an existing database will result in decryption failure and potential data corruption if forced.
- Backups: Regularly backup your
.datfiles. Encryption protects data privacy, not data loss.
This project is licensed under the MIT License.