sequelize-api-query-parser 0.0.0-stage → 0.1.0

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +234 -2
  3. package/dist/cjs/index.js +6 -0
  4. package/dist/cjs/index.js.map +1 -0
  5. package/dist/cjs/package.json +3 -0
  6. package/dist/cjs/parsers/include.js +111 -0
  7. package/dist/cjs/parsers/include.js.map +1 -0
  8. package/dist/cjs/parsers/order.js +49 -0
  9. package/dist/cjs/parsers/order.js.map +1 -0
  10. package/dist/cjs/parsers/search.js +85 -0
  11. package/dist/cjs/parsers/search.js.map +1 -0
  12. package/dist/cjs/parsers/select.js +29 -0
  13. package/dist/cjs/parsers/select.js.map +1 -0
  14. package/dist/cjs/parsers/where.js +173 -0
  15. package/dist/cjs/parsers/where.js.map +1 -0
  16. package/dist/cjs/query-parser.js +81 -0
  17. package/dist/cjs/query-parser.js.map +1 -0
  18. package/dist/cjs/types.js +3 -0
  19. package/dist/cjs/types.js.map +1 -0
  20. package/dist/cjs/utils/fields.js +76 -0
  21. package/dist/cjs/utils/fields.js.map +1 -0
  22. package/dist/cjs/utils/values.js +149 -0
  23. package/dist/cjs/utils/values.js.map +1 -0
  24. package/dist/esm/index.js +2 -0
  25. package/dist/esm/index.js.map +1 -0
  26. package/dist/esm/parsers/include.js +107 -0
  27. package/dist/esm/parsers/include.js.map +1 -0
  28. package/dist/esm/parsers/order.js +45 -0
  29. package/dist/esm/parsers/order.js.map +1 -0
  30. package/dist/esm/parsers/search.js +80 -0
  31. package/dist/esm/parsers/search.js.map +1 -0
  32. package/dist/esm/parsers/select.js +25 -0
  33. package/dist/esm/parsers/select.js.map +1 -0
  34. package/dist/esm/parsers/where.js +169 -0
  35. package/dist/esm/parsers/where.js.map +1 -0
  36. package/dist/esm/query-parser.js +77 -0
  37. package/dist/esm/query-parser.js.map +1 -0
  38. package/dist/esm/types.js +2 -0
  39. package/dist/esm/types.js.map +1 -0
  40. package/dist/esm/utils/fields.js +68 -0
  41. package/dist/esm/utils/fields.js.map +1 -0
  42. package/dist/esm/utils/values.js +138 -0
  43. package/dist/esm/utils/values.js.map +1 -0
  44. package/dist/types/index.d.ts +2 -0
  45. package/dist/types/parsers/include.d.ts +12 -0
  46. package/dist/types/parsers/order.d.ts +2 -0
  47. package/dist/types/parsers/search.d.ts +13 -0
  48. package/dist/types/parsers/select.d.ts +2 -0
  49. package/dist/types/parsers/where.d.ts +13 -0
  50. package/dist/types/query-parser.d.ts +7 -0
  51. package/dist/types/types.d.ts +52 -0
  52. package/dist/types/utils/fields.d.ts +12 -0
  53. package/dist/types/utils/values.d.ts +12 -0
  54. package/package.json +59 -4
