@zero-server/orm 0.9.1 → 0.9.3

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 (61) hide show
  1. package/LICENSE +21 -21
  2. package/index.d.ts +1 -1
  3. package/index.js +35 -35
  4. package/lib/debug.js +372 -0
  5. package/lib/orm/adapters/json.js +290 -0
  6. package/lib/orm/adapters/memory.js +764 -0
  7. package/lib/orm/adapters/mongo.js +764 -0
  8. package/lib/orm/adapters/mysql.js +933 -0
  9. package/lib/orm/adapters/postgres.js +1144 -0
  10. package/lib/orm/adapters/redis.js +1534 -0
  11. package/lib/orm/adapters/sql-base.js +212 -0
  12. package/lib/orm/adapters/sqlite.js +858 -0
  13. package/lib/orm/audit.js +649 -0
  14. package/lib/orm/cache.js +394 -0
  15. package/lib/orm/geo.js +387 -0
  16. package/lib/orm/index.js +784 -0
  17. package/lib/orm/migrate.js +432 -0
  18. package/lib/orm/model.js +1706 -0
  19. package/lib/orm/plugin.js +375 -0
  20. package/lib/orm/procedures.js +836 -0
  21. package/lib/orm/profiler.js +233 -0
  22. package/lib/orm/query.js +1772 -0
  23. package/lib/orm/replicas.js +241 -0
  24. package/lib/orm/schema.js +307 -0
  25. package/lib/orm/search.js +380 -0
  26. package/lib/orm/seed/data/commerce.js +136 -0
  27. package/lib/orm/seed/data/internet.js +111 -0
  28. package/lib/orm/seed/data/locations.js +204 -0
  29. package/lib/orm/seed/data/names.js +338 -0
  30. package/lib/orm/seed/data/person.js +128 -0
  31. package/lib/orm/seed/data/phone.js +211 -0
  32. package/lib/orm/seed/data/words.js +134 -0
  33. package/lib/orm/seed/factory.js +178 -0
  34. package/lib/orm/seed/fake.js +1186 -0
  35. package/lib/orm/seed/index.js +18 -0
  36. package/lib/orm/seed/rng.js +71 -0
  37. package/lib/orm/seed/seeder.js +125 -0
  38. package/lib/orm/seed/unique.js +68 -0
  39. package/lib/orm/snapshot.js +366 -0
  40. package/lib/orm/tenancy.js +605 -0
  41. package/lib/orm/views.js +350 -0
  42. package/package.json +12 -2
  43. package/types/app.d.ts +223 -0
  44. package/types/auth.d.ts +520 -0
  45. package/types/body.d.ts +14 -0
  46. package/types/cli.d.ts +2 -0
  47. package/types/cluster.d.ts +75 -0
  48. package/types/env.d.ts +80 -0
  49. package/types/errors.d.ts +316 -0
  50. package/types/fetch.d.ts +43 -0
  51. package/types/grpc.d.ts +432 -0
  52. package/types/index.d.ts +384 -0
  53. package/types/lifecycle.d.ts +60 -0
  54. package/types/middleware.d.ts +320 -0
  55. package/types/observe.d.ts +304 -0
  56. package/types/orm.d.ts +1887 -0
  57. package/types/request.d.ts +109 -0
  58. package/types/response.d.ts +157 -0
  59. package/types/router.d.ts +78 -0
  60. package/types/sse.d.ts +78 -0
  61. package/types/websocket.d.ts +126 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * @module orm/plugin
