jsql-neo 5.4.2 → 6.0.0-beta2

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 (39) hide show
  1. package/README.md +198 -14
  2. package/index.d.ts +29 -0
  3. package/index.js +12 -2
  4. package/lib/ast.js +214 -0
  5. package/lib/database.js +26 -60
  6. package/lib/mongo_server.js +1 -1
  7. package/lib/native_client.js +43 -66
  8. package/lib/plugin.js +227 -3
  9. package/lib/plugins/audit.js +37 -0
  10. package/lib/plugins/index.js +72 -0
  11. package/lib/plugins/logger.js +35 -0
  12. package/lib/plugins/metrics.js +38 -0
  13. package/lib/plugins/timestamps.js +43 -0
  14. package/lib/plugins/validation.js +128 -0
  15. package/lib/sql.js +1212 -84
  16. package/lib/wasm_client.js +41 -57
  17. package/native/jsql-neo-native.node +0 -0
  18. package/nativesrc/jsql-neo-core/Cargo.lock +8 -1
  19. package/nativesrc/jsql-neo-core/Cargo.toml +3 -1
  20. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +50 -13
  21. package/nativesrc/jsql-neo-core/src/engine/memory.rs +46 -23
  22. package/nativesrc/jsql-neo-core/src/engine/mod.rs +13 -1
  23. package/nativesrc/jsql-neo-core/src/engine/table.rs +338 -42
  24. package/nativesrc/jsql-neo-core/src/storage/wal.rs +245 -32
  25. package/nativesrc/jsql-neo-native/Cargo.lock +9 -2
  26. package/nativesrc/jsql-neo-native/Cargo.toml +9 -1
  27. package/nativesrc/jsql-neo-native/src/lib.rs +14 -2
  28. package/nativesrc/jsql-neo-wasm/Cargo.lock +9 -2
  29. package/nativesrc/jsql-neo-wasm/Cargo.toml +8 -1
  30. package/nativesrc/jsql-neo-wasm/src/lib.rs +18 -2
  31. package/package.json +4 -3
  32. package/test/sql-parser.test.js +450 -0
  33. package/wasm/browser.mjs +12 -0
  34. package/wasm/browser_bg.mjs +269 -248
  35. package/wasm/jsql_neo_wasm.d.ts +6 -0
  36. package/wasm/jsql_neo_wasm.js +248 -127
  37. package/wasm/jsql_neo_wasm_bg.wasm +0 -0
  38. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +22 -22
  39. package/wasm/package.json +1 -1
package/lib/database.js CHANGED
@@ -11,6 +11,10 @@ const crypto = require('crypto');
11
11
  const Table = require('./table');
12
12
  const { createError } = require('./errors');
13
13
  const JSQLFormat = require('./jsql_format');
14
+ const {
15
+ createHookRegistry, applyPlugin, buildPluginContext, registerHook,
16
+ runHooks, getPluginApis
17
+ } = require('./plugin');
14
18
 
15
19
  /**
16
20
  * 解析字段简写类型字符串,如 'integer primary key'、'string unique'、
@@ -91,17 +95,7 @@ class Database {
91
95
  this._versions = {}; // tableName -> [{ rows, timestamp }]
92
96
  this._maxVersions = options.maxVersions || 50;
93
97
  this._plugins = [];
94
- this._hooks = {
95
- beforeInsert: [], afterInsert: [],
96
- beforeUpdate: [], afterUpdate: [],
97
- beforeDelete: [], afterDelete: [],
98
- beforeFind: [], afterFind: [],
99
- beforeCreateTable: [], afterCreateTable: [],
100
- beforeDropTable: [], afterDropTable: [],
101
- beforeFlush: [], afterFlush: [],
102
- beforeCount: [], afterCount: [],
103
- onStart: [], onStop: []
104
- };
98
+ this._hooks = createHookRegistry();
105
99
  this._eventListeners = [];
106
100
  this._tableNames = new Set(Object.keys(this._tables));
107
101
  this._migrations = [];
@@ -1082,53 +1076,18 @@ class Database {
1082
1076
 
1083
1077
  /**
1084
1078
  * 注册插件
1085
- * @param {object|function} plugin - { name, install(db, ctx), hooks, onEvent } 或函数 (作为 install)
1079
+ * @param {object|function} plugin - { name, version, install(db, ctx), hooks, api, onEvent } 或函数 (作为 install)
1086
1080
  */
