@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.
Files changed (130) hide show
  1. package/README.md +709 -0
  2. package/dist/adapters/index.d.ts +3 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +3 -0
  5. package/dist/adapters/index.js.map +1 -0
  6. package/dist/adapters/valibot.adapter.d.ts +14 -0
  7. package/dist/adapters/valibot.adapter.d.ts.map +1 -0
  8. package/dist/adapters/valibot.adapter.js +21 -0
  9. package/dist/adapters/valibot.adapter.js.map +1 -0
  10. package/dist/adapters/zod.adapter.d.ts +29 -0
  11. package/dist/adapters/zod.adapter.d.ts.map +1 -0
  12. package/dist/adapters/zod.adapter.js +42 -0
  13. package/dist/adapters/zod.adapter.js.map +1 -0
  14. package/dist/controllers/generic.controller.d.ts +79 -0
  15. package/dist/controllers/generic.controller.d.ts.map +1 -0
  16. package/dist/controllers/generic.controller.js +279 -0
  17. package/dist/controllers/generic.controller.js.map +1 -0
  18. package/dist/controllers/index.d.ts +2 -0
  19. package/dist/controllers/index.d.ts.map +1 -0
  20. package/dist/controllers/index.js +2 -0
  21. package/dist/controllers/index.js.map +1 -0
  22. package/dist/errors/app-errors.d.ts +17 -0
  23. package/dist/errors/app-errors.d.ts.map +1 -0
  24. package/dist/errors/app-errors.js +28 -0
  25. package/dist/errors/app-errors.js.map +1 -0
  26. package/dist/errors/error-messages.d.ts +80 -0
  27. package/dist/errors/error-messages.d.ts.map +1 -0
  28. package/dist/errors/error-messages.js +75 -0
  29. package/dist/errors/error-messages.js.map +1 -0
  30. package/dist/errors/index.d.ts +3 -0
  31. package/dist/errors/index.d.ts.map +1 -0
  32. package/dist/errors/index.js +3 -0
  33. package/dist/errors/index.js.map +1 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +9 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/query-builder/helpers/order-by.helper.d.ts +4 -0
  39. package/dist/query-builder/helpers/order-by.helper.d.ts.map +1 -0
  40. package/dist/query-builder/helpers/order-by.helper.js +24 -0
  41. package/dist/query-builder/helpers/order-by.helper.js.map +1 -0
  42. package/dist/query-builder/helpers/pagination.helper.d.ts +6 -0
  43. package/dist/query-builder/helpers/pagination.helper.d.ts.map +1 -0
  44. package/dist/query-builder/helpers/pagination.helper.js +9 -0
  45. package/dist/query-builder/helpers/pagination.helper.js.map +1 -0
  46. package/dist/query-builder/helpers/where.helper.d.ts +5 -0
  47. package/dist/query-builder/helpers/where.helper.d.ts.map +1 -0
  48. package/dist/query-builder/helpers/where.helper.js +356 -0
  49. package/dist/query-builder/helpers/where.helper.js.map +1 -0
  50. package/dist/query-builder/index.d.ts +3 -0
  51. package/dist/query-builder/index.d.ts.map +1 -0
  52. package/dist/query-builder/index.js +3 -0
  53. package/dist/query-builder/index.js.map +1 -0
  54. package/dist/query-builder/query-builder.d.ts +56 -0
  55. package/dist/query-builder/query-builder.d.ts.map +1 -0
  56. package/dist/query-builder/query-builder.js +149 -0
  57. package/dist/query-builder/query-builder.js.map +1 -0
  58. package/dist/query-builder/query-builder.types.d.ts +83 -0
  59. package/dist/query-builder/query-builder.types.d.ts.map +1 -0
  60. package/dist/query-builder/query-builder.types.js +2 -0
  61. package/dist/query-builder/query-builder.types.js.map +1 -0
  62. package/dist/repositories/crud.repository.prisma.d.ts +44 -0
  63. package/dist/repositories/crud.repository.prisma.d.ts.map +1 -0
  64. package/dist/repositories/crud.repository.prisma.js +246 -0
  65. package/dist/repositories/crud.repository.prisma.js.map +1 -0
  66. package/dist/repositories/index.d.ts +2 -0
  67. package/dist/repositories/index.d.ts.map +1 -0
  68. package/dist/repositories/index.js +2 -0
  69. package/dist/repositories/index.js.map +1 -0
  70. package/dist/services/audit.service.d.ts +32 -0
  71. package/dist/services/audit.service.d.ts.map +1 -0
  72. package/dist/services/audit.service.js +57 -0
  73. package/dist/services/audit.service.js.map +1 -0
  74. package/dist/services/error-handler.service.d.ts +34 -0
  75. package/dist/services/error-handler.service.d.ts.map +1 -0
  76. package/dist/services/error-handler.service.js +159 -0
  77. package/dist/services/error-handler.service.js.map +1 -0
  78. package/dist/services/index.d.ts +3 -0
  79. package/dist/services/index.d.ts.map +1 -0
  80. package/dist/services/index.js +3 -0
  81. package/dist/services/index.js.map +1 -0
  82. package/dist/types/crud-config.d.ts +20 -0
  83. package/dist/types/crud-config.d.ts.map +1 -0
  84. package/dist/types/crud-config.js +2 -0
  85. package/dist/types/crud-config.js.map +1 -0
  86. package/dist/types/generic-repository.d.ts +36 -0
  87. package/dist/types/generic-repository.d.ts.map +1 -0
  88. package/dist/types/generic-repository.js +2 -0
  89. package/dist/types/generic-repository.js.map +1 -0
  90. package/dist/types/index.d.ts +8 -0
  91. package/dist/types/index.d.ts.map +1 -0
  92. package/dist/types/index.js +8 -0
  93. package/dist/types/index.js.map +1 -0
  94. package/dist/types/logger.d.ts +17 -0
  95. package/dist/types/logger.d.ts.map +1 -0
  96. package/dist/types/logger.js +8 -0
  97. package/dist/types/logger.js.map +1 -0
  98. package/dist/types/pagination.d.ts +11 -0
  99. package/dist/types/pagination.d.ts.map +1 -0
  100. package/dist/types/pagination.js +2 -0
  101. package/dist/types/pagination.js.map +1 -0
  102. package/dist/types/prisma-delegate.types.d.ts +36 -0
  103. package/dist/types/prisma-delegate.types.d.ts.map +1 -0
  104. package/dist/types/prisma-delegate.types.js +15 -0
  105. package/dist/types/prisma-delegate.types.js.map +1 -0
  106. package/dist/types/query-params.d.ts +40 -0
  107. package/dist/types/query-params.d.ts.map +1 -0
  108. package/dist/types/query-params.js +2 -0
  109. package/dist/types/query-params.js.map +1 -0
  110. package/dist/types/schema-adapter.d.ts +19 -0
  111. package/dist/types/schema-adapter.d.ts.map +1 -0
  112. package/dist/types/schema-adapter.js +2 -0
  113. package/dist/types/schema-adapter.js.map +1 -0
  114. package/dist/utils/date.utils.d.ts +13 -0
  115. package/dist/utils/date.utils.d.ts.map +1 -0
  116. package/dist/utils/date.utils.js +75 -0
  117. package/dist/utils/date.utils.js.map +1 -0
  118. package/dist/utils/index.d.ts +4 -0
  119. package/dist/utils/index.d.ts.map +1 -0
  120. package/dist/utils/index.js +4 -0
  121. package/dist/utils/index.js.map +1 -0
  122. package/dist/utils/phone.utils.d.ts +7 -0
  123. package/dist/utils/phone.utils.d.ts.map +1 -0
  124. package/dist/utils/phone.utils.js +15 -0
  125. package/dist/utils/phone.utils.js.map +1 -0
  126. package/dist/utils/response.utils.d.ts +5 -0
  127. package/dist/utils/response.utils.d.ts.map +1 -0
  128. package/dist/utils/response.utils.js +13 -0
  129. package/dist/utils/response.utils.js.map +1 -0
  130. 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,3 @@
1
+ export * from './error-messages.js';
2
+ export * from './app-errors.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export * from './error-messages.js';
2
+ export * from './app-errors.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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,6 @@
1
+ import type { QueryParams } from '../../types/query-params.js';
2
+ export declare function parsePagination(queryParams: QueryParams, pageDefault: number, sizeDefault: number): {
3
+ page: number;
4
+ size: number;
5
+ };
6
+ //# sourceMappingURL=pagination.helper.d.ts.map
@@ -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