@zero-server/orm 0.9.1 → 0.9.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 (41) hide show
  1. package/LICENSE +21 -21
  2. package/index.js +35 -35
  3. package/lib/debug.js +372 -0
  4. package/lib/orm/adapters/json.js +290 -0
  5. package/lib/orm/adapters/memory.js +764 -0
  6. package/lib/orm/adapters/mongo.js +764 -0
  7. package/lib/orm/adapters/mysql.js +933 -0
  8. package/lib/orm/adapters/postgres.js +1144 -0
  9. package/lib/orm/adapters/redis.js +1534 -0
  10. package/lib/orm/adapters/sql-base.js +212 -0
  11. package/lib/orm/adapters/sqlite.js +858 -0
  12. package/lib/orm/audit.js +649 -0
  13. package/lib/orm/cache.js +394 -0
  14. package/lib/orm/geo.js +387 -0
  15. package/lib/orm/index.js +784 -0
  16. package/lib/orm/migrate.js +432 -0
  17. package/lib/orm/model.js +1706 -0
  18. package/lib/orm/plugin.js +375 -0
  19. package/lib/orm/procedures.js +836 -0
  20. package/lib/orm/profiler.js +233 -0
  21. package/lib/orm/query.js +1772 -0
  22. package/lib/orm/replicas.js +241 -0
  23. package/lib/orm/schema.js +307 -0
  24. package/lib/orm/search.js +380 -0
  25. package/lib/orm/seed/data/commerce.js +136 -0
  26. package/lib/orm/seed/data/internet.js +111 -0
  27. package/lib/orm/seed/data/locations.js +204 -0
  28. package/lib/orm/seed/data/names.js +338 -0
  29. package/lib/orm/seed/data/person.js +128 -0
  30. package/lib/orm/seed/data/phone.js +211 -0
  31. package/lib/orm/seed/data/words.js +134 -0
  32. package/lib/orm/seed/factory.js +178 -0
  33. package/lib/orm/seed/fake.js +1186 -0
  34. package/lib/orm/seed/index.js +18 -0
  35. package/lib/orm/seed/rng.js +71 -0
  36. package/lib/orm/seed/seeder.js +125 -0
  37. package/lib/orm/seed/unique.js +68 -0
  38. package/lib/orm/snapshot.js +366 -0
  39. package/lib/orm/tenancy.js +605 -0
  40. package/lib/orm/views.js +350 -0
  41. package/package.json +11 -2