1087
1081
  use(plugin) {
1088
- if (typeof plugin === 'function') {
1089
- plugin = { install: plugin };
1090
- }
1091
- if (plugin.hooks) {
1092
- for (const [event, fn] of Object.entries(plugin.hooks)) {
1093
- if (this._hooks[event]) {
1094
- this._hooks[event].push(fn);
1095
- }
1096
- }
1097
- }
1098
- if (typeof plugin.install === 'function') {
1099
- plugin.install(this, this._buildCtx(plugin));
1100
- }
1101
- if (typeof plugin.onEvent === 'function') {
1102
- this._eventListeners.push(plugin.onEvent);
1103
- }
1104
- this._plugins.push(plugin);
1105
- return this;
1082
+ return applyPlugin(this, plugin);
1106
1083
  }
1107
1084
 
1108
1085
  _buildCtx(plugin) {
1109
- const self = this;
1110
- return {
1111
- name: plugin.name || 'anonymous',
1112
- engine: this,
1113
- plugin,
1114
- on(hook, fn) { return self.on(hook, fn); },
1115
- onEvent(fn) { return self.onEvent(fn); },
1116
- emit(eventName, data) { self._emit(eventName, data); },
1117
- tables() { return Array.from(self._tableNames); },
1118
- hasTable(name) { return self._tableNames.has(name); },
1119
- getTableSchema(name) {
1120
- const t = self._tables[name];
1121
- return t ? t._schema : null;
1122
- },
1123
- table(name) { return self._tables[name] || null; }
1124
- };
1086
+ return buildPluginContext(this, plugin);
1125
1087
  }
1126
1088
 
1127
1089
  on(event, fn) {
1128
- if (this._hooks[event]) {
1129
- this._hooks[event].push(fn);
1130
- }
1131
- return this;
1090
+ return registerHook(this, event, fn);
1132
1091
  }
1133
1092
 
