@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,8 +1,9 @@
1
1
  import AbstractService from "../services/AbstractService";
2
- import {ValidationError} from "@drax/common-back";
2
+ import {CommonConfig, DraxConfig, ValidationError} from "@drax/common-back";
3
3
  import {UnauthorizedError, Rbac} from "@drax/identity-back";
4
4
  import type {FastifyReply, FastifyRequest} from "fastify";
5
- import {IEntityPermission} from "../interfaces/IEntityPermission";
5
+ import {IDraxExportResult, IDraxPermission} from "@drax/crud-share";
6
+ import {join} from "path";
6
7
 
7
8
  declare module 'fastify' {
8
9
  interface FastifyRequest {
@@ -14,37 +15,88 @@ type CustomRequest = FastifyRequest<{
14
15
  Params: {
15
16
  id?: string
16
17
  ids?: string
18
+ format?: string
17
19
  };
18
20
  Querystring:{
21
+ format?: string
19
22
  page?: number
20
23
  limit?: number
21
24
  orderBy?: string
22
25
  order?: 'asc' | 'desc' | boolean
23
26
  search?: string
27
+ filters?: string
28
+ headers?: string
29
+ separator?: string
24
30
  }
25
31
  }>
26
32
 
33
+ const BASE_FILE_DIR = DraxConfig.getOrLoad(CommonConfig.FileDir) || 'files';
34
+ const BASE_URL = DraxConfig.getOrLoad(CommonConfig.BaseUrl) ? DraxConfig.get(CommonConfig.BaseUrl).replace(/\/$/, '') : ''
35
+
36
+
27
37
  class AbstractFastifyController<T,C,U>{
28
38
 
29
39
  protected service: AbstractService<T,C,U>
30
- protected permission: IEntityPermission
40
+ protected permission: IDraxPermission
31
41
 
32
- constructor(service: AbstractService<T, C, U>, permission: IEntityPermission) {
42
+ constructor(service: AbstractService<T, C, U>, permission: IDraxPermission) {
33
43
  this.service = service
34
44
  this.permission = permission
35
45
  console.log("AbstractFastifyController created. Permissions", this.permission)
36
46
  }
37
47
 
48
+ parseFilters(stringFilters: string): any {
49
+ try {
50
+ if (!stringFilters) {
51
+ return {}
52
+ }
53
+ const filterArray = stringFilters.split(";")
54
+ const filters = []
55
+ filterArray.forEach((filter) => {
56
+ const [field, operator, value] = filter.split(",")
57
+ filters.push({field, operator, value})
58
+ })
59
+ return filters
60
+ } catch (e) {
61
+ console.error(e)
62
+ throw e
63
+ }
64
+ }
65
+
38
66
 
39
- async findById(request:CustomRequest, reply: FastifyReply) {
67
+
68
+
69
+ async create(request: CustomRequest, reply: FastifyReply) {
40
70
  try {
41
- request.rbac.assertPermission(this.permission.View)
71
+ request.rbac.assertPermission(this.permission.Create)
72
+ const payload = request.body
73
+ let item = await this.service.create(payload as C)
74
+ return item
75
+ } catch (e) {
76
+ console.error(e)
77
+ if (e instanceof ValidationError) {
78
+ reply.statusCode = e.statusCode
79
+ reply.send({error: e.message, inputErrors: e.errors})
80
+ } else if (e instanceof UnauthorizedError) {
81
+ reply.statusCode = e.statusCode
82
+ reply.send({error: e.message})
83
+ } else {
84
+ reply.statusCode = 500
85
+ reply.send({error: 'INTERNAL_SERVER_ERROR'})
86
+ }
87
+ }
88
+ }
89
+
90
+ async update(request: CustomRequest, reply: FastifyReply) {
91
+ try {
92
+ request.rbac.assertPermission(this.permission.Update)
42
93
  if(!request.params.id){
43
94
  reply.statusCode = 400
44
95
  reply.send({error: 'BAD REQUEST'})
45
96
  }
46
97
  const id = request.params.id
47
- let item = await this.service.findById(id)
98
+ const payload = request.body
99
+ let item = await this.service.update(id, payload as U)
48
100
  return item
49
101
  } catch (e) {
50
102
  console.error(e)
@@ -61,16 +113,16 @@ class AbstractFastifyController<T,C,U>{
61
113
  }
62
114
  }
63
115
 
64
- async findByIds(request:CustomRequest, reply: FastifyReply) {
116
+ async delete(request: CustomRequest, reply: FastifyReply) {
65
117
  try {
66
- request.rbac.assertPermission(this.permission.View)
67
- if(!request.params.ids){
118
+ request.rbac.assertPermission(this.permission.Delete)
119
+ if(!request.params.id){
68
120
  reply.statusCode = 400
69
121
  reply.send({error: 'BAD REQUEST'})
70
122
  }
71
- const ids = request.params.ids.split(",")
72
- let items = await this.service.findByIds(ids)
73
- return items
123
+ const id = request.params.id
124
+ await this.service.delete(id)
125
+ reply.send({message: 'Item deleted successfully'})
74
126
  } catch (e) {
75
127
  console.error(e)
76
128
  if (e instanceof ValidationError) {
@@ -86,11 +138,15 @@ class AbstractFastifyController<T,C,U>{
86
138
  }
87
139
  }
88
140
 
89
- async search(request:CustomRequest, reply: FastifyReply) {
141
+ async findById(request:CustomRequest, reply: FastifyReply) {
90
142
  try {
91
143
  request.rbac.assertPermission(this.permission.View)
92
- const search = request.query.search
93
- let item = await this.service.search(search)
144
+ if(!request.params.id){
145
+ reply.statusCode = 400
146
+ reply.send({error: 'BAD REQUEST'})
147
+ }
148
+ const id = request.params.id
149
+ let item = await this.service.findById(id)
94
150
  return item
95
151
  } catch (e) {
96
152
  console.error(e)
@@ -107,16 +163,16 @@ class AbstractFastifyController<T,C,U>{
107
163
  }
108
164
  }
109
165
 
110
- async paginate(request: CustomRequest, reply: FastifyReply) {
166
+ async findByIds(request:CustomRequest, reply: FastifyReply) {
111
167
  try {
112
168
  request.rbac.assertPermission(this.permission.View)
113
- const page = request.query.page
114
- const limit = request.query.limit
115
- const orderBy = request.query.orderBy
116
- const order = request.query.order
117
- const search = request.query.search
118
- let paginateResult = await this.service.paginate({page, limit, orderBy, order, search})
119
- return paginateResult
169
+ if(!request.params.ids){
170
+ reply.statusCode = 400
171
+ reply.send({error: 'BAD REQUEST'})
172
+ }
173
+ const ids = request.params.ids.split(",")
174
+ let items = await this.service.findByIds(ids)
175
+ return items
120
176
  } catch (e) {
121
177
  console.error(e)
122
178
  if (e instanceof ValidationError) {
@@ -132,11 +188,11 @@ class AbstractFastifyController<T,C,U>{
132
188
  }
133
189
  }
134
190
 
135
- async create(request: CustomRequest, reply: FastifyReply) {
191
+ async search(request:CustomRequest, reply: FastifyReply) {
136
192
  try {
137
- request.rbac.assertPermission(this.permission.Create)
138
- const payload = request.body
139
- let item = await this.service.create(payload as C)
193
+ request.rbac.assertPermission(this.permission.View)
194
+ const search = request.query.search
195
+ let item = await this.service.search(search)
140
196
  return item
141
197
  } catch (e) {
142
198
  console.error(e)
@@ -153,17 +209,17 @@ class AbstractFastifyController<T,C,U>{
153
209
  }
154
210
  }
155
211
 
156
- async update(request: CustomRequest, reply: FastifyReply) {
212
+ async paginate(request: CustomRequest, reply: FastifyReply) {
157
213
  try {
158
- request.rbac.assertPermission(this.permission.Update)
159
- if(!request.params.id){
160
- reply.statusCode = 400
161
- reply.send({error: 'BAD REQUEST'})
162
- }
163
- const id = request.params.id
164
- const payload = request.body
165
- let item = await this.service.update(id, payload as U)
166
- return item
214
+ request.rbac.assertPermission(this.permission.View)
215
+ const page = request.query.page
216
+ const limit = request.query.limit
217
+ const orderBy = request.query.orderBy
218
+ const order = request.query.order
219
+ const search = request.query.search
220
+ //const filters = this.parseFilters(request.query.filters)
221
+ let paginateResult = await this.service.paginate({page, limit, orderBy, order, search})
222
+ return paginateResult
167
223
  } catch (e) {
168
224
  console.error(e)
169
225
  if (e instanceof ValidationError) {
@@ -179,16 +235,44 @@ class AbstractFastifyController<T,C,U>{
179
235
  }
180
236
  }
181
237
 
182
- async delete(request: CustomRequest, reply: FastifyReply) {
238
+ async export(request:CustomRequest, reply: FastifyReply) {
183
239
  try {
184
- request.rbac.assertPermission(this.permission.Delete)
185
- if(!request.params.id){
186
- reply.statusCode = 400
187
- reply.send({error: 'BAD REQUEST'})
240
+ request.rbac.assertPermission(this.permission.View)
241
+
242
+ const format = request.query.format as 'CSV'|'JSON' || 'JSON'
243
+ const headers = request.query.headers ? request.query.headers.split(",") : []
244
+ const separator = request.query.separator || ";"
245
+ const limit = request.query.limit
246
+ const orderBy = request.query.orderBy
247
+ const order = request.query.order
248
+ const search = request.query.search
249
+
250
+ const year = (new Date().getFullYear()).toString()
251
+ const month = (new Date().getMonth() + 1).toString().padStart(2, '0')
252
+ const exportPath = 'exports'
253
+
254
+ const destinationPath = join(BASE_FILE_DIR, 'exports',year,month)
255
+
256
+ let result: IDraxExportResult = await this.service.export({
257
+ separator,
258
+ format,
259
+ headers,
260
+ limit,
261
+ orderBy,
262
+ order,
263
+ search,
264
+ }, destinationPath)
265
+
266
+ const url = `${BASE_URL}/api/file/${exportPath}/${year}/${month}/${result.fileName}`
267
+
268
+
269
+ return {
270
+ url: url,
271
+ rowCount: result.rowCount,
272
+ time: result.time,
273
+ fileName: result.fileName,
188
274
  }
189
- const id = request.params.id
190
- await this.service.delete(id)
191
- reply.send({message: 'Item deleted successfully'})
275
+
192
276
  } catch (e) {
193
277
  console.error(e)
194
278
  if (e instanceof ValidationError) {
@@ -0,0 +1,74 @@
1
+ import {createDirIfNotExist} from "@drax/common-back";
2
+ import crypto from "crypto";
3
+
4
+
5
+ interface ExportOptions {
6
+ cursor: any
7
+ destinationPath: string
8
+ headers: string[] | string
9
+ }
10
+
11
+ export type {ExportOptions}
12
+
13
+ class AbstractExport {
14
+
15
+ cursor: any
16
+ destinationPath: string
17
+ headers: string[]
18
+
19
+ fileName: string
20
+ relativeFilePath: string
21
+
22
+ constructor(options: ExportOptions) {
23
+ this.cursor = options.cursor;
24
+ this.destinationPath = options.destinationPath;
25
+ this.headers = Array.isArray(options.headers) ? options.headers : options.headers.split(',');
26
+ }
27
+
28
+
29
+ createDirIfNotExist(): void {
30
+ createDirIfNotExist(this.destinationPath);
31
+ }
32
+
33
+ generateFileName(extension:string): string {
34
+ if (!this.fileName) {
35
+ const randomUUID = crypto.randomUUID().toString();
36
+ this.fileName = `export_${randomUUID}.${extension}`;
37
+ }
38
+ return this.fileName;
39
+
40
+ }
41
+
42
+ generateFilePath(extension:string) {
43
+ this.createDirIfNotExist();
44
+ this.generateFileName(extension);
45
+ this.relativeFilePath = `${this.destinationPath}/${this.fileName}`;
46
+ return this.relativeFilePath;
47
+ }
48
+
49
+
50
+ isIterableAsync(value: any) {
51
+ return value != null && typeof value[Symbol.asyncIterator] === 'function';
52
+ }
53
+
54
+ isIterableSync(value: any) {
55
+ return value != null && typeof value[Symbol.iterator] === 'function';
56
+ }
57
+
58
+ getNestedProperty(obj: object, path: string) {
59
+ return path.split('.').reduce((acc, part) => {
60
+ if (Array.isArray(acc)) {
61
+ return acc.map(item => item[part]).join('|');
62
+ }
63
+ return acc && acc[part];
64
+ }, obj);
65
+ }
66
+
67
+ }
68
+
69
+
70
+ export {
71
+ AbstractExport,
72
+ }
73
+
74
+ export default AbstractExport;
@@ -0,0 +1,107 @@
1
+ import * as fs from 'fs';
2
+ import AbstractExport from "./AbstractExport.js";
3
+ import type {ExportOptions} from "./AbstractExport";
4
+
5
+
6
+ interface ExportCsvOptions extends ExportOptions {
7
+ separator: string
8
+ }
9
+
10
+ class ExportCsv extends AbstractExport {
11
+
12
+ separator: string = ';'
13
+
14
+ constructor(options: ExportCsvOptions) {
15
+ super(options)
16
+ this.separator = options.separator ? options.separator : ';';
17
+ }
18
+
19
+ // Método principal para procesar los datos y generar el CSV
20
+ process(): Promise<any> {
21
+ return new Promise(async (resolve, reject) => {
22
+
23
+ try {
24
+
25
+ this.generateFilePath('csv')
26
+ let rowCount = 0
27
+ const start = Date.now();
28
+
29
+ const writableStream = fs.createWriteStream(this.relativeFilePath);
30
+
31
+ writableStream.on('error', reject);
32
+ writableStream.on('finish', () => resolve({
33
+ status: 'success',
34
+ destinationPath: this.destinationPath,
35
+ fileName: this.fileName,
36
+ filePath: this.destinationPath + '/' + this.fileName,
37
+ relativeFilePath: this.relativeFilePath,
38
+ rowCount: rowCount,
39
+ time: Date.now() - start,
40
+ message: 'Export successful',
41
+ }))
42
+
43
+
44
+ const csvHeaders = this.headers.join(this.separator);
45
+ writableStream.write(csvHeaders + '\n');
46
+
47
+ if (this.isIterableAsync(this.cursor)) {
48
+ for await (const record of this.cursor) {
49
+ const csvRow = this.convertRecordToCSVrow(record);
50
+ console.log("csvRow", csvRow);
51
+ writableStream.write(csvRow + '\n');
52
+ rowCount++
53
+ }
54
+ } else if (this.isIterableSync(this.cursor)) {
55
+ // Si es un cursor de SQLite (better-sqlite3), usamos iterate()
56
+ for (const record of this.cursor) {
57
+ const csvRow = this.convertRecordToCSVrow(record);
58
+ writableStream.write(csvRow + '\n');
59
+ rowCount++
60
+ }
61
+ }
62
+ writableStream.end();
63
+ } catch (e) {
64
+ reject(e);
65
+ }
66
+
67
+ })
68
+ }
69
+
70
+
71
+ // Método que convierte un registro en una o más filas de CSV
72
+ convertRecordToCSVrow(record: object): string {
73
+ let fields = []
74
+
75
+ for (const header of this.headers) {
76
+
77
+ let value
78
+ if (header.includes('.')) {
79
+ value = this.getNestedProperty(record, header);
80
+ } else {
81
+ value = record[header];
82
+ }
83
+
84
+ if (value === undefined) {
85
+ fields.push('');
86
+ continue;
87
+ }
88
+
89
+ if (Array.isArray(value)) {
90
+ if (value.length > 0 && typeof value[0] === 'object') {
91
+ fields.push(JSON.stringify(value));
92
+ } else {
93
+ fields.push(value.join(','));
94
+ }
95
+ } else if (typeof value === 'object') {
96
+ fields.push(JSON.stringify(value));
97
+
98
+ } else {
99
+ fields.push(value.toString());
100
+ }
101
+ }
102
+ return fields.join(this.separator);
103
+ }
104
+
105
+ }
106
+
107
+ export default ExportCsv;
@@ -0,0 +1,64 @@
1
+ import * as fs from 'fs';
2
+ import AbstractExport from "./AbstractExport.js";
3
+ import type {ExportOptions} from "./AbstractExport";
4
+
5
+
6
+
7
+
8
+ class ExportJson extends AbstractExport {
9
+
10
+ constructor(options: ExportOptions) {
11
+ super(options)
12
+ }
13
+
14
+ // Método principal para procesar los datos y generar el CSV
15
+ process(): Promise<any> {
16
+ return new Promise(async (resolve, reject) => {
17
+
18
+ try{
19
+ this.generateFilePath('json')
20
+ let rowCount = 0
21
+ const start = Date.now();
22
+
23
+ const writableStream = fs.createWriteStream(this.relativeFilePath);
24
+
25
+ writableStream.on('error', reject);
26
+ writableStream.on('finish', () => resolve({
27
+ status: 'success',
28
+ destinationPath: this.destinationPath,
29
+ fileName: this.fileName,
30
+ filePath: this.destinationPath + '/' + this.fileName,
31
+ relativeFilePath: this.relativeFilePath,
32
+ rowCount: rowCount,
33
+ time: Date.now() - start,
34
+ message: 'Export successful',
35
+ }))
36
+
37
+ writableStream.write('[');
38
+
39
+ if (this.isIterableAsync(this.cursor)) {
40
+ for await (const record of this.cursor) {
41
+ const csvRow = JSON.stringify(record);
42
+ console.log("csvRow", csvRow);
43
+ writableStream.write(csvRow + ',\n');
44
+ rowCount++
45
+ }
46
+ } else if (this.isIterableSync(this.cursor)) {
47
+ for (const record of this.cursor) {
48
+ const csvRow = JSON.stringify(record);
49
+ writableStream.write(csvRow + ',\n');
50
+ rowCount++
51
+ }
52
+ }
53
+ writableStream.write(']');
54
+ writableStream.end();
55
+ }catch (e){
56
+ reject(e);
57
+ }
58
+
59
+ })
60
+ }
61
+
62
+ }
63
+
64
+ export default ExportJson;
package/src/index.ts CHANGED
@@ -4,9 +4,6 @@ import AbstractSqliteRepository from "./repository/AbstractSqliteRepository.js";
4
4
  import AbstractService from "./services/AbstractService.js";
5
5
  import AbstractFastifyController from "./controllers/AbstractFastifyController.js";
6
6
 
7
- import type {ICrudRepository} from "./interfaces/ICrudRepository";
8
-
9
- export type {ICrudRepository}
10
7
 
11
8
  export {
12
9
 
@@ -2,13 +2,11 @@ import "mongoose-paginate-v2";
2
2
  import mongoose from "mongoose";
3
3
  import {MongooseQueryFilter, MongooseSort, MongooseErrorToValidationError} from "@drax/common-back";
4
4
  import type {DeleteResult} from "mongodb";
5
- import type {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/common-share";
5
+ import type {IDraxPaginateOptions, IDraxPaginateResult, IDraxFindOptions, IDraxCrud} from "@drax/crud-share";
6
6
  import type {PaginateModel, PaginateOptions, PaginateResult} from "mongoose";
7
- import type {ICrudRepository} from "../interfaces/ICrudRepository";
8
7
 
9
8
 
10
-
11
- class AbstractMongoRepository<T,C,U> implements ICrudRepository<T,C,U> {
9
+ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
12
10
 
13
11
  _model: mongoose.Model<T> & PaginateModel<T>
14
12
  _searchFields: string[] = []
@@ -76,7 +74,7 @@ class AbstractMongoRepository<T,C,U> implements ICrudRepository<T,C,U> {
76
74
  return items
77
75
  }
78
76
 
79
- async search(value: string, limit : number = 1000): Promise<T[]> {
77
+ async search(value: string, limit: number = 1000): Promise<T[]> {
80
78
  const query = {}
81
79
  query['$or'] = this._searchFields.map(field => ({[field]: new RegExp(value, 'i')}))
82
80
  const items: mongoose.HydratedDocument<T>[] = await this._model.find(query).limit(limit).exec()
@@ -112,6 +110,28 @@ class AbstractMongoRepository<T,C,U> implements ICrudRepository<T,C,U> {
112
110
  items: items.docs
113
111
  }
114
112
  }
113
+
114
+ async find({
115
+ limit = 0,
116
+ orderBy = '',
117
+ order = false,
118
+ search = '',
119
+ filters = []
120
+ }: IDraxFindOptions): Promise<T[]> {
121
+
122
+ const query = {}
123
+
124
+ if (search) {
125
+ query['$or'] = this._searchFields.map(field => ({[field]: new RegExp(search, 'i')}))
126
+ }
127
+
128
+ MongooseQueryFilter.applyFilters(query, filters)
129
+
130
+ const sort = MongooseSort.applySort(orderBy, order)
131
+ const populate = this._populateFields
132
+ const items: T[] = await this._model.find(query).sort(sort).populate(populate)
133
+ return items
134
+ }
115
135
  }
116
136
 
117
137
  export default AbstractMongoRepository
@@ -1,5 +1,5 @@
1
1
  import sqlite from "better-sqlite3";
2
- import type {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/common-share";
2
+ import type {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/crud-share";
3
3
  import {randomUUID} from "node:crypto";
4
4
  import {
5
5
  SqlSort, SqlQueryFilter, SqliteTableBuilder, SqliteTableField,