@@ -0,0 +1,290 @@
1
+ /**
2
+ * @module orm/adapters/json
3
+ * @description JSON file-backed database adapter.
4
+ * Persists data to JSON files on disk — one file per table.
5
+ * Zero-dependency, suitable for prototyping, small apps, and
6
+ * embedded scenarios. Uses atomic writes for safety.
7
+ *
8
+ * @example
9
+ * const { Database, Model, TYPES } = require('@zero-server/sdk');
10
+ *
11
+ * const db = Database.connect('json', { directory: './data' });
12
+ *
13
+ * class Note extends Model {
14
+ * static table = 'notes';
15
+ * static schema = {
16
+ * id: { type: TYPES.INTEGER, primaryKey: true, autoIncrement: true },
17
+ * body: { type: TYPES.TEXT, required: true },
18
+ * };
19
+ * }
20
+ *
21
+ * db.register(Note);
22
+ * await db.sync();
23
+ *
24
+ * await Note.create({ body: 'Hello world' });
25
+ * // Data persisted to ./data/notes.json
26
+ */
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const MemoryAdapter = require('./memory');
30
+
31
+ class JsonAdapter extends MemoryAdapter
32
+ {
33
+ /**
34
+ * @constructor
35
+ * @param {object} options - Configuration options.
36
+ * @param {string} options.dir - Directory to store JSON files. Created if needed.
37
+ * @param {boolean} [options.pretty=true] - Pretty-print JSON files.
38
+ * @param {number} [options.flushInterval=50] - Debounce interval in ms for writes.
39
+ * @param {boolean} [options.autoFlush=true] - Automatically flush writes (set false for manual flush()).
40
+ */
41
+ constructor(options = {})
42
+ {
43
+ super();
44
+ if (!options.dir) throw new Error('JsonAdapter requires a "dir" option');
45
+
46
+ /** @private */ this._dir = path.resolve(options.dir);
47
+ /** @private */ this._pretty = options.pretty !== false;
48
+ /** @private */ this._dirty = new Set();
49
+ /** @private */ this._flushTimer = null;
50
+ /** @private */ this._flushInterval = options.flushInterval || 50;
51
+ /** @private */ this._autoFlush = options.autoFlush !== false;
52
+
53
+ // Ensure directory exists
54
+ if (!fs.existsSync(this._dir))
55
+ {
56
+ fs.mkdirSync(this._dir, { recursive: true });
57
+ }
58
+
59
+ // Load any existing tables
60
+ try
61
+ {
62
+ const files = fs.readdirSync(this._dir).filter(f => f.endsWith('.json'));
63
+ for (const file of files)
64
+ {
65
+ const table = path.basename(file, '.json');
66
+ const content = fs.readFileSync(path.join(this._dir, file), 'utf8');
67
+ const parsed = JSON.parse(content);
68
+ this._tables.set(table, parsed.rows || []);
69
+ this._autoIncrements.set(table, parsed.autoIncrement || 1);
70
+ }
71
+ }
72
+ catch (e) { /* fresh start */ }
73
+ }
74
+
75
+ /**
76
+ * Create a table with the given schema.
77
+ * @param {string} table - Table name.
78
+ * @param {object} schema - Column definitions keyed by column name.
79
+ * @returns {Promise<void>}
80
+ */
81
+ async createTable(table, schema)
82
+ {
83
+ await super.createTable(table, schema);
84
+ this._scheduleSave(table);
85
+ }
86
+
87
+ /**
88
+ * Drop a table if it exists.
89
+ * @param {string} table - Table name.
90
+ * @returns {Promise<void>}
91
+ */
92
+ async dropTable(table)
93
+ {
94
+ await super.dropTable(table);
95
+ const filePath = path.join(this._dir, `${table}.json`);
96
+ try { fs.unlinkSync(filePath); } catch (e) { /* ignore */ }
97
+ }
98
+
99
+ /**
100
+ * Insert a single row.
101
+ * @param {string} table - Table name.
102
+ * @param {object} data - Row data as key-value pairs.
103
+ * @returns {Promise<object>} The inserted row.
104
+ */
105
+ async insert(table, data)
106
+ {
107
+ const result = await super.insert(table, data);
108
+ this._scheduleSave(table);
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * Update a single row by primary key.
114
+ * @param {string} table - Table name.
115
+ * @param {string} pk - Primary key column.
116
+ * @param {*} pkVal - Primary key value.
117
+ * @param {object} data - Fields to update.
118
+ * @returns {Promise<void>}
119
+ */
120
+ async update(table, pk, pkVal, data)
121
+ {
122
+ await super.update(table, pk, pkVal, data);
123
+ this._scheduleSave(table);
124
+ }
125
+
126
+ /**
127
+ * Update rows matching the given conditions.
128
+ * @param {string} table - Table name.
129
+ * @param {object} conditions - Filter conditions.
130
+ * @param {object} data - Fields to update.
131
+ * @returns {Promise<number>} Number of affected rows.
132
+ */
133
+ async updateWhere(table, conditions, data)
134
+ {
135
+ const count = await super.updateWhere(table, conditions, data);
136
+ if (count > 0) this._scheduleSave(table);
137
+ return count;
138
+ }
139
+
140
+ /**
141
+ * Delete a single row by primary key.
142
+ * @param {string} table - Table name.
143
+ * @param {string} pk - Primary key column.
144
+ * @param {*} pkVal - Primary key value.
145
+ * @returns {Promise<void>}
146
+ */
147
+ async remove(table, pk, pkVal)
148
+ {
149
+ await super.remove(table, pk, pkVal);
150
+ this._scheduleSave(table);
151
+ }
152
+
153
+ /**
154
+ * Delete rows matching the given conditions.
155
+ * @param {string} table - Table name.
156
+ * @param {object} conditions - Filter conditions.
157
+ * @returns {Promise<number>} Number of deleted rows.
158
+ */
159
+ async deleteWhere(table, conditions)
160
+ {
161
+ const count = await super.deleteWhere(table, conditions);
162
+ if (count > 0) this._scheduleSave(table);
163
+ return count;
164
+ }
165
+
166
+ /**
167
+ * Delete all rows from a table.
168
+ * @param {string} table - Table name.
169
+ * @returns {Promise<void>}
170
+ */
171
+ async clear()
172
+ {
173
+ await super.clear();
174
+ for (const table of this._tables.keys()) this._scheduleSave(table);
175
+ }
176
+
177
+ /**
178
+ * Immediately flush all pending writes.
179
+ * @returns {Promise<void>}
180
+ */
181
+ async flush()
182
+ {
183
+ for (const table of this._dirty) this._saveTable(table);
184
+ this._dirty.clear();
185
+ if (this._flushTimer) { clearTimeout(this._flushTimer); this._flushTimer = null; }
186
+ }
187
+
188
+ /** @private Schedule a debounced save for the given table. */
189
+ _scheduleSave(table)
190
+ {
191
+ this._dirty.add(table);
192
+ if (this._autoFlush && !this._flushTimer)
193
+ {
194
+ this._flushTimer = setTimeout(() =>
195
+ {
196
+ this.flush();
197
+ }, this._flushInterval);
198
+ }
199
+ }
200
+
201
+ /** @private Write table data to JSON file. */
202
+ _saveTable(table)
203
+ {
204
+ const filePath = path.join(this._dir, `${table}.json`);
205
+ const data = {
206
+ autoIncrement: this._autoIncrements.get(table) || 1,
207
+ rows: this._tables.get(table) || [],
208
+ };
209
+ const json = this._pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
210
+
211
+ // Atomic write: write to temp file then rename (retry on Windows EPERM)
212
+ const tmpPath = filePath + '.tmp';
213
+ fs.writeFileSync(tmpPath, json, 'utf8');
214
+ try { fs.renameSync(tmpPath, filePath); }
215
+ catch (err)
216
+ {
217
+ if (err.code === 'EPERM' || err.code === 'EACCES')
218
+ {
219
+ try { fs.unlinkSync(filePath); } catch {}
220
+ try { fs.renameSync(tmpPath, filePath); }
221
+ catch { fs.writeFileSync(filePath, json, 'utf8'); }
222
+ try { fs.unlinkSync(tmpPath); } catch {}
223
+ }
224
+ else throw err;
225
+ }
226
+ }
227
+
228
+ // -- JSON Adapter Utilities --------------------------
229
+
230
+ /**
231
+ * Get the directory where JSON files are stored.
232
+ * @returns {string} Absolute path to the data directory.
233
+ */
234
+ get directory() { return this._dir; }
235
+
236
+ /**
237
+ * Get the total size of all JSON files in bytes.
238
+ * @returns {number} Combined file size in bytes.
239
+ */
240
+ fileSize()
241
+ {
242
+ let total = 0;
243
+ try
244
+ {
245
+ const files = fs.readdirSync(this._dir).filter(f => f.endsWith('.json'));
246
+ for (const file of files)
247
+ {
248
+ total += fs.statSync(path.join(this._dir, file)).size;
249
+ }
250
+ }
251
+ catch { /* empty dir, no files */ }
252
+ return total;
253
+ }
254
+
255
+ /**
256
+ * Check if there are pending writes that haven't been flushed.
257
+ * @returns {boolean} `true` if writes are pending.
258
+ */
259
+ get hasPendingWrites() { return this._dirty.size > 0; }
260
+
261
+ /**
262
+ * Compact a specific table's JSON file (re-serialize, removes whitespace bloat).
263
+ * @param {string} table - Table name to compact.
264
+ * @returns {void}
265
+ */
266
+ compact(table)
267
+ {
268
+ if (this._tables.has(table)) this._saveTable(table);
269
+ }
270
+
271
+ /**
272
+ * Back up the entire data directory to a target path.
273
+ * @param {string} destDir - Destination directory (will be created).
274
+ */
275
+ backup(destDir)
276
+ {
277
+ const dest = path.resolve(destDir);
278
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
279
+ const files = fs.readdirSync(this._dir).filter(f => f.endsWith('.json'));
280
+ for (const file of files)
281
+ {
282
+ fs.copyFileSync(
283
+ path.join(this._dir, file),
284
+ path.join(dest, file)
285
+ );
286
+ }
287
+ }
288
+ }
289
+
290
+ module.exports = JsonAdapter;