@tablecraft/engine 0.1.0-beta.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 (237) hide show
  1. package/dist/src/__tests__/inputValidator.test.d.ts +2 -0
  2. package/dist/src/__tests__/inputValidator.test.d.ts.map +1 -0
  3. package/dist/src/__tests__/inputValidator.test.js +205 -0
  4. package/dist/src/__tests__/inputValidator.test.js.map +1 -0
  5. package/dist/src/__tests__/metadataBuilder.test.d.ts +2 -0
  6. package/dist/src/__tests__/metadataBuilder.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/metadataBuilder.test.js +221 -0
  8. package/dist/src/__tests__/metadataBuilder.test.js.map +1 -0
  9. package/dist/src/core/aggregationBuilder.d.ts +17 -0
  10. package/dist/src/core/aggregationBuilder.d.ts.map +1 -0
  11. package/dist/src/core/aggregationBuilder.js +81 -0
  12. package/dist/src/core/aggregationBuilder.js.map +1 -0
  13. package/dist/src/core/cursorPagination.d.ts +36 -0
  14. package/dist/src/core/cursorPagination.d.ts.map +1 -0
  15. package/dist/src/core/cursorPagination.js +88 -0
  16. package/dist/src/core/cursorPagination.js.map +1 -0
  17. package/dist/src/core/datePresets.d.ts +19 -0
  18. package/dist/src/core/datePresets.d.ts.map +1 -0
  19. package/dist/src/core/datePresets.js +96 -0
  20. package/dist/src/core/datePresets.js.map +1 -0
  21. package/dist/src/core/dialect.d.ts +17 -0
  22. package/dist/src/core/dialect.d.ts.map +1 -0
  23. package/dist/src/core/dialect.js +60 -0
  24. package/dist/src/core/dialect.js.map +1 -0
  25. package/dist/src/core/fieldSelector.d.ts +19 -0
  26. package/dist/src/core/fieldSelector.d.ts.map +1 -0
  27. package/dist/src/core/fieldSelector.js +49 -0
  28. package/dist/src/core/fieldSelector.js.map +1 -0
  29. package/dist/src/core/filterBuilder.d.ts +22 -0
  30. package/dist/src/core/filterBuilder.d.ts.map +1 -0
  31. package/dist/src/core/filterBuilder.js +112 -0
  32. package/dist/src/core/filterBuilder.js.map +1 -0
  33. package/dist/src/core/filterGroupBuilder.d.ts +28 -0
  34. package/dist/src/core/filterGroupBuilder.d.ts.map +1 -0
  35. package/dist/src/core/filterGroupBuilder.js +73 -0
  36. package/dist/src/core/filterGroupBuilder.js.map +1 -0
  37. package/dist/src/core/groupByBuilder.d.ts +23 -0
  38. package/dist/src/core/groupByBuilder.d.ts.map +1 -0
  39. package/dist/src/core/groupByBuilder.js +127 -0
  40. package/dist/src/core/groupByBuilder.js.map +1 -0
  41. package/dist/src/core/inputValidator.d.ts +8 -0
  42. package/dist/src/core/inputValidator.d.ts.map +1 -0
  43. package/dist/src/core/inputValidator.js +117 -0
  44. package/dist/src/core/inputValidator.js.map +1 -0
  45. package/dist/src/core/metadataBuilder.d.ts +91 -0
  46. package/dist/src/core/metadataBuilder.d.ts.map +1 -0
  47. package/dist/src/core/metadataBuilder.js +220 -0
  48. package/dist/src/core/metadataBuilder.js.map +1 -0
  49. package/dist/src/core/paginationBuilder.d.ts +20 -0
  50. package/dist/src/core/paginationBuilder.d.ts.map +1 -0
  51. package/dist/src/core/paginationBuilder.js +42 -0
  52. package/dist/src/core/paginationBuilder.js.map +1 -0
  53. package/dist/src/core/queryBuilder.d.ts +20 -0
  54. package/dist/src/core/queryBuilder.d.ts.map +1 -0
  55. package/dist/src/core/queryBuilder.js +163 -0
  56. package/dist/src/core/queryBuilder.js.map +1 -0
  57. package/dist/src/core/recursiveBuilder.d.ts +25 -0
  58. package/dist/src/core/recursiveBuilder.d.ts.map +1 -0
  59. package/dist/src/core/recursiveBuilder.js +86 -0
  60. package/dist/src/core/recursiveBuilder.js.map +1 -0
  61. package/dist/src/core/relationBuilder.d.ts +19 -0
  62. package/dist/src/core/relationBuilder.d.ts.map +1 -0
  63. package/dist/src/core/relationBuilder.js +118 -0
  64. package/dist/src/core/relationBuilder.js.map +1 -0
  65. package/dist/src/core/roleFilter.d.ts +11 -0
  66. package/dist/src/core/roleFilter.d.ts.map +1 -0
  67. package/dist/src/core/roleFilter.js +24 -0
  68. package/dist/src/core/roleFilter.js.map +1 -0
  69. package/dist/src/core/searchBuilder.d.ts +17 -0
  70. package/dist/src/core/searchBuilder.d.ts.map +1 -0
  71. package/dist/src/core/searchBuilder.js +71 -0
  72. package/dist/src/core/searchBuilder.js.map +1 -0
  73. package/dist/src/core/softDelete.d.ts +12 -0
  74. package/dist/src/core/softDelete.d.ts.map +1 -0
  75. package/dist/src/core/softDelete.js +29 -0
  76. package/dist/src/core/softDelete.js.map +1 -0
  77. package/dist/src/core/sortBuilder.d.ts +14 -0
  78. package/dist/src/core/sortBuilder.d.ts.map +1 -0
  79. package/dist/src/core/sortBuilder.js +58 -0
  80. package/dist/src/core/sortBuilder.js.map +1 -0
  81. package/dist/src/core/subqueryBuilder.d.ts +13 -0
  82. package/dist/src/core/subqueryBuilder.d.ts.map +1 -0
  83. package/dist/src/core/subqueryBuilder.js +47 -0
  84. package/dist/src/core/subqueryBuilder.js.map +1 -0
  85. package/dist/src/core/validator.d.ts +18 -0
  86. package/dist/src/core/validator.d.ts.map +1 -0
  87. package/dist/src/core/validator.js +88 -0
  88. package/dist/src/core/validator.js.map +1 -0
  89. package/dist/src/define.d.ts +274 -0
  90. package/dist/src/define.d.ts.map +1 -0
  91. package/dist/src/define.js +690 -0
  92. package/dist/src/define.js.map +1 -0
  93. package/dist/src/engine.d.ts +17 -0
  94. package/dist/src/engine.d.ts.map +1 -0
  95. package/dist/src/engine.js +429 -0
  96. package/dist/src/engine.js.map +1 -0
  97. package/dist/src/errors.d.ts +53 -0
  98. package/dist/src/errors.d.ts.map +1 -0
  99. package/dist/src/errors.js +80 -0
  100. package/dist/src/errors.js.map +1 -0
  101. package/dist/src/index.d.ts +37 -0
  102. package/dist/src/index.d.ts.map +1 -0
  103. package/dist/src/index.js +41 -0
  104. package/dist/src/index.js.map +1 -0
  105. package/dist/src/types/engine.d.ts +92 -0
  106. package/dist/src/types/engine.d.ts.map +1 -0
  107. package/dist/src/types/engine.js +2 -0
  108. package/dist/src/types/engine.js.map +1 -0
  109. package/dist/src/types/table.d.ts +867 -0
  110. package/dist/src/types/table.d.ts.map +1 -0
  111. package/dist/src/types/table.js +198 -0
  112. package/dist/src/types/table.js.map +1 -0
  113. package/dist/src/utils/adapterUtils.d.ts +16 -0
  114. package/dist/src/utils/adapterUtils.d.ts.map +1 -0
  115. package/dist/src/utils/adapterUtils.js +28 -0
  116. package/dist/src/utils/adapterUtils.js.map +1 -0
  117. package/dist/src/utils/codegen.d.ts +7 -0
  118. package/dist/src/utils/codegen.d.ts.map +1 -0
  119. package/dist/src/utils/codegen.js +126 -0
  120. package/dist/src/utils/codegen.js.map +1 -0
  121. package/dist/src/utils/export.d.ts +15 -0
  122. package/dist/src/utils/export.d.ts.map +1 -0
  123. package/dist/src/utils/export.js +42 -0
  124. package/dist/src/utils/export.js.map +1 -0
  125. package/dist/src/utils/introspect.d.ts +32 -0
  126. package/dist/src/utils/introspect.d.ts.map +1 -0
  127. package/dist/src/utils/introspect.js +174 -0
  128. package/dist/src/utils/introspect.js.map +1 -0
  129. package/dist/src/utils/openapi.d.ts +6 -0
  130. package/dist/src/utils/openapi.d.ts.map +1 -0
  131. package/dist/src/utils/openapi.js +138 -0
  132. package/dist/src/utils/openapi.js.map +1 -0
  133. package/dist/src/utils/operators.d.ts +8 -0
  134. package/dist/src/utils/operators.d.ts.map +1 -0
  135. package/dist/src/utils/operators.js +70 -0
  136. package/dist/src/utils/operators.js.map +1 -0
  137. package/dist/src/utils/requestParser.d.ts +18 -0
  138. package/dist/src/utils/requestParser.d.ts.map +1 -0
  139. package/dist/src/utils/requestParser.js +126 -0
  140. package/dist/src/utils/requestParser.js.map +1 -0
  141. package/dist/src/utils/responseFormatter.d.ts +12 -0
  142. package/dist/src/utils/responseFormatter.d.ts.map +1 -0
  143. package/dist/src/utils/responseFormatter.js +106 -0
  144. package/dist/src/utils/responseFormatter.js.map +1 -0
  145. package/dist/src/utils/typedSql.d.ts +70 -0
  146. package/dist/src/utils/typedSql.d.ts.map +1 -0
  147. package/dist/src/utils/typedSql.js +102 -0
  148. package/dist/src/utils/typedSql.js.map +1 -0
  149. package/dist/test/columnMeta.test.d.ts +2 -0
  150. package/dist/test/columnMeta.test.d.ts.map +1 -0
  151. package/dist/test/columnMeta.test.js +92 -0
  152. package/dist/test/columnMeta.test.js.map +1 -0
  153. package/dist/test/core/aggregationBuilder.test.d.ts +2 -0
  154. package/dist/test/core/aggregationBuilder.test.d.ts.map +1 -0
  155. package/dist/test/core/aggregationBuilder.test.js +64 -0
  156. package/dist/test/core/aggregationBuilder.test.js.map +1 -0
  157. package/dist/test/core/filterBuilder.test.d.ts +2 -0
  158. package/dist/test/core/filterBuilder.test.d.ts.map +1 -0
  159. package/dist/test/core/filterBuilder.test.js +80 -0
  160. package/dist/test/core/filterBuilder.test.js.map +1 -0
  161. package/dist/test/core/paginationBuilder.test.d.ts +2 -0
  162. package/dist/test/core/paginationBuilder.test.d.ts.map +1 -0
  163. package/dist/test/core/paginationBuilder.test.js +63 -0
  164. package/dist/test/core/paginationBuilder.test.js.map +1 -0
  165. package/dist/test/core/queryBuilder.test.d.ts +2 -0
  166. package/dist/test/core/queryBuilder.test.d.ts.map +1 -0
  167. package/dist/test/core/queryBuilder.test.js +92 -0
  168. package/dist/test/core/queryBuilder.test.js.map +1 -0
  169. package/dist/test/core/searchBuilder.test.d.ts +2 -0
  170. package/dist/test/core/searchBuilder.test.d.ts.map +1 -0
  171. package/dist/test/core/searchBuilder.test.js +68 -0
  172. package/dist/test/core/searchBuilder.test.js.map +1 -0
  173. package/dist/test/core/softDelete.test.d.ts +2 -0
  174. package/dist/test/core/softDelete.test.d.ts.map +1 -0
  175. package/dist/test/core/softDelete.test.js +60 -0
  176. package/dist/test/core/softDelete.test.js.map +1 -0
  177. package/dist/test/core/sortBuilder.test.d.ts +2 -0
  178. package/dist/test/core/sortBuilder.test.d.ts.map +1 -0
  179. package/dist/test/core/sortBuilder.test.js +59 -0
  180. package/dist/test/core/sortBuilder.test.js.map +1 -0
  181. package/dist/test/core/subqueryBuilder.test.d.ts +2 -0
  182. package/dist/test/core/subqueryBuilder.test.d.ts.map +1 -0
  183. package/dist/test/core/subqueryBuilder.test.js +48 -0
  184. package/dist/test/core/subqueryBuilder.test.js.map +1 -0
  185. package/dist/test/core/validator.test.d.ts +2 -0
  186. package/dist/test/core/validator.test.d.ts.map +1 -0
  187. package/dist/test/core/validator.test.js +81 -0
  188. package/dist/test/core/validator.test.js.map +1 -0
  189. package/dist/test/datePresets.test.d.ts +2 -0
  190. package/dist/test/datePresets.test.d.ts.map +1 -0
  191. package/dist/test/datePresets.test.js +57 -0
  192. package/dist/test/datePresets.test.js.map +1 -0
  193. package/dist/test/errors.test.d.ts +2 -0
  194. package/dist/test/errors.test.d.ts.map +1 -0
  195. package/dist/test/errors.test.js +62 -0
  196. package/dist/test/errors.test.js.map +1 -0
  197. package/dist/test/inputValidator.test.d.ts +2 -0
  198. package/dist/test/inputValidator.test.d.ts.map +1 -0
  199. package/dist/test/inputValidator.test.js +60 -0
  200. package/dist/test/inputValidator.test.js.map +1 -0
  201. package/dist/test/metadata.test.d.ts +2 -0
  202. package/dist/test/metadata.test.d.ts.map +1 -0
  203. package/dist/test/metadata.test.js +285 -0
  204. package/dist/test/metadata.test.js.map +1 -0
  205. package/dist/test/metadataComplete.test.d.ts +2 -0
  206. package/dist/test/metadataComplete.test.d.ts.map +1 -0
  207. package/dist/test/metadataComplete.test.js +113 -0
  208. package/dist/test/metadataComplete.test.js.map +1 -0
  209. package/dist/test/roleFilter.test.d.ts +2 -0
  210. package/dist/test/roleFilter.test.d.ts.map +1 -0
  211. package/dist/test/roleFilter.test.js +53 -0
  212. package/dist/test/roleFilter.test.js.map +1 -0
  213. package/dist/test/typedSql.test.d.ts +2 -0
  214. package/dist/test/typedSql.test.d.ts.map +1 -0
  215. package/dist/test/typedSql.test.js +63 -0
  216. package/dist/test/typedSql.test.js.map +1 -0
  217. package/dist/test/utils/codegen.test.d.ts +2 -0
  218. package/dist/test/utils/codegen.test.d.ts.map +1 -0
  219. package/dist/test/utils/codegen.test.js +65 -0
  220. package/dist/test/utils/codegen.test.js.map +1 -0
  221. package/dist/test/utils/export.test.d.ts +2 -0
  222. package/dist/test/utils/export.test.d.ts.map +1 -0
  223. package/dist/test/utils/export.test.js +54 -0
  224. package/dist/test/utils/export.test.js.map +1 -0
  225. package/dist/test/utils/openapi.test.d.ts +2 -0
  226. package/dist/test/utils/openapi.test.d.ts.map +1 -0
  227. package/dist/test/utils/openapi.test.js +65 -0
  228. package/dist/test/utils/openapi.test.js.map +1 -0
  229. package/dist/test/utils/requestParser.test.d.ts +2 -0
  230. package/dist/test/utils/requestParser.test.d.ts.map +1 -0
  231. package/dist/test/utils/requestParser.test.js +89 -0
  232. package/dist/test/utils/requestParser.test.js.map +1 -0
  233. package/dist/test/utils/responseFormatter.test.d.ts +2 -0
  234. package/dist/test/utils/responseFormatter.test.d.ts.map +1 -0
  235. package/dist/test/utils/responseFormatter.test.js +79 -0
  236. package/dist/test/utils/responseFormatter.test.js.map +1 -0
  237. package/package.json +42 -0
