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.
- package/README.md +204 -115
- package/bin/jtcsv.ts +2612 -0
- package/browser.d.ts +142 -0
- package/dist/benchmark.js +446 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/bin/jtcsv.js +1940 -0
- package/dist/bin/jtcsv.js.map +1 -0
- package/dist/csv-to-json.js +1262 -0
- package/dist/csv-to-json.js.map +1 -0
- package/dist/errors.js +291 -0
- package/dist/errors.js.map +1 -0
- package/dist/eslint.config.js +147 -0
- package/dist/eslint.config.js.map +1 -0
- package/dist/index-core.js +95 -0
- package/dist/index-core.js.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/json-save.js +229 -0
- package/dist/json-save.js.map +1 -0
- package/dist/json-to-csv.js +576 -0
- package/dist/json-to-csv.js.map +1 -0
- package/dist/jtcsv-core.cjs.js +1736 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1708 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1742 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +2241 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +2209 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +2247 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +768 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +782 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +1996 -2048
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +1992 -2048
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +2157 -2209
- package/dist/jtcsv.umd.js.map +1 -1
- package/dist/plugins/express-middleware/index.js +350 -0
- package/dist/plugins/express-middleware/index.js.map +1 -0
- package/dist/plugins/fastify-plugin/index.js +315 -0
- package/dist/plugins/fastify-plugin/index.js.map +1 -0
- package/dist/plugins/hono/index.js +111 -0
- package/dist/plugins/hono/index.js.map +1 -0
- package/dist/plugins/nestjs/index.js +112 -0
- package/dist/plugins/nestjs/index.js.map +1 -0
- package/dist/plugins/nuxt/index.js +53 -0
- package/dist/plugins/nuxt/index.js.map +1 -0
- package/dist/plugins/remix/index.js +133 -0
- package/dist/plugins/remix/index.js.map +1 -0
- package/dist/plugins/sveltekit/index.js +155 -0
- package/dist/plugins/sveltekit/index.js.map +1 -0
- package/dist/plugins/trpc/index.js +136 -0
- package/dist/plugins/trpc/index.js.map +1 -0
- package/dist/run-demo.js +49 -0
- package/dist/run-demo.js.map +1 -0
- package/dist/src/browser/browser-functions.js +193 -0
- package/dist/src/browser/browser-functions.js.map +1 -0
- package/dist/src/browser/core.js +123 -0
- package/dist/src/browser/core.js.map +1 -0
- package/dist/src/browser/csv-to-json-browser.js +353 -0
- package/dist/src/browser/csv-to-json-browser.js.map +1 -0
- package/dist/src/browser/errors-browser.js +219 -0
- package/dist/src/browser/errors-browser.js.map +1 -0
- package/dist/src/browser/extensions/plugins.js +106 -0
- package/dist/src/browser/extensions/plugins.js.map +1 -0
- package/dist/src/browser/extensions/workers.js +66 -0
- package/dist/src/browser/extensions/workers.js.map +1 -0
- package/dist/src/browser/index.js +140 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/browser/json-to-csv-browser.js +225 -0
- package/dist/src/browser/json-to-csv-browser.js.map +1 -0
- package/dist/src/browser/streams.js +340 -0
- package/dist/src/browser/streams.js.map +1 -0
- package/dist/src/browser/workers/csv-parser.worker.js +264 -0
- package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
- package/dist/src/browser/workers/worker-pool.js +338 -0
- package/dist/src/browser/workers/worker-pool.js.map +1 -0
- package/dist/src/core/delimiter-cache.js +196 -0
- package/dist/src/core/delimiter-cache.js.map +1 -0
- package/dist/src/core/node-optimizations.js +279 -0
- package/dist/src/core/node-optimizations.js.map +1 -0
- package/dist/src/core/plugin-system.js +399 -0
- package/dist/src/core/plugin-system.js.map +1 -0
- package/dist/src/core/transform-hooks.js +348 -0
- package/dist/src/core/transform-hooks.js.map +1 -0
- package/dist/src/engines/fast-path-engine-new.js +262 -0
- package/dist/src/engines/fast-path-engine-new.js.map +1 -0
- package/dist/src/engines/fast-path-engine.js +671 -0
- package/dist/src/engines/fast-path-engine.js.map +1 -0
- package/dist/src/errors.js +18 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/formats/ndjson-parser.js +332 -0
- package/dist/src/formats/ndjson-parser.js.map +1 -0
- package/dist/src/formats/tsv-parser.js +230 -0
- package/dist/src/formats/tsv-parser.js.map +1 -0
- package/dist/src/index-with-plugins.js +259 -0
- package/dist/src/index-with-plugins.js.map +1 -0
- package/dist/src/types/index.js +3 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/utils/bom-utils.js +267 -0
- package/dist/src/utils/bom-utils.js.map +1 -0
- package/dist/src/utils/encoding-support.js +77 -0
- package/dist/src/utils/encoding-support.js.map +1 -0
- package/dist/src/utils/schema-validator.js +609 -0
- package/dist/src/utils/schema-validator.js.map +1 -0
- package/dist/src/utils/transform-loader.js +281 -0
- package/dist/src/utils/transform-loader.js.map +1 -0
- package/dist/src/utils/validators.js +40 -0
- package/dist/src/utils/validators.js.map +1 -0
- package/dist/src/utils/zod-adapter.js +144 -0
- package/dist/src/utils/zod-adapter.js.map +1 -0
- package/dist/src/web-server/index.js +648 -0
- package/dist/src/web-server/index.js.map +1 -0
- package/dist/src/workers/csv-multithreaded.js +211 -0
- package/dist/src/workers/csv-multithreaded.js.map +1 -0
- package/dist/src/workers/csv-parser.worker.js +179 -0
- package/dist/src/workers/csv-parser.worker.js.map +1 -0
- package/dist/src/workers/worker-pool.js +228 -0
- package/dist/src/workers/worker-pool.js.map +1 -0
- package/dist/stream-csv-to-json.js +665 -0
- package/dist/stream-csv-to-json.js.map +1 -0
- package/dist/stream-json-to-csv.js +389 -0
- package/dist/stream-json-to-csv.js.map +1 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.ts +116 -0
- package/examples/advanced/web-worker-usage.html +874 -0
- package/examples/async-multithreaded-example.ts +335 -0
- package/examples/cli-advanced-usage.md +290 -0
- package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
- package/examples/{cli-tool.js → cli-tool.ts} +5 -8
- package/examples/{error-handling.js → error-handling.ts} +356 -324
- package/examples/{express-api.js → express-api.ts} +161 -164
- package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
- package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
- package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
- package/examples/react-integration.tsx +637 -0
- package/examples/{schema-validation.js → schema-validation.ts} +2 -2
- package/examples/simple-usage.ts +194 -0
- package/examples/{streaming-example.js → streaming-example.ts} +12 -12
- package/index.d.ts +187 -18
- package/package.json +75 -81
- package/plugins.d.ts +37 -0
- package/schema.d.ts +103 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.ts +152 -0
- package/src/browser/csv-to-json-browser.d.ts +3 -0
- package/src/browser/csv-to-json-browser.ts +494 -0
- package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.ts +39 -0
- package/src/browser/globals.d.ts +5 -0
- package/src/browser/index.ts +192 -0
- package/src/browser/json-to-csv-browser.d.ts +3 -0
- package/src/browser/json-to-csv-browser.ts +338 -0
- package/src/browser/streams.ts +403 -0
- package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
- package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
- package/src/core/delimiter-cache.ts +320 -0
- package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
- package/src/core/plugin-system.ts +588 -0
- package/src/core/transform-hooks.ts +566 -0
- package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
- package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
- package/src/errors.ts +1 -0
- package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
- package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
- package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
- package/src/types/index.ts +275 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/validators.ts +35 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/{index.js → index.ts} +19 -19
- package/src/workers/csv-multithreaded.ts +310 -0
- package/src/workers/csv-parser.worker.ts +227 -0
- package/src/workers/worker-pool.ts +409 -0
- package/bin/jtcsv.js +0 -2462
- package/csv-to-json.js +0 -688
- package/errors.js +0 -208
- package/examples/simple-usage.js +0 -282
- package/index.js +0 -68
- package/json-save.js +0 -254
- package/json-to-csv.js +0 -526
- package/plugins/README.md +0 -91
- package/plugins/express-middleware/README.md +0 -64
- package/plugins/express-middleware/example.js +0 -136
- package/plugins/express-middleware/index.d.ts +0 -114
- package/plugins/express-middleware/index.js +0 -360
- package/plugins/express-middleware/package.json +0 -52
- package/plugins/fastify-plugin/index.js +0 -406
- package/plugins/fastify-plugin/package.json +0 -55
- package/plugins/hono/README.md +0 -28
- package/plugins/hono/index.d.ts +0 -12
- package/plugins/hono/index.js +0 -36
- package/plugins/hono/package.json +0 -35
- package/plugins/nestjs/README.md +0 -35
- package/plugins/nestjs/index.d.ts +0 -25
- package/plugins/nestjs/index.js +0 -77
- package/plugins/nestjs/package.json +0 -37
- package/plugins/nextjs-api/README.md +0 -57
- package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
- package/plugins/nextjs-api/examples/api-convert.js +0 -69
- package/plugins/nextjs-api/index.js +0 -387
- package/plugins/nextjs-api/package.json +0 -63
- package/plugins/nextjs-api/route.js +0 -371
- package/plugins/nuxt/README.md +0 -24
- package/plugins/nuxt/index.js +0 -21
- package/plugins/nuxt/package.json +0 -35
- package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
- package/plugins/nuxt/runtime/plugin.js +0 -6
- package/plugins/remix/README.md +0 -26
- package/plugins/remix/index.d.ts +0 -16
- package/plugins/remix/index.js +0 -62
- package/plugins/remix/package.json +0 -35
- package/plugins/sveltekit/README.md +0 -28
- package/plugins/sveltekit/index.d.ts +0 -17
- package/plugins/sveltekit/index.js +0 -54
- package/plugins/sveltekit/package.json +0 -33
- package/plugins/trpc/README.md +0 -25
- package/plugins/trpc/index.d.ts +0 -7
- package/plugins/trpc/index.js +0 -32
- package/plugins/trpc/package.json +0 -34
- package/src/browser/browser-functions.js +0 -219
- package/src/browser/csv-to-json-browser.js +0 -700
- package/src/browser/index.js +0 -113
- package/src/browser/json-to-csv-browser.js +0 -309
- package/src/browser/streams.js +0 -393
- package/src/core/delimiter-cache.js +0 -186
- package/src/core/plugin-system.js +0 -476
- package/src/core/transform-hooks.js +0 -350
- package/src/errors.js +0 -26
- package/src/utils/transform-loader.js +0 -205
- package/stream-csv-to-json.js +0 -542
- package/stream-json-to-csv.js +0 -464
- /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
|
+
}
|