3
+ * @description Plugin system for the zero-server ORM.
4
+ * Provides a registration API, lifecycle hooks, and
5
+ * a standard interface for extending the framework.
6
+ *
7
+ * @example
8
+ * const { PluginManager } = require('@zero-server/sdk');
9
+ *
10
+ * // Define a plugin
11
+ * const timestampPlugin = {
12
+ * name: 'timestamps',
13
+ * version: '1.0.0',
14
+ * install(manager, options) {
15
+ * manager.hook('beforeCreate', (model, data) => {
16
+ * data.createdAt = new Date().toISOString();
17
+ * return data;
18
+ * });
19
+ * },
20
+ * };
21
+ *
22
+ * // Register it
23
+ * const plugins = new PluginManager(db);
24
+ * plugins.register(timestampPlugin);
25
+ */
26
+
27
+ const log = require('../debug')('zero:orm:plugin');
28
+
29
+ // -- Plugin Manager ------------------------------------------
30
+
31
+ /**
32
+ * Plugin registration and lifecycle manager.
33
+ * Plugins can hook into ORM events and extend functionality.
34
+ */
35
+ class PluginManager
36
+ {
37
+ /**
38
+ * @constructor
39
+ * @param {import('./index').Database} [db] - Database instance (optional, can be set later).
40
+ *
41
+ * @example
42
+ * const plugins = new PluginManager(db);
43
+ */
44
+ constructor(db)
45
+ {
46
+ /** @type {import('./index').Database|null} */
47
+ this.db = db || null;
48
+
49
+ /** @type {Map<string, object>} Registered plugins keyed by name. */
50
+ this._plugins = new Map();
51
+
52
+ /** @type {Map<string, Function[]>} Hook listeners keyed by hook name. */
53
+ this._hooks = new Map();
54
+
55
+ /** @type {Map<string, object>} Plugin options keyed by name. */
56
+ this._options = new Map();
57
+
58
+ /** @type {boolean} Whether the manager has been booted. */
59
+ this._booted = false;
60
+ }
61
+
62
+ // -- Registration ------------------------------------
63
+
64
+ /**
65
+ * Register a plugin.
66
+ *
67
+ * @param {object} plugin - Plugin definition object.
68
+ * @param {string} plugin.name - Unique plugin name.
69
+ * @param {string} [plugin.version] - Plugin version string.
70
+ * @param {Function} plugin.install - Install function `(manager, options) => {}`.
71
+ * @param {Function} [plugin.boot] - Boot function called after all plugins are registered.
72
+ * @param {Function} [plugin.uninstall] - Cleanup function.
73
+ * @param {string[]} [plugin.dependencies] - Required plugin names.
74
+ * @param {object} [options] - Plugin-specific options.
75
+ * @returns {PluginManager} this (for chaining)
76
+ *
77
+ * @example
78
+ * plugins.register({
79
+ * name: 'soft-delete',
80
+ * install(manager) {
81
+ * manager.hook('beforeDelete', (model, instance) => {
82
+ * // intercept delete
83
+ * });
84
+ * },
85
+ * });
86
+ */
87
+ register(plugin, options = {})
88
+ {
89
+ if (!plugin || typeof plugin !== 'object')
90
+ {
91
+ throw new Error('Plugin must be an object');
92
+ }
93
+ if (!plugin.name || typeof plugin.name !== 'string')
94
+ {
95
+ throw new Error('Plugin must have a "name" string property');
96
+ }
97
+ if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(plugin.name))
98
+ {
99
+ throw new Error(`Invalid plugin name: "${plugin.name}"`);
100
+ }
101
+ if (typeof plugin.install !== 'function')
102
+ {
103
+ throw new Error(`Plugin "${plugin.name}" must have an "install" function`);
104
+ }
105
+ if (this._plugins.has(plugin.name))
106
+ {
107
+ throw new Error(`Plugin "${plugin.name}" is already registered`);
108
+ }
109
+
110
+ // Check dependencies
111
+ if (Array.isArray(plugin.dependencies))
112
+ {
113
+ for (const dep of plugin.dependencies)
114
+ {
115
+ if (!this._plugins.has(dep))
116
+ {
117
+ throw new Error(`Plugin "${plugin.name}" requires "${dep}" which is not registered`);
118
+ }
119
+ }
120
+ }
121
+
122
+ this._plugins.set(plugin.name, plugin);
123
+ this._options.set(plugin.name, options);
124
+
125
+ // Install immediately
126
+ plugin.install(this, options);
127
+
128
+ log('plugin registered', plugin.name, plugin.version || '');
129
+ return this;
130
+ }
131
+
132
+ /**
133
+ * Register multiple plugins at once.
134
+ *
135
+ * @param {...(object|[object, object])} plugins - Plugin objects or [plugin, options] tuples.
136
+ * @returns {PluginManager} this (for chaining)
137
+ *
138
+ * @example
139
+ * plugins.registerAll(
140
+ * pluginA, // no options
141
+ * [pluginB, { key: 'value' }], // with options
142
+ * );
143
+ */
144
+ registerAll(...plugins)
145
+ {
146
+ for (const entry of plugins)
147
+ {
148
+ if (Array.isArray(entry))
149
+ {
150
+ this.register(entry[0], entry[1] || {});
151
+ }
152
+ else
153
+ {
154
+ this.register(entry);
155
+ }
156
+ }
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Unregister a plugin by name.
162
+ *
163
+ * @param {string} name - Plugin name.
164
+ * @returns {PluginManager} this (for chaining)
165
+ */
166
+ unregister(name)
167
+ {
168
+ const plugin = this._plugins.get(name);
169
+ if (!plugin) return this;
170
+
171
+ if (typeof plugin.uninstall === 'function')
172
+ {
173
+ plugin.uninstall(this);
174
+ }
175
+
176
+ this._plugins.delete(name);
177
+ this._options.delete(name);
178
+ log('plugin unregistered', name);
179
+ return this;
180
+ }
181
+
182
+ // -- Lifecycle ---------------------------------------
183
+
184
+ /**
185
+ * Boot all registered plugins.
186
+ * Calls the `boot()` method on each plugin (if defined).
187
+ * Should be called after all plugins are registered.
188
+ *
189
+ * @returns {Promise<PluginManager>} this (for chaining)
190
+ *
191
+ * @example
192
+ * plugins.register(pluginA).register(pluginB);
193
+ * await plugins.boot();
194
+ */
195
+ async boot()
196
+ {
197
+ if (this._booted) return this;
198
+
199
+ for (const [name, plugin] of this._plugins)
200
+ {
201
+ if (typeof plugin.boot === 'function')
202
+ {
203
+ await plugin.boot(this, this._options.get(name) || {});
204
+ log('plugin booted', name);
205
+ }
206
+ }
207
+
208
+ this._booted = true;
209
+ return this;
210
+ }
211
+
212
+ // -- Hook System -------------------------------------
213
+
214
+ /**
215
+ * Register a hook listener.
216
+ *
217
+ * @param {string} name - Hook name (e.g. 'beforeCreate', 'afterUpdate').
218
+ * @param {Function} callback - Hook callback function.
219
+ * @returns {PluginManager} this (for chaining)
220
+ *
221
+ * @example
222
+ * manager.hook('beforeCreate', (model, data) => {
223
+ * data.slug = slugify(data.title);
224
+ * return data;
225
+ * });
226
+ */
227
+ hook(name, callback)
228
+ {
229
+ if (typeof callback !== 'function')
230
+ {
231
+ throw new Error('Hook callback must be a function');
232
+ }
233
+
234
+ if (!this._hooks.has(name))
235
+ {
236
+ this._hooks.set(name, []);
237
+ }
238
+ this._hooks.get(name).push(callback);
239
+ return this;
240
+ }
241
+
242
+ /**
243
+ * Remove a hook listener.
244
+ *
245
+ * @param {string} name - Hook name.
246
+ * @param {Function} callback - The exact function reference to remove.
247
+ * @returns {PluginManager} this (for chaining)
248
+ */
249
+ unhook(name, callback)
250
+ {
251
+ const handlers = this._hooks.get(name);
252
+ if (!handlers) return this;
253
+
254
+ const idx = handlers.indexOf(callback);
255
+ if (idx !== -1) handlers.splice(idx, 1);
256
+ return this;
257
+ }
258
+
259
+ /**
260
+ * Execute all listeners for a hook.
261
+ * Listeners run in registration order. If a listener returns a value,
262
+ * that value is passed to the next listener as the payload.
263
+ *
264
+ * @param {string} name - Hook name.
265
+ * @param {...*} args - Arguments to pass to hook callbacks.
266
+ * @returns {Promise<*>} Final transformed value (or last arg if no transforms).
267
+ *
268
+ * @example
269
+ * const data = await manager.runHook('beforeCreate', model, rawData);
270
+ */
271
+ async runHook(name, ...args)
272
+ {
273
+ const handlers = this._hooks.get(name);
274
+ if (!handlers || handlers.length === 0) return args[args.length - 1];
275
+
276
+ let result = args[args.length - 1];
277
+ for (const fn of handlers)
278
+ {
279
+ const out = await fn(...args.slice(0, -1), result);
280
+ if (out !== undefined) result = out;
281
+ }
282
+ return result;
283
+ }
284
+
285
+ /**
286
+ * Check if any listeners exist for a hook.
287
+ *
288
+ * @param {string} name - Hook name.
289
+ * @returns {boolean}
290
+ */
291
+ hasHook(name)
292
+ {
293
+ const handlers = this._hooks.get(name);
294
+ return !!handlers && handlers.length > 0;
295
+ }
296
+
297
+ // -- Query -------------------------------------------
298
+
299
+ /**
300
+ * Check if a plugin is registered.
301
+ *
302
+ * @param {string} name - Plugin name.
303
+ * @returns {boolean}
304
+ */
305
+ has(name)
306
+ {
307
+ return this._plugins.has(name);
308
+ }
309
+
310
+ /**
311
+ * Get a registered plugin by name.
312
+ *
313
+ * @param {string} name - Plugin name.
314
+ * @returns {object|undefined} Plugin object, or undefined.
315
+ */
316
+ get(name)
317
+ {
318
+ return this._plugins.get(name);
319
+ }
320
+
321
+ /**
322
+ * Get options for a registered plugin.
323
+ *
324
+ * @param {string} name - Plugin name.
325
+ * @returns {object|undefined}
326
+ */
327
+ getOptions(name)
328
+ {
329
+ return this._options.get(name);
330
+ }
331
+
332
+ /**
333
+ * List all registered plugin names.
334
+ *
335
+ * @returns {string[]}
336
+ *
337
+ * @example
338
+ * plugins.list(); // ['timestamps', 'soft-delete']
339
+ */
340
+ list()
341
+ {
342
+ return [...this._plugins.keys()];
343
+ }
344
+
345
+ /**
346
+ * Get detailed info about all registered plugins.
347
+ *
348
+ * @returns {Array<{name: string, version: string, hasBootFn: boolean}>}
349
+ */
350
+ info()
351
+ {
352
+ const result = [];
353
+ for (const [name, plugin] of this._plugins)
354
+ {
355
+ result.push({
356
+ name,
357
+ version: plugin.version || '0.0.0',
358
+ hasBootFn: typeof plugin.boot === 'function',
359
+ });
360
+ }
361
+ return result;
362
+ }
363
+
364
+ /**
365
+ * Number of registered plugins.
366
+ *
367
+ * @type {number}
368
+ */
369
+ get size()
370
+ {
371
+ return this._plugins.size;
372
+ }
373
+ }
374
+
375
+ module.exports = { PluginManager };