@@ -0,0 +1,88 @@
1
+ import { sql, getTableColumns, gt, lt, asc, desc } from 'drizzle-orm';
2
+ /**
3
+ * Cursor-based pagination.
4
+ * Uses the sort column value as the cursor instead of OFFSET.
5
+ * O(1) performance regardless of page depth.
6
+ *
7
+ * Cursor format: base64({ field: value, field2: value2 })
8
+ */
9
+ export class CursorPaginationBuilder {
10
+ schema;
11
+ constructor(schema) {
12
+ this.schema = schema;
13
+ }
14
+ /**
15
+ * Decodes a cursor string and builds WHERE + ORDER BY for the next page.
16
+ */
17
+ build(config, cursor, pageSize, sort) {
18
+ const table = this.schema[config.base];
19
+ if (!table) {
20
+ return { whereCondition: undefined, orderBy: [], limit: pageSize + 1 };
21
+ }
22
+ const columns = getTableColumns(table);
23
+ // Determine sort fields (default to id or createdAt)
24
+ const sortFields = sort?.length
25
+ ? sort
26
+ : config.defaultSort?.length
27
+ ? config.defaultSort
28
+ : [{ field: 'id', order: 'asc' }];
29
+ // Build ORDER BY
30
+ const orderBy = sortFields.map((s) => {
31
+ const col = columns[s.field];
32
+ if (!col)
33
+ return s.order === 'desc' ? desc(sql.identifier(s.field)) : asc(sql.identifier(s.field));
34
+ return s.order === 'desc' ? desc(col) : asc(col);
35
+ });
36
+ // Decode cursor and build WHERE
37
+ let whereCondition;
38
+ if (cursor) {
39
+ const decoded = decodeCursor(cursor);
40
+ if (decoded && sortFields.length > 0) {
41
+ const primary = sortFields[0];
42
+ const col = columns[primary.field];
43
+ if (col && decoded[primary.field] !== undefined) {
44
+ whereCondition = primary.order === 'desc'
45
+ ? lt(col, decoded[primary.field])
46
+ : gt(col, decoded[primary.field]);
47
+ }
48
+ }
49
+ }
50
+ // Fetch one extra row to determine if there's a next page
51
+ return {
52
+ whereCondition,
53
+ orderBy,
54
+ limit: pageSize + 1,
55
+ };
56
+ }
57
+ /**
58
+ * From the fetched data (with 1 extra row), determine next cursor.
59
+ */
60
+ buildMeta(data, pageSize, sort) {
61
+ const hasMore = data.length > pageSize;
62
+ const trimmed = hasMore ? data.slice(0, pageSize) : data;
63
+ let nextCursor = null;
64
+ if (hasMore && trimmed.length > 0) {
65
+ const lastRow = trimmed[trimmed.length - 1];
66
+ const sortField = sort?.[0]?.field ?? 'id';
67
+ nextCursor = encodeCursor({ [sortField]: lastRow[sortField] });
68
+ }
69
+ return {
70
+ data: trimmed,
71
+ meta: { nextCursor, pageSize },
72
+ };
73
+ }
74
+ }
75
+ // ── Cursor encoding ──
76
+ export function encodeCursor(values) {
77
+ return Buffer.from(JSON.stringify(values)).toString('base64url');
78
+ }
79
+ export function decodeCursor(cursor) {
80
+ try {
81
+ const json = Buffer.from(cursor, 'base64url').toString('utf-8');
82
+ return JSON.parse(json);
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ //# sourceMappingURL=cursorPagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursorPagination.js","sourceRoot":"","sources":["../../../src/core/cursorPagination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAclF;;;;;;GAMG;AACH,MAAM,OAAO,uBAAuB;IACd;IAApB,YAAoB,MAA+B;QAA/B,WAAM,GAAN,MAAM,CAAyB;IAAG,CAAC;IAEvD;;OAEG;IACH,KAAK,CACH,MAAmB,EACnB,MAA0B,EAC1B,QAAgB,EAChB,IAAmB;QAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAU,CAAC;QAChD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,EAAE,CAAC;QACzE,CAAC;QAED,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAEvC,qDAAqD;QACrD,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM;YAC7B,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM;gBAC1B,CAAC,CAAC,MAAM,CAAC,WAAW;gBACpB,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;QAE/C,iBAAiB;QACjB,MAAM,OAAO,GAAU,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC7B,IAAI,CAAC,GAAG;gBAAE,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YACnG,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,gCAAgC;QAChC,IAAI,cAA+B,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACrC,IAAI,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrC,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACnC,IAAI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;oBAChD,cAAc,GAAG,OAAO,CAAC,KAAK,KAAK,MAAM;wBACvC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;wBACjC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;QACH,CAAC;QAED,0DAA0D;QAC1D,OAAO;YACL,cAAc;YACd,OAAO;YACP,KAAK,EAAE,QAAQ,GAAG,CAAC;SACpB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,SAAS,CACP,IAA+B,EAC/B,QAAgB,EAChB,IAAmB;QAEnB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;QACvC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAEzD,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC5C,MAAM,SAAS,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC;YAC3C,UAAU,GAAG,YAAY,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;QAED,OAAO;YACL,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;SAC/B,CAAC;IACJ,CAAC;CACF;AAED,wBAAwB;AAExB,MAAM,UAAU,YAAY,CAAC,MAA+B;IAC1D,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { Column, SQL } from 'drizzle-orm';
2
+ import { DatePreset } from '../types/table';
3
+ /**
4
+ * Resolves a date preset name into a concrete date range.
5
+ * All dates are in UTC.
6
+ */
7
+ export declare function resolveDatePreset(preset: DatePreset): {
8
+ start: Date;
9
+ end: Date;
10
+ } | undefined;
11
+ /**
12
+ * Builds a SQL WHERE condition from a date preset.
13
+ */
14
+ export declare function buildDatePresetCondition(column: Column, preset: DatePreset): SQL | undefined;
15
+ /**
16
+ * Checks if a filter value is a date preset name.
17
+ */
18
+ export declare function isDatePreset(value: unknown): value is DatePreset;
19
+ //# sourceMappingURL=datePresets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datePresets.d.ts","sourceRoot":"","sources":["../../../src/core/datePresets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAgB,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CA+D5F;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,GACjB,GAAG,GAAG,SAAS,CAQjB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAOhE"}
@@ -0,0 +1,96 @@
1
+ import { and, gte, lt } from 'drizzle-orm';
2
+ /**
3
+ * Resolves a date preset name into a concrete date range.
4
+ * All dates are in UTC.
5
+ */
6
+ export function resolveDatePreset(preset) {
7
+ const now = new Date();
8
+ const today = startOfDay(now);
9
+ const tomorrow = addDays(today, 1);
10
+ switch (preset) {
11
+ case 'today':
12
+ return { start: today, end: tomorrow };
13
+ case 'yesterday':
14
+ return { start: addDays(today, -1), end: today };
15
+ case 'last7days':
16
+ return { start: addDays(today, -7), end: tomorrow };
17
+ case 'last30days':
18
+ return { start: addDays(today, -30), end: tomorrow };
19
+ case 'last90days':
20
+ return { start: addDays(today, -90), end: tomorrow };
21
+ case 'thisWeek':
22
+ return { start: startOfWeek(today), end: tomorrow };
23
+ case 'lastWeek': {
24
+ const thisWeekStart = startOfWeek(today);
25
+ return { start: addDays(thisWeekStart, -7), end: thisWeekStart };
26
+ }
27
+ case 'thisMonth':
28
+ return { start: startOfMonth(today), end: tomorrow };
29
+ case 'lastMonth': {
30
+ const thisMonthStart = startOfMonth(today);
31
+ const lastMonthStart = new Date(thisMonthStart);
32
+ lastMonthStart.setMonth(lastMonthStart.getMonth() - 1);
33
+ return { start: lastMonthStart, end: thisMonthStart };
34
+ }
35
+ case 'thisQuarter':
36
+ return { start: startOfQuarter(today), end: tomorrow };
37
+ case 'lastQuarter': {
38
+ const thisQ = startOfQuarter(today);
39
+ const lastQ = new Date(thisQ);
40
+ lastQ.setMonth(lastQ.getMonth() - 3);
41
+ return { start: lastQ, end: thisQ };
42
+ }
43
+ case 'thisYear':
44
+ return { start: new Date(Date.UTC(today.getUTCFullYear(), 0, 1)), end: tomorrow };
45
+ case 'lastYear': {
46
+ const year = today.getUTCFullYear();
47
+ return { start: new Date(Date.UTC(year - 1, 0, 1)), end: new Date(Date.UTC(year, 0, 1)) };
48
+ }
49
+ case 'custom':
50
+ return undefined;
51
+ default:
52
+ return undefined;
53
+ }
54
+ }
55
+ /**
56
+ * Builds a SQL WHERE condition from a date preset.
57
+ */
58
+ export function buildDatePresetCondition(column, preset) {
59
+ const range = resolveDatePreset(preset);
60
+ if (!range)
61
+ return undefined;
62
+ return and(gte(column, range.start), lt(column, range.end));
63
+ }
64
+ /**
65
+ * Checks if a filter value is a date preset name.
66
+ */
67
+ export function isDatePreset(value) {
68
+ const presets = new Set([
69
+ 'today', 'yesterday', 'last7days', 'last30days', 'last90days',
70
+ 'thisWeek', 'lastWeek', 'thisMonth', 'lastMonth',
71
+ 'thisQuarter', 'lastQuarter', 'thisYear', 'lastYear', 'custom',
72
+ ]);
73
+ return typeof value === 'string' && presets.has(value);
74
+ }
75
+ // ── Date helpers (no dependencies) ──
76
+ function startOfDay(d) {
77
+ return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
78
+ }
79
+ function addDays(d, n) {
80
+ const result = new Date(d);
81
+ result.setUTCDate(result.getUTCDate() + n);
82
+ return result;
83
+ }
84
+ function startOfWeek(d) {
85
+ const day = d.getUTCDay();
86
+ const diff = day === 0 ? 6 : day - 1; // Monday = start
87
+ return addDays(startOfDay(d), -diff);
88
+ }
89
+ function startOfMonth(d) {
90
+ return new Date(Date.UTC(d.getFullYear(), d.getMonth(), 1));
91
+ }
92
+ function startOfQuarter(d) {
93
+ const q = Math.floor(d.getMonth() / 3) * 3;
94
+ return new Date(Date.UTC(d.getFullYear(), q, 1));
95
+ }
96
+ //# sourceMappingURL=datePresets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datePresets.js","sourceRoot":"","sources":["../../../src/core/datePresets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Database dialect detection and feature gating.
3
+ * Prevents PostgreSQL-only features from silently failing on MySQL/SQLite.
4
+ */
5
+ export type Dialect = 'postgresql' | 'mysql' | 'sqlite' | 'unknown';
6
+ /**
7
+ * Auto-detect dialect from a Drizzle database instance.
8
+ */
9
+ export declare function detectDialect(db: any): Dialect;
10
+ export declare function supportsFeature(dialect: Dialect, feature: string): boolean;
11
+ /**
12
+ * Returns LIKE or ILIKE based on dialect.
13
+ * MySQL/SQLite: LIKE is case-insensitive by default.
14
+ * PostgreSQL: needs ILIKE for case-insensitive.
15
+ */
16
+ export declare function getCaseInsensitiveLike(dialect: Dialect): 'like' | 'ilike';
17
+ //# sourceMappingURL=dialect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../../../src/core/dialect.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpE;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,CAuB9C;AAeD,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAEzE"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Database dialect detection and feature gating.
3
+ * Prevents PostgreSQL-only features from silently failing on MySQL/SQLite.
4
+ */
5
+ /**
6
+ * Auto-detect dialect from a Drizzle database instance.
7
+ */
8
+ export function detectDialect(db) {
9
+ // Drizzle instances have internal markers we can check
10
+ const constructor = db?.constructor?.name?.toLowerCase() ?? '';
11
+ if (constructor.includes('pg') || constructor.includes('postgres') || constructor.includes('neon')) {
12
+ return 'postgresql';
13
+ }
14
+ if (constructor.includes('mysql') || constructor.includes('planetscale')) {
15
+ return 'mysql';
16
+ }
17
+ if (constructor.includes('sqlite') || constructor.includes('libsql') || constructor.includes('turso') || constructor.includes('d1')) {
18
+ return 'sqlite';
19
+ }
20
+ // Check for dialect property that some Drizzle instances expose
21
+ if (db?.dialect?.name) {
22
+ const name = db.dialect.name.toLowerCase();
23
+ if (name.includes('pg'))
24
+ return 'postgresql';
25
+ if (name.includes('mysql'))
26
+ return 'mysql';
27
+ if (name.includes('sqlite'))
28
+ return 'sqlite';
29
+ }
30
+ return 'unknown';
31
+ }
32
+ /**
33
+ * Feature support matrix per dialect.
34
+ */
35
+ const FEATURES = {
36
+ ilike: new Set(['postgresql']),
37
+ fullTextSearch: new Set(['postgresql']),
38
+ recursiveCTE: new Set(['postgresql', 'sqlite']),
39
+ returning: new Set(['postgresql', 'sqlite']),
40
+ lateral: new Set(['postgresql']),
41
+ distinct: new Set(['postgresql', 'mysql', 'sqlite']),
42
+ estimatedCount: new Set(['postgresql']),
43
+ };
44
+ export function supportsFeature(dialect, feature) {
45
+ const supported = FEATURES[feature];
46
+ if (!supported)
47
+ return true; // unknown features assumed supported
48
+ if (dialect === 'unknown')
49
+ return true; // don't block unknown dialects
50
+ return supported.has(dialect);
51
+ }
52
+ /**
53
+ * Returns LIKE or ILIKE based on dialect.
54
+ * MySQL/SQLite: LIKE is case-insensitive by default.
55
+ * PostgreSQL: needs ILIKE for case-insensitive.
56
+ */
57
+ export function getCaseInsensitiveLike(dialect) {
58
+ return dialect === 'postgresql' ? 'ilike' : 'like';
59
+ }
60
+ //# sourceMappingURL=dialect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.js","sourceRoot":"","sources":["../../../src/core/dialect.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,EAAO;IACnC,uDAAuD;IACvD,MAAM,WAAW,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAE/D,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACnG,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACzE,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpI,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,gEAAgE;IAChE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC3C,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,YAAY,CAAC;QAC7C,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QAC3C,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC/C,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,QAAQ,GAAiC;IAC7C,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAC9B,cAAc,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IACvC,YAAY,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC/C,SAAS,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC5C,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAChC,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpD,cAAc,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,OAAgB,EAAE,OAAe;IAC/D,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,qCAAqC;IAClE,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,+BAA+B;IACvE,OAAO,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,OAAO,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACrD,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { Column, SQL } from 'drizzle-orm';
2
+ import { TableConfig } from '../types/table';
3
+ /**
4
+ * Filters the selection to only include requested fields.
5
+ * Supports `?select=id,name,email` from URL.
6
+ */
7
+ export declare class FieldSelector {
8
+ /**
9
+ * Narrows down a selection object to only include the requested fields.
10
+ * If no fields requested, returns the original selection.
11
+ */
12
+ applyFieldSelection(selection: Record<string, SQL | Column>, requestedFields: string[] | undefined, config: TableConfig): Record<string, SQL | Column>;
13
+ /**
14
+ * Filters the response data to only include requested fields.
15
+ * Defense-in-depth — even if SELECT returns extra columns.
16
+ */
17
+ filterResponseFields(data: Record<string, unknown>[], requestedFields: string[] | undefined): Record<string, unknown>[];
18
+ }
19
+ //# sourceMappingURL=fieldSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldSelector.d.ts","sourceRoot":"","sources":["../../../src/core/fieldSelector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;GAGG;AACH,qBAAa,aAAa;IACxB;;;OAGG;IACH,mBAAmB,CACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,CAAC,EACvC,eAAe,EAAE,MAAM,EAAE,GAAG,SAAS,EACrC,MAAM,EAAE,WAAW,GAClB,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,CAAC;IA2B/B;;;OAGG;IACH,oBAAoB,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,eAAe,EAAE,MAAM,EAAE,GAAG,SAAS,GACpC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;CAe7B"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Filters the selection to only include requested fields.
3
+ * Supports `?select=id,name,email` from URL.
4
+ */
5
+ export class FieldSelector {
6
+ /**
7
+ * Narrows down a selection object to only include the requested fields.
8
+ * If no fields requested, returns the original selection.
9
+ */
10
+ applyFieldSelection(selection, requestedFields, config) {
11
+ if (!requestedFields?.length)
12
+ return selection;
13
+ // Build a whitelist of allowed (non-hidden) fields
14
+ const allowed = new Set(config.columns.filter((c) => !c.hidden).map((c) => c.name));
15
+ const filtered = {};
16
+ for (const field of requestedFields) {
17
+ if (allowed.has(field) && selection[field]) {
18
+ filtered[field] = selection[field];
19
+ }
20
+ }
21
+ // Always include primary key if it exists (needed for includes/relations)
22
+ if (selection['id'] && !filtered['id']) {
23
+ const idCol = config.columns.find((c) => c.name === 'id');
24
+ if (idCol && !idCol.hidden) {
25
+ filtered['id'] = selection['id'];
26
+ }
27
+ }
28
+ return Object.keys(filtered).length > 0 ? filtered : selection;
29
+ }
30
+ /**
31
+ * Filters the response data to only include requested fields.
32
+ * Defense-in-depth — even if SELECT returns extra columns.
33
+ */
34
+ filterResponseFields(data, requestedFields) {
35
+ if (!requestedFields?.length)
36
+ return data;
37
+ const fieldSet = new Set(requestedFields);
38
+ return data.map((row) => {
39
+ const filtered = {};
40
+ for (const field of fieldSet) {
41
+ if (field in row) {
42
+ filtered[field] = row[field];
43
+ }
44
+ }
45
+ return filtered;
46
+ });
47
+ }
48
+ }
49
+ //# sourceMappingURL=fieldSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldSelector.js","sourceRoot":"","sources":["../../../src/core/fieldSelector.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,OAAO,aAAa;IACxB;;;OAGG;IACH,mBAAmB,CACjB,SAAuC,EACvC,eAAqC,EACrC,MAAmB;QAEnB,IAAI,CAAC,eAAe,EAAE,MAAM;YAAE,OAAO,SAAS,CAAC;QAE/C,mDAAmD;QACnD,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAC3D,CAAC;QAEF,MAAM,QAAQ,GAAiC,EAAE,CAAC;QAElD,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;YACpC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3C,QAAQ,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAED,0EAA0E;QAC1E,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAC1D,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,CAAC;IAED;;;OAGG;IACH,oBAAoB,CAClB,IAA+B,EAC/B,eAAqC;QAErC,IAAI,CAAC,eAAe,EAAE,MAAM;YAAE,OAAO,IAAI,CAAC;QAE1C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,CAAC;QAE1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACtB,MAAM,QAAQ,GAA4B,EAAE,CAAC;YAC7C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;gBAC7B,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;oBACjB,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC/B,CAAC;YACH,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import { SQL } from 'drizzle-orm';
2
+ import { TableConfig } from '../types/table';
3
+ import { FilterParam } from '../types/engine';
4
+ export declare class FilterBuilder {
5
+ private schema;
6
+ constructor(schema: Record<string, unknown>);
7
+ /**
8
+ * Builds dynamic WHERE conditions from user-provided filter params.
9
+ * Only allows filtering on columns explicitly marked as filterable.
10
+ */
11
+ buildFilters(config: TableConfig, params: Record<string, FilterParam>): SQL | undefined;
12
+ /**
13
+ * Builds conditions for filters with type='static' (preset values in config).
14
+ */
15
+ buildStaticFilters(config: TableConfig): SQL | undefined;
16
+ /**
17
+ * Resolves a column reference. Supports "joinedTable.column" dot-syntax
18
+ * for columns on joined tables.
19
+ */
20
+ private resolveColumn;
21
+ }
22
+ //# sourceMappingURL=filterBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterBuilder.d.ts","sourceRoot":"","sources":["../../../src/core/filterBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,GAAG,EAGJ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAI9C,qBAAa,aAAa;IACZ,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEnD;;;OAGG;IACH,YAAY,CACV,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAClC,GAAG,GAAG,SAAS;IAyDlB;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,GAAG,SAAS;IAwBxD;;;OAGG;IACH,OAAO,CAAC,aAAa;CA4BtB"}
@@ -0,0 +1,112 @@
1
+ import { getTableColumns, and, } from 'drizzle-orm';
2
+ import { applyOperator } from '../utils/operators';
3
+ import { isDatePreset, buildDatePresetCondition } from './datePresets';
4
+ export class FilterBuilder {
5
+ schema;
6
+ constructor(schema) {
7
+ this.schema = schema;
8
+ }
9
+ /**
10
+ * Builds dynamic WHERE conditions from user-provided filter params.
11
+ * Only allows filtering on columns explicitly marked as filterable.
12
+ */
13
+ buildFilters(config, params) {
14
+ if (!params || Object.keys(params).length === 0) {
15
+ return undefined;
16
+ }
17
+ const table = this.schema[config.base];
18
+ if (!table)
19
+ return undefined;
20
+ const columns = getTableColumns(table);
21
+ // Build a whitelist of filterable field names
22
+ const filterableFields = new Set();
23
+ // From explicit dynamic filter definitions
24
+ if (config.filters) {
25
+ for (const f of config.filters) {
26
+ if (f.type !== 'static') {
27
+ filterableFields.add(f.field);
28
+ }
29
+ }
30
+ }
31
+ // From columns marked filterable (default true)
32
+ for (const col of config.columns) {
33
+ if (col.filterable !== false) {
34
+ filterableFields.add(col.name);
35
+ }
36
+ }
37
+ const conditions = [];
38
+ for (const [field, param] of Object.entries(params)) {
39
+ // Security: reject fields not in the whitelist
40
+ if (!filterableFields.has(field))
41
+ continue;
42
+ // Find the config for this field to resolve "field" property
43
+ const colConfig = config.columns.find(c => c.name === field);
44
+ const dbFieldName = colConfig?.field ?? field;
45
+ // Resolve the column — could be on the base table or a joined table
46
+ const col = this.resolveColumn(config, columns, dbFieldName);
47
+ if (!col)
48
+ continue;
49
+ // Check if value is a date preset
50
+ if (isDatePreset(param.value)) {
51
+ const presetCondition = buildDatePresetCondition(col, param.value);
52
+ if (presetCondition)
53
+ conditions.push(presetCondition);
54
+ continue;
55
+ }
56
+ const condition = applyOperator(param.operator, col, param.value);
57
+ if (condition)
58
+ conditions.push(condition);
59
+ }
60
+ return conditions.length > 0 ? and(...conditions) : undefined;
61
+ }
62
+ /**
63
+ * Builds conditions for filters with type='static' (preset values in config).
64
+ */
65
+ buildStaticFilters(config) {
66
+ if (!config.filters)
67
+ return undefined;
68
+ const table = this.schema[config.base];
69
+ if (!table)
70
+ return undefined;
71
+ const columns = getTableColumns(table);
72
+ const conditions = [];
73
+ for (const filter of config.filters) {
74
+ if (filter.type !== 'static' || filter.value === undefined)
75
+ continue;
76
+ const col = columns[filter.field];
77
+ if (!col)
78
+ continue;
79
+ // Default to 'eq' if not specified (though schema defaults it, z.input might miss it)
80
+ const op = filter.operator ?? 'eq';
81
+ const condition = applyOperator(op, col, filter.value);
82
+ if (condition)
83
+ conditions.push(condition);
84
+ }
85
+ return conditions.length > 0 ? and(...conditions) : undefined;
86
+ }
87
+ /**
88
+ * Resolves a column reference. Supports "joinedTable.column" dot-syntax
89
+ * for columns on joined tables.
90
+ */
91
+ resolveColumn(config, baseColumns, field) {
92
+ // Direct column on the base table
93
+ if (baseColumns[field]) {
94
+ return baseColumns[field];
95
+ }
96
+ // Dot-syntax for joined tables: "orders.total"
97
+ if (field.includes('.')) {
98
+ const [tableName, colName] = field.split('.');
99
+ const joinedTable = this.schema[tableName];
100
+ if (!joinedTable)
101
+ return undefined;
102
+ // Verify this table is actually joined
103
+ const isJoined = config.joins?.some((j) => j.table === tableName || j.alias === tableName);
104
+ if (!isJoined)
105
+ return undefined;
106
+ const joinedCols = getTableColumns(joinedTable);
107
+ return joinedCols[colName];
108
+ }
109
+ return undefined;
110
+ }
111
+ }
112
+ //# sourceMappingURL=filterBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterBuilder.js","sourceRoot":"","sources":["../../../src/core/filterBuilder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { SQL } from 'drizzle-orm';
2
+ import { FilterExpression, TableConfig } from '../types/table';
3
+ export declare class FilterGroupBuilder {
4
+ private schema;
5
+ constructor(schema: Record<string, unknown>);
6
+ /**
7
+ * Builds SQL from a filter expression tree.
8
+ * Supports arbitrary nesting of AND/OR groups.
9
+ *
10
+ * Example:
11
+ * { type: 'or', conditions: [
12
+ * { field: 'status', operator: 'eq', value: 'active' },
13
+ * { type: 'and', conditions: [
14
+ * { field: 'priority', operator: 'eq', value: 'high' },
15
+ * { field: 'total', operator: 'gt', value: 1000 },
16
+ * ]}
17
+ * ]}
18
+ * → (status = 'active' OR (priority = 'high' AND total > 1000))
19
+ */
20
+ build(expression: FilterExpression, config: TableConfig): SQL | undefined;
21
+ /**
22
+ * Builds SQL from an array of filter expressions (top-level AND).
23
+ */
24
+ buildAll(expressions: FilterExpression[], config: TableConfig): SQL | undefined;
25
+ private resolve;
26
+ private resolveCondition;
27
+ }
28
+ //# sourceMappingURL=filterGroupBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterGroupBuilder.d.ts","sourceRoot":"","sources":["../../../src/core/filterGroupBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,GAAG,EAIJ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAmB,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGhF,qBAAa,kBAAkB;IACjB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEnD;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,GAAG,GAAG,SAAS;IAQzE;;OAEG;IACH,QAAQ,CAAC,WAAW,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,GAAG,GAAG,SAAS;IAc/E,OAAO,CAAC,OAAO;IAqBf,OAAO,CAAC,gBAAgB;CAQzB"}
@@ -0,0 +1,73 @@
1
+ import { getTableColumns, and, or, } from 'drizzle-orm';
2
+ import { applyOperator } from '../utils/operators';
3
+ export class FilterGroupBuilder {
4
+ schema;
5
+ constructor(schema) {
6
+ this.schema = schema;
7
+ }
8
+ /**
9
+ * Builds SQL from a filter expression tree.
10
+ * Supports arbitrary nesting of AND/OR groups.
11
+ *
12
+ * Example:
13
+ * { type: 'or', conditions: [
14
+ * { field: 'status', operator: 'eq', value: 'active' },
15
+ * { type: 'and', conditions: [
16
+ * { field: 'priority', operator: 'eq', value: 'high' },
17
+ * { field: 'total', operator: 'gt', value: 1000 },
18
+ * ]}
19
+ * ]}
20
+ * → (status = 'active' OR (priority = 'high' AND total > 1000))
21
+ */
22
+ build(expression, config) {
23
+ const table = this.schema[config.base];
24
+ if (!table)
25
+ return undefined;
26
+ const columns = getTableColumns(table);
27
+ return this.resolve(expression, columns);
28
+ }
29
+ /**
30
+ * Builds SQL from an array of filter expressions (top-level AND).
31
+ */
32
+ buildAll(expressions, config) {
33
+ if (!expressions.length)
34
+ return undefined;
35
+ const parts = [];
36
+ for (const expr of expressions) {
37
+ const sql = this.build(expr, config);
38
+ if (sql)
39
+ parts.push(sql);
40
+ }
41
+ if (parts.length === 0)
42
+ return undefined;
43
+ if (parts.length === 1)
44
+ return parts[0];
45
+ return and(...parts);
46
+ }
47
+ resolve(expr, columns) {
48
+ // Leaf node: a single condition
49
+ if ('field' in expr && 'operator' in expr) {
50
+ return this.resolveCondition(expr, columns);
51
+ }
52
+ // Branch node: AND/OR group
53
+ const group = expr;
54
+ const parts = [];
55
+ for (const child of group.conditions) {
56
+ const childSql = this.resolve(child, columns);
57
+ if (childSql)
58
+ parts.push(childSql);
59
+ }
60
+ if (parts.length === 0)
61
+ return undefined;
62
+ if (parts.length === 1)
63
+ return parts[0];
64
+ return group.type === 'or' ? or(...parts) : and(...parts);
65
+ }
66
+ resolveCondition(condition, columns) {
67
+ const col = columns[condition.field];
68
+ if (!col)
69
+ return undefined;
70
+ return applyOperator(condition.operator, col, condition.value);
71
+ }
72
+ }
73
+ //# sourceMappingURL=filterGroupBuilder.js.map