@angelps/prisma-query-builder 0.1.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.
- package/README.md +709 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/valibot.adapter.d.ts +14 -0
- package/dist/adapters/valibot.adapter.d.ts.map +1 -0
- package/dist/adapters/valibot.adapter.js +21 -0
- package/dist/adapters/valibot.adapter.js.map +1 -0
- package/dist/adapters/zod.adapter.d.ts +29 -0
- package/dist/adapters/zod.adapter.d.ts.map +1 -0
- package/dist/adapters/zod.adapter.js +42 -0
- package/dist/adapters/zod.adapter.js.map +1 -0
- package/dist/controllers/generic.controller.d.ts +79 -0
- package/dist/controllers/generic.controller.d.ts.map +1 -0
- package/dist/controllers/generic.controller.js +279 -0
- package/dist/controllers/generic.controller.js.map +1 -0
- package/dist/controllers/index.d.ts +2 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/index.js +2 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/errors/app-errors.d.ts +17 -0
- package/dist/errors/app-errors.d.ts.map +1 -0
- package/dist/errors/app-errors.js +28 -0
- package/dist/errors/app-errors.js.map +1 -0
- package/dist/errors/error-messages.d.ts +80 -0
- package/dist/errors/error-messages.d.ts.map +1 -0
- package/dist/errors/error-messages.js +75 -0
- package/dist/errors/error-messages.js.map +1 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/query-builder/helpers/order-by.helper.d.ts +4 -0
- package/dist/query-builder/helpers/order-by.helper.d.ts.map +1 -0
- package/dist/query-builder/helpers/order-by.helper.js +24 -0
- package/dist/query-builder/helpers/order-by.helper.js.map +1 -0
- package/dist/query-builder/helpers/pagination.helper.d.ts +6 -0
- package/dist/query-builder/helpers/pagination.helper.d.ts.map +1 -0
- package/dist/query-builder/helpers/pagination.helper.js +9 -0
- package/dist/query-builder/helpers/pagination.helper.js.map +1 -0
- package/dist/query-builder/helpers/where.helper.d.ts +5 -0
- package/dist/query-builder/helpers/where.helper.d.ts.map +1 -0
- package/dist/query-builder/helpers/where.helper.js +356 -0
- package/dist/query-builder/helpers/where.helper.js.map +1 -0
- package/dist/query-builder/index.d.ts +3 -0
- package/dist/query-builder/index.d.ts.map +1 -0
- package/dist/query-builder/index.js +3 -0
- package/dist/query-builder/index.js.map +1 -0
- package/dist/query-builder/query-builder.d.ts +56 -0
- package/dist/query-builder/query-builder.d.ts.map +1 -0
- package/dist/query-builder/query-builder.js +149 -0
- package/dist/query-builder/query-builder.js.map +1 -0
- package/dist/query-builder/query-builder.types.d.ts +83 -0
- package/dist/query-builder/query-builder.types.d.ts.map +1 -0
- package/dist/query-builder/query-builder.types.js +2 -0
- package/dist/query-builder/query-builder.types.js.map +1 -0
- package/dist/repositories/crud.repository.prisma.d.ts +44 -0
- package/dist/repositories/crud.repository.prisma.d.ts.map +1 -0
- package/dist/repositories/crud.repository.prisma.js +246 -0
- package/dist/repositories/crud.repository.prisma.js.map +1 -0
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.d.ts.map +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/repositories/index.js.map +1 -0
- package/dist/services/audit.service.d.ts +32 -0
- package/dist/services/audit.service.d.ts.map +1 -0
- package/dist/services/audit.service.js +57 -0
- package/dist/services/audit.service.js.map +1 -0
- package/dist/services/error-handler.service.d.ts +34 -0
- package/dist/services/error-handler.service.d.ts.map +1 -0
- package/dist/services/error-handler.service.js +159 -0
- package/dist/services/error-handler.service.js.map +1 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -0
- package/dist/types/crud-config.d.ts +20 -0
- package/dist/types/crud-config.d.ts.map +1 -0
- package/dist/types/crud-config.js +2 -0
- package/dist/types/crud-config.js.map +1 -0
- package/dist/types/generic-repository.d.ts +36 -0
- package/dist/types/generic-repository.d.ts.map +1 -0
- package/dist/types/generic-repository.js +2 -0
- package/dist/types/generic-repository.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/logger.d.ts +17 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logger.js +8 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/types/pagination.d.ts +11 -0
- package/dist/types/pagination.d.ts.map +1 -0
- package/dist/types/pagination.js +2 -0
- package/dist/types/pagination.js.map +1 -0
- package/dist/types/prisma-delegate.types.d.ts +36 -0
- package/dist/types/prisma-delegate.types.d.ts.map +1 -0
- package/dist/types/prisma-delegate.types.js +15 -0
- package/dist/types/prisma-delegate.types.js.map +1 -0
- package/dist/types/query-params.d.ts +40 -0
- package/dist/types/query-params.d.ts.map +1 -0
- package/dist/types/query-params.js +2 -0
- package/dist/types/query-params.js.map +1 -0
- package/dist/types/schema-adapter.d.ts +19 -0
- package/dist/types/schema-adapter.d.ts.map +1 -0
- package/dist/types/schema-adapter.js +2 -0
- package/dist/types/schema-adapter.js.map +1 -0
- package/dist/utils/date.utils.d.ts +13 -0
- package/dist/utils/date.utils.d.ts.map +1 -0
- package/dist/utils/date.utils.js +75 -0
- package/dist/utils/date.utils.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/phone.utils.d.ts +7 -0
- package/dist/utils/phone.utils.d.ts.map +1 -0
- package/dist/utils/phone.utils.js +15 -0
- package/dist/utils/phone.utils.js.map +1 -0
- package/dist/utils/response.utils.d.ts +5 -0
- package/dist/utils/response.utils.d.ts.map +1 -0
- package/dist/utils/response.utils.js +13 -0
- package/dist/utils/response.utils.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export interface ErrorResponse {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
message: string;
|
|
4
|
+
details?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const ErrorMessages: {
|
|
7
|
+
readonly PERMISSION_DENIED: {
|
|
8
|
+
readonly message: "Permiso denegado";
|
|
9
|
+
readonly statusCode: 403;
|
|
10
|
+
};
|
|
11
|
+
readonly INVALID_CREDENTIALS: {
|
|
12
|
+
readonly message: "Credenciales inválidas";
|
|
13
|
+
readonly statusCode: 401;
|
|
14
|
+
};
|
|
15
|
+
readonly UNAUTHORIZED_ACCESS: {
|
|
16
|
+
readonly message: "Acceso no autorizado";
|
|
17
|
+
readonly statusCode: 401;
|
|
18
|
+
};
|
|
19
|
+
readonly FORBIDDEN_ACTION: {
|
|
20
|
+
readonly message: "Acción prohibida";
|
|
21
|
+
readonly statusCode: 403;
|
|
22
|
+
};
|
|
23
|
+
readonly RESOURCE_ALREADY_EXISTS: {
|
|
24
|
+
readonly message: "El recurso ya existe";
|
|
25
|
+
readonly statusCode: 409;
|
|
26
|
+
};
|
|
27
|
+
readonly INTERNAL_SERVER_ERROR: {
|
|
28
|
+
readonly message: "Ha ocurrido un error desconocido";
|
|
29
|
+
readonly statusCode: 500;
|
|
30
|
+
};
|
|
31
|
+
readonly BAD_REQUEST: {
|
|
32
|
+
readonly message: "Solicitud incorrecta";
|
|
33
|
+
readonly statusCode: 400;
|
|
34
|
+
};
|
|
35
|
+
readonly RESOURCE_NOT_FOUND: {
|
|
36
|
+
readonly message: "Recurso no encontrado";
|
|
37
|
+
readonly statusCode: 404;
|
|
38
|
+
};
|
|
39
|
+
readonly USER_NOT_FOUND: {
|
|
40
|
+
readonly message: "Usuario no encontrado";
|
|
41
|
+
readonly statusCode: 404;
|
|
42
|
+
};
|
|
43
|
+
readonly CONFLICT_ERROR: {
|
|
44
|
+
readonly message: "Error de conflicto";
|
|
45
|
+
readonly statusCode: 409;
|
|
46
|
+
};
|
|
47
|
+
readonly VALIDATION_ERROR: {
|
|
48
|
+
readonly message: "Error de validación";
|
|
49
|
+
readonly statusCode: 422;
|
|
50
|
+
};
|
|
51
|
+
readonly DIFFERENT_PASSWORDS: {
|
|
52
|
+
readonly message: "Las contraseñas no coinciden";
|
|
53
|
+
readonly statusCode: 400;
|
|
54
|
+
};
|
|
55
|
+
readonly MISSING_FIELDS: {
|
|
56
|
+
readonly message: "Campos faltantes";
|
|
57
|
+
readonly statusCode: 400;
|
|
58
|
+
};
|
|
59
|
+
readonly INVALID_QUERY_PARAMS: {
|
|
60
|
+
readonly message: "Parámetros de consulta inválidos";
|
|
61
|
+
readonly statusCode: 400;
|
|
62
|
+
};
|
|
63
|
+
readonly INVALID_SCHEMA: {
|
|
64
|
+
readonly message: "Esquema inválido";
|
|
65
|
+
readonly statusCode: 500;
|
|
66
|
+
};
|
|
67
|
+
readonly INVALID_DATE: {
|
|
68
|
+
readonly message: "Fecha inválida";
|
|
69
|
+
readonly statusCode: 400;
|
|
70
|
+
};
|
|
71
|
+
readonly INVALID_TOKEN: {
|
|
72
|
+
readonly message: "El token ha expirado o es inválido";
|
|
73
|
+
readonly statusCode: 400;
|
|
74
|
+
};
|
|
75
|
+
readonly USER_NOT_ACTIVE: {
|
|
76
|
+
readonly message: "El usuario no está activo o ha expirado";
|
|
77
|
+
readonly statusCode: 403;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=error-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-messages.d.ts","sourceRoot":"","sources":["../../src/errors/error-messages.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyEhB,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const ErrorMessages = {
|
|
2
|
+
PERMISSION_DENIED: {
|
|
3
|
+
message: 'Permiso denegado',
|
|
4
|
+
statusCode: 403,
|
|
5
|
+
},
|
|
6
|
+
INVALID_CREDENTIALS: {
|
|
7
|
+
message: 'Credenciales inválidas',
|
|
8
|
+
statusCode: 401,
|
|
9
|
+
},
|
|
10
|
+
UNAUTHORIZED_ACCESS: {
|
|
11
|
+
message: 'Acceso no autorizado',
|
|
12
|
+
statusCode: 401,
|
|
13
|
+
},
|
|
14
|
+
FORBIDDEN_ACTION: {
|
|
15
|
+
message: 'Acción prohibida',
|
|
16
|
+
statusCode: 403,
|
|
17
|
+
},
|
|
18
|
+
RESOURCE_ALREADY_EXISTS: {
|
|
19
|
+
message: 'El recurso ya existe',
|
|
20
|
+
statusCode: 409,
|
|
21
|
+
},
|
|
22
|
+
INTERNAL_SERVER_ERROR: {
|
|
23
|
+
message: 'Ha ocurrido un error desconocido',
|
|
24
|
+
statusCode: 500,
|
|
25
|
+
},
|
|
26
|
+
BAD_REQUEST: {
|
|
27
|
+
message: 'Solicitud incorrecta',
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
},
|
|
30
|
+
RESOURCE_NOT_FOUND: {
|
|
31
|
+
message: 'Recurso no encontrado',
|
|
32
|
+
statusCode: 404,
|
|
33
|
+
},
|
|
34
|
+
USER_NOT_FOUND: {
|
|
35
|
+
message: 'Usuario no encontrado',
|
|
36
|
+
statusCode: 404,
|
|
37
|
+
},
|
|
38
|
+
CONFLICT_ERROR: {
|
|
39
|
+
message: 'Error de conflicto',
|
|
40
|
+
statusCode: 409,
|
|
41
|
+
},
|
|
42
|
+
VALIDATION_ERROR: {
|
|
43
|
+
message: 'Error de validación',
|
|
44
|
+
statusCode: 422,
|
|
45
|
+
},
|
|
46
|
+
DIFFERENT_PASSWORDS: {
|
|
47
|
+
message: 'Las contraseñas no coinciden',
|
|
48
|
+
statusCode: 400,
|
|
49
|
+
},
|
|
50
|
+
MISSING_FIELDS: {
|
|
51
|
+
message: 'Campos faltantes',
|
|
52
|
+
statusCode: 400,
|
|
53
|
+
},
|
|
54
|
+
INVALID_QUERY_PARAMS: {
|
|
55
|
+
message: 'Parámetros de consulta inválidos',
|
|
56
|
+
statusCode: 400,
|
|
57
|
+
},
|
|
58
|
+
INVALID_SCHEMA: {
|
|
59
|
+
message: 'Esquema inválido',
|
|
60
|
+
statusCode: 500,
|
|
61
|
+
},
|
|
62
|
+
INVALID_DATE: {
|
|
63
|
+
message: 'Fecha inválida',
|
|
64
|
+
statusCode: 400,
|
|
65
|
+
},
|
|
66
|
+
INVALID_TOKEN: {
|
|
67
|
+
message: 'El token ha expirado o es inválido',
|
|
68
|
+
statusCode: 400,
|
|
69
|
+
},
|
|
70
|
+
USER_NOT_ACTIVE: {
|
|
71
|
+
message: 'El usuario no está activo o ha expirado',
|
|
72
|
+
statusCode: 403,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=error-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-messages.js","sourceRoot":"","sources":["../../src/errors/error-messages.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,iBAAiB,EAAE;QACjB,OAAO,EAAE,kBAAkB;QAC3B,UAAU,EAAE,GAAG;KAChB;IACD,mBAAmB,EAAE;QACnB,OAAO,EAAE,wBAAwB;QACjC,UAAU,EAAE,GAAG;KAChB;IACD,mBAAmB,EAAE;QACnB,OAAO,EAAE,sBAAsB;QAC/B,UAAU,EAAE,GAAG;KAChB;IACD,gBAAgB,EAAE;QAChB,OAAO,EAAE,kBAAkB;QAC3B,UAAU,EAAE,GAAG;KAChB;IACD,uBAAuB,EAAE;QACvB,OAAO,EAAE,sBAAsB;QAC/B,UAAU,EAAE,GAAG;KAChB;IACD,qBAAqB,EAAE;QACrB,OAAO,EAAE,kCAAkC;QAC3C,UAAU,EAAE,GAAG;KAChB;IACD,WAAW,EAAE;QACX,OAAO,EAAE,sBAAsB;QAC/B,UAAU,EAAE,GAAG;KAChB;IACD,kBAAkB,EAAE;QAClB,OAAO,EAAE,uBAAuB;QAChC,UAAU,EAAE,GAAG;KAChB;IACD,cAAc,EAAE;QACd,OAAO,EAAE,uBAAuB;QAChC,UAAU,EAAE,GAAG;KAChB;IACD,cAAc,EAAE;QACd,OAAO,EAAE,oBAAoB;QAC7B,UAAU,EAAE,GAAG;KAChB;IACD,gBAAgB,EAAE;QAChB,OAAO,EAAE,qBAAqB;QAC9B,UAAU,EAAE,GAAG;KAChB;IACD,mBAAmB,EAAE;QACnB,OAAO,EAAE,8BAA8B;QACvC,UAAU,EAAE,GAAG;KAChB;IACD,cAAc,EAAE;QACd,OAAO,EAAE,kBAAkB;QAC3B,UAAU,EAAE,GAAG;KAChB;IACD,oBAAoB,EAAE;QACpB,OAAO,EAAE,kCAAkC;QAC3C,UAAU,EAAE,GAAG;KAChB;IACD,cAAc,EAAE;QACd,OAAO,EAAE,kBAAkB;QAC3B,UAAU,EAAE,GAAG;KAChB;IACD,YAAY,EAAE;QACZ,OAAO,EAAE,gBAAgB;QACzB,UAAU,EAAE,GAAG;KAChB;IACD,aAAa,EAAE;QACb,OAAO,EAAE,oCAAoC;QAC7C,UAAU,EAAE,GAAG;KAChB;IACD,eAAe,EAAE;QACf,OAAO,EAAE,yCAAyC;QAClD,UAAU,EAAE,GAAG;KAChB;CACO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './types/index.js';
|
|
2
|
+
export * from './errors/index.js';
|
|
3
|
+
export * from './utils/index.js';
|
|
4
|
+
export * from './services/index.js';
|
|
5
|
+
export * from './query-builder/index.js';
|
|
6
|
+
export * from './repositories/index.js';
|
|
7
|
+
export * from './controllers/index.js';
|
|
8
|
+
export * from './adapters/index.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './types/index.js';
|
|
2
|
+
export * from './errors/index.js';
|
|
3
|
+
export * from './utils/index.js';
|
|
4
|
+
export * from './services/index.js';
|
|
5
|
+
export * from './query-builder/index.js';
|
|
6
|
+
export * from './repositories/index.js';
|
|
7
|
+
export * from './controllers/index.js';
|
|
8
|
+
export * from './adapters/index.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CrudConfig } from '../../types/crud-config.js';
|
|
2
|
+
import type { QueryBuilderOptions } from '../query-builder.types.js';
|
|
3
|
+
export declare function buildOrderByClause<TResult, TOrderBy>(sort: string | undefined, options: QueryBuilderOptions<TResult>, config: CrudConfig<any>): TOrderBy[] | undefined;
|
|
4
|
+
//# sourceMappingURL=order-by.helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-by.helper.d.ts","sourceRoot":"","sources":["../../../src/query-builder/helpers/order-by.helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAIrE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,QAAQ,EAClD,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,EACrC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,GACtB,QAAQ,EAAE,GAAG,SAAS,CAyBxB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BadRequestError } from '../../errors/app-errors.js';
|
|
2
|
+
export function buildOrderByClause(sort, options, config) {
|
|
3
|
+
const { sortableFields = [], omitFields = [], orderBy = [] } = options;
|
|
4
|
+
const defaultOrderBy = config.defaultOrderBy ?? orderBy;
|
|
5
|
+
if (!sort || typeof sort !== 'string' || sort.trim() === '') {
|
|
6
|
+
if (defaultOrderBy.length === 0)
|
|
7
|
+
return undefined;
|
|
8
|
+
return defaultOrderBy.map((o) => ({ [o.field]: o.direction }));
|
|
9
|
+
}
|
|
10
|
+
const availableSortFields = sortableFields.filter((f) => !omitFields.includes(f));
|
|
11
|
+
return sort
|
|
12
|
+
.split(',')
|
|
13
|
+
.map((s) => s.trim())
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((p) => {
|
|
16
|
+
const desc = p.startsWith('-');
|
|
17
|
+
const field = desc ? p.slice(1) : p;
|
|
18
|
+
if (availableSortFields.length > 0 && !availableSortFields.includes(field)) {
|
|
19
|
+
throw new BadRequestError(`El campo "${field}" no es un campo de ordenamiento válido`);
|
|
20
|
+
}
|
|
21
|
+
return { [field]: desc ? 'desc' : 'asc' };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=order-by.helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-by.helper.js","sourceRoot":"","sources":["../../../src/query-builder/helpers/order-by.helper.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,MAAM,UAAU,kBAAkB,CAChC,IAAwB,EACxB,OAAqC,EACrC,MAAuB;IAEvB,MAAM,EAAE,cAAc,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IACvE,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,OAAO,CAAC;IAExD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC5D,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAClD,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,CAAwB,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,mBAAmB,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAElF,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpC,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAsB,CAAC,EAAE,CAAC;YAC5F,MAAM,IAAI,eAAe,CAAC,aAAa,KAAK,yCAAyC,CAAC,CAAC;QACzF,CAAC;QAED,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAyB,CAAC;IACnE,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.helper.d.ts","sourceRoot":"","sources":["../../../src/query-builder/helpers/pagination.helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,wBAAgB,eAAe,CAC7B,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQhC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function parsePagination(queryParams, pageDefault, sizeDefault) {
|
|
2
|
+
const rawPage = queryParams.page ? parseInt(String(queryParams.page), 10) : pageDefault;
|
|
3
|
+
const rawSize = queryParams.size ? parseInt(String(queryParams.size), 10) : sizeDefault;
|
|
4
|
+
return {
|
|
5
|
+
page: Number.isFinite(rawPage) && rawPage > 0 ? rawPage : pageDefault,
|
|
6
|
+
size: Number.isFinite(rawSize) && rawSize > 0 ? rawSize : sizeDefault,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=pagination.helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.helper.js","sourceRoot":"","sources":["../../../src/query-builder/helpers/pagination.helper.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,WAAwB,EACxB,WAAmB,EACnB,WAAmB;IAEnB,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IACxF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IAExF,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW;QACrE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW;KACtE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { QueryParams } from '../../types/query-params.js';
|
|
2
|
+
import type { CrudConfig } from '../../types/crud-config.js';
|
|
3
|
+
import type { QueryBuilderOptions } from '../query-builder.types.js';
|
|
4
|
+
export declare function buildWhereClause<TResult, TWhere>(params: QueryParams, options: QueryBuilderOptions<TResult>, config: CrudConfig<any>, additionalWhere?: Record<string, unknown>): TWhere;
|
|
5
|
+
//# sourceMappingURL=where.helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"where.helper.d.ts","sourceRoot":"","sources":["../../../src/query-builder/helpers/where.helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAkMrE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAC9C,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,EACrC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EACvB,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAC5C,MAAM,CA6LR"}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { startDateOfDay, endDateOfDay } from '../../utils/date.utils.js';
|
|
2
|
+
import { defaultLogger } from '../../types/logger.js';
|
|
3
|
+
import { BadRequestError } from '../../errors/app-errors.js';
|
|
4
|
+
// ─── Operator suffix mapping ──────────────────────────────────────────────
|
|
5
|
+
const OPERATOR_SUFFIXES = {
|
|
6
|
+
_gte: 'gte',
|
|
7
|
+
_lte: 'lte',
|
|
8
|
+
_gt: 'gt',
|
|
9
|
+
_lt: 'lt',
|
|
10
|
+
_contains: 'contains',
|
|
11
|
+
_startsWith: 'startsWith',
|
|
12
|
+
_in: 'in',
|
|
13
|
+
_not: 'not',
|
|
14
|
+
};
|
|
15
|
+
const SUFFIX_KEYS = Object.keys(OPERATOR_SUFFIXES).sort((a, b) => b.length - a.length);
|
|
16
|
+
/**
|
|
17
|
+
* Try to split a param key like "price_gte" into { field: "price", operator: "gte" }.
|
|
18
|
+
* Returns null when no known suffix matches.
|
|
19
|
+
*/
|
|
20
|
+
function parseOperatorSuffix(key) {
|
|
21
|
+
for (const suffix of SUFFIX_KEYS) {
|
|
22
|
+
if (key.endsWith(suffix)) {
|
|
23
|
+
return {
|
|
24
|
+
field: key.slice(0, -suffix.length),
|
|
25
|
+
operator: OPERATOR_SUFFIXES[suffix],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
// ─── Nested / relational filter helpers ───────────────────────────────────
|
|
32
|
+
/**
|
|
33
|
+
* Build a nested Prisma where object from a dot-notated key.
|
|
34
|
+
*
|
|
35
|
+
* `"orders.items.status"` → `{ orders: { some: { items: { some: { status: value } } } } }`
|
|
36
|
+
*/
|
|
37
|
+
function buildNestedWhere(dotPath, value) {
|
|
38
|
+
const parts = dotPath.split('.');
|
|
39
|
+
let current = {};
|
|
40
|
+
const root = current;
|
|
41
|
+
for (let i = 0; i < parts.length; i++) {
|
|
42
|
+
const part = parts[i];
|
|
43
|
+
if (i === parts.length - 1) {
|
|
44
|
+
// leaf — assign the value
|
|
45
|
+
current[part] = value;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// intermediate relation — wrap in `some`
|
|
49
|
+
const nested = {};
|
|
50
|
+
current[part] = { some: nested };
|
|
51
|
+
current = nested;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
// ─── OR compound helpers ──────────────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Parse `or[0][field]=value` style query params into an array of condition objects.
|
|
59
|
+
*
|
|
60
|
+
* Example input (from Express query string):
|
|
61
|
+
* ```
|
|
62
|
+
* { 'or[0][status]': 'ACTIVE', 'or[0][role]': 'ADMIN', 'or[1][status]': 'PENDING' }
|
|
63
|
+
* ```
|
|
64
|
+
* Output: `[{ status: 'ACTIVE', role: 'ADMIN' }, { status: 'PENDING' }]`
|
|
65
|
+
*/
|
|
66
|
+
function parseOrConditions(params) {
|
|
67
|
+
const orPattern = /^or\[(\d+)]\[(.+)]$/;
|
|
68
|
+
const groups = new Map();
|
|
69
|
+
for (const [key, value] of Object.entries(params)) {
|
|
70
|
+
const match = key.match(orPattern);
|
|
71
|
+
if (!match)
|
|
72
|
+
continue;
|
|
73
|
+
const index = parseInt(match[1], 10);
|
|
74
|
+
const field = match[2];
|
|
75
|
+
if (!groups.has(index))
|
|
76
|
+
groups.set(index, {});
|
|
77
|
+
groups.get(index)[field] = castValue(value);
|
|
78
|
+
}
|
|
79
|
+
if (groups.size === 0)
|
|
80
|
+
return null;
|
|
81
|
+
// Sort by index for deterministic output
|
|
82
|
+
return [...groups.entries()]
|
|
83
|
+
.sort(([a], [b]) => a - b)
|
|
84
|
+
.map(([, condition]) => condition);
|
|
85
|
+
}
|
|
86
|
+
// ─── Value casting ────────────────────────────────────────────────────────
|
|
87
|
+
/** Auto-cast a raw query-string value to its most specific JS type. */
|
|
88
|
+
function castValue(value) {
|
|
89
|
+
if (value === undefined || value === '')
|
|
90
|
+
return value;
|
|
91
|
+
// null / not-null literals
|
|
92
|
+
if (value === 'null')
|
|
93
|
+
return null;
|
|
94
|
+
if (value === '!null')
|
|
95
|
+
return { not: null };
|
|
96
|
+
// booleans
|
|
97
|
+
if (value === 'true')
|
|
98
|
+
return true;
|
|
99
|
+
if (value === 'false')
|
|
100
|
+
return false;
|
|
101
|
+
// numeric
|
|
102
|
+
const numValue = Number(value);
|
|
103
|
+
if (!Number.isNaN(numValue) && String(numValue) === String(value)) {
|
|
104
|
+
return numValue;
|
|
105
|
+
}
|
|
106
|
+
// CSV → { in: [...] }
|
|
107
|
+
if (typeof value === 'string' && value.includes(',')) {
|
|
108
|
+
const values = value.split(',').map((v) => v.trim());
|
|
109
|
+
const numericValues = values.map((v) => Number(v));
|
|
110
|
+
const allNumeric = numericValues.every((n, i) => !Number.isNaN(n) && String(n) === values[i]);
|
|
111
|
+
return { in: allNumeric ? numericValues : values };
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
// ─── Runtime query-param validation ──────────────────────────────────────
|
|
116
|
+
/** Params that are part of the query contract and should never be treated as filters. */
|
|
117
|
+
const RESERVED_PARAMS = new Set([
|
|
118
|
+
'page', 'size', 'sort', 'search', 'from', 'to',
|
|
119
|
+
'notLike', 'greaterThan', 'lessThan', 'cursor', 'fields',
|
|
120
|
+
]);
|
|
121
|
+
/**
|
|
122
|
+
* Throw a 400 if any query param is not a recognised filter.
|
|
123
|
+
* Only runs when `filterableFields` is explicitly configured.
|
|
124
|
+
*/
|
|
125
|
+
function validateQueryParams(params, availableFieldStrings, filterableFields) {
|
|
126
|
+
if (filterableFields.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
for (const paramKey of Object.keys(params)) {
|
|
129
|
+
if (RESERVED_PARAMS.has(paramKey))
|
|
130
|
+
continue;
|
|
131
|
+
if (paramKey.startsWith('or['))
|
|
132
|
+
continue;
|
|
133
|
+
if (paramKey.includes('.'))
|
|
134
|
+
continue; // nested relations are always allowed
|
|
135
|
+
if (availableFieldStrings.has(paramKey))
|
|
136
|
+
continue;
|
|
137
|
+
const parsed = parseOperatorSuffix(paramKey);
|
|
138
|
+
if (parsed && availableFieldStrings.has(parsed.field))
|
|
139
|
+
continue;
|
|
140
|
+
throw new BadRequestError(`El parámetro de filtro "${paramKey}" no es válido`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ─── Dev-time field validation ────────────────────────────────────────────
|
|
144
|
+
function validateFieldArrays(options, logger) {
|
|
145
|
+
if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] === 'production')
|
|
146
|
+
return;
|
|
147
|
+
const fieldArrays = [
|
|
148
|
+
['filterableFields', options.filterableFields],
|
|
149
|
+
['likeFields', options.likeFields],
|
|
150
|
+
['notLikeFields', options.notLikeFields],
|
|
151
|
+
['sortableFields', options.sortableFields],
|
|
152
|
+
['searchDateFields', options.searchDateFields],
|
|
153
|
+
['operationFields', options.operationFields],
|
|
154
|
+
['omitFields', options.omitFields],
|
|
155
|
+
];
|
|
156
|
+
for (const [name, fields] of fieldArrays) {
|
|
157
|
+
if (!fields)
|
|
158
|
+
continue;
|
|
159
|
+
for (const field of fields) {
|
|
160
|
+
if (typeof field !== 'string' || field.trim() === '') {
|
|
161
|
+
logger.warn(`[PrismaQueryBuilder] ${name} contains an invalid entry: ${JSON.stringify(field)}. ` +
|
|
162
|
+
'Expected a non-empty string matching a model field.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ─── Main builder ─────────────────────────────────────────────────────────
|
|
168
|
+
export function buildWhereClause(params, options, config, additionalWhere = {}) {
|
|
169
|
+
const logger = config.logger ?? defaultLogger;
|
|
170
|
+
const where = { ...additionalWhere };
|
|
171
|
+
// Dev-time validation (mejora #9)
|
|
172
|
+
validateFieldArrays(options, logger);
|
|
173
|
+
// Soft delete
|
|
174
|
+
const softDeleteField = config.softDeleteField ?? 'deletedAt';
|
|
175
|
+
const excludeSoftDeleted = config.excludeSoftDeleted ?? true;
|
|
176
|
+
if (excludeSoftDeleted && softDeleteField) {
|
|
177
|
+
where[softDeleteField] = null;
|
|
178
|
+
}
|
|
179
|
+
const { filterableFields = [], likeFields = [], notLikeFields = [], searchDateFields = [], operationFields = [], omitFields = [], } = options;
|
|
180
|
+
const availableFields = filterableFields.filter((f) => !omitFields.includes(f));
|
|
181
|
+
const availableFieldStrings = new Set(availableFields.map(String));
|
|
182
|
+
validateQueryParams(params, availableFieldStrings, filterableFields);
|
|
183
|
+
// ── Exact filters on filterable fields ────────────────────────────────
|
|
184
|
+
for (const field of availableFields) {
|
|
185
|
+
const value = params[field];
|
|
186
|
+
if (value === undefined || value === '')
|
|
187
|
+
continue;
|
|
188
|
+
const casted = castValue(value);
|
|
189
|
+
where[field] = casted;
|
|
190
|
+
}
|
|
191
|
+
// ── Operator-suffix filters (mejora #2): price_gte=100, name_contains=shirt ──
|
|
192
|
+
for (const [paramKey, rawValue] of Object.entries(params)) {
|
|
193
|
+
if (rawValue === undefined || rawValue === '')
|
|
194
|
+
continue;
|
|
195
|
+
const parsed = parseOperatorSuffix(paramKey);
|
|
196
|
+
if (!parsed)
|
|
197
|
+
continue;
|
|
198
|
+
const { field, operator } = parsed;
|
|
199
|
+
// Only apply to filterable fields
|
|
200
|
+
if (!availableFieldStrings.has(field))
|
|
201
|
+
continue;
|
|
202
|
+
// Build the Prisma operator value
|
|
203
|
+
let operatorValue;
|
|
204
|
+
if (operator === 'in') {
|
|
205
|
+
// _in expects CSV → array
|
|
206
|
+
const values = String(rawValue).split(',').map((v) => v.trim());
|
|
207
|
+
const numericValues = values.map((v) => Number(v));
|
|
208
|
+
const allNumeric = numericValues.every((n, i) => !Number.isNaN(n) && String(n) === values[i]);
|
|
209
|
+
operatorValue = allNumeric ? numericValues : values;
|
|
210
|
+
}
|
|
211
|
+
else if (operator === 'not') {
|
|
212
|
+
operatorValue = castValue(rawValue);
|
|
213
|
+
}
|
|
214
|
+
else if (['gte', 'lte', 'gt', 'lt'].includes(operator)) {
|
|
215
|
+
const num = Number(rawValue);
|
|
216
|
+
operatorValue = Number.isNaN(num) ? rawValue : num;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// contains, startsWith — string operators
|
|
220
|
+
operatorValue = String(rawValue);
|
|
221
|
+
}
|
|
222
|
+
// Merge onto potentially existing operator object
|
|
223
|
+
if (operator === 'contains' || operator === 'startsWith') {
|
|
224
|
+
where[field] = {
|
|
225
|
+
...(typeof where[field] === 'object' && where[field] !== null ? where[field] : {}),
|
|
226
|
+
[operator]: operatorValue,
|
|
227
|
+
mode: 'insensitive',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
where[field] = {
|
|
232
|
+
...(typeof where[field] === 'object' && where[field] !== null ? where[field] : {}),
|
|
233
|
+
[operator]: operatorValue,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// ── Nested / relational filters (mejora #1): orders.status=PENDING ────
|
|
238
|
+
for (const [paramKey, rawValue] of Object.entries(params)) {
|
|
239
|
+
if (rawValue === undefined || rawValue === '')
|
|
240
|
+
continue;
|
|
241
|
+
if (!paramKey.includes('.'))
|
|
242
|
+
continue;
|
|
243
|
+
// Don't process reserved params or OR conditions
|
|
244
|
+
if (paramKey.startsWith('or['))
|
|
245
|
+
continue;
|
|
246
|
+
const casted = castValue(rawValue);
|
|
247
|
+
const nested = buildNestedWhere(paramKey, casted);
|
|
248
|
+
// Deep-merge the nested object into where
|
|
249
|
+
deepMerge(where, nested);
|
|
250
|
+
}
|
|
251
|
+
// ── OR compound conditions (mejora #4): or[0][status]=ACTIVE ──────────
|
|
252
|
+
const orConditions = parseOrConditions(params);
|
|
253
|
+
if (orConditions && orConditions.length > 0) {
|
|
254
|
+
// If there's already an OR from search, we need to merge them with AND
|
|
255
|
+
if (where.OR) {
|
|
256
|
+
where.AND = where.AND ?? [];
|
|
257
|
+
where.AND.push({ OR: where.OR });
|
|
258
|
+
where.AND.push({ OR: orConditions });
|
|
259
|
+
delete where.OR;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
where.OR = orConditions;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// ── LIKE search (OR between multiple fields) ──────────────────────────
|
|
266
|
+
const searchValue = params.search;
|
|
267
|
+
if (searchValue !== undefined && searchValue !== '') {
|
|
268
|
+
const availableLikeFields = likeFields.filter((f) => !omitFields.includes(f));
|
|
269
|
+
if (availableLikeFields.length > 0) {
|
|
270
|
+
const searchOR = availableLikeFields.map((field) => ({
|
|
271
|
+
[field]: { contains: String(searchValue), mode: 'insensitive' },
|
|
272
|
+
}));
|
|
273
|
+
if (where.OR) {
|
|
274
|
+
// Existing OR (from compound or other) — wrap both in AND
|
|
275
|
+
where.AND = where.AND ?? [];
|
|
276
|
+
where.AND.push({ OR: where.OR });
|
|
277
|
+
where.AND.push({ OR: searchOR });
|
|
278
|
+
delete where.OR;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
where.OR = searchOR;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ── NOT LIKE filter (AND between multiple values) ─────────────────────
|
|
286
|
+
const notLike = params.notLike;
|
|
287
|
+
if (notLike !== undefined && notLike !== '') {
|
|
288
|
+
const notLikeArray = String(notLike).includes(',')
|
|
289
|
+
? String(notLike).split(',').map((s) => s.trim())
|
|
290
|
+
: [String(notLike).trim()];
|
|
291
|
+
const availableNotLikeFields = notLikeFields.filter((f) => !omitFields.includes(f));
|
|
292
|
+
if (availableNotLikeFields.length > 0) {
|
|
293
|
+
where.AND = where.AND ?? [];
|
|
294
|
+
for (const field of availableNotLikeFields) {
|
|
295
|
+
for (const term of notLikeArray) {
|
|
296
|
+
where.AND.push({
|
|
297
|
+
[field]: { not: { contains: term, mode: 'insensitive' } },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ── Date range filter ─────────────────────────────────────────────────
|
|
304
|
+
const { from, to } = params;
|
|
305
|
+
if ((from || to) && searchDateFields.length > 0) {
|
|
306
|
+
const availableDateFields = searchDateFields.filter((f) => !omitFields.includes(f));
|
|
307
|
+
where.AND = where.AND ?? [];
|
|
308
|
+
for (const field of availableDateFields) {
|
|
309
|
+
where.AND.push({
|
|
310
|
+
[field]: {
|
|
311
|
+
gte: startDateOfDay(from),
|
|
312
|
+
lte: endDateOfDay(to),
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// ── Comparison operators (greaterThan, lessThan) ──────────────────────
|
|
318
|
+
const { greaterThan, lessThan } = params;
|
|
319
|
+
if (greaterThan !== undefined && greaterThan !== '') {
|
|
320
|
+
const numValue = Number(greaterThan);
|
|
321
|
+
if (!Number.isNaN(numValue)) {
|
|
322
|
+
for (const field of operationFields) {
|
|
323
|
+
where[field] = { ...(where[field] ?? {}), gt: numValue };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (lessThan !== undefined && lessThan !== '') {
|
|
328
|
+
const numValue = Number(lessThan);
|
|
329
|
+
if (!Number.isNaN(numValue)) {
|
|
330
|
+
for (const field of operationFields) {
|
|
331
|
+
where[field] = { ...(where[field] ?? {}), lt: numValue };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return where;
|
|
336
|
+
}
|
|
337
|
+
// ─── Utilities ────────────────────────────────────────────────────────────
|
|
338
|
+
/** Deep-merge `source` into `target` (mutates target). */
|
|
339
|
+
function deepMerge(target, source) {
|
|
340
|
+
for (const key of Object.keys(source)) {
|
|
341
|
+
const tVal = target[key];
|
|
342
|
+
const sVal = source[key];
|
|
343
|
+
if (tVal &&
|
|
344
|
+
sVal &&
|
|
345
|
+
typeof tVal === 'object' &&
|
|
346
|
+
typeof sVal === 'object' &&
|
|
347
|
+
!Array.isArray(tVal) &&
|
|
348
|
+
!Array.isArray(sVal)) {
|
|
349
|
+
deepMerge(tVal, sVal);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
target[key] = sVal;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=where.helper.js.map
|