@drax/crud-back 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/controllers/AbstractFastifyController.js +113 -44
  2. package/dist/exports/AbstractExport.js +41 -0
  3. package/dist/exports/ExportCsv.js +86 -0
  4. package/dist/exports/ExportJson.js +51 -0
  5. package/dist/repository/AbstractMongoRepository.js +11 -0
  6. package/dist/services/AbstractService.js +44 -0
  7. package/dist/workers/ExportCsvWorker.js +12 -0
  8. package/package.json +8 -6
  9. package/src/controllers/AbstractFastifyController.ts +130 -46
  10. package/src/exports/AbstractExport.ts +74 -0
  11. package/src/exports/ExportCsv.ts +107 -0
  12. package/src/exports/ExportJson.ts +64 -0
  13. package/src/index.ts +0 -3
  14. package/src/repository/AbstractMongoRepository.ts +25 -5
  15. package/src/repository/AbstractSqliteRepository.ts +1 -1
  16. package/src/services/AbstractService.ts +81 -10
  17. package/src/workers/ExportCsvWorker.js +12 -0
  18. package/src/workers/ExportCsvWorker.ts +12 -0
  19. package/test/_mocks/MockRepository.ts +58 -0
  20. package/test/exports/ExportCsv.test.ts +137 -0
  21. package/test/services/AbstractService.test.ts +40 -0
  22. package/test/workers/ExportCsvWorker.test.ts +92 -0
  23. package/tsconfig.json +1 -1
  24. package/tsconfig.tsbuildinfo +1 -1
  25. package/types/controllers/AbstractFastifyController.d.ts +19 -7
  26. package/types/controllers/AbstractFastifyController.d.ts.map +1 -1
  27. package/types/exports/AbstractExport.d.ts +23 -0
  28. package/types/exports/AbstractExport.d.ts.map +1 -0
  29. package/types/exports/ExportCsv.d.ts +13 -0
  30. package/types/exports/ExportCsv.d.ts.map +1 -0
  31. package/types/exports/ExportFsCsv.d.ts +17 -0
  32. package/types/exports/ExportFsCsv.d.ts.map +1 -0
  33. package/types/exports/ExportJson.d.ts +8 -0
  34. package/types/exports/ExportJson.d.ts.map +1 -0
  35. package/types/index.d.ts +0 -2
  36. package/types/index.d.ts.map +1 -1
  37. package/types/interfaces/ICrudRepository.d.ts +2 -1
  38. package/types/interfaces/ICrudRepository.d.ts.map +1 -1
  39. package/types/repository/AbstractMongoRepository.d.ts +3 -3
  40. package/types/repository/AbstractMongoRepository.d.ts.map +1 -1
  41. package/types/repository/AbstractSqliteRepository.d.ts +1 -1
  42. package/types/repository/AbstractSqliteRepository.d.ts.map +1 -1
  43. package/types/services/AbstractService.d.ts +8 -5
  44. package/types/services/AbstractService.d.ts.map +1 -1
  45. package/types/workers/ExportCsvWorker.d.ts +2 -0
  46. package/types/workers/ExportCsvWorker.d.ts.map +1 -0
  47. package/src/interfaces/ICrudRepository.ts +0 -16
  48. package/src/interfaces/IEntityPermission.ts +0 -10
@@ -1,22 +1,31 @@
1
1
  import {ValidationError, ZodErrorToValidationError} from "@drax/common-back"
2
2
  import {ZodError} from "zod";
3
3
  import type {ZodSchema} from "zod";
