jtcsv 2.2.7 → 3.0.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 +31 -1
- package/bin/jtcsv.js +891 -821
- package/bin/jtcsv.ts +2534 -0
- package/csv-to-json.js +168 -145
- package/dist/jtcsv-core.cjs.js +1407 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1379 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1413 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +1912 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +1880 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +1918 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +759 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +773 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +61 -19
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +61 -19
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +61 -19
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +188 -2
- package/examples/advanced/conditional-transformations.js +446 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.js +89 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.js +306 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.js +504 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.js +116 -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 +288 -0
- package/examples/cli-batch-processing.ts +38 -0
- package/examples/cli-tool.js +0 -3
- package/examples/cli-tool.ts +183 -0
- package/examples/error-handling.js +21 -7
- package/examples/error-handling.ts +356 -0
- package/examples/express-api.js +0 -3
- package/examples/express-api.ts +164 -0
- package/examples/large-dataset-example.js +0 -3
- package/examples/large-dataset-example.ts +204 -0
- package/examples/ndjson-processing.js +1 -1
- package/examples/ndjson-processing.ts +456 -0
- package/examples/plugin-excel-exporter.js +3 -4
- package/examples/plugin-excel-exporter.ts +406 -0
- package/examples/react-integration.tsx +637 -0
- package/examples/schema-validation.ts +640 -0
- package/examples/simple-usage.js +254 -254
- package/examples/simple-usage.ts +194 -0
- package/examples/streaming-example.js +4 -5
- package/examples/streaming-example.ts +419 -0
- package/examples/web-workers-advanced.ts +28 -0
- package/index.d.ts +1 -3
- package/index.js +15 -1
- package/json-save.js +9 -3
- package/json-to-csv.js +168 -21
- package/package.json +69 -10
- package/plugins/express-middleware/README.md +21 -2
- package/plugins/express-middleware/example.js +3 -4
- package/plugins/express-middleware/example.ts +135 -0
- package/plugins/express-middleware/index.d.ts +1 -1
- package/plugins/express-middleware/index.js +270 -118
- package/plugins/express-middleware/index.ts +557 -0
- package/plugins/fastify-plugin/index.js +2 -4
- package/plugins/fastify-plugin/index.ts +443 -0
- package/plugins/hono/index.ts +226 -0
- package/plugins/nestjs/index.ts +201 -0
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +0 -2
- package/plugins/nextjs-api/examples/api-convert.ts +67 -0
- package/plugins/nextjs-api/index.tsx +339 -0
- package/plugins/nextjs-api/route.js +2 -3
- package/plugins/nextjs-api/route.ts +370 -0
- package/plugins/nuxt/index.ts +94 -0
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
- package/plugins/nuxt/runtime/plugin.ts +71 -0
- package/plugins/remix/index.js +1 -1
- package/plugins/remix/index.ts +260 -0
- package/plugins/sveltekit/index.js +1 -1
- package/plugins/sveltekit/index.ts +301 -0
- package/plugins/trpc/index.ts +267 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.js +92 -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.js +36 -14
- package/src/browser/csv-to-json-browser.ts +264 -0
- package/src/browser/errors-browser.ts +303 -0
- package/src/browser/extensions/plugins.js +92 -0
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.js +39 -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.js +13 -3
- package/src/browser/json-to-csv-browser.ts +262 -0
- package/src/browser/streams.js +12 -2
- package/src/browser/streams.ts +336 -0
- package/src/browser/workers/csv-parser.worker.ts +377 -0
- package/src/browser/workers/worker-pool.ts +548 -0
- package/src/core/delimiter-cache.js +22 -8
- package/src/core/delimiter-cache.ts +310 -0
- package/src/core/node-optimizations.ts +449 -0
- package/src/core/plugin-system.js +29 -11
- package/src/core/plugin-system.ts +400 -0
- package/src/core/transform-hooks.ts +558 -0
- package/src/engines/fast-path-engine-new.ts +347 -0
- package/src/engines/fast-path-engine.ts +854 -0
- package/src/errors.ts +72 -0
- package/src/formats/ndjson-parser.ts +469 -0
- package/src/formats/tsv-parser.ts +334 -0
- package/src/index-with-plugins.js +16 -9
- package/src/index-with-plugins.ts +395 -0
- package/src/types/index.ts +255 -0
- package/src/utils/bom-utils.js +259 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.js +124 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/schema-validator.js +19 -19
- package/src/utils/schema-validator.ts +819 -0
- package/src/utils/transform-loader.js +1 -1
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/zod-adapter.js +170 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/index.js +10 -10
- package/src/web-server/index.ts +683 -0
- 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/stream-csv-to-json.js +26 -8
- package/stream-json-to-csv.js +1 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform Hooks System
|
|
3
|
+
* Система хуков для трансформации данных перед/после конвертации
|
|
4
|
+
*
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
* @date 2026-01-29
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ValidationError } from '../errors';
|
|
10
|
+
|
|
11
|
+
export interface HookContext {
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type HookFunction<T = any, R = any> = (data: T, context?: HookContext) => R;
|
|
16
|
+
export type PerRowHookFunction<T = any, R = any> = (row: T, index: number, context?: HookContext) => R;
|
|
17
|
+
export type AsyncHookFunction<T = any, R = any> = (data: T, context?: HookContext) => Promise<R>;
|
|
18
|
+
export type AsyncPerRowHookFunction<T = any, R = any> = (row: T, index: number, context?: HookContext) => Promise<R>;
|
|
19
|
+
|
|
20
|
+
export interface TransformHooksOptions {
|
|
21
|
+
hooks?: {
|
|
22
|
+
beforeConvert?: Array<HookFunction | AsyncHookFunction>;
|
|
23
|
+
afterConvert?: Array<HookFunction | AsyncHookFunction>;
|
|
24
|
+
perRow?: Array<PerRowHookFunction | AsyncPerRowHookFunction>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class TransformHooks {
|
|
29
|
+
private hooks: {
|
|
30
|
+
beforeConvert: Array<HookFunction | AsyncHookFunction>;
|
|
31
|
+
afterConvert: Array<HookFunction | AsyncHookFunction>;
|
|
32
|
+
perRow: Array<PerRowHookFunction | AsyncPerRowHookFunction>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
constructor(options?: TransformHooksOptions) {
|
|
36
|
+
this.hooks = {
|
|
37
|
+
beforeConvert: options?.hooks?.beforeConvert || [],
|
|
38
|
+
afterConvert: options?.hooks?.afterConvert || [],
|
|
39
|
+
perRow: options?.hooks?.perRow || []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Регистрирует хук beforeConvert
|
|
45
|
+
* @param hook - Функция хука
|
|
46
|
+
* @returns this для цепочки вызовов
|
|
47
|
+
*/
|
|
48
|
+
beforeConvert(hook: HookFunction | AsyncHookFunction): this {
|
|
49
|
+
if (typeof hook !== 'function') {
|
|
50
|
+
throw new ValidationError('beforeConvert hook must be a function');
|
|
51
|
+
}
|
|
52
|
+
this.hooks.beforeConvert.push(hook);
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Регистрирует хук afterConvert
|
|
58
|
+
* @param hook - Функция хука
|
|
59
|
+
* @returns this для цепочки вызовов
|
|
60
|
+
*/
|
|
61
|
+
afterConvert(hook: HookFunction | AsyncHookFunction): this {
|
|
62
|
+
if (typeof hook !== 'function') {
|
|
63
|
+
throw new ValidationError('afterConvert hook must be a function');
|
|
64
|
+
}
|
|
65
|
+
this.hooks.afterConvert.push(hook);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Регистрирует per-row хук
|
|
71
|
+
* @param hook - Функция хука
|
|
72
|
+
* @returns this для цепочки вызовов
|
|
73
|
+
*/
|
|
74
|
+
perRow(hook: PerRowHookFunction | AsyncPerRowHookFunction): this {
|
|
75
|
+
if (typeof hook !== 'function') {
|
|
76
|
+
throw new ValidationError('perRow hook must be a function');
|
|
77
|
+
}
|
|
78
|
+
this.hooks.perRow.push(hook);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Применяет beforeConvert хуки
|
|
84
|
+
* @param data - Входные данные
|
|
85
|
+
* @param context - Контекст выполнения
|
|
86
|
+
* @returns Трансформированные данные
|
|
87
|
+
*/
|
|
88
|
+
applyBeforeConvert<T = any, R = any>(data: T, context: HookContext = {}): R {
|
|
89
|
+
let result: any = data;
|
|
90
|
+
for (const hook of this.hooks.beforeConvert) {
|
|
91
|
+
result = hook(result, context);
|
|
92
|
+
}
|
|
93
|
+
return result as R;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Применяет beforeConvert хуки асинхронно
|
|
98
|
+
* @param data - Входные данные
|
|
99
|
+
* @param context - Контекст выполнения
|
|
100
|
+
* @returns Promise с трансформированными данными
|
|
101
|
+
*/
|
|
102
|
+
async applyBeforeConvertAsync<T = any, R = any>(data: T, context: HookContext = {}): Promise<R> {
|
|
103
|
+
let result: any = data;
|
|
104
|
+
for (const hook of this.hooks.beforeConvert) {
|
|
105
|
+
if (this.isAsyncFunction(hook)) {
|
|
106
|
+
result = await (hook as AsyncHookFunction)(result, context);
|
|
107
|
+
} else {
|
|
108
|
+
result = (hook as HookFunction)(result, context);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return result as R;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Применяет afterConvert хуки
|
|
116
|
+
* @param data - Выходные данные
|
|
117
|
+
* @param context - Контекст выполнения
|
|
118
|
+
* @returns Трансформированные данные
|
|
119
|
+
*/
|
|
120
|
+
applyAfterConvert<T = any, R = any>(data: T, context: HookContext = {}): R {
|
|
121
|
+
let result: any = data;
|
|
122
|
+
for (const hook of this.hooks.afterConvert) {
|
|
123
|
+
result = hook(result, context);
|
|
124
|
+
}
|
|
125
|
+
return result as R;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Применяет afterConvert хуки асинхронно
|
|
130
|
+
* @param data - Выходные данные
|
|
131
|
+
* @param context - Контекст выполнения
|
|
132
|
+
* @returns Promise с трансформированными данными
|
|
133
|
+
*/
|
|
134
|
+
async applyAfterConvertAsync<T = any, R = any>(data: T, context: HookContext = {}): Promise<R> {
|
|
135
|
+
let result: any = data;
|
|
136
|
+
for (const hook of this.hooks.afterConvert) {
|
|
137
|
+
if (this.isAsyncFunction(hook)) {
|
|
138
|
+
result = await (hook as AsyncHookFunction)(result, context);
|
|
139
|
+
} else {
|
|
140
|
+
result = (hook as HookFunction)(result, context);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result as R;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Применяет per-row хуки
|
|
148
|
+
* @param row - Строка данных
|
|
149
|
+
* @param index - Индекс строки
|
|
150
|
+
* @param context - Контекст выполнения
|
|
151
|
+
* @returns Трансформированная строка
|
|
152
|
+
*/
|
|
153
|
+
applyPerRow<T = any, R = any>(row: T, index: number, context: HookContext = {}): R {
|
|
154
|
+
let result: any = row;
|
|
155
|
+
for (const hook of this.hooks.perRow) {
|
|
156
|
+
result = hook(result, index, context);
|
|
157
|
+
}
|
|
158
|
+
return result as R;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Применяет per-row хуки асинхронно
|
|
163
|
+
* @param row - Строка данных
|
|
164
|
+
* @param index - Индекс строки
|
|
165
|
+
* @param context - Контекст выполнения
|
|
166
|
+
* @returns Promise с трансформированной строкой
|
|
167
|
+
*/
|
|
168
|
+
async applyPerRowAsync<T = any, R = any>(row: T, index: number, context: HookContext = {}): Promise<R> {
|
|
169
|
+
let result: any = row;
|
|
170
|
+
for (const hook of this.hooks.perRow) {
|
|
171
|
+
if (this.isAsyncFunction(hook)) {
|
|
172
|
+
result = await (hook as AsyncPerRowHookFunction)(result, index, context);
|
|
173
|
+
} else {
|
|
174
|
+
result = (hook as PerRowHookFunction)(result, index, context);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return result as R;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Применяет все хуки к массиву данных
|
|
182
|
+
* @param data - Массив данных
|
|
183
|
+
* @param context - Контекст выполнения
|
|
184
|
+
* @returns Трансформированный массив
|
|
185
|
+
*/
|
|
186
|
+
applyAll<T = any, R = any>(data: T[], context: HookContext = {}): R[] {
|
|
187
|
+
if (!Array.isArray(data)) {
|
|
188
|
+
throw new ValidationError('Data must be an array for applyAll');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Применяем beforeConvert хуки
|
|
192
|
+
let processedData: any[] = this.applyBeforeConvert(data, context);
|
|
193
|
+
|
|
194
|
+
// Применяем per-row хуки к каждой строке
|
|
195
|
+
if (this.hooks.perRow.length > 0) {
|
|
196
|
+
processedData = processedData.map((row: any, index: number) =>
|
|
197
|
+
this.applyPerRow(row, index, context)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Применяем afterConvert хуки
|
|
202
|
+
return this.applyAfterConvert(processedData, context) as R[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Применяет все хуки к массиву данных асинхронно
|
|
207
|
+
* @param data - Массив данных
|
|
208
|
+
* @param context - Контекст выполнения
|
|
209
|
+
* @returns Promise с трансформированным массивом
|
|
210
|
+
*/
|
|
211
|
+
async applyAllAsync<T = any, R = any>(data: T[], context: HookContext = {}): Promise<R[]> {
|
|
212
|
+
if (!Array.isArray(data)) {
|
|
213
|
+
throw new ValidationError('Data must be an array for applyAll');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Применяем beforeConvert хуки асинхронно
|
|
217
|
+
let processedData = await this.applyBeforeConvertAsync(data, context);
|
|
218
|
+
|
|
219
|
+
// Применяем per-row хуки асинхронно к каждой строке
|
|
220
|
+
if (this.hooks.perRow.length > 0) {
|
|
221
|
+
const processedRows: any[] = [];
|
|
222
|
+
for (let i = 0; i < processedData.length; i++) {
|
|
223
|
+
const processedRow = await this.applyPerRowAsync(processedData[i], i, context);
|
|
224
|
+
processedRows.push(processedRow);
|
|
225
|
+
}
|
|
226
|
+
processedData = processedRows;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Применяем afterConvert хуки асинхронно
|
|
230
|
+
return await this.applyAfterConvertAsync(processedData, context) as R[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Создает копию системы хуков
|
|
235
|
+
* @returns Новая копия
|
|
236
|
+
*/
|
|
237
|
+
clone(): TransformHooks {
|
|
238
|
+
const cloned = new TransformHooks();
|
|
239
|
+
cloned.hooks = {
|
|
240
|
+
beforeConvert: [...this.hooks.beforeConvert],
|
|
241
|
+
afterConvert: [...this.hooks.afterConvert],
|
|
242
|
+
perRow: [...this.hooks.perRow]
|
|
243
|
+
};
|
|
244
|
+
return cloned;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Очищает все хуки
|
|
249
|
+
*/
|
|
250
|
+
clear(): void {
|
|
251
|
+
this.hooks = {
|
|
252
|
+
beforeConvert: [],
|
|
253
|
+
afterConvert: [],
|
|
254
|
+
perRow: []
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Возвращает статистику по хукам
|
|
260
|
+
* @returns Статистика
|
|
261
|
+
*/
|
|
262
|
+
getStats(): { beforeConvert: number; afterConvert: number; perRow: number; total: number } {
|
|
263
|
+
return {
|
|
264
|
+
beforeConvert: this.hooks.beforeConvert.length,
|
|
265
|
+
afterConvert: this.hooks.afterConvert.length,
|
|
266
|
+
perRow: this.hooks.perRow.length,
|
|
267
|
+
total: this.hooks.beforeConvert.length +
|
|
268
|
+
this.hooks.afterConvert.length +
|
|
269
|
+
this.hooks.perRow.length
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Проверяет, является ли функция асинхронной
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
private isAsyncFunction(fn: any): boolean {
|
|
278
|
+
return fn.constructor.name === 'AsyncFunction' ||
|
|
279
|
+
(typeof fn === 'function' && fn.constructor === (async () => {}).constructor);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Предопределенные хуки
|
|
285
|
+
*/
|
|
286
|
+
export const predefinedHooks = {
|
|
287
|
+
/**
|
|
288
|
+
* Хук для фильтрации данных
|
|
289
|
+
* @param predicate - Функция-предикат
|
|
290
|
+
* @returns Хук фильтрации
|
|
291
|
+
*/
|
|
292
|
+
filter<T>(predicate: (item: T, index: number) => boolean): HookFunction<T[], T[]> {
|
|
293
|
+
return (data: T[]) => {
|
|
294
|
+
if (Array.isArray(data)) {
|
|
295
|
+
return data.filter(predicate);
|
|
296
|
+
}
|
|
297
|
+
return data;
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Асинхронный хук для фильтрации данных
|
|
303
|
+
* @param predicate - Асинхронная функция-предикат
|
|
304
|
+
* @returns Асинхронный хук фильтрации
|
|
305
|
+
*/
|
|
306
|
+
filterAsync<T>(predicate: (item: T, index: number) => Promise<boolean>): AsyncHookFunction<T[], T[]> {
|
|
307
|
+
return async (data: T[]) => {
|
|
308
|
+
if (Array.isArray(data)) {
|
|
309
|
+
const filtered: T[] = [];
|
|
310
|
+
for (let i = 0; i < data.length; i++) {
|
|
311
|
+
if (await predicate(data[i], i)) {
|
|
312
|
+
filtered.push(data[i]);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return filtered;
|
|
316
|
+
}
|
|
317
|
+
return data;
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Хук для маппинга данных
|
|
323
|
+
* @param mapper - Функция-маппер
|
|
324
|
+
* @returns Хук маппинга
|
|
325
|
+
*/
|
|
326
|
+
map<T, R>(mapper: (item: T, index: number) => R): HookFunction<T[], R[]> {
|
|
327
|
+
return (data: T[]) => {
|
|
328
|
+
if (Array.isArray(data)) {
|
|
329
|
+
return data.map(mapper);
|
|
330
|
+
}
|
|
331
|
+
return data as any;
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Асинхронный хук для маппинга данных
|
|
337
|
+
* @param mapper - Асинхронная функция-маппер
|
|
338
|
+
* @returns Асинхронный хук маппинга
|
|
339
|
+
*/
|
|
340
|
+
mapAsync<T, R>(mapper: (item: T, index: number) => Promise<R>): AsyncHookFunction<T[], R[]> {
|
|
341
|
+
return async (data: T[]) => {
|
|
342
|
+
if (Array.isArray(data)) {
|
|
343
|
+
const mapped: R[] = [];
|
|
344
|
+
for (let i = 0; i < data.length; i++) {
|
|
345
|
+
mapped.push(await mapper(data[i], i));
|
|
346
|
+
}
|
|
347
|
+
return mapped;
|
|
348
|
+
}
|
|
349
|
+
return data as any;
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Хук для сортировки данных
|
|
355
|
+
* @param compareFn - Функция сравнения
|
|
356
|
+
* @returns Хук сортировки
|
|
357
|
+
*/
|
|
358
|
+
sort<T>(compareFn?: (a: T, b: T) => number): HookFunction<T[], T[]> {
|
|
359
|
+
return (data: T[]) => {
|
|
360
|
+
if (Array.isArray(data)) {
|
|
361
|
+
return [...data].sort(compareFn);
|
|
362
|
+
}
|
|
363
|
+
return data;
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Хук для ограничения количества записей
|
|
369
|
+
* @param limit - Максимальное количество записей
|
|
370
|
+
* @returns Хук ограничения
|
|
371
|
+
*/
|
|
372
|
+
limit<T>(limit: number): HookFunction<T[], T[]> {
|
|
373
|
+
return (data: T[]) => {
|
|
374
|
+
if (Array.isArray(data)) {
|
|
375
|
+
return data.slice(0, limit);
|
|
376
|
+
}
|
|
377
|
+
return data;
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Хук для добавления метаданных
|
|
383
|
+
* @param metadata - Метаданные для добавления
|
|
384
|
+
* @returns Хук добавления метаданных
|
|
385
|
+
*/
|
|
386
|
+
addMetadata<T extends Record<string, any>>(metadata: Record<string, any>): HookFunction<T[], Array<T & { _metadata: any }>> {
|
|
387
|
+
return (data: T[], context?: HookContext) => {
|
|
388
|
+
if (Array.isArray(data)) {
|
|
389
|
+
return data.map(item => ({
|
|
390
|
+
...item,
|
|
391
|
+
_metadata: {
|
|
392
|
+
...metadata,
|
|
393
|
+
timestamp: new Date().toISOString(),
|
|
394
|
+
context
|
|
395
|
+
}
|
|
396
|
+
})) as Array<T & { _metadata: any }>;
|
|
397
|
+
}
|
|
398
|
+
return data as any;
|
|
399
|
+
};
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Хук для преобразования ключей
|
|
404
|
+
* @param keyTransformer - Функция преобразования ключей
|
|
405
|
+
* @returns Хук преобразования ключей
|
|
406
|
+
*/
|
|
407
|
+
transformKeys<T extends Record<string, any>>(keyTransformer: (key: string) => string): HookFunction<T[], Array<Record<string, any>>> {
|
|
408
|
+
return (data: T[]) => {
|
|
409
|
+
if (Array.isArray(data)) {
|
|
410
|
+
return data.map(item => {
|
|
411
|
+
const transformed: Record<string, any> = {};
|
|
412
|
+
for (const [key, value] of Object.entries(item)) {
|
|
413
|
+
transformed[keyTransformer(key)] = value;
|
|
414
|
+
}
|
|
415
|
+
return transformed;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return data;
|
|
419
|
+
};
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Хук для преобразования значений
|
|
424
|
+
* @param valueTransformer - Функция преобразования значений
|
|
425
|
+
* @returns Хук преобразования значений
|
|
426
|
+
*/
|
|
427
|
+
transformValues<T extends Record<string, any>>(valueTransformer: (value: any, key: string) => any): HookFunction<T[], Array<Record<string, any>>> {
|
|
428
|
+
return (data: T[]) => {
|
|
429
|
+
if (Array.isArray(data)) {
|
|
430
|
+
return data.map(item => {
|
|
431
|
+
const transformed: Record<string, any> = {};
|
|
432
|
+
for (const [key, value] of Object.entries(item)) {
|
|
433
|
+
transformed[key] = valueTransformer(value, key);
|
|
434
|
+
}
|
|
435
|
+
return transformed;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return data;
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Хук для валидации данных
|
|
444
|
+
* @param validator - Функция валидации
|
|
445
|
+
* @param onError - Обработчик ошибки
|
|
446
|
+
* @returns Хук валидации
|
|
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[] = [];
|
|
453
|
+
|
|
454
|
+
data.forEach((item, index) => {
|
|
455
|
+
try {
|
|
456
|
+
const isValid = validator(item, index);
|
|
457
|
+
if (isValid) {
|
|
458
|
+
validData.push(item);
|
|
459
|
+
} else {
|
|
460
|
+
errors.push({ index, item, reason: 'Validation failed' });
|
|
461
|
+
}
|
|
462
|
+
} catch (error: any) {
|
|
463
|
+
errors.push({ index, item, error: error.message });
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
if (errors.length > 0) {
|
|
468
|
+
onError(errors);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return validData;
|
|
472
|
+
}
|
|
473
|
+
return data;
|
|
474
|
+
};
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Асинхронный хук для валидации данных
|
|
479
|
+
* @param validator - Асинхронная функция валидации
|
|
480
|
+
* @param onError - Обработчик ошибки
|
|
481
|
+
* @returns Асинхронный хук валидации
|
|
482
|
+
*/
|
|
483
|
+
validateAsync<T>(validator: (item: T, index: number) => Promise<boolean>, onError: (errors: any[]) => void = console.error): AsyncHookFunction<T[], T[]> {
|
|
484
|
+
return async (data: T[]) => {
|
|
485
|
+
if (Array.isArray(data)) {
|
|
486
|
+
const validData: T[] = [];
|
|
487
|
+
const errors: any[] = [];
|
|
488
|
+
|
|
489
|
+
for (let i = 0; i < data.length; i++) {
|
|
490
|
+
try {
|
|
491
|
+
const isValid = await validator(data[i], i);
|
|
492
|
+
if (isValid) {
|
|
493
|
+
validData.push(data[i]);
|
|
494
|
+
} else {
|
|
495
|
+
errors.push({ index: i, item: data[i], reason: 'Validation failed' });
|
|
496
|
+
}
|
|
497
|
+
} catch (error: any) {
|
|
498
|
+
errors.push({ index: i, item: data[i], error: error.message });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (errors.length > 0) {
|
|
503
|
+
onError(errors);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return validData;
|
|
507
|
+
}
|
|
508
|
+
return data;
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Хук для дедупликации данных
|
|
514
|
+
* @param keySelector - Функция выбора ключа
|
|
515
|
+
* @returns Хук дедупликации
|
|
516
|
+
*/
|
|
517
|
+
deduplicate<T>(keySelector: (item: T) => string = JSON.stringify): HookFunction<T[], T[]> {
|
|
518
|
+
return (data: T[]) => {
|
|
519
|
+
if (Array.isArray(data)) {
|
|
520
|
+
const seen = new Set<string>();
|
|
521
|
+
return data.filter(item => {
|
|
522
|
+
const key = keySelector(item);
|
|
523
|
+
if (seen.has(key)) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
seen.add(key);
|
|
527
|
+
return true;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return data;
|
|
531
|
+
};
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Асинхронный хук для дедупликации данных
|
|
536
|
+
* @param keySelector - Асинхронная функция выбора ключа
|
|
537
|
+
* @returns Асинхронный хук дедупликации
|
|
538
|
+
*/
|
|
539
|
+
deduplicateAsync<T>(keySelector: (item: T) => Promise<string> = async (item) => JSON.stringify(item)): AsyncHookFunction<T[], T[]> {
|
|
540
|
+
return async (data: T[]) => {
|
|
541
|
+
if (Array.isArray(data)) {
|
|
542
|
+
const seen = new Set<string>();
|
|
543
|
+
const filtered: T[] = [];
|
|
544
|
+
|
|
545
|
+
for (const item of data) {
|
|
546
|
+
const key = await keySelector(item);
|
|
547
|
+
if (!seen.has(key)) {
|
|
548
|
+
seen.add(key);
|
|
549
|
+
filtered.push(item);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return filtered;
|
|
554
|
+
}
|
|
555
|
+
return data;
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
};
|