jtcsv 3.0.0 → 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 +205 -146
- package/bin/jtcsv.ts +280 -202
- 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 +336 -7
- package/dist/jtcsv-core.cjs.js.map +1 -1
- package/dist/jtcsv-core.esm.js +336 -7
- package/dist/jtcsv-core.esm.js.map +1 -1
- package/dist/jtcsv-core.umd.js +336 -7
- package/dist/jtcsv-core.umd.js.map +1 -1
- package/dist/jtcsv-full.cjs.js +336 -7
- package/dist/jtcsv-full.cjs.js.map +1 -1
- package/dist/jtcsv-full.esm.js +336 -7
- package/dist/jtcsv-full.esm.js.map +1 -1
- package/dist/jtcsv-full.umd.js +336 -7
- package/dist/jtcsv-full.umd.js.map +1 -1
- package/dist/jtcsv-workers.esm.js +9 -0
- package/dist/jtcsv-workers.esm.js.map +1 -1
- package/dist/jtcsv-workers.umd.js +9 -0
- package/dist/jtcsv-workers.umd.js.map +1 -1
- package/dist/jtcsv.cjs.js +1998 -2092
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +1994 -2092
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +2157 -2251
- 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/{src → dist/src}/web-server/index.js +251 -286
- 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 +2 -2
- package/examples/advanced/performance-optimization.ts +2 -2
- package/examples/cli-advanced-usage.md +2 -0
- package/examples/cli-tool.ts +1 -1
- package/examples/large-dataset-example.ts +2 -2
- package/examples/simple-usage.ts +2 -2
- package/examples/streaming-example.ts +1 -1
- package/index.d.ts +186 -15
- package/package.json +43 -108
- package/plugins.d.ts +37 -0
- package/schema.d.ts +103 -0
- package/src/browser/csv-to-json-browser.ts +233 -3
- package/src/browser/errors-browser.ts +45 -28
- package/src/browser/json-to-csv-browser.ts +81 -5
- package/src/browser/streams.ts +73 -6
- package/src/core/delimiter-cache.ts +21 -11
- package/src/core/plugin-system.ts +343 -155
- package/src/core/transform-hooks.ts +20 -12
- package/src/engines/fast-path-engine.ts +48 -32
- package/src/errors.ts +1 -72
- package/src/formats/ndjson-parser.ts +6 -0
- package/src/formats/tsv-parser.ts +6 -0
- package/src/types/index.ts +21 -1
- package/src/utils/validators.ts +35 -0
- package/src/web-server/index.ts +1 -1
- package/bin/jtcsv.js +0 -2532
- package/csv-to-json.js +0 -711
- package/errors.js +0 -394
- package/examples/advanced/conditional-transformations.js +0 -446
- package/examples/advanced/csv-parser.worker.js +0 -89
- package/examples/advanced/nested-objects-example.js +0 -306
- package/examples/advanced/performance-optimization.js +0 -504
- package/examples/advanced/run-demo-server.js +0 -116
- package/examples/cli-batch-processing.js +0 -38
- package/examples/cli-tool.js +0 -183
- package/examples/error-handling.js +0 -338
- package/examples/express-api.js +0 -164
- package/examples/large-dataset-example.js +0 -182
- package/examples/ndjson-processing.js +0 -434
- package/examples/plugin-excel-exporter.js +0 -406
- package/examples/schema-validation.js +0 -640
- package/examples/simple-usage.js +0 -282
- package/examples/streaming-example.js +0 -418
- package/examples/web-workers-advanced.js +0 -28
- package/index.js +0 -82
- package/json-save.js +0 -255
- package/json-to-csv.js +0 -668
- package/plugins/README.md +0 -91
- package/plugins/express-middleware/README.md +0 -83
- package/plugins/express-middleware/example.js +0 -135
- package/plugins/express-middleware/example.ts +0 -135
- package/plugins/express-middleware/index.d.ts +0 -114
- package/plugins/express-middleware/index.js +0 -512
- package/plugins/express-middleware/index.ts +0 -557
- package/plugins/express-middleware/package.json +0 -52
- package/plugins/fastify-plugin/index.js +0 -404
- package/plugins/fastify-plugin/index.ts +0 -443
- 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/index.ts +0 -226
- 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/index.ts +0 -201
- 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/ConverterComponent.tsx +0 -386
- package/plugins/nextjs-api/examples/api-convert.js +0 -67
- package/plugins/nextjs-api/examples/api-convert.ts +0 -67
- package/plugins/nextjs-api/index.js +0 -387
- package/plugins/nextjs-api/index.tsx +0 -339
- package/plugins/nextjs-api/package.json +0 -63
- package/plugins/nextjs-api/route.js +0 -370
- package/plugins/nextjs-api/route.ts +0 -370
- package/plugins/nuxt/README.md +0 -24
- package/plugins/nuxt/index.js +0 -21
- package/plugins/nuxt/index.ts +0 -94
- package/plugins/nuxt/package.json +0 -35
- package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +0 -100
- package/plugins/nuxt/runtime/plugin.js +0 -6
- package/plugins/nuxt/runtime/plugin.ts +0 -71
- 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/index.ts +0 -260
- 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/index.ts +0 -301
- 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/index.ts +0 -267
- package/plugins/trpc/package.json +0 -34
- package/src/browser/browser-functions.js +0 -219
- package/src/browser/core.js +0 -92
- package/src/browser/csv-to-json-browser.js +0 -722
- package/src/browser/errors-browser.js +0 -212
- package/src/browser/extensions/plugins.js +0 -92
- package/src/browser/extensions/workers.js +0 -39
- package/src/browser/index.js +0 -113
- package/src/browser/json-to-csv-browser.js +0 -319
- package/src/browser/streams.js +0 -403
- package/src/browser/workers/csv-parser.worker.js +0 -377
- package/src/browser/workers/worker-pool.js +0 -527
- package/src/core/delimiter-cache.js +0 -200
- package/src/core/node-optimizations.js +0 -408
- package/src/core/plugin-system.js +0 -494
- package/src/core/transform-hooks.js +0 -350
- package/src/engines/fast-path-engine-new.js +0 -338
- package/src/engines/fast-path-engine.js +0 -844
- package/src/errors.js +0 -26
- package/src/formats/ndjson-parser.js +0 -467
- package/src/formats/tsv-parser.js +0 -339
- package/src/index-with-plugins.js +0 -378
- package/src/utils/bom-utils.js +0 -259
- package/src/utils/encoding-support.js +0 -124
- package/src/utils/schema-validator.js +0 -594
- package/src/utils/transform-loader.js +0 -205
- package/src/utils/zod-adapter.js +0 -170
- package/stream-csv-to-json.js +0 -560
- package/stream-json-to-csv.js +0 -465
|
@@ -6,15 +6,22 @@
|
|
|
6
6
|
* @date 2026-01-22
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
interface
|
|
9
|
+
interface PluginStatsCounters {
|
|
10
10
|
pluginLoads: number;
|
|
11
11
|
hookExecutions: number;
|
|
12
12
|
middlewareExecutions: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
interface PluginStats extends PluginStatsCounters {
|
|
16
|
+
plugins: number;
|
|
17
|
+
hooks: number;
|
|
18
|
+
middlewares: number;
|
|
19
|
+
uniqueHooks: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
interface Plugin {
|
|
16
23
|
name: string;
|
|
17
|
-
version
|
|
24
|
+
version: string;
|
|
18
25
|
description?: string;
|
|
19
26
|
hooks?: Record<string, Function>;
|
|
20
27
|
middlewares?: Function[];
|
|
@@ -22,6 +29,23 @@ interface Plugin {
|
|
|
22
29
|
destroy?: () => void;
|
|
23
30
|
}
|
|
24
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
|
+
|
|
25
49
|
type HookName =
|
|
26
50
|
| 'before:csvToJson'
|
|
27
51
|
| 'after:csvToJson'
|
|
@@ -36,14 +60,16 @@ type HookName =
|
|
|
36
60
|
| 'transformation'
|
|
37
61
|
| string;
|
|
38
62
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
private
|
|
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[];
|
|
43
69
|
private context: Record<string, any>;
|
|
44
|
-
private stats:
|
|
70
|
+
private stats: PluginStatsCounters;
|
|
45
71
|
|
|
46
|
-
constructor() {
|
|
72
|
+
constructor() {
|
|
47
73
|
this.plugins = new Map();
|
|
48
74
|
this.hooks = new Map();
|
|
49
75
|
this.middlewares = [];
|
|
@@ -56,14 +82,14 @@ export class PluginManager {
|
|
|
56
82
|
|
|
57
83
|
// Регистрируем стандартные hooks
|
|
58
84
|
this._registerDefaultHooks();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Backwards-compatible alias for registerPlugin.
|
|
63
|
-
*/
|
|
64
|
-
use(name: string, plugin: Plugin): void {
|
|
65
|
-
this.registerPlugin(name, plugin);
|
|
66
|
-
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Backwards-compatible alias for registerPlugin.
|
|
89
|
+
*/
|
|
90
|
+
use(name: string, plugin: Plugin): void {
|
|
91
|
+
this.registerPlugin(name, plugin);
|
|
92
|
+
}
|
|
67
93
|
|
|
68
94
|
/**
|
|
69
95
|
* Регистрирует стандартные hooks
|
|
@@ -94,176 +120,333 @@ export class PluginManager {
|
|
|
94
120
|
* @param plugin - Объект плагина
|
|
95
121
|
*/
|
|
96
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
|
+
|
|
97
127
|
if (this.plugins.has(name)) {
|
|
98
|
-
throw new Error(`Plugin "${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');
|
|
99
133
|
}
|
|
100
134
|
|
|
101
|
-
|
|
102
|
-
|
|
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);
|
|
103
163
|
this.stats.pluginLoads++;
|
|
104
164
|
|
|
105
|
-
//
|
|
165
|
+
// ?????????????????????? hooks ??????????????
|
|
106
166
|
if (plugin.hooks) {
|
|
107
167
|
Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
|
|
108
|
-
this.registerHook(hookName as HookName, handler);
|
|
168
|
+
this.registerHook(hookName as HookName, handler, name);
|
|
109
169
|
});
|
|
110
170
|
}
|
|
111
171
|
|
|
112
|
-
//
|
|
172
|
+
// ?????????????????????? middleware ??????????????
|
|
113
173
|
if (plugin.middlewares) {
|
|
114
174
|
plugin.middlewares.forEach(middleware => {
|
|
115
|
-
this.registerMiddleware(middleware);
|
|
175
|
+
this.registerMiddleware(middleware, name);
|
|
116
176
|
});
|
|
117
177
|
}
|
|
118
178
|
|
|
119
|
-
//
|
|
179
|
+
// ???????????????? init ???????? ????????
|
|
120
180
|
if (plugin.init) {
|
|
121
181
|
plugin.init(this);
|
|
122
182
|
}
|
|
123
|
-
|
|
124
|
-
console.log(`✅ Plugin "${name}" registered successfully`);
|
|
125
183
|
}
|
|
126
184
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*/
|
|
132
|
-
registerHook(hookName: HookName, handler: Function): void {
|
|
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
|
+
}
|
|
133
189
|
if (!this.hooks.has(hookName)) {
|
|
134
190
|
this.hooks.set(hookName, []);
|
|
135
191
|
}
|
|
136
192
|
|
|
137
193
|
const handlers = this.hooks.get(hookName)!;
|
|
138
|
-
handlers.push(
|
|
194
|
+
handlers.push({
|
|
195
|
+
handler,
|
|
196
|
+
pluginName,
|
|
197
|
+
executionCount: 0
|
|
198
|
+
});
|
|
139
199
|
}
|
|
140
200
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
});
|
|
147
210
|
}
|
|
148
211
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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> {
|
|
156
238
|
const handlers = this.hooks.get(hookName);
|
|
157
|
-
|
|
239
|
+
|
|
158
240
|
if (!handlers || handlers.length === 0) {
|
|
159
241
|
return data;
|
|
160
242
|
}
|
|
161
243
|
|
|
162
|
-
this.stats.hookExecutions++;
|
|
163
244
|
let result = data;
|
|
245
|
+
let executed = false;
|
|
164
246
|
|
|
165
|
-
|
|
166
|
-
|
|
247
|
+
for (const handlerEntry of handlers) {
|
|
248
|
+
if (!this._isPluginEnabled(handlerEntry.pluginName)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
executed = true;
|
|
253
|
+
const startTime = Date.now();
|
|
167
254
|
try {
|
|
168
|
-
result = await handler(result, { ...this.context, ...context, hookName });
|
|
255
|
+
result = await handlerEntry.handler(result, { ...this.context, ...context, hookName, plugin: handlerEntry.pluginName });
|
|
256
|
+
handlerEntry.executionCount++;
|
|
169
257
|
} catch (error) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
for (const errorHandler of errorHandlers) {
|
|
176
|
-
try {
|
|
177
|
-
errorHandler(error, { ...this.context, ...context, hookName, data: result });
|
|
178
|
-
} catch {
|
|
179
|
-
// Игнорируем ошибки в error handlers
|
|
180
|
-
}
|
|
181
|
-
}
|
|
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)`);
|
|
182
263
|
}
|
|
183
264
|
}
|
|
184
265
|
}
|
|
185
266
|
|
|
267
|
+
if (executed) {
|
|
268
|
+
this.stats.hookExecutions++;
|
|
269
|
+
}
|
|
270
|
+
|
|
186
271
|
return result;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Backwards-compatible alias for executeHook.
|
|
191
|
-
*/
|
|
192
|
-
async executeHooks(hookName: HookName, data: any, context: any = {}): Promise<any> {
|
|
193
|
-
return this.executeHook(hookName, data, context);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Executes an operation with before/after hooks and middleware.
|
|
198
|
-
*/
|
|
199
|
-
async executeWithPlugins(
|
|
200
|
-
operation: string,
|
|
201
|
-
input: any,
|
|
202
|
-
options: any,
|
|
203
|
-
handler: (input: any, options: any) => any | Promise<any>
|
|
204
|
-
): Promise<any> {
|
|
205
|
-
const context = { operation, options, metadata: {} as Record<string, any> };
|
|
206
|
-
const beforeHook = `before:${operation}` as HookName;
|
|
207
|
-
const afterHook = `after:${operation}` as HookName;
|
|
208
|
-
|
|
209
|
-
const beforeInput = await this.executeHook(beforeHook, input, context);
|
|
210
|
-
const middlewareInput = { input: beforeInput, options, operation, metadata: context.metadata };
|
|
211
|
-
const middlewareResult = await this.executeMiddleware(middlewareInput, context);
|
|
212
|
-
const result = await handler(middlewareResult.input ?? beforeInput, options);
|
|
213
|
-
return this.executeHook(afterHook, result, context);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Returns registered plugin names.
|
|
218
|
-
*/
|
|
219
|
-
listPlugins(): string[] {
|
|
220
|
-
return Array.from(this.plugins.keys());
|
|
221
|
-
}
|
|
272
|
+
}
|
|
222
273
|
|
|
223
274
|
/**
|
|
224
|
-
*
|
|
225
|
-
* @param input - Входные данные
|
|
226
|
-
* @param context - Контекст выполнения
|
|
275
|
+
* Backwards-compatible alias for executeHook.
|
|
227
276
|
*/
|
|
228
|
-
async
|
|
229
|
-
|
|
230
|
-
|
|
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}" не найден`);
|
|
231
360
|
}
|
|
361
|
+
plugin.enabled = Boolean(enabled);
|
|
362
|
+
}
|
|
232
363
|
|
|
233
|
-
|
|
234
|
-
|
|
364
|
+
removePlugin(name: string): boolean {
|
|
365
|
+
const plugin = this.plugins.get(name);
|
|
366
|
+
if (!plugin) {
|
|
367
|
+
throw new Error(`Plugin "${name}" не найден`);
|
|
368
|
+
}
|
|
235
369
|
|
|
236
|
-
|
|
237
|
-
for (const middleware of this.middlewares) {
|
|
370
|
+
if (plugin.destroy) {
|
|
238
371
|
try {
|
|
239
|
-
|
|
372
|
+
plugin.destroy();
|
|
240
373
|
} catch (error) {
|
|
241
|
-
console.error(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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);
|
|
253
418
|
}
|
|
254
|
-
|
|
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 });
|
|
255
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
|
+
}
|
|
256
436
|
}
|
|
257
|
-
}
|
|
437
|
+
};
|
|
258
438
|
|
|
259
|
-
|
|
439
|
+
await dispatch(0);
|
|
440
|
+
return ctx;
|
|
260
441
|
}
|
|
261
442
|
|
|
262
443
|
/**
|
|
263
|
-
*
|
|
264
|
-
* @param key - Ключ контекста
|
|
265
|
-
* @param value - Значение
|
|
444
|
+
* Backwards-compatible alias for executeMiddlewares.
|
|
266
445
|
*/
|
|
446
|
+
async executeMiddleware(input: any, context: any = {}): Promise<any> {
|
|
447
|
+
return this.executeMiddlewares(input, context);
|
|
448
|
+
}
|
|
449
|
+
|
|
267
450
|
setContext(key: string, value: any): void {
|
|
268
451
|
this.context[key] = value;
|
|
269
452
|
}
|
|
@@ -283,7 +466,18 @@ export class PluginManager {
|
|
|
283
466
|
* Возвращает статистику
|
|
284
467
|
*/
|
|
285
468
|
getStats(): PluginStats {
|
|
286
|
-
|
|
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
|
+
};
|
|
287
481
|
}
|
|
288
482
|
|
|
289
483
|
/**
|
|
@@ -305,28 +499,12 @@ export class PluginManager {
|
|
|
305
499
|
* @param name - Имя плагина
|
|
306
500
|
*/
|
|
307
501
|
unregisterPlugin(name: string): boolean {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
502
|
+
try {
|
|
503
|
+
this.removePlugin(name);
|
|
504
|
+
return true;
|
|
505
|
+
} catch {
|
|
311
506
|
return false;
|
|
312
507
|
}
|
|
313
|
-
|
|
314
|
-
// Вызываем destroy если есть
|
|
315
|
-
if (plugin.destroy) {
|
|
316
|
-
try {
|
|
317
|
-
plugin.destroy();
|
|
318
|
-
} catch (error) {
|
|
319
|
-
console.error(`Error destroying plugin "${name}":`, error);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Удаляем плагин
|
|
324
|
-
this.plugins.delete(name);
|
|
325
|
-
|
|
326
|
-
// TODO: Удалить связанные hooks и middleware
|
|
327
|
-
|
|
328
|
-
console.log(`🗑️ Plugin "${name}" unregistered`);
|
|
329
|
-
return true;
|
|
330
508
|
}
|
|
331
509
|
|
|
332
510
|
/**
|
|
@@ -366,7 +544,7 @@ export class PluginManager {
|
|
|
366
544
|
* Асинхронная версия executeMiddleware
|
|
367
545
|
*/
|
|
368
546
|
async executeMiddlewareAsync(input: any, context: any = {}): Promise<any> {
|
|
369
|
-
return this.
|
|
547
|
+
return this.executeMiddlewares(input, context);
|
|
370
548
|
}
|
|
371
549
|
}
|
|
372
550
|
|
|
@@ -390,11 +568,21 @@ export async function getGlobalPluginManagerAsync(): Promise<PluginManager> {
|
|
|
390
568
|
return getGlobalPluginManager();
|
|
391
569
|
}
|
|
392
570
|
|
|
571
|
+
export default PluginManager;
|
|
572
|
+
|
|
393
573
|
// Экспорт для CommonJS
|
|
394
574
|
if (typeof module !== 'undefined' && module.exports) {
|
|
395
|
-
module.exports
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
+
}
|
|
@@ -445,11 +445,11 @@ export const predefinedHooks = {
|
|
|
445
445
|
* @param onError - Обработчик ошибки
|
|
446
446
|
* @returns Хук валидации
|
|
447
447
|
*/
|
|
448
|
-
validate<T>(validator: (item: T, index: number) => boolean, onError: (errors: any[]) => void = console.error): HookFunction<T[], T[]> {
|
|
449
|
-
return (data: T[]) => {
|
|
450
|
-
if (Array.isArray(data)) {
|
|
451
|
-
const validData: T[] = [];
|
|
452
|
-
const errors: any[] = [];
|
|
448
|
+
validate<T>(validator: (item: T, index: number) => boolean, onError: (errors: any[] | string, details?: any[]) => void = console.error): HookFunction<T[], T[]> {
|
|
449
|
+
return (data: T[]) => {
|
|
450
|
+
if (Array.isArray(data)) {
|
|
451
|
+
const validData: T[] = [];
|
|
452
|
+
const errors: any[] = [];
|
|
453
453
|
|
|
454
454
|
data.forEach((item, index) => {
|
|
455
455
|
try {
|
|
@@ -464,9 +464,13 @@ export const predefinedHooks = {
|
|
|
464
464
|
}
|
|
465
465
|
});
|
|
466
466
|
|
|
467
|
-
if (errors.length > 0) {
|
|
468
|
-
onError
|
|
469
|
-
|
|
467
|
+
if (errors.length > 0) {
|
|
468
|
+
if (onError.length >= 2) {
|
|
469
|
+
onError('Validation errors', errors);
|
|
470
|
+
} else {
|
|
471
|
+
onError(errors);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
470
474
|
|
|
471
475
|
return validData;
|
|
472
476
|
}
|
|
@@ -480,7 +484,7 @@ export const predefinedHooks = {
|
|
|
480
484
|
* @param onError - Обработчик ошибки
|
|
481
485
|
* @returns Асинхронный хук валидации
|
|
482
486
|
*/
|
|
483
|
-
validateAsync<T>(validator: (item: T, index: number) => Promise<boolean>, onError: (errors: any[]) => void = console.error): AsyncHookFunction<T[], T[]> {
|
|
487
|
+
validateAsync<T>(validator: (item: T, index: number) => Promise<boolean>, onError: (errors: any[] | string, details?: any[]) => void = console.error): AsyncHookFunction<T[], T[]> {
|
|
484
488
|
return async (data: T[]) => {
|
|
485
489
|
if (Array.isArray(data)) {
|
|
486
490
|
const validData: T[] = [];
|
|
@@ -499,9 +503,13 @@ export const predefinedHooks = {
|
|
|
499
503
|
}
|
|
500
504
|
}
|
|
501
505
|
|
|
502
|
-
if (errors.length > 0) {
|
|
503
|
-
onError
|
|
504
|
-
|
|
506
|
+
if (errors.length > 0) {
|
|
507
|
+
if (onError.length >= 2) {
|
|
508
|
+
onError('Validation errors', errors);
|
|
509
|
+
} else {
|
|
510
|
+
onError(errors);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
505
513
|
|
|
506
514
|
return validData;
|
|
507
515
|
}
|