jtcsv 2.2.8 → 3.1.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.
Files changed (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/dist/src/web-server/index.js +648 -0
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
@@ -0,0 +1,588 @@
1
+ /**
2
+ * Plugin System для JTCSV
3
+ * Middleware-like архитектура с поддержкой hooks и плагинов
4
+ *
5
+ * @version 1.0.0
6
+ * @date 2026-01-22
7
+ */
8
+
9
+ interface PluginStatsCounters {
10
+ pluginLoads: number;
11
+ hookExecutions: number;
12
+ middlewareExecutions: number;
13
+ }
14
+
15
+ interface PluginStats extends PluginStatsCounters {
16
+ plugins: number;
17
+ hooks: number;
18
+ middlewares: number;
19
+ uniqueHooks: number;
20
+ }
21
+
22
+ interface Plugin {
23
+ name: string;
24
+ version: string;
25
+ description?: string;
26
+ hooks?: Record<string, Function>;
27
+ middlewares?: Function[];
28
+ init?: (manager: PluginManager) => void;
29
+ destroy?: () => void;
30
+ }
31
+
32
+ interface PluginRecord extends Plugin {
33
+ id: string;
34
+ enabled: boolean;
35
+ }
36
+
37
+ interface HookHandlerEntry {
38
+ handler: Function;
39
+ pluginName?: string;
40
+ executionCount: number;
41
+ }
42
+
43
+ interface MiddlewareEntry {
44
+ handler: Function;
45
+ pluginName?: string;
46
+ executionCount: number;
47
+ }
48
+
49
+ type HookName =
50
+ | 'before:csvToJson'
51
+ | 'after:csvToJson'
52
+ | 'before:jsonToCsv'
53
+ | 'after:jsonToCsv'
54
+ | 'before:parse'
55
+ | 'after:parse'
56
+ | 'before:serialize'
57
+ | 'after:serialize'
58
+ | 'error'
59
+ | 'validation'
60
+ | 'transformation'
61
+ | string;
62
+
63
+ const SLOW_HOOK_THRESHOLD_MS = 100;
64
+
65
+ export class PluginManager {
66
+ private plugins: Map<string, PluginRecord>;
67
+ private hooks: Map<HookName, HookHandlerEntry[]>;
68
+ private middlewares: MiddlewareEntry[];
69
+ private context: Record<string, any>;
70
+ private stats: PluginStatsCounters;
71
+
72
+ constructor() {
73
+ this.plugins = new Map();
74
+ this.hooks = new Map();
75
+ this.middlewares = [];
76
+ this.context = {};
77
+ this.stats = {
78
+ pluginLoads: 0,
79
+ hookExecutions: 0,
80
+ middlewareExecutions: 0
81
+ };
82
+
83
+ // Регистрируем стандартные hooks
84
+ this._registerDefaultHooks();
85
+ }
86
+
87
+ /**
88
+ * Backwards-compatible alias for registerPlugin.
89
+ */
90
+ use(name: string, plugin: Plugin): void {
91
+ this.registerPlugin(name, plugin);
92
+ }
93
+
94
+ /**
95
+ * Регистрирует стандартные hooks
96
+ */
97
+ private _registerDefaultHooks(): void {
98
+ const defaultHooks: HookName[] = [
99
+ 'before:csvToJson',
100
+ 'after:csvToJson',
101
+ 'before:jsonToCsv',
102
+ 'after:jsonToCsv',
103
+ 'before:parse',
104
+ 'after:parse',
105
+ 'before:serialize',
106
+ 'after:serialize',
107
+ 'error',
108
+ 'validation',
109
+ 'transformation'
110
+ ];
111
+
112
+ defaultHooks.forEach(hook => {
113
+ this.hooks.set(hook, []);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Регистрирует плагин
119
+ * @param name - Уникальное имя плагина
120
+ * @param plugin - Объект плагина
121
+ */
122
+ registerPlugin(name: string, plugin: Plugin): void {
123
+ if (!plugin || typeof plugin !== 'object' || !plugin.name || !plugin.version) {
124
+ throw new Error('Plugin должен иметь name и version');
125
+ }
126
+
127
+ if (this.plugins.has(name)) {
128
+ throw new Error(`Plugin "${name}" уже зарегистрирован`);
129
+ }
130
+
131
+ if (plugin.hooks && (typeof plugin.hooks !== 'object' || Array.isArray(plugin.hooks))) {
132
+ throw new Error('hooks must be an object');
133
+ }
134
+
135
+ if (plugin.hooks) {
136
+ for (const [hookName, handler] of Object.entries(plugin.hooks)) {
137
+ if (typeof handler !== 'function') {
138
+ throw new Error(`Hook handler for "${hookName}" must be a function`);
139
+ }
140
+ }
141
+ }
142
+
143
+ if (plugin.middlewares && !Array.isArray(plugin.middlewares)) {
144
+ throw new Error('middlewares must be an array');
145
+ }
146
+
147
+ if (plugin.middlewares) {
148
+ plugin.middlewares.forEach((middleware, index) => {
149
+ if (typeof middleware !== 'function') {
150
+ throw new Error(`Middleware ${index} must be a function`);
151
+ }
152
+ });
153
+ }
154
+
155
+ const record: PluginRecord = {
156
+ id: name,
157
+ enabled: true,
158
+ ...plugin
159
+ };
160
+
161
+ // ?????????????????? ????????????
162
+ this.plugins.set(name, record);
163
+ this.stats.pluginLoads++;
164
+
165
+ // ?????????????????????? hooks ??????????????
166
+ if (plugin.hooks) {
167
+ Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
168
+ this.registerHook(hookName as HookName, handler, name);
169
+ });
170
+ }
171
+
172
+ // ?????????????????????? middleware ??????????????
173
+ if (plugin.middlewares) {
174
+ plugin.middlewares.forEach(middleware => {
175
+ this.registerMiddleware(middleware, name);
176
+ });
177
+ }
178
+
179
+ // ???????????????? init ???????? ????????
180
+ if (plugin.init) {
181
+ plugin.init(this);
182
+ }
183
+ }
184
+
185
+ registerHook(hookName: HookName, handler: Function, pluginName?: string): void {
186
+ if (typeof handler != 'function') {
187
+ throw new Error('Hook handler must be a function');
188
+ }
189
+ if (!this.hooks.has(hookName)) {
190
+ this.hooks.set(hookName, []);
191
+ }
192
+
193
+ const handlers = this.hooks.get(hookName)!;
194
+ handlers.push({
195
+ handler,
196
+ pluginName,
197
+ executionCount: 0
198
+ });
199
+ }
200
+
201
+ registerMiddleware(middleware: Function, pluginName?: string): void {
202
+ if (typeof middleware !== 'function') {
203
+ throw new Error('Middleware must be a function');
204
+ }
205
+ this.middlewares.push({
206
+ handler: middleware,
207
+ pluginName,
208
+ executionCount: 0
209
+ });
210
+ }
211
+
212
+ private _isPluginEnabled(pluginName?: string): boolean {
213
+ if (!pluginName) {
214
+ return true;
215
+ }
216
+ const plugin = this.plugins.get(pluginName);
217
+ if (!plugin) {
218
+ return true;
219
+ }
220
+ return plugin.enabled !== false;
221
+ }
222
+
223
+ private _runErrorHooks(error: any, context: any): void {
224
+ const errorHandlers = this.hooks.get('error');
225
+ if (!errorHandlers || errorHandlers.length == 0) {
226
+ return;
227
+ }
228
+ for (const handlerEntry of errorHandlers) {
229
+ try {
230
+ handlerEntry.handler(error, context);
231
+ } catch {
232
+ // ignore errors in error handlers
233
+ }
234
+ }
235
+ }
236
+
237
+ async executeHook(hookName: HookName, data: any, context: any = {}): Promise<any> {
238
+ const handlers = this.hooks.get(hookName);
239
+
240
+ if (!handlers || handlers.length === 0) {
241
+ return data;
242
+ }
243
+
244
+ let result = data;
245
+ let executed = false;
246
+
247
+ for (const handlerEntry of handlers) {
248
+ if (!this._isPluginEnabled(handlerEntry.pluginName)) {
249
+ continue;
250
+ }
251
+
252
+ executed = true;
253
+ const startTime = Date.now();
254
+ try {
255
+ result = await handlerEntry.handler(result, { ...this.context, ...context, hookName, plugin: handlerEntry.pluginName });
256
+ handlerEntry.executionCount++;
257
+ } catch (error) {
258
+ this._runErrorHooks(error, { ...this.context, ...context, hookName, data: result });
259
+ } finally {
260
+ const duration = Date.now() - startTime;
261
+ if (duration > SLOW_HOOK_THRESHOLD_MS) {
262
+ console.warn(`Slow hook "${hookName}" detected (${duration}ms)`);
263
+ }
264
+ }
265
+ }
266
+
267
+ if (executed) {
268
+ this.stats.hookExecutions++;
269
+ }
270
+
271
+ return result;
272
+ }
273
+
274
+ /**
275
+ * Backwards-compatible alias for executeHook.
276
+ */
277
+ async executeHooks(hookName: HookName, data: any, context: any = {}): Promise<any> {
278
+ return this.executeHook(hookName, data, context);
279
+ }
280
+
281
+ /**
282
+ * Executes an operation with before/after hooks and middleware.
283
+ */
284
+ async executeWithPlugins(
285
+ operation: string,
286
+ input: any,
287
+ options: any,
288
+ handler: (input: any, options: any) => any | Promise<any>
289
+ ): Promise<any> {
290
+ const metadata = options && options.metadata ? options.metadata : {};
291
+ const context = { operation, options, metadata };
292
+ const beforeHook = `before:${operation}` as HookName;
293
+ const afterHook = `after:${operation}` as HookName;
294
+
295
+ const beforeInput = await this.executeHook(beforeHook, input, context);
296
+ const middlewareContext: { input: any; options: any; operation: string; metadata: any; result?: any } = {
297
+ input: beforeInput,
298
+ options,
299
+ operation,
300
+ metadata
301
+ };
302
+ const resultHolder = { set: false, value: undefined as any };
303
+
304
+ try {
305
+ await this.executeMiddlewares(middlewareContext, context, async (ctx: any) => {
306
+ const handlerInput = ctx && Object.prototype.hasOwnProperty.call(ctx, 'input')
307
+ ? ctx.input
308
+ : beforeInput;
309
+ const result = await handler(handlerInput, options);
310
+ ctx.result = result;
311
+ resultHolder.set = true;
312
+ resultHolder.value = result;
313
+ return result;
314
+ });
315
+ } catch (error) {
316
+ this._runErrorHooks(error, { ...this.context, ...context, data: beforeInput });
317
+ throw error;
318
+ }
319
+
320
+ const finalResult = resultHolder.set
321
+ ? resultHolder.value
322
+ : (Object.prototype.hasOwnProperty.call(middlewareContext, 'result') ? middlewareContext.result : undefined);
323
+
324
+ return this.executeHook(afterHook, finalResult, context);
325
+ }
326
+
327
+ /**
328
+ * Returns registered plugin names.
329
+ */
330
+ listPlugins(): Array<{ id: string; pluginName: string; version: string; description?: string; enabled: boolean }> {
331
+ return Array.from(this.plugins.values()).map((plugin) => ({
332
+ id: plugin.id,
333
+ pluginName: plugin.name,
334
+ version: plugin.version,
335
+ description: plugin.description,
336
+ enabled: plugin.enabled
337
+ }));
338
+ }
339
+
340
+ listHooks(): Record<string, { count: number; handlers: Array<{ handler: Function; pluginName?: string; executionCount: number }> }> {
341
+ const result: Record<string, { count: number; handlers: Array<{ handler: Function; pluginName?: string; executionCount: number }> }> = {};
342
+ for (const [hookName, handlers] of this.hooks.entries()) {
343
+ result[hookName] = {
344
+ count: handlers.length,
345
+ handlers: handlers.map((handlerEntry) => ({
346
+ handler: handlerEntry.handler,
347
+ pluginName: handlerEntry.pluginName,
348
+ executionCount: handlerEntry.executionCount
349
+ }))
350
+ };
351
+ }
352
+ return result;
353
+ }
354
+
355
+
356
+ setPluginEnabled(name: string, enabled: boolean): void {
357
+ const plugin = this.plugins.get(name);
358
+ if (!plugin) {
359
+ throw new Error(`Plugin "${name}" не найден`);
360
+ }
361
+ plugin.enabled = Boolean(enabled);
362
+ }
363
+
364
+ removePlugin(name: string): boolean {
365
+ const plugin = this.plugins.get(name);
366
+ if (!plugin) {
367
+ throw new Error(`Plugin "${name}" не найден`);
368
+ }
369
+
370
+ if (plugin.destroy) {
371
+ try {
372
+ plugin.destroy();
373
+ } catch (error) {
374
+ console.error(`Error destroying plugin "${name}":`, error);
375
+ }
376
+ }
377
+
378
+ this.plugins.delete(name);
379
+
380
+ for (const [hookName, handlers] of this.hooks.entries()) {
381
+ if (!handlers.length) {
382
+ continue;
383
+ }
384
+ const remaining = handlers.filter((handlerEntry) => handlerEntry.pluginName !== name);
385
+ this.hooks.set(hookName, remaining);
386
+ }
387
+
388
+ this.middlewares = this.middlewares.filter((middleware) => middleware.pluginName !== name);
389
+
390
+ return true;
391
+ }
392
+
393
+ resetStats(): void {
394
+ this.stats.pluginLoads = 0;
395
+ this.stats.hookExecutions = 0;
396
+ this.stats.middlewareExecutions = 0;
397
+ }
398
+
399
+ async executeMiddlewares(ctx: any, context: any = {}, finalHandler?: (ctx: any) => any | Promise<any>): Promise<any> {
400
+ const entries = this.middlewares.filter((entry) => this._isPluginEnabled(entry.pluginName));
401
+ if (entries.length === 0) {
402
+ if (finalHandler) {
403
+ await finalHandler(ctx);
404
+ }
405
+ return ctx;
406
+ }
407
+
408
+ let index = -1;
409
+ const dispatch = async (i: number): Promise<any> => {
410
+ if (i <= index) {
411
+ throw new Error('next() вызван несколько раз');
412
+ }
413
+ index = i;
414
+ const entry = entries[i];
415
+ if (!entry) {
416
+ if (finalHandler) {
417
+ return finalHandler(ctx);
418
+ }
419
+ return;
420
+ }
421
+
422
+ const startTime = Date.now();
423
+ try {
424
+ const result = entry.handler(ctx, () => dispatch(i + 1));
425
+ await result;
426
+ entry.executionCount++;
427
+ this.stats.middlewareExecutions++;
428
+ } catch (error) {
429
+ this._runErrorHooks(error, { ...this.context, ...context, data: ctx });
430
+ throw error;
431
+ } finally {
432
+ const duration = Date.now() - startTime;
433
+ if (duration > SLOW_HOOK_THRESHOLD_MS) {
434
+ console.warn(`Slow middleware "${entry.pluginName || 'anonymous'}" detected (${duration}ms)`);
435
+ }
436
+ }
437
+ };
438
+
439
+ await dispatch(0);
440
+ return ctx;
441
+ }
442
+
443
+ /**
444
+ * Backwards-compatible alias for executeMiddlewares.
445
+ */
446
+ async executeMiddleware(input: any, context: any = {}): Promise<any> {
447
+ return this.executeMiddlewares(input, context);
448
+ }
449
+
450
+ setContext(key: string, value: any): void {
451
+ this.context[key] = value;
452
+ }
453
+
454
+ /**
455
+ * Получает контекст
456
+ * @param key - Ключ контекста (опционально)
457
+ */
458
+ getContext(key?: string): any {
459
+ if (key) {
460
+ return this.context[key];
461
+ }
462
+ return { ...this.context };
463
+ }
464
+
465
+ /**
466
+ * Возвращает статистику
467
+ */
468
+ getStats(): PluginStats {
469
+ let hookCount = 0;
470
+ for (const handlers of this.hooks.values()) {
471
+ hookCount += handlers.length;
472
+ }
473
+
474
+ return {
475
+ ...this.stats,
476
+ plugins: this.plugins.size,
477
+ hooks: hookCount,
478
+ middlewares: this.middlewares.length,
479
+ uniqueHooks: this.hooks.size
480
+ };
481
+ }
482
+
483
+ /**
484
+ * Возвращает список зарегистрированных плагинов
485
+ */
486
+ getPlugins(): string[] {
487
+ return Array.from(this.plugins.keys());
488
+ }
489
+
490
+ /**
491
+ * Возвращает список зарегистрированных hooks
492
+ */
493
+ getHooks(): HookName[] {
494
+ return Array.from(this.hooks.keys());
495
+ }
496
+
497
+ /**
498
+ * Удаляет плагин
499
+ * @param name - Имя плагина
500
+ */
501
+ unregisterPlugin(name: string): boolean {
502
+ try {
503
+ this.removePlugin(name);
504
+ return true;
505
+ } catch {
506
+ return false;
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Очищает все плагины и hooks
512
+ */
513
+ clear(): void {
514
+ // Вызываем destroy для всех плагинов
515
+ this.plugins.forEach((plugin, name) => {
516
+ if (plugin.destroy) {
517
+ try {
518
+ plugin.destroy();
519
+ } catch (error) {
520
+ console.error(`Error destroying plugin "${name}":`, error);
521
+ }
522
+ }
523
+ });
524
+
525
+ this.plugins.clear();
526
+ this.hooks.clear();
527
+ this.middlewares = [];
528
+ this.context = {};
529
+
530
+ // Регистрируем стандартные hooks заново
531
+ this._registerDefaultHooks();
532
+
533
+ console.log('🧹 Plugin system cleared');
534
+ }
535
+
536
+ /**
537
+ * Асинхронная версия executeHook
538
+ */
539
+ async executeHookAsync(hookName: HookName, data: any, context: any = {}): Promise<any> {
540
+ return this.executeHook(hookName, data, context);
541
+ }
542
+
543
+ /**
544
+ * Асинхронная версия executeMiddleware
545
+ */
546
+ async executeMiddlewareAsync(input: any, context: any = {}): Promise<any> {
547
+ return this.executeMiddlewares(input, context);
548
+ }
549
+ }
550
+
551
+ // Создание глобального экземпляра PluginManager
552
+ let globalPluginManager: PluginManager | null = null;
553
+
554
+ /**
555
+ * Возвращает глобальный экземпляр PluginManager
556
+ */
557
+ export function getGlobalPluginManager(): PluginManager {
558
+ if (!globalPluginManager) {
559
+ globalPluginManager = new PluginManager();
560
+ }
561
+ return globalPluginManager;
562
+ }
563
+
564
+ /**
565
+ * Асинхронная версия getGlobalPluginManager
566
+ */
567
+ export async function getGlobalPluginManagerAsync(): Promise<PluginManager> {
568
+ return getGlobalPluginManager();
569
+ }
570
+
571
+ export default PluginManager;
572
+
573
+ // Экспорт для CommonJS
574
+ if (typeof module !== 'undefined' && module.exports) {
575
+ const current = module.exports;
576
+ if (current && current.__esModule) {
577
+ current.PluginManager = PluginManager;
578
+ current.getGlobalPluginManager = getGlobalPluginManager;
579
+ current.getGlobalPluginManagerAsync = getGlobalPluginManagerAsync;
580
+ current.default = PluginManager;
581
+ } else {
582
+ module.exports = PluginManager;
583
+ module.exports.PluginManager = PluginManager;
584
+ module.exports.getGlobalPluginManager = getGlobalPluginManager;
585
+ module.exports.getGlobalPluginManagerAsync = getGlobalPluginManagerAsync;
586
+ module.exports.default = PluginManager;
587
+ }
588
+ }