@ealforque/sequelize-field-parser 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Sequelize Field Parser Package
2
+
3
+ ![npm version](https://img.shields.io/npm/v/sequelize-field-parser)
4
+ ![build](https://img.shields.io/github/workflow/status/ealforque/sequelize-field-parser/CI)
5
+
6
+ ## Description
7
+
8
+ A TypeScript utility for Sequelize models that lets users specify fields to include in queries and automatically builds the `include` parameter for Sequelize. It simplifies selecting fields and relationships, making complex query construction and model association management more efficient.
9
+
10
+ ## Features
11
+
12
+ - Parse Sequelize model fields and relationships
13
+ - Generate field trees for complex models
14
+ - Type-safe interfaces and types
15
+ - Easy integration with MySQL via Sequelize
16
+ - Test-driven development with Jest
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install ealforque/sequelize-field-parser
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Import and use in your project:
27
+
28
+ ```typescript
29
+ import FieldParserService from "sequelize-field-parser";
30
+ import Status from "./path/to/status.model";
31
+
32
+ const parser = new FieldParserService();
33
+
34
+ // query parameter
35
+ // api/resource?fields='status.uuid,status.name,status.category.uuid,status.category.name'
36
+ const queryParams =
37
+ "status.uuid,status.name,status.category.uuid,status.category.name";
38
+
39
+ // Parse the query parameter
40
+ const { columns, relationshipTree } = parser.parseFields(queryParams, Model);
41
+
42
+ // Build the sequelize include
43
+ const include = parser.buildSequelizeInclude(relationshipTree, Model);
44
+
45
+ console.log(include);
46
+ /* Example output:
47
+ [
48
+ {
49
+ model: Status,
50
+ as: 'status',
51
+ attributes: ['uuid', 'name'],
52
+ include: [
53
+ {
54
+ model: Category,
55
+ as: 'category',
56
+ attributes: ['uuid', 'name'],
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ */
62
+ ```
@@ -0,0 +1,13 @@
1
+ import { IncludeOptions, Model, ModelStatic } from "sequelize";
2
+ import ModelStaticWithFields from "./interfaces/model_fields.interface";
3
+ import { RelationshipTree } from "./types/model.relationship.tree.type";
4
+ declare class FieldParserService {
5
+ private fields;
6
+ constructor();
7
+ parseFields: <M extends Model>(fields: string, model: ModelStaticWithFields<M>) => {
8
+ columns: string[];
9
+ relationshipTree: RelationshipTree;
10
+ };
11
+ buildSequelizeInclude: <M extends Model>(tree: RelationshipTree, model: ModelStatic<M>) => IncludeOptions[];
12
+ }
13
+ export default FieldParserService;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class FieldParserService {
4
+ fields;
5
+ constructor() {
6
+ this.fields = [];
7
+ }
8
+ parseFields = (fields, model) => {
9
+ this.fields = fields
10
+ .split(",")
11
+ .map((f) => f.trim())
12
+ .filter((f) => f.length > 0);
13
+ const relationshipTree = {};
14
+ const columns = [...model.DEFAULT_FIELDS];
15
+ const selectableFields = model.SELECTABLE_FIELDS;
16
+ for (const field of this.fields) {
17
+ const segments = field.split(".");
18
+ if (segments.length === 1) {
19
+ const topField = segments[0];
20
+ if (selectableFields.includes(topField)) {
21
+ columns.push(topField);
22
+ }
23
+ }
24
+ else {
25
+ let relationship = relationshipTree;
26
+ for (let i = 0; i < segments.length; i++) {
27
+ const field = segments[i];
28
+ if (!relationship[field]) {
29
+ relationship[field] = i === segments.length - 1 ? true : {};
30
+ }
31
+ if (relationship[field] !== true) {
32
+ relationship = relationship[field];
33
+ }
34
+ }
35
+ }
36
+ }
37
+ return { columns, relationshipTree };
38
+ };
39
+ buildSequelizeInclude = (tree, model) => {
40
+ const includeOptions = Object.entries(tree).flatMap(([relation, nested]) => {
41
+ const relationship = model.associations?.[relation];
42
+ if (!relationship || !relationship.target)
43
+ return [];
44
+ const currentModel = relationship.target;
45
+ const selectableFields = currentModel.SELECTABLE_FIELDS;
46
+ if (nested === true) {
47
+ return {
48
+ model: currentModel,
49
+ as: relation,
50
+ attributes: [],
51
+ };
52
+ }
53
+ const deeperIncludes = this.buildSequelizeInclude(nested, currentModel);
54
+ const leafAttributes = Object.entries(nested)
55
+ .filter(([, value]) => value === true)
56
+ .map(([key]) => key)
57
+ .filter((attr) => selectableFields.includes(attr));
58
+ return {
59
+ model: currentModel,
60
+ as: relation,
61
+ attributes: leafAttributes,
62
+ include: deeperIncludes,
63
+ };
64
+ });
65
+ return includeOptions;
66
+ };
67
+ }
68
+ exports.default = FieldParserService;
@@ -0,0 +1,5 @@
1
+ import { Model, ModelStatic } from "sequelize";
2
+ export default interface ModelStaticWithFields<T extends Model> extends ModelStatic<T> {
3
+ DEFAULT_FIELDS: string[];
4
+ SELECTABLE_FIELDS: string[];
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export type RelationshipTree = {
2
+ [key: string]: RelationshipTree | true;
3
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@ealforque/sequelize-field-parser",
3
+ "version": "1.0.1",
4
+ "main": "dist/field_parser.service.js",
5
+ "types": "dist/field_parser.service.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "rm -rf dist && tsc",
11
+ "test": "jest --clearCache && jest --coverage --detectOpenHandles",
12
+ "format": "prettier --write .",
13
+ "lint": "eslint .",
14
+ "lint:fix": "eslint . --fix"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "dependencies": {
20
+ "sequelize": "^6.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/jest": "^29.5.14",
24
+ "eslint": "^9.26.0",
25
+ "eslint-config-prettier": "^10.1.3",
26
+ "eslint-plugin-import": "^2.31.0",
27
+ "eslint-plugin-simple-import-sort": "^12.1.1",
28
+ "http-status-codes": "^2.3.0",
29
+ "jest": "^29.7.0",
30
+ "prettier": "^3.5.3",
31
+ "sequelize-cli": "^6.6.2",
32
+ "supertest": "^7.1.0",
33
+ "ts-jest": "^29.3.2",
34
+ "ts-node": "^10.9.2",
35
+ "typescript": "^5.8.3",
36
+ "typescript-eslint": "^8.32.0"
37
+ }
38
+ }