toge-db 1.0.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/.env.example ADDED
@@ -0,0 +1,2 @@
1
+ Toge_DB_ADMIN_USER=admin-env
2
+ Toge_DB_ADMIN_PASSWORD=password
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 M Iqbal Revantama
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # Toge-DB ORM Documentation
2
+
3
+ Toge-DB is a lightweight JSON-based database with a simple ORM (Object-Relational Mapping) layer. It is specifically designed to be highly suitable for rapid prototyping and offline applications, such as those built with Electron or other JavaScript frameworks. This documentation explains how to define models, perform queries, and manage data using the TogeORM.
4
+
5
+ > [**WARNING**]
6
+ > **Disclaimer: AI-Generated Software**
7
+ > This library is approximately 99.99% generated by artificial intelligence (AI). While it is designed to provide a lightweight database and ORM solution, we cannot guarantee its reliability or performance in all scenarios. Use this library at your own risk. We are not responsible for any issues or damages that may arise from its use in your application.
8
+
9
+ ## Table of Contents
10
+ - [CLI Usage](#cli-usage)
11
+ - [Initialization](#initialization)
12
+ - [Defining Models](#defining-models)
13
+ - [Creating Data](#creating-data)
14
+ - [Querying Data](#querying-data)
15
+ - [Updating Data](#updating-data)
16
+ - [Deleting Data](#deleting-data)
17
+
18
+
19
+ ---
20
+ ## CLI Usage
21
+
22
+ TogeDB comes with a raw CLI that supports SQL-like queries. You can start the CLI by running `npm run toge-start`.
23
+
24
+ ### Authentication
25
+ Please set the in .env file with the following credentials:
26
+ ```
27
+ TOGE_DB_ADMIN_USER=your-username
28
+ TOGE_DB_ADMIN_PASSWORD=your-password
29
+ ```
30
+
31
+ ### Supported SQL Commands
32
+
33
+ The CLI supports the following raw commands:
34
+
35
+ #### CREATE TABLE
36
+ ```sql
37
+ CREATE TABLE users (username string PRIMARY KEY, email string, age int)
38
+ ```
39
+
40
+ #### INSERT INTO
41
+ ```sql
42
+ INSERT INTO users VALUES ('johndoe', 'john@example.com', 30)
43
+ ```
44
+
45
+ #### SELECT
46
+ Supports column selection, `WHERE` clause (simple `col=val`), and `LIMIT`.
47
+ ```sql
48
+ SELECT * FROM users
49
+ SELECT username, email FROM users WHERE age=30
50
+ SELECT * FROM users LIMIT 5
51
+ ```
52
+
53
+ #### UPDATE
54
+ ```sql
55
+ UPDATE users SET age=31 WHERE username='johndoe'
56
+ ```
57
+
58
+ #### DELETE
59
+ ```sql
60
+ DELETE FROM users WHERE username='johndoe'
61
+ ```
62
+
63
+ #### DROP TABLE
64
+ ```sql
65
+ DROP TABLE users
66
+ ```
67
+
68
+ #### ALTER TABLE
69
+ ```sql
70
+ ALTER TABLE users ADD bio string
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Initialization
76
+
77
+ To start using TogeDB ORM, initialize the `TogeORM` with a directory path where your data will be stored.
78
+
79
+ ```javascript
80
+ import { TogeORM } from 'toge-db';
81
+ const orm = new TogeORM('./data');
82
+ ```
83
+
84
+ ## Defining Models
85
+
86
+ Use `orm.define(modelName, schema)` to create a new model. If no primary key is defined in the schema, an auto-incrementing `id` field will be added automatically.
87
+
88
+ ### Schema Options
89
+ - `type`: Data type (e.g., 'string', 'int').
90
+ - `primaryKey`: Boolean, marks the column as a primary key.
91
+ - `autoIncrement`: Boolean, enables auto-increment for the column.
92
+
93
+ ```javascript
94
+ const User = orm.define('user', {
95
+ username: { type: 'string', primaryKey: true },
96
+ email: { type: 'string' },
97
+ age: { type: 'int' }
98
+ });
99
+
100
+ const Post = orm.define('post', {
101
+ title: { type: 'string' },
102
+ content: { type: 'string' }
103
+ }); // Automatically adds an 'id' primary key
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Creating Data
109
+
110
+ There are two ways to create and persist data:
111
+
112
+ ### 1. Using `Model.create()`
113
+ Directly creates and saves a new record.
114
+
115
+ ```javascript
116
+ const newUser = User.create({
117
+ username: 'johndoe',
118
+ email: 'john@example.com',
119
+ age: 30
120
+ });
121
+ ```
122
+
123
+ ### 2. Using `new Model()` and `save()`
124
+ Instantiate a model and save it later.
125
+
126
+ ```javascript
127
+ const post = new Post({
128
+ title: 'Hello TogeDB',
129
+ content: 'This is my first post.'
130
+ });
131
+ post.save();
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Querying Data
137
+
138
+ TogeDB uses JavaScript functions as conditions for querying.
139
+
140
+ ### Find All
141
+ `Model.find(condition)` returns an array of model instances. If no condition is provided, it returns all records.
142
+
143
+ ```javascript
144
+ // Find all users
145
+ const allUsers = User.find();
146
+
147
+ // Find users older than 25
148
+ const seniors = User.find(user => user.age > 25);
149
+ ```
150
+
151
+ ### Find One
152
+ `Model.findOne(condition)` returns the first matching model instance or `null`.
153
+
154
+ ```javascript
155
+ const user = User.findOne(u => u.username === 'johndoe');
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Updating Data
161
+
162
+ ### 1. Static Update
163
+ Update multiple records matching a condition.
164
+
165
+ ```javascript
166
+ User.update(
167
+ user => user.username === 'johndoe',
168
+ { age: 31 }
169
+ );
170
+ ```
171
+
172
+ ### 2. Instance Update
173
+ Modify an instance and call `save()`. This requires the model to have a primary key.
174
+
175
+ ```javascript
176
+ const user = User.findOne(u => u.username === 'johndoe');
177
+ if (user) {
178
+ user.email = 'newjohn@example.com';
179
+ user.save();
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Deleting Data
186
+
187
+ Use `Model.delete(condition)` to remove records from the database.
188
+
189
+ ```javascript
190
+ // Delete a specific user
191
+ User.delete(user => user.username === 'johndoe');
192
+
193
+ // Delete all posts
194
+ Post.delete(() => true);
195
+ ```
196
+
197
+ ---
@@ -0,0 +1,2 @@
1
+ Toge_DB_ADMIN_USER=admin-env
2
+ Toge_DB_ADMIN_PASSWORD=password
@@ -0,0 +1,17 @@
1
+ import { TogeORM } from 'toge-db';
2
+ const orm = new TogeORM('./data');
3
+
4
+ const User = orm.define('user', {
5
+ username: { type: 'string', primaryKey: true },
6
+ email: { type: 'string' },
7
+ age: { type: 'int' }
8
+ });
9
+
10
+ const newUser = User.create({
11
+ username: 'johntest',
12
+ email: 'john@example.com',
13
+ age: 30
14
+ });
15
+
16
+ const allUsers = User.find();
17
+ console.log('New user:', allUsers);
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "example-app",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "type": "module",
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ }
12
+ }
package/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import Storage from './src/engine/storage.js';
2
+ import Parser from './src/parser/parser.js';
3
+ import CLI from './src/cli/repl.js';
4
+ import TogeORM from './src/orm/orm.js';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ export {
8
+ Storage,
9
+ Parser,
10
+ CLI,
11
+ TogeORM
12
+ };
13
+
14
+ // Only start CLI if this file is run directly
15
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
16
+ const storage = new Storage();
17
+ const parser = new Parser(storage);
18
+ const cli = new CLI(parser);
19
+ cli.start();
20
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "toge-db",
3
+ "version": "1.0.0",
4
+ "description": "Toge-DB is a lightweight JSON-based database with a simple ORM (Object-Relational Mapping) layer. It is specifically designed to be highly suitable for rapid prototyping and offline applications, such as those built with Electron or other JavaScript frameworks. This documentation explains how to define models, perform queries, and manage data using the TogeORM.\n",
5
+ "keywords": [
6
+ "database",
7
+ "sql"
8
+ ],
9
+ "license": "MIT",
10
+ "author": "Revtm",
11
+ "type": "module",
12
+ "main": "index.js",
13
+ "scripts": {
14
+ "toge-start": "node index.js",
15
+ "toge-test": "node test.js"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/Revtm/toge-db"
20
+ },
21
+ "dependencies": {
22
+ "dotenv": "^17.2.3"
23
+ }
24
+ }
@@ -0,0 +1,69 @@
1
+ import readline from 'readline';
2
+ import dotenv from 'dotenv';
3
+ dotenv.config();
4
+
5
+ class CLI {
6
+ constructor(parser) {
7
+ this.parser = parser;
8
+ this.rl = readline.createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout,
11
+ prompt: 'Toge-DB> '
12
+ });
13
+ this.authenticated = false;
14
+
15
+ const adminUser = process.env.Toge_DB_ADMIN_USER;
16
+ const adminPass = process.env.Toge_DB_ADMIN_PASSWORD;
17
+ this.credentials = { [adminUser]: adminPass };
18
+ }
19
+
20
+ start() {
21
+ console.log('Welcome to Toge-DB');
22
+ this.login();
23
+ }
24
+
25
+ login() {
26
+ this.rl.question('Username: ', (username) => {
27
+ this.rl.question('Password: ', (password) => {
28
+ if (this.credentials[username] && this.credentials[username] === password) {
29
+ this.authenticated = true;
30
+ console.log('Login successful.');
31
+ this.runREPL();
32
+ } else {
33
+ console.log('Invalid credentials.');
34
+ this.login();
35
+ }
36
+ });
37
+ });
38
+ }
39
+
40
+ runREPL() {
41
+ this.rl.prompt();
42
+ this.rl.on('line', (line) => {
43
+ const query = line.trim();
44
+ if (query.toLowerCase() === 'exit' || query.toLowerCase() === 'quit') {
45
+ this.rl.close();
46
+ return;
47
+ }
48
+
49
+ if (query) {
50
+ try {
51
+ const result = this.parser.execute(query);
52
+ if (Array.isArray(result)) {
53
+ console.table(result);
54
+ } else {
55
+ console.log(result);
56
+ }
57
+ } catch (err) {
58
+ console.error(`Error: ${err.message}`);
59
+ }
60
+ }
61
+ this.rl.prompt();
62
+ }).on('close', () => {
63
+ console.log('Goodbye!');
64
+ process.exit(0);
65
+ });
66
+ }
67
+ }
68
+
69
+ export default CLI;
@@ -0,0 +1,163 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ class Storage {
5
+ constructor(dataDir = 'data') {
6
+ this.dataDir = path.resolve(dataDir);
7
+ if (!fs.existsSync(this.dataDir)) {
8
+ fs.mkdirSync(this.dataDir);
9
+ }
10
+ this.metadataPath = path.join(this.dataDir, 'metadata.json');
11
+ this.metadata = this.loadMetadata();
12
+ }
13
+
14
+ loadMetadata() {
15
+ if (fs.existsSync(this.metadataPath)) {
16
+ return JSON.parse(fs.readFileSync(this.metadataPath, 'utf8'));
17
+ }
18
+ return { tables: {} };
19
+ }
20
+
21
+ saveMetadata() {
22
+ fs.writeFileSync(this.metadataPath, JSON.stringify(this.metadata, null, 2));
23
+ }
24
+
25
+ getTablePath(tableName) {
26
+ return path.join(this.dataDir, `${tableName}.json`);
27
+ }
28
+
29
+ createTable(tableName, schema) {
30
+ if (this.metadata.tables[tableName]) {
31
+ throw new Error(`Table "${tableName}" already exists.`);
32
+ }
33
+
34
+ // Check for primary key, if not exists add auto-incrementing 'id'
35
+ let hasPK = Object.values(schema).some(col => col.primaryKey);
36
+ if (!hasPK) {
37
+ schema = { id: { type: 'int', primaryKey: true, autoIncrement: true }, ...schema };
38
+ }
39
+
40
+ this.metadata.tables[tableName] = {
41
+ schema,
42
+ nextId: 1
43
+ };
44
+ this.saveMetadata();
45
+ fs.writeFileSync(this.getTablePath(tableName), JSON.stringify([], null, 2));
46
+ }
47
+
48
+ dropTable(tableName) {
49
+ if (!this.metadata.tables[tableName]) {
50
+ throw new Error(`Table "${tableName}" does not exist.`);
51
+ }
52
+ delete this.metadata.tables[tableName];
53
+ this.saveMetadata();
54
+ const tablePath = this.getTablePath(tableName);
55
+ if (fs.existsSync(tablePath)) {
56
+ fs.unlinkSync(tablePath);
57
+ }
58
+ }
59
+
60
+ loadRows(tableName) {
61
+ if (!this.metadata.tables[tableName]) {
62
+ throw new Error(`Table "${tableName}" does not exist.`);
63
+ }
64
+ const tablePath = this.getTablePath(tableName);
65
+ return JSON.parse(fs.readFileSync(tablePath, 'utf8'));
66
+ }
67
+
68
+ saveRows(tableName, rows) {
69
+ fs.writeFileSync(this.getTablePath(tableName), JSON.stringify(rows, null, 2));
70
+ }
71
+
72
+ insert(tableName, rowData) {
73
+ const tableMeta = this.metadata.tables[tableName];
74
+ if (!tableMeta) throw new Error(`Table "${tableName}" does not exist.`);
75
+
76
+ const rows = this.loadRows(tableName);
77
+ const newRow = {};
78
+
79
+ // Prepare new row and handle auto-increment
80
+ for (const [colName, colSpec] of Object.entries(tableMeta.schema)) {
81
+ if (colSpec.autoIncrement) {
82
+ newRow[colName] = tableMeta.nextId++;
83
+ } else {
84
+ newRow[colName] = rowData[colName] !== undefined ? rowData[colName] : null;
85
+ }
86
+ }
87
+
88
+ // Enforce primary key uniqueness
89
+ const pkColumns = Object.entries(tableMeta.schema)
90
+ .filter(([_, spec]) => spec.primaryKey)
91
+ .map(([name, _]) => name);
92
+
93
+ if (pkColumns.length > 0) {
94
+ const isDuplicate = rows.some(row =>
95
+ pkColumns.every(col => String(row[col]) === String(newRow[col]))
96
+ );
97
+ if (isDuplicate) {
98
+ throw new Error(`Duplicate entry for primary key.`);
99
+ }
100
+ }
101
+
102
+ rows.push(newRow);
103
+ this.saveRows(tableName, rows);
104
+ this.saveMetadata();
105
+ return newRow;
106
+ }
107
+
108
+ update(tableName, condition, newData) {
109
+ const tableMeta = this.metadata.tables[tableName];
110
+ if (!tableMeta) throw new Error(`Table "${tableName}" does not exist.`);
111
+
112
+ let rows = this.loadRows(tableName);
113
+ let updatedCount = 0;
114
+
115
+ rows = rows.map(row => {
116
+ if (condition(row)) {
117
+ updatedCount++;
118
+ return { ...row, ...newData };
119
+ }
120
+ return row;
121
+ });
122
+
123
+ this.saveRows(tableName, rows);
124
+ return updatedCount;
125
+ }
126
+
127
+ delete(tableName, condition) {
128
+ if (!this.metadata.tables[tableName]) throw new Error(`Table "${tableName}" does not exist.`);
129
+
130
+ let rows = this.loadRows(tableName);
131
+ const initialCount = rows.length;
132
+ rows = rows.filter(row => !condition(row));
133
+
134
+ this.saveRows(tableName, rows);
135
+ return initialCount - rows.length;
136
+ }
137
+
138
+ select(tableName, condition = () => true) {
139
+ const rows = this.loadRows(tableName);
140
+ return rows.filter(condition);
141
+ }
142
+
143
+ alterTable(tableName, action, details) {
144
+ const tableMeta = this.metadata.tables[tableName];
145
+ if (!tableMeta) throw new Error(`Table "${tableName}" does not exist.`);
146
+
147
+ if (action === 'ADD_COLUMN') {
148
+ const { colName, colSpec } = details;
149
+ if (tableMeta.schema[colName]) throw new Error(`Column "${colName}" already exists.`);
150
+
151
+ tableMeta.schema[colName] = colSpec;
152
+ this.saveMetadata();
153
+
154
+ const rows = this.loadRows(tableName);
155
+ const updatedRows = rows.map(row => ({ ...row, [colName]: null }));
156
+ this.saveRows(tableName, updatedRows);
157
+ } else {
158
+ throw new Error(`Action "${action}" not supported for ALTER TABLE.`);
159
+ }
160
+ }
161
+ }
162
+
163
+ export default Storage;
@@ -0,0 +1,74 @@
1
+ class Model {
2
+ constructor(data = {}) {
3
+ Object.assign(this, data);
4
+ }
5
+
6
+ static init(tableName, schema, storage) {
7
+ this.tableName = tableName;
8
+ this.schema = schema;
9
+ this.storage = storage;
10
+
11
+ // Create table if it doesn't exist
12
+ try {
13
+ this.storage.createTable(this.tableName, this.schema);
14
+ } catch (e) {
15
+ // Ignore if table already exists
16
+ if (!e.message.includes('already exists')) {
17
+ throw e;
18
+ }
19
+ }
20
+ }
21
+
22
+ static create(data) {
23
+ const result = this.storage.insert(this.tableName, data);
24
+ return new this(result);
25
+ }
26
+
27
+ static find(condition = () => true) {
28
+ const results = this.storage.select(this.tableName, condition);
29
+ return results.map(row => new this(row));
30
+ }
31
+
32
+ static findOne(condition) {
33
+ const results = this.find(condition);
34
+ return results.length > 0 ? results[0] : null;
35
+ }
36
+
37
+ save() {
38
+ const tableMeta = this.constructor.storage.metadata.tables[this.constructor.tableName];
39
+ const pkColumns = Object.entries(tableMeta.schema)
40
+ .filter(([_, spec]) => spec.primaryKey)
41
+ .map(([name, _]) => name);
42
+
43
+ if (pkColumns.length === 0) {
44
+ throw new Error('Cannot save model without primary key');
45
+ }
46
+
47
+ // Check if all primary keys are present
48
+ const condition = (row) => pkColumns.every(col => row[col] === this[col]);
49
+
50
+ // Try to find if it exists
51
+ const existing = this.constructor.storage.select(this.constructor.tableName, condition);
52
+
53
+ if (existing.length > 0) {
54
+ // Update
55
+ const dataToUpdate = { ...this };
56
+ // Remove storage and metadata from data if they were somehow attached
57
+ this.constructor.storage.update(this.constructor.tableName, condition, dataToUpdate);
58
+ } else {
59
+ // Insert
60
+ const result = this.constructor.storage.insert(this.constructor.tableName, this);
61
+ Object.assign(this, result);
62
+ }
63
+ }
64
+
65
+ static update(condition, newData) {
66
+ return this.storage.update(this.tableName, condition, newData);
67
+ }
68
+
69
+ static delete(condition) {
70
+ return this.storage.delete(this.tableName, condition);
71
+ }
72
+ }
73
+
74
+ export default Model;
package/src/orm/orm.js ADDED
@@ -0,0 +1,18 @@
1
+ import Storage from '../engine/storage.js';
2
+ import Model from './model.js';
3
+
4
+ class TogeORM {
5
+ constructor(dataDir) {
6
+ this.storage = new Storage(dataDir);
7
+ this.models = {};
8
+ }
9
+
10
+ define(modelName, schema) {
11
+ const NewModel = class extends Model {};
12
+ NewModel.init(modelName, schema, this.storage);
13
+ this.models[modelName] = NewModel;
14
+ return NewModel;
15
+ }
16
+ }
17
+
18
+ export default TogeORM;
@@ -0,0 +1,215 @@
1
+ class Parser {
2
+ constructor(storage) {
3
+ this.storage = storage;
4
+ }
5
+
6
+ execute(query) {
7
+ query = query.trim();
8
+ if (query.toUpperCase().startsWith('CREATE TABLE')) {
9
+ return this.handleCreateTable(query);
10
+ } else if (query.toUpperCase().startsWith('INSERT INTO')) {
11
+ return this.handleInsert(query);
12
+ } else if (query.toUpperCase().startsWith('SELECT')) {
13
+ return this.handleSelect(query);
14
+ } else if (query.toUpperCase().startsWith('UPDATE')) {
15
+ return this.handleUpdate(query);
16
+ } else if (query.toUpperCase().startsWith('DELETE FROM')) {
17
+ return this.handleDelete(query);
18
+ } else if (query.toUpperCase().startsWith('DROP TABLE')) {
19
+ return this.handleDropTable(query);
20
+ } else if (query.toUpperCase().startsWith('ALTER TABLE')) {
21
+ return this.handleAlterTable(query);
22
+ } else {
23
+ throw new Error('Unsupported command.');
24
+ }
25
+ }
26
+
27
+ handleCreateTable(query) {
28
+ // CREATE TABLE <table_name> (<col1> <type> [PRIMARY KEY], ..., [PRIMARY KEY (<col1>, <col2>)])
29
+ const match = query.match(/CREATE TABLE (\w+) \((.*)\)/i);
30
+ if (!match) throw new Error('Invalid CREATE TABLE syntax.');
31
+
32
+ const tableName = match[1];
33
+ const columnsStr = match[2];
34
+ const schema = {};
35
+ let compositePK = null;
36
+
37
+ // Split by comma but ignore commas inside parentheses (for composite PK)
38
+ const parts = [];
39
+ let currentPart = '';
40
+ let bracketLevel = 0;
41
+ for (let i = 0; i < columnsStr.length; i++) {
42
+ const char = columnsStr[i];
43
+ if (char === '(') bracketLevel++;
44
+ if (char === ')') bracketLevel--;
45
+ if (char === ',' && bracketLevel === 0) {
46
+ parts.push(currentPart.trim());
47
+ currentPart = '';
48
+ } else {
49
+ currentPart += char;
50
+ }
51
+ }
52
+ parts.push(currentPart.trim());
53
+
54
+ parts.forEach(part => {
55
+ if (part.toUpperCase().startsWith('PRIMARY KEY')) {
56
+ const pkMatch = part.match(/PRIMARY KEY\s*\((.*)\)/i);
57
+ if (pkMatch) {
58
+ compositePK = pkMatch[1].split(',').map(c => c.trim());
59
+ }
60
+ } else {
61
+ const segments = part.split(/\s+/);
62
+ const name = segments[0];
63
+ const type = segments[1];
64
+ const isPK = part.toUpperCase().includes('PRIMARY KEY');
65
+ schema[name] = { type: type.toLowerCase() };
66
+ if (isPK) {
67
+ schema[name].primaryKey = true;
68
+ }
69
+ }
70
+ });
71
+
72
+ if (compositePK) {
73
+ compositePK.forEach(colName => {
74
+ if (schema[colName]) {
75
+ schema[colName].primaryKey = true;
76
+ } else {
77
+ throw new Error(`Primary key column "${colName}" not found in schema.`);
78
+ }
79
+ });
80
+ }
81
+
82
+ this.storage.createTable(tableName, schema);
83
+ return `Table "${tableName}" created successfully.`;
84
+ }
85
+
86
+ handleInsert(query) {
87
+ // INSERT INTO <table_name> VALUES (...)
88
+ // Simplified: assuming values are in same order as schema (excluding auto-increment)
89
+ const match = query.match(/INSERT INTO (\w+) VALUES \((.*)\)/i);
90
+ if (!match) throw new Error('Invalid INSERT INTO syntax.');
91
+
92
+ const tableName = match[1];
93
+ const valuesStr = match[2];
94
+ const values = valuesStr.split(',').map(v => v.trim().replace(/^'|'$/g, ''));
95
+
96
+ const tableMeta = this.storage.metadata.tables[tableName];
97
+ if (!tableMeta) throw new Error(`Table "${tableName}" does not exist.`);
98
+
99
+ const rowData = {};
100
+ const colNames = Object.keys(tableMeta.schema).filter(name => !tableMeta.schema[name].autoIncrement);
101
+
102
+ colNames.forEach((name, index) => {
103
+ rowData[name] = values[index];
104
+ });
105
+
106
+ const newRow = this.storage.insert(tableName, rowData);
107
+ return `Row inserted into "${tableName}": ${JSON.stringify(newRow)}`;
108
+ }
109
+
110
+ handleSelect(query) {
111
+ // SELECT <columns> FROM <table_name> [WHERE <condition>] [LIMIT <number>]
112
+ const match = query.match(/SELECT\s+(.*?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.*?))?(?:\s+LIMIT\s+(\d+))?$/i);
113
+ if (!match) throw new Error('Invalid SELECT syntax.');
114
+
115
+ const columnsStr = match[1].trim();
116
+ const tableName = match[2];
117
+ const whereClause = match[3];
118
+ const limit = match[4] ? parseInt(match[4], 10) : null;
119
+
120
+ let condition = () => true;
121
+ if (whereClause) {
122
+ condition = this.parseCondition(whereClause);
123
+ }
124
+
125
+ let rows = this.storage.select(tableName, condition);
126
+
127
+ // Handle columns
128
+ if (columnsStr !== '*') {
129
+ const columns = columnsStr.split(',').map(c => c.trim());
130
+ rows = rows.map(row => {
131
+ const filteredRow = {};
132
+ columns.forEach(col => {
133
+ filteredRow[col] = row[col];
134
+ });
135
+ return filteredRow;
136
+ });
137
+ }
138
+
139
+ // Handle limit
140
+ if (limit !== null) {
141
+ rows = rows.slice(0, limit);
142
+ }
143
+
144
+ return rows;
145
+ }
146
+
147
+ handleUpdate(query) {
148
+ // UPDATE <table_name> SET <col>=<val> WHERE <condition>
149
+ const match = query.match(/UPDATE (\w+) SET (\w+)=['"]?(.*?)['"]? WHERE (.*)/i);
150
+ if (!match) throw new Error('Invalid UPDATE syntax.');
151
+
152
+ const tableName = match[1];
153
+ const colName = match[2];
154
+ const newVal = match[3];
155
+ const whereClause = match[4];
156
+
157
+ const condition = this.parseCondition(whereClause);
158
+ const count = this.storage.update(tableName, condition, { [colName]: newVal });
159
+
160
+ return `${count} row(s) updated in "${tableName}".`;
161
+ }
162
+
163
+ handleDelete(query) {
164
+ // DELETE FROM <table_name> WHERE <condition>
165
+ const match = query.match(/DELETE FROM (\w+) WHERE (.*)/i);
166
+ if (!match) throw new Error('Invalid DELETE syntax.');
167
+
168
+ const tableName = match[1];
169
+ const whereClause = match[2];
170
+
171
+ const condition = this.parseCondition(whereClause);
172
+ const count = this.storage.delete(tableName, condition);
173
+
174
+ return `${count} row(s) deleted from "${tableName}".`;
175
+ }
176
+
177
+ handleDropTable(query) {
178
+ // DROP TABLE <table_name>
179
+ const match = query.match(/DROP TABLE (\w+)/i);
180
+ if (!match) throw new Error('Invalid DROP TABLE syntax.');
181
+
182
+ const tableName = match[1];
183
+ this.storage.dropTable(tableName);
184
+ return `Table "${tableName}" dropped.`;
185
+ }
186
+
187
+ handleAlterTable(query) {
188
+ // ALTER TABLE <table_name> ADD <col> <type>
189
+ const match = query.match(/ALTER TABLE (\w+) ADD (\w+) (\w+)/i);
190
+ if (!match) throw new Error('Invalid ALTER TABLE syntax.');
191
+
192
+ const tableName = match[1];
193
+ const colName = match[2];
194
+ const colType = match[3];
195
+
196
+ this.storage.alterTable(tableName, 'ADD_COLUMN', { colName, colSpec: { type: colType.toLowerCase() } });
197
+ return `Table "${tableName}" altered: column "${colName}" added.`;
198
+ }
199
+
200
+ parseCondition(whereClause) {
201
+ // Simple condition parser: col=val
202
+ const match = whereClause.match(/(\w+)\s*=\s*(.*)$/);
203
+ if (!match) throw new Error('Invalid WHERE clause syntax (only col=val supported).');
204
+
205
+ const col = match[1];
206
+ let val = match[2].trim();
207
+
208
+ // Remove surrounding quotes if any
209
+ val = val.replace(/^['"](.*)['"]$/, '$1');
210
+
211
+ return (row) => String(row[col]) === String(val);
212
+ }
213
+ }
214
+
215
+ export default Parser;
package/test.js ADDED
@@ -0,0 +1,177 @@
1
+ import Storage from './src/engine/storage.js';
2
+ import Parser from './src/parser/parser.js';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ async function runTests() {
7
+ console.log('Running All Tests...');
8
+ const testDataDir = 'test_data';
9
+
10
+ function cleanup() {
11
+ if (fs.existsSync(testDataDir)) {
12
+ fs.rmSync(testDataDir, { recursive: true, force: true });
13
+ }
14
+ }
15
+
16
+ cleanup();
17
+
18
+ const storage = new Storage(testDataDir);
19
+ const parser = new Parser(storage);
20
+
21
+ try {
22
+ // --- Core Functionality Tests (from original test.js) ---
23
+ console.log('\n--- Core Functionality Tests ---');
24
+
25
+ // Test CREATE TABLE
26
+ console.log('Testing CREATE TABLE...');
27
+ parser.execute('CREATE TABLE users (name string, age int)');
28
+ if (!storage.metadata.tables.users) throw new Error('Table users not created');
29
+ if (!storage.metadata.tables.users.schema.id) throw new Error('Auto-increment ID not added');
30
+ console.log('✓ CREATE TABLE passed');
31
+
32
+ // Test INSERT
33
+ console.log('Testing INSERT...');
34
+ parser.execute("INSERT INTO users VALUES ('Alice', 30)");
35
+ parser.execute("INSERT INTO users VALUES ('Bob', 25)");
36
+ const users = storage.loadRows('users');
37
+ if (users.length !== 2) throw new Error('Rows not inserted correctly');
38
+ if (users[0].id !== 1 || users[1].id !== 2) throw new Error('Auto-increment ID mismatch');
39
+ console.log('✓ INSERT passed');
40
+
41
+ // Test SELECT (Simple)
42
+ console.log('Testing SELECT...');
43
+ const result = parser.execute('SELECT * FROM users WHERE name=Alice');
44
+ if (result.length !== 1 || result[0].name !== 'Alice') throw new Error('SELECT failed');
45
+ console.log('✓ SELECT passed');
46
+
47
+ // Test UPDATE
48
+ console.log('Testing UPDATE...');
49
+ parser.execute("UPDATE users SET age=31 WHERE name=Alice");
50
+ const alice = storage.select('users', (r) => r.name === 'Alice')[0];
51
+ if (alice.age !== '31') throw new Error('UPDATE failed'); // Parser converts to string currently
52
+ console.log('✓ UPDATE passed');
53
+
54
+ // Test ALTER TABLE
55
+ console.log('Testing ALTER TABLE...');
56
+ parser.execute("ALTER TABLE users ADD email string");
57
+ if (!storage.metadata.tables.users.schema.email) throw new Error('ALTER TABLE failed');
58
+ const updatedUsers = storage.loadRows('users');
59
+ if (updatedUsers[0].email !== null) throw new Error('ALTER TABLE did not update existing rows');
60
+ console.log('✓ ALTER TABLE passed');
61
+
62
+ // Test DELETE
63
+ console.log('Testing DELETE...');
64
+ parser.execute("DELETE FROM users WHERE name=Bob");
65
+ if (storage.loadRows('users').length !== 1) throw new Error('DELETE failed');
66
+ console.log('✓ DELETE passed');
67
+
68
+ // Test DROP TABLE
69
+ console.log('Testing DROP TABLE...');
70
+ parser.execute("DROP TABLE users");
71
+ if (storage.metadata.tables.users) throw new Error('DROP TABLE failed');
72
+ console.log('✓ DROP TABLE passed');
73
+
74
+
75
+ // --- Primary Key Tests (from test_pk.js) ---
76
+ console.log('\n--- Primary Key Tests ---');
77
+
78
+ console.log("Test: Auto-increment primary key when none specified");
79
+ parser.execute("CREATE TABLE test1 (name TEXT)");
80
+ parser.execute("INSERT INTO test1 VALUES ('Alice')");
81
+ parser.execute("INSERT INTO test1 VALUES ('Bob')");
82
+ const rows1 = parser.execute("SELECT * FROM test1");
83
+ if (rows1[0].id === 1 && rows1[1].id === 2) {
84
+ console.log("✓ Auto-increment id added");
85
+ } else {
86
+ throw new Error("Auto-increment id missing or incorrect");
87
+ }
88
+
89
+ console.log("Test: Single column primary key");
90
+ parser.execute("CREATE TABLE test2 (email TEXT PRIMARY KEY, name TEXT)");
91
+ parser.execute("INSERT INTO test2 VALUES ('alice@example.com', 'Alice')");
92
+ try {
93
+ parser.execute("INSERT INTO test2 VALUES ('alice@example.com', 'Alice Duplicate')");
94
+ throw new Error("Duplicate primary key allowed");
95
+ } catch (e) {
96
+ console.log("✓ Duplicate primary key rejected: " + e.message);
97
+ }
98
+
99
+ console.log("Test: Composite primary key");
100
+ parser.execute("CREATE TABLE test3 (order_id INT, item_id INT, PRIMARY KEY (order_id, item_id))");
101
+ parser.execute("INSERT INTO test3 VALUES (1, 101)");
102
+ parser.execute("INSERT INTO test3 VALUES (1, 102)");
103
+ parser.execute("INSERT INTO test3 VALUES (2, 101)");
104
+ try {
105
+ parser.execute("INSERT INTO test3 VALUES (1, 101)");
106
+ throw new Error("Duplicate composite primary key allowed");
107
+ } catch (e) {
108
+ console.log("✓ Duplicate composite primary key rejected: " + e.message);
109
+ }
110
+
111
+ console.log("Test: Mixed case and inline PRIMARY KEY");
112
+ parser.execute("CREATE TABLE test4 (id INT primary key, code TEXT)");
113
+ parser.execute("INSERT INTO test4 VALUES (10, 'A1')");
114
+ try {
115
+ parser.execute("INSERT INTO test4 VALUES (10, 'A2')");
116
+ throw new Error("Duplicate primary key allowed (mixed case)");
117
+ } catch (e) {
118
+ console.log("✓ Duplicate primary key rejected: " + e.message);
119
+ }
120
+
121
+
122
+ // --- SELECT Advanced Tests (from test_select.js) ---
123
+ console.log('\n--- SELECT Advanced Tests ---');
124
+
125
+ parser.execute('CREATE TABLE sel_users (name string, age int, country string)');
126
+ parser.execute("INSERT INTO sel_users VALUES ('Alice', 30, 'USA')");
127
+ parser.execute("INSERT INTO sel_users VALUES ('Bob', 25, 'UK')");
128
+ parser.execute("INSERT INTO sel_users VALUES ('Charlie', 35, 'USA')");
129
+ parser.execute("INSERT INTO sel_users VALUES ('David', 40, 'Canada')");
130
+
131
+ // 1. asterisk (*)
132
+ console.log('Testing SELECT * ...');
133
+ const all = parser.execute('SELECT * FROM sel_users');
134
+ if (all.length !== 4) throw new Error(`Expected 4 rows, got ${all.length}`);
135
+ if (!all[0].id || !all[0].name || !all[0].age || !all[0].country) throw new Error('Missing columns in SELECT *');
136
+ console.log('✓ SELECT * passed');
137
+
138
+ // 2. select some columns
139
+ console.log('Testing SELECT name, age ...');
140
+ const some = parser.execute('SELECT name, age FROM sel_users');
141
+ if (some.length !== 4) throw new Error(`Expected 4 rows, got ${some.length}`);
142
+ if (some[0].name === undefined || some[0].age === undefined) throw new Error('Selected columns missing');
143
+ if (some[0].id !== undefined || some[0].country !== undefined) throw new Error('Unselected columns present');
144
+ console.log('✓ SELECT columns passed');
145
+
146
+ // 3. WHERE clause
147
+ console.log('Testing SELECT * WHERE country=USA ...');
148
+ const usa = parser.execute('SELECT * FROM sel_users WHERE country=USA');
149
+ if (usa.length !== 2) throw new Error(`Expected 2 rows for USA, got ${usa.length}`);
150
+
151
+ console.log('Testing SELECT name WHERE id=1 ...');
152
+ const id1 = parser.execute('SELECT name FROM sel_users WHERE id=1');
153
+ if (id1.length !== 1 || id1[0].name !== 'Alice') throw new Error('SELECT WHERE id=1 failed');
154
+ console.log('✓ SELECT WHERE passed');
155
+
156
+ // 5. LIMIT
157
+ console.log('Testing SELECT * LIMIT 2 ...');
158
+ const limited = parser.execute('SELECT * FROM sel_users LIMIT 2');
159
+ if (limited.length !== 2) throw new Error(`Expected 2 rows, got ${limited.length}`);
160
+
161
+ console.log('Testing SELECT name FROM sel_users WHERE country=USA LIMIT 1 ...');
162
+ const limitedUsa = parser.execute('SELECT name FROM sel_users WHERE country=USA LIMIT 1');
163
+ if (limitedUsa.length !== 1) throw new Error(`Expected 1 row, got ${limitedUsa.length}`);
164
+ if (limitedUsa[0].name !== 'Alice') throw new Error(`Expected Alice, got ${limitedUsa[0].name}`);
165
+ console.log('✓ SELECT LIMIT passed');
166
+
167
+ console.log('\nAll tests passed successfully!');
168
+ } catch (err) {
169
+ console.error('\nTest failed!');
170
+ console.error(err);
171
+ process.exit(1);
172
+ } finally {
173
+ cleanup();
174
+ }
175
+ }
176
+
177
+ runTests();
@@ -0,0 +1,44 @@
1
+ import { TogeORM } from './index.js';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+
5
+ async function testORM() {
6
+ console.log('Testing TogeORM with ES6 Import...');
7
+ const dataDir = './test_orm_data';
8
+
9
+ if (fs.existsSync(dataDir)) {
10
+ fs.rmSync(dataDir, { recursive: true, force: true });
11
+ }
12
+
13
+ try {
14
+ const orm = new TogeORM(dataDir);
15
+
16
+ const User = orm.define('users', {
17
+ username: { type: 'string', primaryKey: true },
18
+ email: { type: 'string' }
19
+ });
20
+
21
+ console.log('Creating user...');
22
+ const user = User.create({ username: 'revtm', email: 'revtm@example.com' });
23
+ console.log('User created:', user);
24
+
25
+ console.log('Finding user...');
26
+ const found = User.findOne(u => u.username === 'revtm');
27
+ if (found && found.username === 'revtm') {
28
+ console.log('✓ Found user');
29
+ } else {
30
+ throw new Error('User not found');
31
+ }
32
+
33
+ console.log('✓ TogeORM ES6 test passed');
34
+ } catch (err) {
35
+ console.error('TogeORM ES6 test failed:', err);
36
+ process.exit(1);
37
+ } finally {
38
+ if (fs.existsSync(dataDir)) {
39
+ fs.rmSync(dataDir, { recursive: true, force: true });
40
+ }
41
+ }
42
+ }
43
+
44
+ testORM();