1134
1093
  onEvent(fn) {
@@ -1136,6 +1095,22 @@ class Database {
1136
1095
  return this;
1137
1096
  }
1138
1097
 
1098
+ /** 广播事件(插件上下文 / 外部均可调用) */
1099
+ emit(eventName, data) {
1100
+ this._emit(eventName, data);
1101
+ return this;
1102
+ }
1103
+
1104
+ /** 主动触发一个钩子(含插件自定义钩子) */
1105
+ runHook(hookName, ...args) {
1106
+ return this._runHooks(hookName, args);
1107
+ }
1108
+
1109
+ /** 取插件对外暴露的 API(由 plugin.api 声明) */
1110
+ plugin(name) {
1111
+ return getPluginApis(this).get(name) || null;
1112
+ }
1113
+
1139
1114
  _emit(eventName, data) {
1140
1115
  for (const fn of this._eventListeners) {
1141
1116
  try { fn(eventName, data); } catch (e) { /* ignore */ }
@@ -1143,16 +1118,7 @@ class Database {
1143
1118
  }
1144
1119
 
1145
1120
  _runHooks(hookName, args) {
1146
- const hooks = this._hooks[hookName];
1147
- if (!hooks || hooks.length === 0) return true;
1148
- for (const fn of hooks) {
1149
- const r = fn(...args);
1150
- if (r === false) return false;
1151
- if (r !== undefined && args.length > 0) {
1152
- args[0] = r;
1153
- }
1154
- }
1155
- return true;
1121
+ return runHooks(this, hookName, args);
1156
1122
  }
1157
1123
 
1158
1124
  // ============================================================
@@ -322,7 +322,7 @@ class MongoServer {
322
322
  localTime: new Date(), logicalSessionTimeoutMinutes: 30, connectionId: 1,
323
323
  };
324
324
  case 'ping': return { ok: 1 };
325
- case 'buildInfo': return { ok: 1, version: '5.4.0-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 4, 0, 0] };
325
+ case 'buildInfo': return { ok: 1, version: '5.6.0-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 6, 0, 0] };
326
326
  case 'getParameter': return { ok: 1 };
327
327
  case 'endSessions': return { ok: 1 };
328
328
  case 'listDatabases':
@@ -1,6 +1,10 @@
1
1
  const path = require('path');
2
2
  const os = require('os');
3
3
  const { executeSQL } = require('./sql');
4
+ const {
5
+ createHookRegistry, applyPlugin, buildPluginContext, registerHook,
6
+ runHooks, getPluginApis
7
+ } = require('./plugin');
4
8
  const native = require(path.join(__dirname, '..', 'native', 'jsql-neo-native.node'));
5
9
  const INT64_TAG = 1;
6
10
  const FLOAT_TAG = 2;
@@ -229,17 +233,9 @@ class JSQL {
229
233
  this._evictTimer = null;
230
234
 
231
235
  this._plugins = [];
232
- this._hooks = {
233
- beforeInsert: [], afterInsert: [],
234
- beforeUpdate: [], afterUpdate: [],
235
- beforeDelete: [], afterDelete: [],
236
- beforeFind: [], afterFind: [],
237
- beforeCreateTable: [], afterCreateTable: [],
238
- beforeDropTable: [], afterDropTable: [],
239
- beforeFlush: [], afterFlush: [],
240
- beforeCount: [], afterCount: [],
241
- onStart: [], onStop: []
242
- };
236
+ this._hooks = createHookRegistry();
237
+ // native 引擎:钩子内再次调用引擎方法会触发 N-API 重入崩溃,需显式防护
238
+ this._forbidReentry = true;
243
239
  this._eventListeners = [];
244
240
  if (this._autoPlugins) {
245
241
  const { ModuleManager } = require('./mod');
@@ -247,47 +243,16 @@ class JSQL {
247
243
  }
248
244
  }
249
245
 
250
- use(plugin) {
251
- if (typeof plugin === 'function') {
252
- plugin = { install: plugin };
253
- }
254
- if (plugin.hooks) {
255
- for (const [event, fn] of Object.entries(plugin.hooks)) {
256
- if (this._hooks[event]) {
257
- this._hooks[event].push(fn);
258
- }
259
- }
260
- }
261
- if (typeof plugin.install === 'function') {
262
- plugin.install(this, this._buildCtx(plugin));
263
- }
264
- if (typeof plugin.onEvent === 'function') {
265
- this._eventListeners.push(plugin.onEvent);
266
- }
267
- this._plugins.push(plugin);
268
- return this;
246
+ use(plugin, opts) {
247
+ return applyPlugin(this, plugin, opts);
269
248
  }
270
249
 
271
250
  _buildCtx(plugin) {
272
- const self = this;
273
- return {
274
- name: plugin.name || 'anonymous',
275
- engine: this,
276
- plugin,
277
- on(hook, fn) { return self.on(hook, fn); },
278
- onEvent(fn) { return self.onEvent(fn); },
279
- emit(eventName, data) { self._emit(eventName, data); },
280
- tables() { return Array.from(self._tableNames); },
281
- hasTable(name) { return self._tableNames.has(name); },
282
- getTableSchema(name) { return self._schemas[name] || null; }
283
- };
251
+ return buildPluginContext(this, plugin);
284
252
  }
285
253
 
286
254
  on(event, fn) {
287
- if (this._hooks[event]) {
288
- this._hooks[event].push(fn);
289
- }
290
- return this;
255
+ return registerHook(this, event, fn);
291
256
  }
292
257
 
293
258
  onEvent(fn) {
@@ -295,6 +260,22 @@ class JSQL {
295
260
  return this;
296
261
  }
297
262
 
263
+ /** 广播事件(插件上下文 / 外部均可调用) */
264
+ emit(eventName, data) {
265
+ this._emit(eventName, data);
266
+ return this;
267
+ }
268
+
269
+ /** 主动触发一个钩子(含插件自定义钩子) */
270
+ runHook(hookName, ...args) {
271
+ return this._runHooks(hookName, args);
272
+ }
273
+
274
+ /** 取插件对外暴露的 API(由 plugin.api 声明) */
275
+ plugin(name) {
276
+ return getPluginApis(this).get(name) || null;
277
+ }
278
+
298
279
  _emit(eventName, data) {
299
280
  for (const fn of this._eventListeners) {
300
281
  try { fn(eventName, data); } catch (e) { /* ignore */ }
@@ -302,25 +283,7 @@ class JSQL {
302
283
  }
303
284
 
304
285
  _runHooks(hookName, args) {
305
- const hooks = this._hooks[hookName];
306
- if (!hooks || hooks.length === 0) return true;
307
- if (this._inHook) {
308
- // 防止插件 hook 内重入 native 调用导致 N-API 崩溃(段错误)
309
- throw new Error('ER_PLUGIN_REENTRY: plugin hook "' + hookName + '" re-entered native call; plugin must not call engine methods inside its own hook');
310
- }
311
- this._inHook = true;
312
- try {
313
- for (const fn of hooks) {
314
- const r = fn(...args);
315
- if (r === false) return false;
316
- if (r !== undefined && args.length > 0) {
317
- args[0] = r;
318
- }
319
- }
320
- return true;
321
- } finally {
322
- this._inHook = false;
323
- }
286
+ return runHooks(this, hookName, args);
324
287
  }
325
288
 
326
289
  async start() {
@@ -551,6 +514,17 @@ class JSQL {
551
514
  return r;
552
515
  }
553
516
 
517
+ async addColumn(table, name, def) {
518
+ await this._flush();
519
+ const mapped = mapNativeSchema({ [name]: def })[name];
520
+ const r = safeParse(native.jsqlAddColumn(table, name, JSON.stringify(mapped)));
521
+ if (r && r.ok === false) throw new Error(r.error || 'add column failed');
522
+ if (!this._schemas[table]) this._schemas[table] = {};
523
+ this._schemas[table][name] = mapped;
524
+ this._emit('addColumn', { table, name, def: mapped });
525
+ return r;
526
+ }
527
+
554
528
  async dropTable(name) {
555
529
  await this._flush();
556
530
  if (!this._runHooks('beforeDropTable', [name])) return null;
@@ -676,7 +650,10 @@ class JSQL {
676
650
  * await db.query("UPDATE users SET age = age + 1 WHERE name = 'A'");
677
651
  */
678
652
  async query(sql, paramsOrOpts, opts) {
679
- return executeSQL(this, sql, paramsOrOpts, opts);
653
+ if (!this._runHooks('beforeQuery', [sql, paramsOrOpts, opts])) return null;
654
+ const result = await executeSQL(this, sql, paramsOrOpts, opts);
655
+ this._runHooks('afterQuery', [sql, paramsOrOpts, opts, result]);
656
+ return result;
680
657
  }
681
658
 
682
659
  async stop() {
package/lib/plugin.js CHANGED
@@ -1,7 +1,59 @@
1
+ /**
2
+ * JSQL-NEO 可编程插件系统
3
+ *
4
+ * 统一的插件运行时,被 native / wasm / js 三个引擎共用,开放四类扩展点:
5
+ * 1. 生命周期钩子(HOOKS)——在引擎操作前后触发,返回 false 可中止操作;
6
+ * 2. 事件监听(onEvent / emit)——操作完成后广播;
7
+ * 3. 上下文接口(ctx)——插件拿到引擎能力、schema、日志、配置与私有存储;
8
+ * 4. 插件 API(api / db.plugin(name))——插件对外暴露可编程接口。
9
+ *
10
+ * 约定:钩子内不要调用会再次触发钩子的引擎方法(native 引擎会抛
11
+ * ER_PLUGIN_REENTRY 保护 N-API 不崩溃);需要修改数据时直接原地修改传入的
12
+ * 数组 / 对象,返回 undefined;返回 false 表示中止本次操作。
13
+ */
14
+
15
+ /** 内置生命周期钩子名(自定义钩子名也会被自动登记,无需在此声明) */
16
+ const HOOKS = Object.freeze([
17
+ 'beforeInsert', 'afterInsert',
18
+ 'beforeUpdate', 'afterUpdate',
19
+ 'beforeDelete', 'afterDelete',
20
+ 'beforeFind', 'afterFind',
21
+ 'beforeCreateTable', 'afterCreateTable',
22
+ 'beforeDropTable', 'afterDropTable',
23
+ 'beforeFlush', 'afterFlush',
24
+ 'beforeCount', 'afterCount',
25
+ 'beforeQuery', 'afterQuery',
26
+ 'onStart', 'onStop'
27
+ ]);
28
+
29
+ /** 创建一个预置了全部内置钩子的空注册表 */
30
+ function createHookRegistry() {
31
+ const reg = {};
32
+ for (const h of HOOKS) reg[h] = [];
33
+ return reg;
34
+ }
35
+
1
36
  class Plugin {
37
+ /**
38
+ * @param {string} name 插件名(用于 db.plugin(name) 取 API、私有存储隔离)
39
+ * @param {object} [opts]
40
+ * @param {string} [opts.version]
41
+ * @param {object} [opts.hooks] { hookName: fn | fn[] }
42
+ * @param {string[]} [opts.depends] 依赖的其它插件名
43
+ * @param {number} [opts.priority]
44
+ * @param {object} [opts.config] 传给插件的配置
45
+ * @param {object} [opts.api] 插件对外暴露的接口对象
46
+ * @param {function} [opts.onEvent]
47
+ * @param {function} [opts.install] install(engine, ctx)
48
+ */
2
49
  constructor(name, opts = {}) {
3
50
  this.name = name;
51
+ this.version = opts.version || null;
4
52
  this.hooks = opts.hooks || {};
53
+ this.depends = opts.depends || [];
54
+ this.priority = opts.priority || 0;
55
+ this.config = opts.config || {};
56
+ this.api = opts.api || null;
5
57
  this._onEvent = opts.onEvent || null;
6
58
  this._install = opts.install || null;
7
59
  }
@@ -11,8 +63,11 @@ class Plugin {
11
63
  return this;
12
64
  }
13
65
 
66
+ /** 注册钩子;同名钩子可多次注册,按注册顺序执行 */
14
67
  on(hook, fn) {
15
- this.hooks[hook] = fn;
68
+ if (!this.hooks[hook]) this.hooks[hook] = [];
69
+ if (!Array.isArray(this.hooks[hook])) this.hooks[hook] = [this.hooks[hook]];
70
+ this.hooks[hook].push(fn);
16
71
  return this;
17
72
  }
18
73
 
@@ -21,15 +76,184 @@ class Plugin {
21
76
  return this;
22
77
  }
23
78
 
79
+ /** 声明插件对外暴露的接口 */
80
+ expose(api) {
81
+ this.api = api;
82
+ return this;
83
+ }
84
+
24
85
  build() {
25
86
  const inst = {
26
87
  name: this.name,
27
- hooks: this.hooks
88
+ version: this.version,
89
+ hooks: this.hooks,
90
+ depends: this.depends,
91
+ priority: this.priority,
92
+ config: this.config
28
93
  };
94
+ if (this.api) inst.api = this.api;
29
95
  if (this._onEvent) inst.onEvent = this._onEvent;
30
96
  if (this._install) inst.install = this._install;
31
97
  return inst;
32
98
  }
33
99
  }
34
100
 
35
- module.exports = { Plugin };
101
+ /** 以函数式风格定义插件:definePlugin('name', { hooks, api, install }) */
102
+ function definePlugin(name, def = {}) {
103
+ return new Plugin(name, def).build();
104
+ }
105
+
106
+ /** 登记单个钩子;未知钩子名会自动创建,便于插件自定义扩展点 */
107
+ function registerHook(engine, hook, fn) {
108
+ if (typeof fn !== 'function') return engine;
109
+ if (!engine._hooks) engine._hooks = createHookRegistry();
110
+ if (!engine._hooks[hook]) engine._hooks[hook] = [];
111
+ engine._hooks[hook].push(fn);
112
+ return engine;
113
+ }
114
+
115
+ /** 把一个插件声明的所有钩子登记到引擎上 */
116
+ function registerHooks(engine, plugin) {
117
+ if (!plugin || !plugin.hooks) return engine;
118
+ for (const [hook, val] of Object.entries(plugin.hooks)) {
119
+ const fns = Array.isArray(val) ? val : [val];
120
+ for (const fn of fns) registerHook(engine, hook, fn);
121
+ }
122
+ return engine;
123
+ }
124
+
125
+ /** 引擎 → 插件 API 注册表 */
126
+ function getPluginApis(engine) {
127
+ if (!engine._pluginApis) engine._pluginApis = new Map();
128
+ return engine._pluginApis;
129
+ }
130
+
131
+ /** 引擎 → 插件私有存储(按插件名隔离) */
132
+ function getPluginStore(engine, name) {
133
+ if (!engine._pluginStores) engine._pluginStores = new Map();
134
+ if (!engine._pluginStores.has(name)) engine._pluginStores.set(name, new Map());
135
+ return engine._pluginStores.get(name);
136
+ }
137
+
138
+ /**
139
+ * 构建插件上下文。优先走引擎自身的公开方法,缺失时回退到约定的内部字段,
140
+ * 使三个引擎共享同一套插件 API。
141
+ */
142
+ function buildPluginContext(engine, plugin) {
143
+ const name = plugin.name || 'anonymous';
144
+ const version = plugin.version || null;
145
+ const store = getPluginStore(engine, name);
146
+ const ctx = {
147
+ name,
148
+ version,
149
+ engine,
150
+ plugin,
151
+ config: plugin.config || {},
152
+ store,
153
+ /** 注册生命周期钩子 / 自定义钩子 */
154
+ on(hook, fn) { return engine.on(hook, fn); },
155
+ /** 注册事件监听 */
156
+ onEvent(fn) { return engine.onEvent(fn); },
157
+ /** 广播事件 */
158
+ emit(eventName, data) { return engine.emit(eventName, data); },
159
+ /** 主动触发一个钩子(含自定义钩子) */
160
+ hook(hookName, ...args) { return engine.runHook(hookName, ...args); },
161
+ /** 组合:加载另一个插件 */
162
+ use(other) { return engine.use(other); },
163
+ /** 表清单(同步) */
164
+ tables() {
165
+ if (engine._tableNames) return Array.from(engine._tableNames);
166
+ if (typeof engine.getTables === 'function') return engine.getTables();
167
+ return [];
168
+ },
169
+ /** 表是否存在 */
170
+ hasTable(t) {
171
+ if (typeof engine.hasTable === 'function') return engine.hasTable(t);
172
+ return !!(engine._tableNames && engine._tableNames.has(t));
173
+ },
174
+ /** 表 schema(同步,可能为 null) */
175
+ getTableSchema(t) {
176
+ if (engine._schemas && engine._schemas[t] !== undefined) return engine._schemas[t];
177
+ if (typeof engine.getTableSchema === 'function') return engine.getTableSchema(t);
178
+ return null;
179
+ },
180
+ /** 带插件名前缀的日志 */
181
+ log(...args) { console.log(`[jsql:${name}]`, ...args); },
182
+ /** 运行期声明插件 API */
183
+ expose(api) {
184
+ plugin.api = api;
185
+ getPluginApis(engine).set(name, api);
186
+ return api;
187
+ }
188
+ };
189
+ return ctx;
190
+ }
191
+
192
+ /**
193
+ * 把一个插件应用到引擎:登记钩子、调用 install、挂载事件与 API。
194
+ * 引擎的 `use()` 只需 `return applyPlugin(this, plugin)`。
195
+ * @returns {object} engine(便于链式调用)
196
+ */
197
+ function applyPlugin(engine, plugin, opts) {
198
+ // 字符串 → 内置插件名(懒加载,避免与 plugins/index 形成加载期循环依赖)
199
+ if (typeof plugin === 'string') {
200
+ plugin = require('./plugins').resolve(plugin, opts);
201
+ }
202
+ if (typeof plugin === 'function') plugin = { install: plugin };
203
+ if (!plugin || typeof plugin !== 'object') {
204
+ throw new Error('use: plugin must be an object, a function, or a builtin plugin name');
205
+ }
206
+ if (!engine._plugins) engine._plugins = [];
207
+ if (!engine._eventListeners) engine._eventListeners = [];
208
+
209
+ registerHooks(engine, plugin);
210
+
211
+ const ctx = buildPluginContext(engine, plugin);
212
+ if (typeof plugin.install === 'function') plugin.install(engine, ctx);
213
+ if (typeof plugin.onEvent === 'function') engine._eventListeners.push(plugin.onEvent);
214
+
215
+ engine._plugins.push(plugin);
216
+ if (plugin.name && plugin.api) getPluginApis(engine).set(plugin.name, plugin.api);
217
+ return engine;
218
+ }
219
+
220
+ /**
221
+ * 运行某个钩子链。
222
+ * - 任一钩子返回 false → 中止(返回 false),引擎据此跳过本次操作;
223
+ * - native 引擎置 `_forbidReentry`,钩子内再次触发引擎操作会抛 ER_PLUGIN_REENTRY。
224
+ */
225
+ function runHooks(engine, hookName, args) {
226
+ const hooks = engine._hooks && engine._hooks[hookName];
227
+ if (!hooks || hooks.length === 0) return true;
228
+
229
+ const guard = !!engine._forbidReentry;
230
+ if (guard) {
231
+ if (engine._inHook) {
232
+ throw new Error('ER_PLUGIN_REENTRY: plugin hook "' + hookName +
233
+ '" re-entered native call; plugin must not call engine methods inside its own hook');
234
+ }
235
+ engine._inHook = true;
236
+ }
237
+ try {
238
+ for (const fn of hooks) {
239
+ if (fn(...args) === false) return false;
240
+ }
241
+ return true;
242
+ } finally {
243
+ if (guard) engine._inHook = false;
244
+ }
245
+ }
246
+
247
+ module.exports = {
248
+ HOOKS,
249
+ Plugin,
250
+ definePlugin,
251
+ createHookRegistry,
252
+ registerHook,
253
+ registerHooks,
254
+ buildPluginContext,
255
+ applyPlugin,
256
+ runHooks,
257
+ getPluginApis,
258
+ getPluginStore
259
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * audit —— 记录数据变更审计日志(内存环形缓冲)。
3
+ *
4
+ * @example
5
+ * db.use(require('jsql-neo/lib/plugins').audit({ limit: 500 }));
6
+ * db.plugin('audit').entries(); // [{ at, op, table, count }]
7
+ */
8
+ const { definePlugin } = require('../plugin');
9
+
10
+ function createAudit(opts = {}) {
11
+ const limit = opts.limit || 1000;
12
+ const entries = [];
13
+
14
+ const push = e => {
15
+ entries.push({ at: Date.now(), ...e });
16
+ if (entries.length > limit) entries.splice(0, entries.length - limit);
17
+ };
18
+ const rowCount = rows => (Array.isArray(rows) ? rows.length : 1);
19
+
20
+ return definePlugin('audit', {
21
+ config: { limit },
22
+ api: {
23
+ entries: () => entries.map(e => ({ ...e })),
24
+ size: () => entries.length,
25
+ clear: () => { entries.length = 0; }
26
+ },
27
+ hooks: {
28
+ afterInsert(table, rows) { push({ op: 'insert', table, count: rowCount(rows) }); },
29
+ afterUpdate(table) { push({ op: 'update', table }); },
30
+ afterDelete(table) { push({ op: 'delete', table }); },
31
+ afterCreateTable(name) { push({ op: 'createTable', table: name }); },
32
+ afterDropTable(name) { push({ op: 'dropTable', table: name }); }
33
+ }
34
+ });
35
+ }
36
+
37
+ module.exports = { createAudit, audit: createAudit };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * JSQL-NEO 内置插件集
3
+ *
4
+ * 每个插件都是「工厂函数」,调用后返回可交给 `db.use()` 的插件对象。
5
+ * 也可以直接用名字加载:`db.use('logger')` / `db.use('timestamps', { ... })`。
6
+ *
7
+ * @example
8
+ * const plugins = require('jsql-neo/lib/plugins');
9
+ * db.use(plugins.timestamps());
10
+ * db.use(plugins.logger({ logger: myLog }));
11
+ * db.use(plugins.metrics());
12
+ * const audit = db.plugin('audit');
13
+ */
14
+ const { createTimestamps } = require('./timestamps');
15
+ const { createLogger } = require('./logger');
16
+ const { createMetrics } = require('./metrics');
17
+ const { createAudit } = require('./audit');
18
+ const { createValidation } = require('./validation');
19
+
20
+ /** 名称 → 插件工厂 */
21
+ const builtins = {
22
+ timestamps: createTimestamps,
23
+ logger: createLogger,
24
+ metrics: createMetrics,
25
+ audit: createAudit,
26
+ validation: createValidation
27
+ };
28
+
29
+ /** 内置插件名清单 */
30
+ function list() {
31
+ return Object.keys(builtins);
32
+ }
33
+
34
+ /**
35
+ * 按名字构造内置插件。
36
+ * @param {string} name
37
+ * @param {object} [opts]
38
+ * @returns {object} 插件对象,可直接 db.use()
39
+ */
40
+ function create(name, opts = {}) {
41
+ const factory = builtins[name];
42
+ if (!factory) {
43
+ throw new Error(`Unknown builtin plugin: ${name}. Available: ${list().join(', ')}`);
44
+ }
45
+ return factory(opts);
46
+ }
47
+
48
+ /**
49
+ * 名称或插件对象 → 插件对象。供引擎的 `use()` 解析字符串用。
50
+ * 插件对象原样返回;字符串走内置注册表。
51
+ */
52
+ function resolve(nameOrPlugin, opts) {
53
+ if (typeof nameOrPlugin === 'string') return create(nameOrPlugin, opts);
54
+ return nameOrPlugin;
55
+ }
56
+
57
+ module.exports = {
58
+ createTimestamps,
59
+ createLogger,
60
+ createMetrics,
61
+ createAudit,
62
+ createValidation,
63
+ timestamps: createTimestamps,
64
+ logger: createLogger,
65
+ metrics: createMetrics,
66
+ audit: createAudit,
67
+ validation: createValidation,
68
+ builtins,
69
+ list,
70
+ create,
71
+ resolve
72
+ };