@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 +62 -0
- package/dist/field_parser.service.d.ts +13 -0
- package/dist/field_parser.service.js +68 -0
- package/dist/interfaces/model_fields.interface.d.ts +5 -0
- package/dist/interfaces/model_fields.interface.js +2 -0
- package/dist/types/model.relationship.tree.type.d.ts +3 -0
- package/dist/types/model.relationship.tree.type.js +2 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Sequelize Field Parser Package
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
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;
|
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
|
+
}
|