Neon-AI commited on
Commit
f9dc8ac
Β·
verified Β·
1 Parent(s): a8d635f

Create api/db.js

Browse files
Files changed (1) hide show
  1. api/db.js +165 -0
api/db.js ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // api/db.js β€” NT DB Core Engine
2
+ import { readFile, writeFile } from 'fs/promises';
3
+ import { existsSync, mkdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import crypto from 'crypto';
6
+
7
+ const DB_DIR = process.env.DB_DIR || join(process.cwd(), 'db');
8
+ if (!existsSync(DB_DIR)) mkdirSync(DB_DIR, { recursive: true });
9
+
10
+ // ── File helpers ──────────────────────────────────────────────────────────────
11
+ async function readTable(table) {
12
+ try { return JSON.parse(await readFile(join(DB_DIR, `${table}.json`), 'utf8')); }
13
+ catch { return []; }
14
+ }
15
+
16
+ async function writeTable(table, rows) {
17
+ await writeFile(join(DB_DIR, `${table}.json`), JSON.stringify(rows, null, 2), 'utf8');
18
+ }
19
+
20
+ export async function readSchema() {
21
+ try { return JSON.parse(await readFile(join(DB_DIR, '_schema.json'), 'utf8')); }
22
+ catch { return { tables: {} }; }
23
+ }
24
+
25
+ export async function writeSchema(schema) {
26
+ await writeFile(join(DB_DIR, '_schema.json'), JSON.stringify(schema, null, 2), 'utf8');
27
+ }
28
+
29
+ // ── Query helpers ─────────────────────────────────────────────────────────────
30
+ function applyFilters(rows, filters) {
31
+ return rows.filter(row => filters.every(({ col, op, val }) => {
32
+ const cell = row[col];
33
+ switch (op) {
34
+ case 'eq': return String(cell) == String(val);
35
+ case 'neq': return String(cell) != String(val);
36
+ case 'gt': return Number(cell) > Number(val);
37
+ case 'gte': return Number(cell) >= Number(val);
38
+ case 'lt': return Number(cell) < Number(val);
39
+ case 'lte': return Number(cell) <= Number(val);
40
+ case 'like': return String(cell).toLowerCase().includes(String(val).replace(/%/g, '').toLowerCase());
41
+ case 'in': return Array.isArray(val) && val.includes(cell);
42
+ default: return true;
43
+ }
44
+ }));
45
+ }
46
+
47
+ function applySelect(rows, columns) {
48
+ if (!columns || columns === '*') return rows;
49
+ const cols = columns.split(',').map(c => c.trim());
50
+ return rows.map(row => Object.fromEntries(cols.filter(c => c in row).map(c => [c, row[c]])));
51
+ }
52
+
53
+ function applyOrder(rows, col, dir = 'asc') {
54
+ return [...rows].sort((a, b) => {
55
+ if (a[col] < b[col]) return dir === 'asc' ? -1 : 1;
56
+ if (a[col] > b[col]) return dir === 'asc' ? 1 : -1;
57
+ return 0;
58
+ });
59
+ }
60
+
61
+ function autoFill(record, tableDef) {
62
+ const out = { ...record };
63
+ for (const [col, def] of Object.entries(tableDef?.columns || {})) {
64
+ if (def.auto) {
65
+ if (def.type === 'uuid' && def.primaryKey) out[col] = out[col] || crypto.randomUUID();
66
+ if (def.type === 'timestamp') out[col] = out[col] || new Date().toISOString();
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+
72
+ function validate(record, tableDef) {
73
+ const errors = [];
74
+ for (const [col, def] of Object.entries(tableDef?.columns || {})) {
75
+ if (def.required && !def.auto && !(col in record)) errors.push(`Missing required field: ${col}`);
76
+ }
77
+ return errors;
78
+ }
79
+
80
+ // ── Query Builder ─────────────────────────────────────────────────────────────
81
+ class QueryBuilder {
82
+ constructor(table) {
83
+ this._table = table;
84
+ this._filters = [];
85
+ this._select = '*';
86
+ this._orderCol = null;
87
+ this._orderDir = 'asc';
88
+ this._limit = null;
89
+ this._offset = 0;
90
+ this._op = 'select';
91
+ this._payload = null;
92
+ }
93
+
94
+ select(cols = '*') { this._select = cols; return this; }
95
+ insert(data) { this._op = 'insert'; this._payload = data; return this; }
96
+ update(data) { this._op = 'update'; this._payload = data; return this; }
97
+ delete() { this._op = 'delete'; return this; }
98
+
99
+ eq(col, val) { this._filters.push({ col, op: 'eq', val }); return this; }
100
+ neq(col, val) { this._filters.push({ col, op: 'neq', val }); return this; }
101
+ gt(col, val) { this._filters.push({ col, op: 'gt', val }); return this; }
102
+ gte(col, val) { this._filters.push({ col, op: 'gte', val }); return this; }
103
+ lt(col, val) { this._filters.push({ col, op: 'lt', val }); return this; }
104
+ lte(col, val) { this._filters.push({ col, op: 'lte', val }); return this; }
105
+ like(col, val) { this._filters.push({ col, op: 'like', val }); return this; }
106
+ in(col, val) { this._filters.push({ col, op: 'in', val }); return this; }
107
+
108
+ order(col, dir = 'asc') { this._orderCol = col; this._orderDir = dir; return this; }
109
+ limit(n) { this._limit = n; return this; }
110
+ offset(n) { this._offset = n; return this; }
111
+
112
+ async execute() {
113
+ const schema = await readSchema();
114
+ const tableDef = schema.tables?.[this._table];
115
+
116
+ // INSERT
117
+ if (this._op === 'insert') {
118
+ const rows = await readTable(this._table);
119
+ const records = Array.isArray(this._payload) ? this._payload : [this._payload];
120
+ const inserted = [];
121
+ for (const rec of records) {
122
+ const errors = validate(rec, tableDef);
123
+ if (errors.length) throw new Error(errors.join(', '));
124
+ const filled = autoFill(rec, tableDef);
125
+ rows.push(filled);
126
+ inserted.push(filled);
127
+ }
128
+ await writeTable(this._table, rows);
129
+ return { data: inserted, error: null };
130
+ }
131
+
132
+ // UPDATE
133
+ if (this._op === 'update') {
134
+ const all = await readTable(this._table);
135
+ const updated = all.map(row => {
136
+ if (applyFilters([row], this._filters).length === 0) return row;
137
+ return { ...row, ...this._payload, updated_at: new Date().toISOString() };
138
+ });
139
+ await writeTable(this._table, updated);
140
+ return { data: applyFilters(updated, this._filters), error: null };
141
+ }
142
+
143
+ // DELETE
144
+ if (this._op === 'delete') {
145
+ const all = await readTable(this._table);
146
+ const gone = applyFilters(all, this._filters);
147
+ await writeTable(this._table, all.filter(row => !gone.includes(row)));
148
+ return { data: gone, error: null };
149
+ }
150
+
151
+ // SELECT
152
+ let rows = applyFilters(await readTable(this._table), this._filters);
153
+ if (this._orderCol) rows = applyOrder(rows, this._orderCol, this._orderDir);
154
+ if (this._limit) rows = rows.slice(this._offset, this._offset + this._limit);
155
+ else if (this._offset) rows = rows.slice(this._offset);
156
+ return { data: applySelect(rows, this._select), error: null };
157
+ }
158
+
159
+ then(resolve, reject) { return this.execute().then(resolve, reject); }
160
+ }
161
+
162
+ export default {
163
+ from: (table) => new QueryBuilder(table),
164
+ schema: readSchema,
165
+ };