@@ -0,0 +1,107 @@
1
+ import { isSafeIdentifier } from '../utils/fields.js';
2
+ import { isRecord, parseBoolean, parsePositiveInteger } from '../utils/values.js';
3
+ import { parseOrder } from './order.js';
4
+ import { parseSelection } from './select.js';
5
+ import { parseWhere } from './where.js';
6
+ const MAX_INCLUDE_DEPTH = 5;
7
+ const DEFAULT_MAX_LIMIT = 100;
8
+ const getAssociationTarget = (model, associationName) => {
9
+ if (!isSafeIdentifier(associationName)) {
10
+ return null;
11
+ }
12
+ const associations = model.associations;
13
+ if (!associations || !Object.prototype.hasOwnProperty.call(associations, associationName)) {
14
+ return null;
15
+ }
16
+ const target = associations[associationName]?.target;
17
+ return target && typeof target.getAttributes === 'function' ? target : null;
18
+ };
19
+ const parseOptionalPagination = (value, maxLimit) => {
20
+ if (value.limit === undefined) {
21
+ return {};
22
+ }
23
+ const limit = parsePositiveInteger(value.limit, 0, maxLimit);
24
+ if (limit === 0) {
25
+ return {};
26
+ }
27
+ const page = parsePositiveInteger(value.page, 1, Number.MAX_SAFE_INTEGER);
28
+ return {
29
+ limit,
30
+ offset: Math.min(limit * (page - 1), Number.MAX_SAFE_INTEGER),
31
+ };
32
+ };
33
+ const parseStringInclude = (associationName, target, value, isGroupBy) => ({
34
+ as: associationName,
35
+ model: target,
36
+ required: false,
37
+ attributes: isGroupBy ? [] : value === 'all' ? undefined : parseSelection(target, value),
38
+ });
39
+ export const parseIncludes = ({ input, model, isGroupBy = false, depth = 0, seen = new WeakSet(), associationPath = [], maxLimit = DEFAULT_MAX_LIMIT, }) => {
40
+ if (!isRecord(input) || depth >= MAX_INCLUDE_DEPTH || seen.has(input)) {
41
+ return [];
42
+ }
43
+ seen.add(input);
44
+ try {
45
+ const normalizedMaxLimit = parsePositiveInteger(maxLimit, DEFAULT_MAX_LIMIT, Number.MAX_SAFE_INTEGER);
46
+ const result = [];
47
+ for (const associationName of Object.keys(input)) {
48
+ const target = getAssociationTarget(model, associationName);
49
+ if (!target) {
50
+ continue;
51
+ }
52
+ const value = input[associationName];
53
+ if (typeof value === 'string') {
54
+ result.push(parseStringInclude(associationName, target, value, isGroupBy));
55
+ continue;
56
+ }
57
+ if (!isRecord(value)) {
58
+ continue;
59
+ }
60
+ const currentAssociationPath = [...associationPath, associationName];
61
+ const required = parseBoolean(value.required, false) ?? false;
62
+ const separate = parseBoolean(value.separate, false) ?? false;
63
+ const paranoid = parseBoolean(value.paranoid, true) ?? true;
64
+ const queryAssociationPath = separate ? [] : currentAssociationPath;
65
+ const parsedWhere = Object.prototype.hasOwnProperty.call(value, 'q')
66
+ ? parseWhere({ input: value.q, model: target, associationPath: queryAssociationPath }).where
67
+ : undefined;
68
+ const where = parsedWhere && Reflect.ownKeys(parsedWhere).length > 0 ? parsedWhere : undefined;
69
+ const order = typeof value.sort === 'string' && value.sort.trim()
70
+ ? parseOrder(target, value.sort)
71
+ : undefined;
72
+ const attributes = isGroupBy
73
+ ? []
74
+ : typeof value.select === 'string'
75
+ ? parseSelection(target, value.select)
76
+ : undefined;
77
+ const nestedIncludes = Object.prototype.hasOwnProperty.call(value, 'include')
78
+ ? parseIncludes({
79
+ input: value.include,
80
+ model: target,
81
+ isGroupBy,
82
+ depth: depth + 1,
83
+ seen,
84
+ associationPath: queryAssociationPath,
85
+ maxLimit: normalizedMaxLimit,
86
+ })
87
+ : undefined;
88
+ result.push({
89
+ as: associationName,
90
+ model: target,
91
+ required,
92
+ separate,
93
+ paranoid,
94
+ where,
95
+ order,
96
+ ...parseOptionalPagination(value, normalizedMaxLimit),
97
+ attributes,
98
+ include: nestedIncludes,
99
+ });
100
+ }
101
+ return result;
102
+ }
103
+ finally {
104
+ seen.delete(input);
105
+ }
106
+ };
107
+ //# sourceMappingURL=include.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"include.js","sourceRoot":"","sources":["../../../src/parsers/include.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Includeable, Model, ModelStatic, Order, WhereOptions } from 'sequelize';\n\nimport { isSafeIdentifier } from '../utils/fields.js';\nimport { isRecord, parseBoolean, parsePositiveInteger } from '../utils/values.js';\nimport { parseOrder } from './order.js';\nimport { parseSelection } from './select.js';\nimport { parseWhere } from './where.js';\n\nconst MAX_INCLUDE_DEPTH = 5;\nconst DEFAULT_MAX_LIMIT = 100;\n\ninterface ParseIncludesOptions {\n readonly input: unknown;\n readonly model: ModelStatic<Model>;\n readonly isGroupBy?: boolean;\n readonly depth?: number;\n readonly seen?: WeakSet<object>;\n readonly associationPath?: readonly string[];\n readonly maxLimit?: number;\n}\n\ninterface AssociationTarget {\n readonly target?: ModelStatic<Model>;\n}\n\ninterface ParsedInclude {\n readonly as: string;\n readonly model: ModelStatic<Model>;\n readonly required: boolean;\n readonly separate?: boolean;\n readonly paranoid?: boolean;\n readonly where?: WhereOptions;\n readonly order?: Order;\n readonly limit?: number;\n readonly offset?: number;\n readonly attributes?: string[];\n readonly include?: Includeable[];\n}\n\nconst getAssociationTarget = (\n model: ModelStatic<Model>,\n associationName: string,\n): ModelStatic<Model> | null => {\n if (!isSafeIdentifier(associationName)) {\n return null;\n }\n\n const associations = model.associations as Record<string, AssociationTarget> | undefined;\n if (!associations || !Object.prototype.hasOwnProperty.call(associations, associationName)) {\n return null;\n }\n\n const target = associations[associationName]?.target;\n return target && typeof target.getAttributes === 'function' ? target : null;\n};\n\nconst parseOptionalPagination = (value: Record<string, unknown>, maxLimit: number) => {\n if (value.limit === undefined) {\n return {};\n }\n\n const limit = parsePositiveInteger(value.limit, 0, maxLimit);\n if (limit === 0) {\n return {};\n }\n\n const page = parsePositiveInteger(value.page, 1, Number.MAX_SAFE_INTEGER);\n return {\n limit,\n offset: Math.min(limit * (page - 1), Number.MAX_SAFE_INTEGER),\n };\n};\n\nconst parseStringInclude = (\n associationName: string,\n target: ModelStatic<Model>,\n value: string,\n isGroupBy: boolean,\n): ParsedInclude => ({\n as: associationName,\n model: target,\n required: false,\n attributes: isGroupBy ? [] : value === 'all' ? undefined : parseSelection(target, value),\n});\n\nexport const parseIncludes = ({\n input,\n model,\n isGroupBy = false,\n depth = 0,\n seen = new WeakSet<object>(),\n associationPath = [],\n maxLimit = DEFAULT_MAX_LIMIT,\n}: ParseIncludesOptions): Includeable[] => {\n if (!isRecord(input) || depth >= MAX_INCLUDE_DEPTH || seen.has(input)) {\n return [];\n }\n\n seen.add(input);\n try {\n const normalizedMaxLimit = parsePositiveInteger(\n maxLimit,\n DEFAULT_MAX_LIMIT,\n Number.MAX_SAFE_INTEGER,\n );\n const result: Includeable[] = [];\n for (const associationName of Object.keys(input)) {\n const target = getAssociationTarget(model, associationName);\n if (!target) {\n continue;\n }\n\n const value = input[associationName];\n if (typeof value === 'string') {\n result.push(parseStringInclude(associationName, target, value, isGroupBy) as Includeable);\n continue;\n }\n if (!isRecord(value)) {\n continue;\n }\n\n const currentAssociationPath = [...associationPath, associationName];\n const required = parseBoolean(value.required, false) ?? false;\n const separate = parseBoolean(value.separate, false) ?? false;\n const paranoid = parseBoolean(value.paranoid, true) ?? true;\n const queryAssociationPath = separate ? [] : currentAssociationPath;\n const parsedWhere = Object.prototype.hasOwnProperty.call(value, 'q')\n ? parseWhere({ input: value.q, model: target, associationPath: queryAssociationPath }).where\n : undefined;\n const where = parsedWhere && Reflect.ownKeys(parsedWhere).length > 0 ? parsedWhere : undefined;\n const order = typeof value.sort === 'string' && value.sort.trim()\n ? parseOrder(target, value.sort)\n : undefined;\n const attributes = isGroupBy\n ? []\n : typeof value.select === 'string'\n ? parseSelection(target, value.select)\n : undefined;\n const nestedIncludes = Object.prototype.hasOwnProperty.call(value, 'include')\n ? parseIncludes({\n input: value.include,\n model: target,\n isGroupBy,\n depth: depth + 1,\n seen,\n associationPath: queryAssociationPath,\n maxLimit: normalizedMaxLimit,\n })\n : undefined;\n\n result.push({\n as: associationName,\n model: target,\n required,\n separate,\n paranoid,\n where,\n order,\n ...parseOptionalPagination(value, normalizedMaxLimit),\n attributes,\n include: nestedIncludes,\n } as Includeable);\n }\n return result;\n } finally {\n seen.delete(input);\n }\n};\n"]}
@@ -0,0 +1,45 @@
1
+ import { resolveField } from '../utils/fields.js';
2
+ const tokenizeSort = (input) => {
3
+ const tokens = [];
4
+ let token = '';
5
+ let bracketDepth = 0;
6
+ const pushToken = () => {
7
+ const trimmed = token.trim();
8
+ if (trimmed) {
9
+ tokens.push(trimmed);
10
+ }
11
+ token = '';
12
+ };
13
+ for (const character of input) {
14
+ if (character === ',' && bracketDepth === 0) {
15
+ pushToken();
16
+ continue;
17
+ }
18
+ token += character;
19
+ if (character === '[') {
20
+ bracketDepth += 1;
21
+ }
22
+ else if (character === ']') {
23
+ bracketDepth = Math.max(0, bracketDepth - 1);
24
+ }
25
+ }
26
+ pushToken();
27
+ return tokens;
28
+ };
29
+ const defaultOrder = (model) => resolveField(model, 'id') ? [['id', 'DESC']] : [];
30
+ export const parseOrder = (model, input) => {
31
+ if (typeof input !== 'string' || input.trim() === '') {
32
+ return defaultOrder(model);
33
+ }
34
+ const order = tokenizeSort(input).flatMap((token) => {
35
+ const descending = token.startsWith('-');
36
+ const fieldPath = descending ? token.slice(1) : token;
37
+ const resolved = resolveField(model, fieldPath);
38
+ if (!resolved) {
39
+ return [];
40
+ }
41
+ return [[...resolved.path, descending ? 'DESC' : 'ASC']];
42
+ });
43
+ return order.length > 0 ? order : defaultOrder(model);
44
+ };
45
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.js","sourceRoot":"","sources":["../../../src/parsers/order.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,YAAY,GAAG,CAAC,KAAa,EAAY,EAAE;IAC/C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;QACD,KAAK,GAAG,EAAE,CAAC;IACb,CAAC,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC9B,IAAI,SAAS,KAAK,GAAG,IAAI,YAAY,KAAK,CAAC,EAAE,CAAC;YAC5C,SAAS,EAAE,CAAC;YACZ,SAAS;QACX,CAAC;QAED,KAAK,IAAI,SAAS,CAAC;QACnB,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,YAAY,IAAI,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YAC7B,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,SAAS,EAAE,CAAC;IAEZ,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAyB,EAAS,EAAE,CACxD,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAEpD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAyB,EAAE,KAAc,EAAS,EAAE;IAC7E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QAClD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACtD,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAE,KAAe,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC,CAAC","sourcesContent":["import type { Model, ModelStatic, Order } from 'sequelize';\n\nimport { resolveField } from '../utils/fields.js';\n\nconst tokenizeSort = (input: string): string[] => {\n const tokens: string[] = [];\n let token = '';\n let bracketDepth = 0;\n\n const pushToken = () => {\n const trimmed = token.trim();\n if (trimmed) {\n tokens.push(trimmed);\n }\n token = '';\n };\n\n for (const character of input) {\n if (character === ',' && bracketDepth === 0) {\n pushToken();\n continue;\n }\n\n token += character;\n if (character === '[') {\n bracketDepth += 1;\n } else if (character === ']') {\n bracketDepth = Math.max(0, bracketDepth - 1);\n }\n }\n pushToken();\n\n return tokens;\n};\n\nconst defaultOrder = (model: ModelStatic<Model>): Order =>\n resolveField(model, 'id') ? [['id', 'DESC']] : [];\n\nexport const parseOrder = (model: ModelStatic<Model>, input: unknown): Order => {\n if (typeof input !== 'string' || input.trim() === '') {\n return defaultOrder(model);\n }\n\n const order = tokenizeSort(input).flatMap((token) => {\n const descending = token.startsWith('-');\n const fieldPath = descending ? token.slice(1) : token;\n const resolved = resolveField(model, fieldPath);\n if (!resolved) {\n return [];\n }\n\n return [[...resolved.path, descending ? 'DESC' : 'ASC']];\n });\n\n return order.length > 0 ? (order as Order) : defaultOrder(model);\n};\n"]}
@@ -0,0 +1,80 @@
1
+ import { Op, literal, where as sequelizeWhere } from 'sequelize';
2
+ import { getSqlFieldReference, resolveField } from '../utils/fields.js';
3
+ import { isValidDateText, isValidTimeZone, parseBoolean } from '../utils/values.js';
4
+ const STRING_TYPES = new Set(['CHAR', 'CITEXT', 'STRING', 'TEXT', 'VARCHAR']);
5
+ const DATE_TYPES = new Set(['DATE', 'DATEONLY', 'DATETIME']);
6
+ const getOutputKey = (path) => path.length > 1 ? `$${path.join('.')}$` : path[0] ?? '';
7
+ const parseSearchFields = (value) => {
8
+ if (typeof value !== 'string') {
9
+ return [];
10
+ }
11
+ return [...new Set(value.split(',').map((field) => field.trim()).filter(Boolean))];
12
+ };
13
+ const parseYesNo = (value) => {
14
+ const normalized = value.trim().toLowerCase();
15
+ if (normalized && 'yes'.includes(normalized)) {
16
+ return true;
17
+ }
18
+ if (normalized && 'no'.includes(normalized)) {
19
+ return false;
20
+ }
21
+ return null;
22
+ };
23
+ const buildTextCast = (reference, searchText) => sequelizeWhere(literal(`${reference}::text`), Op.iLike, `%${searchText}%`);
24
+ export const parseSearch = ({ searchText, searchFields, checkYesNo, timezone, model, columns = {}, }) => {
25
+ if (typeof searchText !== 'string' || !searchText.trim()) {
26
+ return null;
27
+ }
28
+ const fields = parseSearchFields(searchFields);
29
+ const clauses = [];
30
+ const yesNo = parseBoolean(checkYesNo, false) ? parseYesNo(searchText) : null;
31
+ const validTimezone = isValidTimeZone(timezone) ? timezone : undefined;
32
+ for (const field of fields) {
33
+ const resolved = resolveField(model, field);
34
+ if (!resolved) {
35
+ continue;
36
+ }
37
+ const typeName = resolved.typeName ?? columns[field]?.type;
38
+ const outputKey = getOutputKey(resolved.path);
39
+ if (typeName === 'BOOLEAN') {
40
+ if (yesNo !== null) {
41
+ clauses.push({ [outputKey]: yesNo });
42
+ }
43
+ continue;
44
+ }
45
+ if (STRING_TYPES.has(typeName ?? '')) {
46
+ clauses.push({ [outputKey]: { [Op.iLike]: `%${searchText}%` } });
47
+ continue;
48
+ }
49
+ const reference = getSqlFieldReference(model, resolved.path);
50
+ if (!reference) {
51
+ continue;
52
+ }
53
+ if (DATE_TYPES.has(typeName ?? '')) {
54
+ if (!isValidDateText(searchText)) {
55
+ continue;
56
+ }
57
+ const timezoneSql = validTimezone ? ` AT TIME ZONE '${validTimezone}'` : '';
58
+ clauses.push(sequelizeWhere(literal(`TO_CHAR(${reference}${timezoneSql}, 'MM/DD/YYYY')`), Op.iLike, `%${searchText}%`));
59
+ continue;
60
+ }
61
+ clauses.push(buildTextCast(reference, searchText));
62
+ }
63
+ return clauses.length > 0 ? { [Op.or]: clauses } : null;
64
+ };
65
+ export const mergeSearchWhere = (filterWhere, searchWhere) => {
66
+ if (!searchWhere) {
67
+ return filterWhere;
68
+ }
69
+ const filterRecord = filterWhere;
70
+ const searchRecord = searchWhere;
71
+ const filterOrValue = filterRecord[Op.or];
72
+ const searchOrValue = searchRecord[Op.or];
73
+ const filterOr = Array.isArray(filterOrValue) ? filterOrValue : [];
74
+ const searchOr = Array.isArray(searchOrValue) ? searchOrValue : [];
75
+ return {
76
+ ...filterRecord,
77
+ [Op.or]: [...filterOr, ...searchOr],
78
+ };
79
+ };
80
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/parsers/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { Op, literal, where as sequelizeWhere } from 'sequelize';\nimport type { Model, ModelStatic, WhereOptions } from 'sequelize';\n\nimport type { ColumnDetail } from '../types.js';\nimport { getSqlFieldReference, resolveField } from '../utils/fields.js';\nimport { isValidDateText, isValidTimeZone, parseBoolean } from '../utils/values.js';\n\nconst STRING_TYPES = new Set(['CHAR', 'CITEXT', 'STRING', 'TEXT', 'VARCHAR']);\nconst DATE_TYPES = new Set(['DATE', 'DATEONLY', 'DATETIME']);\n\ninterface ParseSearchOptions {\n readonly searchText: unknown;\n readonly searchFields: unknown;\n readonly checkYesNo?: unknown;\n readonly timezone?: unknown;\n readonly model: ModelStatic<Model>;\n readonly columns?: Readonly<Partial<Record<string, ColumnDetail>>>;\n}\n\nconst getOutputKey = (path: readonly string[]): string =>\n path.length > 1 ? `$${path.join('.')}$` : path[0] ?? '';\n\nconst parseSearchFields = (value: unknown): string[] => {\n if (typeof value !== 'string') {\n return [];\n }\n\n return [...new Set(value.split(',').map((field) => field.trim()).filter(Boolean))];\n};\n\nconst parseYesNo = (value: string): boolean | null => {\n const normalized = value.trim().toLowerCase();\n if (normalized && 'yes'.includes(normalized)) {\n return true;\n }\n if (normalized && 'no'.includes(normalized)) {\n return false;\n }\n return null;\n};\n\nconst buildTextCast = (reference: string, searchText: string) =>\n sequelizeWhere(literal(`${reference}::text`), Op.iLike, `%${searchText}%`);\n\nexport const parseSearch = ({\n searchText,\n searchFields,\n checkYesNo,\n timezone,\n model,\n columns = {},\n}: ParseSearchOptions): WhereOptions | null => {\n if (typeof searchText !== 'string' || !searchText.trim()) {\n return null;\n }\n\n const fields = parseSearchFields(searchFields);\n const clauses: unknown[] = [];\n const yesNo = parseBoolean(checkYesNo, false) ? parseYesNo(searchText) : null;\n const validTimezone = isValidTimeZone(timezone) ? timezone : undefined;\n\n for (const field of fields) {\n const resolved = resolveField(model, field);\n if (!resolved) {\n continue;\n }\n\n const typeName = resolved.typeName ?? columns[field]?.type;\n const outputKey = getOutputKey(resolved.path);\n if (typeName === 'BOOLEAN') {\n if (yesNo !== null) {\n clauses.push({ [outputKey]: yesNo });\n }\n continue;\n }\n\n if (STRING_TYPES.has(typeName ?? '')) {\n clauses.push({ [outputKey]: { [Op.iLike]: `%${searchText}%` } });\n continue;\n }\n\n const reference = getSqlFieldReference(model, resolved.path);\n if (!reference) {\n continue;\n }\n\n if (DATE_TYPES.has(typeName ?? '')) {\n if (!isValidDateText(searchText)) {\n continue;\n }\n\n const timezoneSql = validTimezone ? ` AT TIME ZONE '${validTimezone}'` : '';\n clauses.push(\n sequelizeWhere(\n literal(`TO_CHAR(${reference}${timezoneSql}, 'MM/DD/YYYY')`),\n Op.iLike,\n `%${searchText}%`,\n ),\n );\n continue;\n }\n\n clauses.push(buildTextCast(reference, searchText));\n }\n\n return clauses.length > 0 ? ({ [Op.or]: clauses } as WhereOptions) : null;\n};\n\nexport const mergeSearchWhere = (\n filterWhere: WhereOptions,\n searchWhere: WhereOptions | null,\n): WhereOptions => {\n if (!searchWhere) {\n return filterWhere;\n }\n\n const filterRecord = filterWhere as Record<PropertyKey, unknown>;\n const searchRecord = searchWhere as Record<PropertyKey, unknown>;\n const filterOrValue = filterRecord[Op.or];\n const searchOrValue = searchRecord[Op.or];\n const filterOr = Array.isArray(filterOrValue) ? filterOrValue : [];\n const searchOr = Array.isArray(searchOrValue) ? searchOrValue : [];\n\n return {\n ...filterRecord,\n [Op.or]: [...filterOr, ...searchOr],\n } as WhereOptions;\n};\n"]}
@@ -0,0 +1,25 @@
1
+ import { getAttributeNames, resolveField } from '../utils/fields.js';
2
+ const IDENTITY_FIELDS = ['id', 'deleted_at'];
3
+ export const parseSelection = (model, input) => {
4
+ if (typeof input !== 'string' || input.trim() === '' || input === 'all') {
5
+ return getAttributeNames(model);
6
+ }
7
+ if (input === 'notAll') {
8
+ return [];
9
+ }
10
+ const selected = [];
11
+ for (const candidate of input.split(',')) {
12
+ const field = candidate.trim();
13
+ const resolved = resolveField(model, field);
14
+ if (resolved?.path.length === 1 && !selected.includes(resolved.attribute)) {
15
+ selected.push(resolved.attribute);
16
+ }
17
+ }
18
+ for (const identityField of IDENTITY_FIELDS) {
19
+ if (resolveField(model, identityField) && !selected.includes(identityField)) {
20
+ selected.push(identityField);
21
+ }
22
+ }
23
+ return selected;
24
+ };
25
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/parsers/select.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAErE,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,YAAY,CAAU,CAAC;AAEtD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAyB,EAAE,KAAc,EAAY,EAAE;IACpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QACxE,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC5C,IAAI,QAAQ,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1E,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,KAAK,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC;QAC5C,IAAI,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YAC5E,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC","sourcesContent":["import type { Model, ModelStatic } from 'sequelize';\n\nimport { getAttributeNames, resolveField } from '../utils/fields.js';\n\nconst IDENTITY_FIELDS = ['id', 'deleted_at'] as const;\n\nexport const parseSelection = (model: ModelStatic<Model>, input: unknown): string[] => {\n if (typeof input !== 'string' || input.trim() === '' || input === 'all') {\n return getAttributeNames(model);\n }\n\n if (input === 'notAll') {\n return [];\n }\n\n const selected: string[] = [];\n for (const candidate of input.split(',')) {\n const field = candidate.trim();\n const resolved = resolveField(model, field);\n if (resolved?.path.length === 1 && !selected.includes(resolved.attribute)) {\n selected.push(resolved.attribute);\n }\n }\n\n for (const identityField of IDENTITY_FIELDS) {\n if (resolveField(model, identityField) && !selected.includes(identityField)) {\n selected.push(identityField);\n }\n }\n\n return selected;\n};\n"]}
@@ -0,0 +1,169 @@
1
+ import { Op, literal, where as sequelizeWhere } from 'sequelize';
2
+ import { getSqlFieldReference, resolveField } from '../utils/fields.js';
3
+ import { isRecord, isScalarValue, parseTypedList, parseTypedValue } from '../utils/values.js';
4
+ const MAX_LOGICAL_DEPTH = 5;
5
+ const OPERATOR_MAP = {
6
+ eq: Op.eq,
7
+ ne: Op.ne,
8
+ gt: Op.gt,
9
+ gte: Op.gte,
10
+ lt: Op.lt,
11
+ lte: Op.lte,
12
+ like: Op.like,
13
+ notLike: Op.notLike,
14
+ iLike: Op.iLike,
15
+ notILike: Op.notILike,
16
+ iEq: Op.iLike,
17
+ startsWith: Op.startsWith,
18
+ endsWith: Op.endsWith,
19
+ contains: Op.contains,
20
+ regexp: Op.regexp,
21
+ notRegexp: Op.notRegexp,
22
+ iRegexp: Op.iRegexp,
23
+ notIRegexp: Op.notIRegexp,
24
+ between: Op.between,
25
+ notBetween: Op.notBetween,
26
+ in: Op.in,
27
+ notIn: Op.notIn,
28
+ };
29
+ const LIST_OPERATORS = new Set(['between', 'notBetween', 'in', 'notIn']);
30
+ const JSON_TYPES = new Set(['JSON', 'JSONB', 'JSONTYPE']);
31
+ const getOperator = (name) => Object.prototype.hasOwnProperty.call(OPERATOR_MAP, name) ? OPERATOR_MAP[name] : undefined;
32
+ const hasEntries = (value) => Reflect.ownKeys(value).length > 0;
33
+ const fieldOutputKey = (path) => path.length > 1 ? `$${path.join('.')}$` : path[0] ?? '';
34
+ const convertOperatorValue = (operator, value, typeName) => {
35
+ const parsed = LIST_OPERATORS.has(operator) ? parseTypedList(value, typeName) : parseTypedValue(value, typeName);
36
+ if (!parsed.valid) {
37
+ return parsed;
38
+ }
39
+ if ((operator === 'iLike' || operator === 'notILike') && typeof parsed.value === 'string') {
40
+ return { valid: true, value: `%${parsed.value}%` };
41
+ }
42
+ return parsed;
43
+ };
44
+ const mergeAndExpressions = (result, expressions) => {
45
+ if (expressions.length === 0) {
46
+ return;
47
+ }
48
+ const existing = result[Op.and];
49
+ result[Op.and] = Array.isArray(existing) ? [...existing, ...expressions] : expressions;
50
+ };
51
+ const parseJsonOperators = (reference, input) => {
52
+ const expression = `${reference}::text`;
53
+ const clauses = [];
54
+ for (const [operator, rawValue] of Object.entries(input)) {
55
+ const sequelizeOperator = getOperator(operator);
56
+ if (!sequelizeOperator || LIST_OPERATORS.has(operator) || !isScalarValue(rawValue)) {
57
+ continue;
58
+ }
59
+ const parsed = convertOperatorValue(operator, rawValue);
60
+ if (!parsed.valid) {
61
+ continue;
62
+ }
63
+ const value = operator === 'eq' || operator === 'ne' ? JSON.stringify(parsed.value) : parsed.value;
64
+ clauses.push(sequelizeWhere(literal(expression), sequelizeOperator, value));
65
+ }
66
+ return clauses;
67
+ };
68
+ const parseArrayField = (outputKey, input, expressions) => {
69
+ const result = {};
70
+ const conditions = {};
71
+ for (const [operator, rawValue] of Object.entries(input)) {
72
+ if (!['contains', 'eq', 'ne'].includes(operator)) {
73
+ continue;
74
+ }
75
+ const parsed = Array.isArray(rawValue) ? parseTypedList(rawValue) : parseTypedValue(rawValue);
76
+ if (!parsed.valid) {
77
+ continue;
78
+ }
79
+ const values = Array.isArray(parsed.value) ? parsed.value : [parsed.value];
80
+ if (operator === 'ne') {
81
+ expressions.push({ [Op.not]: { [outputKey]: { [Op.contains]: values } } });
82
+ }
83
+ else {
84
+ conditions[Op.contains] = values;
85
+ }
86
+ }
87
+ if (Reflect.ownKeys(conditions).length > 0) {
88
+ result[outputKey] = conditions;
89
+ }
90
+ return result;
91
+ };
92
+ const parseRegularOperators = (input, typeName) => {
93
+ const result = {};
94
+ for (const [operator, rawValue] of Object.entries(input)) {
95
+ const sequelizeOperator = getOperator(operator);
96
+ if (!sequelizeOperator) {
97
+ continue;
98
+ }
99
+ const parsed = convertOperatorValue(operator, rawValue, typeName);
100
+ if (parsed.valid) {
101
+ result[sequelizeOperator] = parsed.value;
102
+ }
103
+ }
104
+ return result;
105
+ };
106
+ const parseObject = (input, context, depth) => {
107
+ if (!isRecord(input) || depth > MAX_LOGICAL_DEPTH) {
108
+ return {};
109
+ }
110
+ const result = {};
111
+ const expressions = [];
112
+ for (const key of Object.keys(input)) {
113
+ const rawValue = input[key];
114
+ if (key === 'and' || key === 'or' || key === 'not') {
115
+ const rawBranches = Array.isArray(rawValue) ? rawValue : [rawValue];
116
+ const branches = rawBranches
117
+ .map((branch) => parseObject(branch, context, depth + 1))
118
+ .filter(hasEntries);
119
+ if (branches.length === 0) {
120
+ continue;
121
+ }
122
+ if (key === 'not') {
123
+ result[Op.not] = branches.length === 1 ? branches[0] : { [Op.or]: branches };
124
+ }
125
+ else {
126
+ result[key === 'and' ? Op.and : Op.or] = branches;
127
+ }
128
+ continue;
129
+ }
130
+ const resolved = resolveField(context.model, key);
131
+ if (!resolved) {
132
+ continue;
133
+ }
134
+ if (Object.prototype.hasOwnProperty.call(context.columns, key)) {
135
+ context.fields.add(key);
136
+ }
137
+ const outputKey = fieldOutputKey(resolved.path);
138
+ if (isRecord(rawValue)) {
139
+ if (JSON_TYPES.has(resolved.typeName ?? '')) {
140
+ const reference = getSqlFieldReference(context.model, resolved.path, context.associationPath);
141
+ if (reference) {
142
+ expressions.push(...parseJsonOperators(reference, rawValue));
143
+ }
144
+ }
145
+ else if (resolved.typeName === 'ARRAY') {
146
+ Object.assign(result, parseArrayField(outputKey, rawValue, expressions));
147
+ }
148
+ else {
149
+ const operators = parseRegularOperators(rawValue, resolved.typeName);
150
+ if (Reflect.ownKeys(operators).length > 0) {
151
+ result[outputKey] = operators;
152
+ }
153
+ }
154
+ continue;
155
+ }
156
+ const parsed = parseTypedValue(rawValue, resolved.typeName);
157
+ if (parsed.valid) {
158
+ result[outputKey] = parsed.value;
159
+ }
160
+ }
161
+ mergeAndExpressions(result, expressions);
162
+ return result;
163
+ };
164
+ export const parseWhere = ({ input, model, columns = {}, associationPath = [] }) => {
165
+ const fields = new Set();
166
+ const where = parseObject(input, { model, columns, fields, associationPath }, 0);
167
+ return { where: where, whereFields: [...fields] };
168
+ };
169
+ //# sourceMappingURL=where.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"where.js","sourceRoot":"","sources":["../../../src/parsers/where.ts"],"names":[],"mappings":"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","sourcesContent":["import { Op, literal, where as sequelizeWhere } from 'sequelize';\nimport type { Model, ModelStatic, WhereOptions } from 'sequelize';\n\nimport type { ColumnDetail } from '../types.js';\nimport { getSqlFieldReference, resolveField } from '../utils/fields.js';\nimport { isRecord, isScalarValue, parseTypedList, parseTypedValue } from '../utils/values.js';\n\nconst MAX_LOGICAL_DEPTH = 5;\n\nconst OPERATOR_MAP: Readonly<Record<string, symbol>> = {\n eq: Op.eq,\n ne: Op.ne,\n gt: Op.gt,\n gte: Op.gte,\n lt: Op.lt,\n lte: Op.lte,\n like: Op.like,\n notLike: Op.notLike,\n iLike: Op.iLike,\n notILike: Op.notILike,\n iEq: Op.iLike,\n startsWith: Op.startsWith,\n endsWith: Op.endsWith,\n contains: Op.contains,\n regexp: Op.regexp,\n notRegexp: Op.notRegexp,\n iRegexp: Op.iRegexp,\n notIRegexp: Op.notIRegexp,\n between: Op.between,\n notBetween: Op.notBetween,\n in: Op.in,\n notIn: Op.notIn,\n};\n\nconst LIST_OPERATORS = new Set(['between', 'notBetween', 'in', 'notIn']);\nconst JSON_TYPES = new Set(['JSON', 'JSONB', 'JSONTYPE']);\n\nconst getOperator = (name: string): symbol | undefined =>\n Object.prototype.hasOwnProperty.call(OPERATOR_MAP, name) ? OPERATOR_MAP[name] : undefined;\n\ninterface ParseWhereOptions {\n readonly input: unknown;\n readonly model: ModelStatic<Model>;\n readonly columns?: Readonly<Partial<Record<string, ColumnDetail>>>;\n readonly associationPath?: readonly string[];\n}\n\ninterface ParserContext {\n readonly model: ModelStatic<Model>;\n readonly columns: Readonly<Partial<Record<string, ColumnDetail>>>;\n readonly fields: Set<string>;\n readonly associationPath: readonly string[];\n}\n\nconst hasEntries = (value: Record<PropertyKey, unknown>): boolean => Reflect.ownKeys(value).length > 0;\n\nconst fieldOutputKey = (path: readonly string[]): string =>\n path.length > 1 ? `$${path.join('.')}$` : path[0] ?? '';\n\nconst convertOperatorValue = (operator: string, value: unknown, typeName?: string) => {\n const parsed = LIST_OPERATORS.has(operator) ? parseTypedList(value, typeName) : parseTypedValue(value, typeName);\n if (!parsed.valid) {\n return parsed;\n }\n\n if ((operator === 'iLike' || operator === 'notILike') && typeof parsed.value === 'string') {\n return { valid: true, value: `%${parsed.value}%` };\n }\n\n return parsed;\n};\n\nconst mergeAndExpressions = (result: Record<PropertyKey, unknown>, expressions: unknown[]) => {\n if (expressions.length === 0) {\n return;\n }\n\n const existing = result[Op.and];\n result[Op.and] = Array.isArray(existing) ? [...existing, ...expressions] : expressions;\n};\n\nconst parseJsonOperators = (\n reference: string,\n input: Record<string, unknown>,\n): unknown[] => {\n const expression = `${reference}::text`;\n const clauses: unknown[] = [];\n for (const [operator, rawValue] of Object.entries(input)) {\n const sequelizeOperator = getOperator(operator);\n if (!sequelizeOperator || LIST_OPERATORS.has(operator) || !isScalarValue(rawValue)) {\n continue;\n }\n\n const parsed = convertOperatorValue(operator, rawValue);\n if (!parsed.valid) {\n continue;\n }\n\n const value = operator === 'eq' || operator === 'ne' ? JSON.stringify(parsed.value) : parsed.value;\n clauses.push(sequelizeWhere(literal(expression), sequelizeOperator as any, value));\n }\n\n return clauses;\n};\n\nconst parseArrayField = (\n outputKey: string,\n input: Record<string, unknown>,\n expressions: unknown[],\n): Record<PropertyKey, unknown> => {\n const result: Record<PropertyKey, unknown> = {};\n const conditions: Record<symbol, unknown> = {};\n\n for (const [operator, rawValue] of Object.entries(input)) {\n if (!['contains', 'eq', 'ne'].includes(operator)) {\n continue;\n }\n\n const parsed = Array.isArray(rawValue) ? parseTypedList(rawValue) : parseTypedValue(rawValue);\n if (!parsed.valid) {\n continue;\n }\n const values = Array.isArray(parsed.value) ? parsed.value : [parsed.value];\n\n if (operator === 'ne') {\n expressions.push({ [Op.not]: { [outputKey]: { [Op.contains]: values } } });\n } else {\n conditions[Op.contains] = values;\n }\n }\n\n if (Reflect.ownKeys(conditions).length > 0) {\n result[outputKey] = conditions;\n }\n return result;\n};\n\nconst parseRegularOperators = (\n input: Record<string, unknown>,\n typeName?: string,\n): Record<symbol, unknown> => {\n const result: Record<symbol, unknown> = {};\n for (const [operator, rawValue] of Object.entries(input)) {\n const sequelizeOperator = getOperator(operator);\n if (!sequelizeOperator) {\n continue;\n }\n\n const parsed = convertOperatorValue(operator, rawValue, typeName);\n if (parsed.valid) {\n result[sequelizeOperator] = parsed.value;\n }\n }\n return result;\n};\n\nconst parseObject = (\n input: unknown,\n context: ParserContext,\n depth: number,\n): Record<PropertyKey, unknown> => {\n if (!isRecord(input) || depth > MAX_LOGICAL_DEPTH) {\n return {};\n }\n\n const result: Record<PropertyKey, unknown> = {};\n const expressions: unknown[] = [];\n\n for (const key of Object.keys(input)) {\n const rawValue = input[key];\n if (key === 'and' || key === 'or' || key === 'not') {\n const rawBranches = Array.isArray(rawValue) ? rawValue : [rawValue];\n const branches = rawBranches\n .map((branch) => parseObject(branch, context, depth + 1))\n .filter(hasEntries);\n\n if (branches.length === 0) {\n continue;\n }\n\n if (key === 'not') {\n result[Op.not] = branches.length === 1 ? branches[0] : { [Op.or]: branches };\n } else {\n result[key === 'and' ? Op.and : Op.or] = branches;\n }\n continue;\n }\n\n const resolved = resolveField(context.model, key);\n if (!resolved) {\n continue;\n }\n\n if (Object.prototype.hasOwnProperty.call(context.columns, key)) {\n context.fields.add(key);\n }\n\n const outputKey = fieldOutputKey(resolved.path);\n if (isRecord(rawValue)) {\n if (JSON_TYPES.has(resolved.typeName ?? '')) {\n const reference = getSqlFieldReference(context.model, resolved.path, context.associationPath);\n if (reference) {\n expressions.push(...parseJsonOperators(reference, rawValue));\n }\n } else if (resolved.typeName === 'ARRAY') {\n Object.assign(result, parseArrayField(outputKey, rawValue, expressions));\n } else {\n const operators = parseRegularOperators(rawValue, resolved.typeName);\n if (Reflect.ownKeys(operators).length > 0) {\n result[outputKey] = operators;\n }\n }\n continue;\n }\n\n const parsed = parseTypedValue(rawValue, resolved.typeName);\n if (parsed.valid) {\n result[outputKey] = parsed.value;\n }\n }\n\n mergeAndExpressions(result, expressions);\n return result;\n};\n\nexport const parseWhere = ({ input, model, columns = {}, associationPath = [] }: ParseWhereOptions) => {\n const fields = new Set<string>();\n const where = parseObject(input, { model, columns, fields, associationPath }, 0);\n return { where: where as WhereOptions, whereFields: [...fields] };\n};\n"]}
@@ -0,0 +1,77 @@
1
+ import { parseIncludes } from './parsers/include.js';
2
+ import { parseOrder } from './parsers/order.js';
3
+ import { mergeSearchWhere, parseSearch } from './parsers/search.js';
4
+ import { parseSelection } from './parsers/select.js';
5
+ import { parseWhere } from './parsers/where.js';
6
+ import { isRecord, parseBoolean, parsePositiveInteger } from './utils/values.js';
7
+ const DEFAULT_LIMIT = 10;
8
+ const DEFAULT_MAX_LIMIT = 100;
9
+ const isSequelizeModel = (value) => {
10
+ if ((typeof value !== 'object' || value === null) && typeof value !== 'function') {
11
+ return false;
12
+ }
13
+ return typeof value.getAttributes === 'function';
14
+ };
15
+ export class QueryParser {
16
+ #options;
17
+ #maxLimit;
18
+ constructor(options) {
19
+ if (!options || !isRecord(options.request)) {
20
+ throw new TypeError('QueryParser requires a request object');
21
+ }
22
+ if (!isSequelizeModel(options.model)) {
23
+ throw new TypeError('QueryParser requires a Sequelize model');
24
+ }
25
+ this.#options = options;
26
+ this.#maxLimit = parsePositiveInteger(options.maxLimit, DEFAULT_MAX_LIMIT, Number.MAX_SAFE_INTEGER);
27
+ }
28
+ getFullQuery(target = {}) {
29
+ const query = this.#getQuery();
30
+ const limit = parsePositiveInteger(query.limit, DEFAULT_LIMIT, this.#maxLimit);
31
+ const page = parsePositiveInteger(query.page, 1, Number.MAX_SAFE_INTEGER);
32
+ const offset = Math.min(limit * (page - 1), Number.MAX_SAFE_INTEGER);
33
+ const parsedWhere = parseWhere({
34
+ input: query.q,
35
+ model: this.#options.model,
36
+ columns: this.#options.columns,
37
+ });
38
+ const searchWhere = parseSearch({
39
+ searchText: query.searchText,
40
+ searchFields: query.searchFields,
41
+ checkYesNo: query.checkYesNo,
42
+ timezone: this.#getHeader('client-tz'),
43
+ model: this.#options.model,
44
+ columns: this.#options.columns,
45
+ });
46
+ return {
47
+ ...target,
48
+ where: mergeSearchWhere(parsedWhere.where, searchWhere),
49
+ order: parseOrder(this.#options.model, query.sort),
50
+ limit,
51
+ offset,
52
+ include: parseIncludes({
53
+ input: query.include,
54
+ model: this.#options.model,
55
+ isGroupBy: this.#options.isGroupBy,
56
+ maxLimit: this.#maxLimit,
57
+ }),
58
+ attributes: parseSelection(this.#options.model, query.select),
59
+ subQuery: parseBoolean(query.subQuery),
60
+ paranoid: parseBoolean(query.paranoid),
61
+ whereFields: parsedWhere.whereFields,
62
+ };
63
+ }
64
+ #getQuery() {
65
+ return isRecord(this.#options.request.query) ? this.#options.request.query : {};
66
+ }
67
+ #getHeader(name) {
68
+ const headers = this.#options.request.headers;
69
+ if (!headers) {
70
+ return undefined;
71
+ }
72
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name);
73
+ const value = entry?.[1];
74
+ return typeof value === 'string' ? value : value?.[0];
75
+ }
76
+ }
77
+ //# sourceMappingURL=query-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-parser.js","sourceRoot":"","sources":["../../src/query-parser.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Model } from 'sequelize';\n\nimport { parseIncludes } from './parsers/include.js';\nimport { parseOrder } from './parsers/order.js';\nimport { mergeSearchWhere, parseSearch } from './parsers/search.js';\nimport { parseSelection } from './parsers/select.js';\nimport { parseWhere } from './parsers/where.js';\nimport type { ParsedQuery, QueryParserOptions, QueryParserQuery } from './types.js';\nimport { isRecord, parseBoolean, parsePositiveInteger } from './utils/values.js';\n\nconst DEFAULT_LIMIT = 10;\nconst DEFAULT_MAX_LIMIT = 100;\n\nconst isSequelizeModel = (value: unknown): boolean => {\n if ((typeof value !== 'object' || value === null) && typeof value !== 'function') {\n return false;\n }\n\n return typeof (value as { getAttributes?: unknown }).getAttributes === 'function';\n};\n\nexport class QueryParser<M extends Model> {\n readonly #options: QueryParserOptions<M>;\n readonly #maxLimit: number;\n\n constructor(options: QueryParserOptions<M>) {\n if (!options || !isRecord(options.request)) {\n throw new TypeError('QueryParser requires a request object');\n }\n\n if (!isSequelizeModel(options.model)) {\n throw new TypeError('QueryParser requires a Sequelize model');\n }\n\n this.#options = options;\n this.#maxLimit = parsePositiveInteger(options.maxLimit, DEFAULT_MAX_LIMIT, Number.MAX_SAFE_INTEGER);\n }\n\n getFullQuery(target: Record<string, unknown> = {}): ParsedQuery {\n const query = this.#getQuery();\n const limit = parsePositiveInteger(query.limit, DEFAULT_LIMIT, this.#maxLimit);\n const page = parsePositiveInteger(query.page, 1, Number.MAX_SAFE_INTEGER);\n const offset = Math.min(limit * (page - 1), Number.MAX_SAFE_INTEGER);\n const parsedWhere = parseWhere({\n input: query.q,\n model: this.#options.model,\n columns: this.#options.columns,\n });\n const searchWhere = parseSearch({\n searchText: query.searchText,\n searchFields: query.searchFields,\n checkYesNo: query.checkYesNo,\n timezone: this.#getHeader('client-tz'),\n model: this.#options.model,\n columns: this.#options.columns,\n });\n\n return {\n ...target,\n where: mergeSearchWhere(parsedWhere.where, searchWhere),\n order: parseOrder(this.#options.model, query.sort),\n limit,\n offset,\n include: parseIncludes({\n input: query.include,\n model: this.#options.model,\n isGroupBy: this.#options.isGroupBy,\n maxLimit: this.#maxLimit,\n }),\n attributes: parseSelection(this.#options.model, query.select),\n subQuery: parseBoolean(query.subQuery),\n paranoid: parseBoolean(query.paranoid),\n whereFields: parsedWhere.whereFields,\n };\n }\n\n #getQuery(): QueryParserQuery {\n return isRecord(this.#options.request.query) ? this.#options.request.query : {};\n }\n\n #getHeader(name: string): string | undefined {\n const headers = this.#options.request.headers;\n if (!headers) {\n return undefined;\n }\n\n const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name);\n const value = entry?.[1];\n return typeof value === 'string' ? value : value?.[0];\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Includeable, Model, ModelStatic, Order, WhereOptions } from 'sequelize';\n\nexport type QueryParserQuery = Record<string, unknown> & {\n checkYesNo?: unknown;\n include?: unknown;\n limit?: unknown;\n page?: unknown;\n paranoid?: unknown;\n q?: unknown;\n searchFields?: unknown;\n searchText?: unknown;\n select?: unknown;\n sort?: unknown;\n subQuery?: unknown;\n};\n\nexport interface QueryParserRequest {\n readonly query?: QueryParserQuery;\n readonly headers?: Readonly<Record<string, string | readonly string[] | undefined>>;\n}\n\nexport interface ColumnDetail {\n readonly displayName?: string;\n readonly fieldName: string;\n readonly type: string;\n readonly show?: boolean;\n readonly custom?: boolean;\n readonly default?: boolean;\n readonly width?: number;\n readonly minWidth?: number;\n readonly isPinned?: boolean;\n readonly include?: Readonly<Record<string, unknown>>;\n readonly searchKeys?: readonly string[];\n readonly tableName?: string;\n readonly foreignKey?: string;\n readonly fieldType?: 'column' | 'association' | 'filter';\n}\n\nexport interface QueryParserOptions<M extends Model = Model> {\n readonly request: QueryParserRequest;\n readonly model: ModelStatic<M>;\n readonly columns?: Readonly<Partial<Record<string, ColumnDetail>>>;\n readonly isGroupBy?: boolean;\n readonly maxLimit?: number;\n}\n\nexport type ParsedQuery = Record<string, unknown> & {\n readonly where: WhereOptions;\n readonly order: Order;\n readonly limit: number;\n readonly offset: number;\n readonly include: Includeable[];\n readonly attributes: string[];\n readonly subQuery: boolean | undefined;\n readonly paranoid: boolean | undefined;\n readonly whereFields: string[];\n};\n"]}