4
- import type {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/common-share";
5
- import type {ICrudRepository} from "../interfaces/ICrudRepository";
6
-
7
- class AbstractService<T,C,U> {
8
-
9
- _repository: ICrudRepository<T,C,U>
4
+ import type {
5
+ IDraxPaginateOptions,
6
+ IDraxPaginateResult,
7
+ IDraxFindOptions,
8
+ IDraxCrud,
9
+ IDraxExportOptions
10
+ } from "@drax/crud-share";
11
+ import {IDraxCrudService} from "@drax/crud-share";
12
+ import ExportCsv from "../exports/ExportCsv.js";
13
+ import ExportJson from "../exports/ExportJson.js";
14
+ import {IDraxExportResult} from "@drax/crud-share";
15
+
16
+ class AbstractService<T, C, U> implements IDraxCrudService<T, C, U> {
17
+
18
+ _repository: IDraxCrud<T, C, U>
10
19
  _schema?: ZodSchema | undefined
11
20
 
12
- constructor(repository: ICrudRepository<T,C,U>, schema?: ZodSchema) {
21
+ constructor(repository: IDraxCrud<T, C, U>, schema?: ZodSchema) {
13
22
  this._repository = repository
14
23
  this._schema = schema
15
24
  }
16
25
 
17
26
  async create(data: C): Promise<T> {
18
27
  try {
19
- if(this._schema){
28
+ if (this._schema) {
20
29
  await this._schema.parseAsync(data)
21
30
  }
22
31
  const item: T = await this._repository.create(data)
@@ -32,10 +41,10 @@ class AbstractService<T,C,U> {
32
41
 
33
42
  async update(id: string, data: U): Promise<T> {
34
43
  try {
35
- if(this._schema){
44
+ if (this._schema) {
36
45
  await this._schema.parseAsync(data)
37
46
  }
38
- const item : T = await this._repository.update(id, data)
47
+ const item: T = await this._repository.update(id, data)
39
48
  return item
40
49
  } catch (e) {
41
50
  console.error("Error updating", e)
@@ -139,6 +148,68 @@ class AbstractService<T,C,U> {
139
148
 
140
149
  }
141
150
 
151
+ async find({
152
+ orderBy = '',
153
+ order = false,
154
+ search = '',
155
+ filters = []
156
+ }: IDraxFindOptions): Promise<T[]> {
157
+ try {
158
+ const items = await this._repository.find({orderBy, order, search, filters});
159
+ return items;
160
+ } catch (e) {
161
+ console.error("Error paginating", e)
162
+ throw e;
163
+ }
164
+
165
+ }
166
+
167
+ async export({
168
+ format = 'JSON',
169
+ headers = [],
170
+ separator = ';',
171
+ orderBy = '',
172
+ order = false,
173
+ search = '',
174
+ filters = []
175
+ }: IDraxExportOptions,
176
+ destinationPath: string): Promise<IDraxExportResult> {
177
+ try {
178
+
179
+ console.log("ExportOptions", {
180
+ format,
181
+ headers,
182
+ separator,
183
+ outputPath: destinationPath,
184
+ orderBy,
185
+ order,
186
+ search,
187
+ filters
188
+ })
189
+
190
+ let cursor:any
191
+ let exporter:any
192
+
193
+ switch (format) {
194
+ case 'JSON':
195
+ cursor = await this._repository.find({orderBy, order, search, filters});
196
+ exporter = new ExportJson({cursor, destinationPath: destinationPath, headers});
197
+ return await exporter.process()
198
+ case 'CSV':
199
+ cursor = await this._repository.find({orderBy, order, search, filters});
200
+ exporter = new ExportCsv({cursor, destinationPath: destinationPath, headers, separator});
201
+ return await exporter.process()
202
+ default:
203
+ throw new Error(`Unsupported export format: ${format}`);
204
+ }
205
+
206
+ } catch (e) {
207
+ console.error("Error exporting", e)
208
+ throw e;
209
+ }
210
+
211
+ }
212
+
142
213
  }
143
214
 
144
215
  export default AbstractService
@@ -0,0 +1,12 @@
1
+ import { parentPort } from 'worker_threads';
2
+ import ExportCsv from '../exports/ExportCsv.js'; // Ajusta la ruta según sea necesario
3
+
4
+ parentPort.on('message', async (options) => {
5
+ const exporter = new ExportCsv(options);
6
+ try {
7
+ let result = await exporter.process();
8
+ parentPort.postMessage({result: result});
9
+ } catch (error) {
10
+ parentPort.postMessage({error: error.message});
11
+ }
12
+ });
@@ -0,0 +1,12 @@
1
+ import { parentPort } from 'worker_threads';
2
+ import ExportCsv from '../exports/ExportCsv'; // Ajusta la ruta según sea necesario
3
+
4
+ parentPort.on('message', async (options) => {
5
+ const exporter = new ExportCsv(options);
6
+ try {
7
+ let result = await exporter.process();
8
+ parentPort.postMessage({result: result});
9
+ } catch (error) {
10
+ parentPort.postMessage({error: error.message});
11
+ }
12
+ });
@@ -0,0 +1,58 @@
1
+ import type {IDraxCrud, IDraxPaginateOptions, IDraxPaginateResult} from "@drax/common-share";
2
+
3
+ class MockRepository implements IDraxCrud<any, any, any> {
4
+ async create(data: any): Promise<any> {
5
+ return {id: '1',...data}
6
+ }
7
+
8
+ async update(id: string, data: any): Promise<any> {
9
+ return {...data}
10
+ }
11
+
12
+ async delete(id: string): Promise<boolean> {
13
+ return true
14
+ }
15
+
16
+ async findById(id: string): Promise<any | null> {
17
+ return {id: '1', name: 'John Doe'}
18
+ }
19
+
20
+ async findByIds(ids: string[]): Promise<any[]> {
21
+ return [{id: '1', name: 'John Doe'}, {id: '2', name: 'Jane Doe'}]
22
+ }
23
+
24
+ async findOneBy(field: string, value: string): Promise<any | null> {
25
+ return {id: '1', name: 'John Doe'}
26
+ }
27
+
28
+ async findBy(field: string, value: string): Promise<any[]> {
29
+ return [{id: '1', name: 'John Doe'}, {id: '2', name: 'Jane Doe'}]
30
+ }
31
+
32
+ async fetchAll(): Promise<any[]> {
33
+ return [{id: '1', name: 'John Doe'}, {id: '2', name: 'Jane Doe'}]
34
+ }
35
+
36
+ async search(value: string): Promise<any[]> {
37
+ return [{id: '1', name: 'John Doe'}, {id: '2', name: 'Jane Doe'}]
38
+ }
39
+
40
+ async find(options?: any): Promise<any[]> {
41
+ return [{id: '1', name: 'John Doe'}, {id: '2', name: 'Jane Doe'}]
42
+ }
43
+
44
+ paginate(options: IDraxPaginateOptions): Promise<IDraxPaginateResult<any>> {
45
+
46
+ return Promise.resolve({
47
+ page: 1,
48
+ limit: 10,
49
+ total: 2,
50
+ items: [{id: '1', name: 'John Doe'}, {id: '2', name: 'Jane Doe'}]
51
+ })
52
+ }
53
+
54
+
55
+ }
56
+
57
+ export {MockRepository}
58
+ export default MockRepository
@@ -0,0 +1,137 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import * as fs from 'fs';
4
+ import ExportCsv from '../../src/exports/ExportCsv';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ // Simulación de un cursor para MongoDB o SQLite
9
+ function createMockCursor(data: any[]) {
10
+ let index = 0;
11
+ return {
12
+ async *[Symbol.asyncIterator]() {
13
+ while (index < data.length) {
14
+ yield data[index++];
15
+ }
16
+ },
17
+ };
18
+ }
19
+
20
+ //@ts-ignore
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+
24
+
25
+ // Utilidad para leer el contenido del CSV generado
26
+ function readGeneratedCSV(filePath: string): string[] {
27
+ const content = fs.readFileSync(filePath, 'utf-8');
28
+ return content.split('\n').filter(line => line.length > 0);
29
+ }
30
+
31
+ // Eliminar el archivo de salida después de cada prueba
32
+ function cleanUp(outputFilePath) {
33
+ if (fs.existsSync(outputFilePath)) {
34
+ fs.unlinkSync(outputFilePath);
35
+ }
36
+ }
37
+
38
+ const mockData = [
39
+ {
40
+ id: '123',
41
+ name: 'Cristian',
42
+ lastname: 'Incarnato',
43
+ age: 39,
44
+ address: {
45
+ street: 'Directorio',
46
+ number: '3935',
47
+ floor: '6',
48
+ depto: 'c'
49
+ },
50
+ phones: [
51
+ { number: '1234567', type: 'personal' },
52
+ { number: '87456123', type: 'laboral' },
53
+ ],
54
+ hobbies: ['soccer', 'gym'],
55
+ pets: [
56
+ {name: 'capitan', colors: ['grey'], skills: [{name: 'jump', level: 2}] },
57
+ {name: 'indio', colors: ['grey', 'white'], skills: [{name: 'jump', level: 5}, {name: 'run', level: 4}]},
58
+ ],
59
+ jobs: ['developer','devops']
60
+ },
61
+ ];
62
+
63
+
64
+ // Prueba para exportar datos con arrays de subdocumentos
65
+ test('Debe exportar atributos base a un archivo CSV', async () => {
66
+ const outputFilePath = path.resolve(__dirname, 'output_base_test.csv');
67
+
68
+ cleanUp(outputFilePath);
69
+
70
+ const mockCursor = createMockCursor(mockData);
71
+ const exportCsv = new ExportCsv({
72
+ cursor: mockCursor,
73
+ destinationPath: outputFilePath,
74
+ headers: ['name', 'lastname', 'address','phones', 'hobbies'],
75
+ separator: ';',
76
+ });
77
+
78
+ await exportCsv.process();
79
+
80
+ const output = readGeneratedCSV(outputFilePath);
81
+
82
+ // Verificar que el archivo CSV tenga las filas correctas para los arrays
83
+ assert.strictEqual(output[0], 'name;lastname;address;phones;hobbies');
84
+ assert.strictEqual(output[1], 'Cristian;Incarnato;{"street":"Directorio","number":"3935","floor":"6","depto":"c"};[{"number":"1234567","type":"personal"},{"number":"87456123","type":"laboral"}];soccer,gym');
85
+
86
+ //cleanUp(outputFilePath);
87
+ })
88
+
89
+ test('Debe exportar atributos de objeto a un archivo CSV', async () => {
90
+ const outputFilePath = path.resolve(__dirname, 'output_object_test.csv');
91
+
92
+ cleanUp(outputFilePath);
93
+
94
+ const mockCursor = createMockCursor(mockData);
95
+ const exportCsv = new ExportCsv({
96
+ cursor: mockCursor,
97
+ destinationPath: outputFilePath,
98
+ headers: ['name', 'lastname', 'address.street','address.number'],
99
+ separator: ';',
100
+ });
101
+
102
+ await exportCsv.process();
103
+
104
+ const output = readGeneratedCSV(outputFilePath);
105
+
106
+ // Verificar que el archivo CSV tenga las filas correctas para los arrays
107
+ assert.strictEqual(output[0], 'name;lastname;address.street;address.number');
108
+ assert.strictEqual(output[1], 'Cristian;Incarnato;Directorio;3935');
109
+
110
+ //cleanUp(outputFilePath);
111
+ })
112
+
113
+
114
+
115
+ test('Debe exportar atributos de un array de objetos a un archivo CSV', async () => {
116
+ const outputFilePath = path.resolve(__dirname, 'output_object_test.csv');
117
+
118
+ cleanUp(outputFilePath);
119
+
120
+ const mockCursor = createMockCursor(mockData);
121
+ const exportCsv = new ExportCsv({
122
+ cursor: mockCursor,
123
+ destinationPath: outputFilePath,
124
+ headers: ['name', 'lastname', 'phones.number','phones.type'],
125
+ separator: ';',
126
+ });
127
+
128
+ await exportCsv.process();
129
+
130
+ const output = readGeneratedCSV(outputFilePath);
131
+
132
+ // Verificar que el archivo CSV tenga las filas correctas para los arrays
133
+ assert.strictEqual(output[0], 'name;lastname;phones.number;phones.type');
134
+ assert.strictEqual(output[1], 'Cristian;Incarnato;1234567|87456123;personal|laboral');
135
+
136
+ //cleanUp(outputFilePath);
137
+ })
@@ -0,0 +1,40 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import MockRepository from "../_mocks/MockRepository.js";
4
+ import {AbstractService} from "../../src/services/AbstractService.js";
5
+ import {fileURLToPath} from "url";
6
+ import path from "path";
7
+
8
+ //@ts-ignore
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const mockRepository = new MockRepository();
13
+ const abstractService = new AbstractService(mockRepository);
14
+
15
+ test('create', async () => {
16
+ const item: any = await abstractService.create({name: 'John Doe'})
17
+ assert.deepStrictEqual(item.name, 'John Doe');
18
+ })
19
+
20
+
21
+ test('export', async () => {
22
+ const file = 'test.csv'
23
+ const outputPath = path.resolve(__dirname, file);
24
+
25
+ const result:any = await abstractService.export(
26
+ {
27
+ format: 'CSV',
28
+ headers: ['name'],
29
+ separator: ';',
30
+ limit: 0,
31
+ search: '',
32
+ filters: [],
33
+ }
34
+ )
35
+
36
+ console.log("result",result)
37
+
38
+ assert.deepStrictEqual(outputPath, result.outputPath);
39
+ assert.deepStrictEqual(2, result.rowCount);
40
+ })
@@ -0,0 +1,92 @@
1
+
2
+ // Reemplazo de __dirname para ES modules
3
+ import {test} from "node:test";
4
+ import path from "path";
5
+ import assert from "node:assert";
6
+ import {WorkerHandler} from "@drax/common-back"
7
+ import {fileURLToPath} from "url";
8
+ import * as fs from 'fs';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ function createMockCursor(data: any[]) {
14
+ let index = 0;
15
+ return {
16
+ async *[Symbol.asyncIterator]() {
17
+ while (index < data.length) {
18
+ yield data[index++];
19
+ }
20
+ },
21
+ };
22
+ }
23
+
24
+ // Utilidad para leer el contenido del CSV generado
25
+ function readGeneratedCSV(filePath: string): string[] {
26
+ const content = fs.readFileSync(filePath, 'utf-8');
27
+ return content.split('\n').filter(line => line.length > 0);
28
+ }
29
+
30
+ // Eliminar el archivo de salida después de cada prueba
31
+ function cleanUp(outputFilePath) {
32
+ if (fs.existsSync(outputFilePath)) {
33
+ fs.unlinkSync(outputFilePath);
34
+ }
35
+ }
36
+
37
+
38
+ const mockData = [
39
+ {
40
+ id: '123',
41
+ name: 'Cristian',
42
+ lastname: 'Incarnato',
43
+ age: 39,
44
+ address: {
45
+ street: 'Directorio',
46
+ number: '3935',
47
+ floor: '6',
48
+ depto: 'c'
49
+ },
50
+ phones: [
51
+ { number: '1234567', type: 'personal' },
52
+ { number: '87456123', type: 'laboral' },
53
+ ],
54
+ hobbies: ['soccer', 'gym'],
55
+ pets: [
56
+ {name: 'capitan', colors: ['grey'], skills: [{name: 'jump', level: 2}] },
57
+ {name: 'indio', colors: ['grey', 'white'], skills: [{name: 'jump', level: 5}, {name: 'run', level: 4}]},
58
+ ],
59
+ jobs: ['developer','devops']
60
+ },
61
+ ];
62
+
63
+ test('Debe exportar atributos base a un archivo CSV', async () => {
64
+ const outputFilePath = path.resolve(__dirname, 'output_base_worker_test.csv');
65
+
66
+ cleanUp(outputFilePath);
67
+
68
+ const mockCursor = createMockCursor(mockData);
69
+ const workerFile = './src/workers/ExportCsvWorker.js'
70
+ const params = {
71
+ cursor: mockCursor,
72
+ outputPath: outputFilePath,
73
+ headers: ['name', 'lastname', 'address','phones', 'hobbies'],
74
+ separator: ';',
75
+ }
76
+
77
+ try{
78
+ const result = await WorkerHandler(workerFile,params)
79
+ console.log("result", result)
80
+ }catch (error) {
81
+ console.log("error", error)
82
+ }
83
+
84
+
85
+ const output = readGeneratedCSV(outputFilePath);
86
+
87
+ // Verificar que el archivo CSV tenga las filas correctas para los arrays
88
+ assert.strictEqual(output[0], 'name;lastname;address;phones;hobbies');
89
+ assert.strictEqual(output[1], 'Cristian;Incarnato;{"street":"Directorio","number":"3935","floor":"6","depto":"c"};[{"number":"1234567","type":"personal"},{"number":"87456123","type":"laboral"}];soccer,gym');
90
+
91
+ //cleanUp(outputFilePath);
92
+ })
package/tsconfig.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "rootDir": "src",
5
5
  "outDir": "dist",
6
- "declarationDir": "./types",
6
+ "declarationDir": "./types"
7
7
  },
8
8
  "exclude": ["test", "types","dist"],
9
9
  "ts-node": {