@ackplus/nest-crud-request 0.1.51 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -130
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/{src → dist}/lib/query-builder.d.ts +1 -0
- package/dist/lib/query-builder.d.ts.map +1 -0
- package/{src → dist}/lib/query-builder.js +23 -16
- package/{src → dist}/lib/relation-builder.d.ts +1 -0
- package/dist/lib/relation-builder.d.ts.map +1 -0
- package/{src → dist}/lib/relation-builder.js +2 -6
- package/dist/lib/types.d.ts +53 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +31 -0
- package/{src → dist}/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +11 -0
- package/{src → dist}/lib/where-builder.d.ts +4 -0
- package/dist/lib/where-builder.d.ts.map +1 -0
- package/{src → dist}/lib/where-builder.js +30 -13
- package/eslint.config.mjs +22 -0
- package/jest.config.ts +10 -0
- package/package.json +2 -20
- package/project.json +46 -0
- package/src/index.ts +3 -0
- package/src/lib/query-builder.ts +189 -0
- package/src/lib/relation-builder.ts +68 -0
- package/src/lib/types.js +1 -0
- package/src/lib/types.js.map +1 -0
- package/src/lib/types.ts +61 -0
- package/src/lib/utils.ts +11 -0
- package/src/lib/where-builder.ts +159 -0
- package/src/test/query-builder-where.spec.ts +173 -0
- package/src/test/query-builder.spec.ts +140 -0
- package/src/test/relation-builder.spec.ts +32 -0
- package/src/test/where-builder-complex.spec.ts +173 -0
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +15 -0
- package/src/index.js +0 -6
- package/src/lib/utils.js +0 -11
- package/tsconfig.tsbuildinfo +0 -1
- /package/{src/index.d.ts → dist/index.js} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { QueryBuilder } from '../lib/query-builder';
|
|
2
|
+
import { OrderDirectionEnum } from '../lib/types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('QueryBuilder', () => {
|
|
6
|
+
let queryBuilder: QueryBuilder;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
queryBuilder = new QueryBuilder({});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should set options', () => {
|
|
13
|
+
const options = { select: ['name', 'age'] };
|
|
14
|
+
queryBuilder.setOptions(options);
|
|
15
|
+
expect(queryBuilder.toObject(true).select).toEqual(options.select);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should merge options shallowly', () => {
|
|
19
|
+
queryBuilder.setOptions({ select: ['name'] });
|
|
20
|
+
queryBuilder.mergeOptions({ select: ['age'] });
|
|
21
|
+
expect(queryBuilder.toObject(true).select).toEqual(['age']);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should merge options deeply', () => {
|
|
25
|
+
queryBuilder.setOptions({ where: { age: { $gt: 18 } } });
|
|
26
|
+
queryBuilder.mergeOptions({ where: { name: { $eq: 'John' } } }, true);
|
|
27
|
+
expect(queryBuilder.toObject(true).where).toEqual({
|
|
28
|
+
age: { $gt: 18 },
|
|
29
|
+
name: { $eq: 'John' },
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should add select fields', () => {
|
|
34
|
+
queryBuilder.addSelect('name');
|
|
35
|
+
queryBuilder.addSelect(['age', 'email']);
|
|
36
|
+
expect(queryBuilder.toObject(true).select).toEqual([
|
|
37
|
+
'name',
|
|
38
|
+
'age',
|
|
39
|
+
'email',
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should remove select fields', () => {
|
|
44
|
+
queryBuilder.addSelect([
|
|
45
|
+
'name',
|
|
46
|
+
'age',
|
|
47
|
+
'email',
|
|
48
|
+
]);
|
|
49
|
+
queryBuilder.removeSelect('age');
|
|
50
|
+
expect(queryBuilder.toObject(true).select).toEqual(['name', 'email']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should add and remove relations as array of objects', () => {
|
|
54
|
+
queryBuilder.addRelation('profile', ['id', 'bio']);
|
|
55
|
+
queryBuilder.addRelation('posts', ['title', 'content']);
|
|
56
|
+
|
|
57
|
+
const relations = queryBuilder.toObject(true).relations;
|
|
58
|
+
expect(relations).toEqual(
|
|
59
|
+
expect.objectContaining({
|
|
60
|
+
profile: { select: ['id', 'bio'] },
|
|
61
|
+
posts: { select: ['title', 'content'] },
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
queryBuilder.removeRelation('profile');
|
|
66
|
+
const updatedRelations = queryBuilder.toObject(true).relations;
|
|
67
|
+
expect(updatedRelations).toEqual(
|
|
68
|
+
expect.objectContaining({
|
|
69
|
+
posts: { select: ['title', 'content'] },
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
expect(updatedRelations).not.toEqual(
|
|
73
|
+
expect.objectContaining({ profile: true }),
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle null or empty relations', () => {
|
|
78
|
+
queryBuilder.addRelation('profile');
|
|
79
|
+
expect(queryBuilder.toObject(true).relations).not.toEqual(undefined);
|
|
80
|
+
|
|
81
|
+
queryBuilder.addRelation('profile');
|
|
82
|
+
queryBuilder.removeRelation('profile');
|
|
83
|
+
expect(queryBuilder.toObject(true).relations).toEqual(undefined);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should add and remove relations as array of strings', () => {
|
|
87
|
+
queryBuilder.addRelation('profile');
|
|
88
|
+
queryBuilder.addRelation('posts');
|
|
89
|
+
|
|
90
|
+
const relations = queryBuilder.toObject(true).relations;
|
|
91
|
+
expect(relations).toEqual(
|
|
92
|
+
expect.objectContaining({ profile: true, posts: true }),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
queryBuilder.removeRelation('profile');
|
|
96
|
+
const updatedRelations = queryBuilder.toObject(true).relations;
|
|
97
|
+
expect(updatedRelations).toEqual(
|
|
98
|
+
expect.objectContaining({ posts: true }),
|
|
99
|
+
);
|
|
100
|
+
expect(updatedRelations).not.toEqual(
|
|
101
|
+
expect.objectContaining({ profile: true }),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should add where conditions', () => {
|
|
106
|
+
queryBuilder.where('age', 25);
|
|
107
|
+
expect(queryBuilder.toObject(true).where).toEqual({ age: { $eq: 25 } });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle empty where conditions', () => {
|
|
111
|
+
queryBuilder.where(() => {
|
|
112
|
+
// Do nothing
|
|
113
|
+
});
|
|
114
|
+
expect(queryBuilder.toObject(true).where).toEqual(undefined);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should add order', () => {
|
|
118
|
+
queryBuilder.addOrder('name', OrderDirectionEnum.ASC);
|
|
119
|
+
expect(queryBuilder.toObject(true).order).toEqual({ name: OrderDirectionEnum.ASC });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should remove order', () => {
|
|
123
|
+
queryBuilder.addOrder('name', OrderDirectionEnum.ASC);
|
|
124
|
+
queryBuilder.removeOrder('name');
|
|
125
|
+
expect(queryBuilder.toObject(true).order).toEqual(undefined);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should set skip and take', () => {
|
|
129
|
+
queryBuilder.setSkip(10);
|
|
130
|
+
queryBuilder.setTake(5);
|
|
131
|
+
expect(queryBuilder.toObject(true).skip).toBe(10);
|
|
132
|
+
expect(queryBuilder.toObject(true).take).toBe(5);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should convert to JSON', () => {
|
|
136
|
+
queryBuilder.setOptions({ select: ['name'] });
|
|
137
|
+
const json = queryBuilder.toJson();
|
|
138
|
+
expect(json).toBe(JSON.stringify(queryBuilder.toObject(true)));
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RelationBuilder } from '../lib/relation-builder';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
describe('RelationBuilder', () => {
|
|
5
|
+
let relationBuilder: RelationBuilder;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
relationBuilder = new RelationBuilder();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should handle empty relations', () => {
|
|
12
|
+
// Initially, the relations should be empty
|
|
13
|
+
expect(relationBuilder.toObject()).toEqual({});
|
|
14
|
+
|
|
15
|
+
// Clear any relations if set
|
|
16
|
+
relationBuilder.clear();
|
|
17
|
+
expect(relationBuilder.toObject()).toEqual({});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should handle setting empty relations', () => {
|
|
21
|
+
// Set empty relations
|
|
22
|
+
relationBuilder.setRelations([]);
|
|
23
|
+
expect(relationBuilder.toObject()).toEqual({});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle clearing relations', () => {
|
|
27
|
+
// Add some relations and then clear them
|
|
28
|
+
relationBuilder.setRelations(['relation1', 'relation2']);
|
|
29
|
+
relationBuilder.clear();
|
|
30
|
+
expect(relationBuilder.toObject()).toEqual({});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { WhereBuilder } from '../lib/where-builder';
|
|
2
|
+
import { WhereOperatorEnum } from '../lib/types';
|
|
3
|
+
|
|
4
|
+
describe('WhereBuilder - Complex Nested Structures', () => {
|
|
5
|
+
let whereBuilder: WhereBuilder;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
whereBuilder = new WhereBuilder();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should create complex nested where structure with mixed conditions', () => {
|
|
12
|
+
// Target structure:
|
|
13
|
+
// {
|
|
14
|
+
// customerId: { $in: ['cust_123', 'cust_456'] },
|
|
15
|
+
// $or: [
|
|
16
|
+
// { status: { $eq: 'pending' } },
|
|
17
|
+
// { status: { $eq: 'processing' } },
|
|
18
|
+
// {
|
|
19
|
+
// $and: [
|
|
20
|
+
// { total: { $gt: 1000 } },
|
|
21
|
+
// { tax: { $lte: 200 } },
|
|
22
|
+
// ],
|
|
23
|
+
// },
|
|
24
|
+
// ],
|
|
25
|
+
// }
|
|
26
|
+
|
|
27
|
+
whereBuilder
|
|
28
|
+
.where('customerId', WhereOperatorEnum.IN, ['cust_123', 'cust_456'])
|
|
29
|
+
.orWhere('status', WhereOperatorEnum.EQ, 'pending')
|
|
30
|
+
.orWhere('status', WhereOperatorEnum.EQ, 'processing')
|
|
31
|
+
.orWhere((builder) => {
|
|
32
|
+
builder.where('total', WhereOperatorEnum.GT, 1000)
|
|
33
|
+
.where('tax', WhereOperatorEnum.LT_OR_EQ, 200);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const result = whereBuilder.toObject();
|
|
37
|
+
|
|
38
|
+
expect(result).toEqual({
|
|
39
|
+
customerId: { $in: ['cust_123', 'cust_456'] },
|
|
40
|
+
$or: [
|
|
41
|
+
{ status: { $eq: 'pending' } },
|
|
42
|
+
{ status: { $eq: 'processing' } },
|
|
43
|
+
{
|
|
44
|
+
total: { $gt: 1000 },
|
|
45
|
+
tax: { $lte: 200 },
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should handle multiple field conditions with same logical operator', () => {
|
|
52
|
+
whereBuilder
|
|
53
|
+
.where('name', 'John')
|
|
54
|
+
.orWhere('age', WhereOperatorEnum.GT, 18)
|
|
55
|
+
.orWhere('status', 'active')
|
|
56
|
+
.where('country', 'USA');
|
|
57
|
+
|
|
58
|
+
const result = whereBuilder.toObject();
|
|
59
|
+
|
|
60
|
+
expect(result).toEqual({
|
|
61
|
+
name: { $eq: 'John' },
|
|
62
|
+
country: { $eq: 'USA' },
|
|
63
|
+
$or: [
|
|
64
|
+
{ age: { $gt: 18 } },
|
|
65
|
+
{ status: { $eq: 'active' } },
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle nested $and and $or conditions', () => {
|
|
71
|
+
whereBuilder
|
|
72
|
+
.where('userId', '123')
|
|
73
|
+
.orWhere((builder) => {
|
|
74
|
+
builder.where('role', 'admin')
|
|
75
|
+
.where('department', 'IT');
|
|
76
|
+
})
|
|
77
|
+
.orWhere((builder) => {
|
|
78
|
+
builder.where('role', 'manager')
|
|
79
|
+
.where('level', WhereOperatorEnum.GT, 5);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const result = whereBuilder.toObject();
|
|
83
|
+
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
userId: { $eq: '123' },
|
|
86
|
+
$or: [
|
|
87
|
+
{
|
|
88
|
+
role: { $eq: 'admin' },
|
|
89
|
+
department: { $eq: 'IT' },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
role: { $eq: 'manager' },
|
|
93
|
+
level: { $gt: 5 },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle direct object conditions', () => {
|
|
100
|
+
whereBuilder
|
|
101
|
+
.where({
|
|
102
|
+
name: { $eq: 'John' },
|
|
103
|
+
age: { $gt: 18 },
|
|
104
|
+
})
|
|
105
|
+
.orWhere({
|
|
106
|
+
status: { $eq: 'active' },
|
|
107
|
+
verified: { $eq: true },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const result = whereBuilder.toObject();
|
|
111
|
+
|
|
112
|
+
expect(result).toEqual({
|
|
113
|
+
name: { $eq: 'John' },
|
|
114
|
+
age: { $gt: 18 },
|
|
115
|
+
$or: [
|
|
116
|
+
{
|
|
117
|
+
status: { $eq: 'active' },
|
|
118
|
+
verified: { $eq: true },
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should merge multiple $or conditions correctly', () => {
|
|
125
|
+
whereBuilder
|
|
126
|
+
.orWhere('category', 'tech')
|
|
127
|
+
.orWhere('category', 'design')
|
|
128
|
+
.where('published', true)
|
|
129
|
+
.orWhere('priority', WhereOperatorEnum.GT, 5);
|
|
130
|
+
|
|
131
|
+
const result = whereBuilder.toObject();
|
|
132
|
+
|
|
133
|
+
expect(result).toEqual({
|
|
134
|
+
published: { $eq: true },
|
|
135
|
+
$or: [
|
|
136
|
+
{ category: { $eq: 'tech' } },
|
|
137
|
+
{ category: { $eq: 'design' } },
|
|
138
|
+
{ priority: { $gt: 5 } },
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle complex nested logical operations', () => {
|
|
144
|
+
whereBuilder
|
|
145
|
+
.where('companyId', '456')
|
|
146
|
+
.orWhere((builder) => {
|
|
147
|
+
builder.where('department', 'Sales')
|
|
148
|
+
.orWhere((innerBuilder) => {
|
|
149
|
+
innerBuilder.where('region', 'North')
|
|
150
|
+
.where('quota', WhereOperatorEnum.GT, 100000);
|
|
151
|
+
});
|
|
152
|
+
})
|
|
153
|
+
.where('active', true);
|
|
154
|
+
|
|
155
|
+
const result = whereBuilder.toObject();
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual({
|
|
158
|
+
companyId: { $eq: '456' },
|
|
159
|
+
active: { $eq: true },
|
|
160
|
+
$or: [
|
|
161
|
+
{
|
|
162
|
+
department: { $eq: 'Sales' },
|
|
163
|
+
$or: [
|
|
164
|
+
{
|
|
165
|
+
region: { $eq: 'North' },
|
|
166
|
+
quota: { $gt: 100000 },
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"forceConsistentCasingInFileNames": true
|
|
6
|
+
},
|
|
7
|
+
"files": [],
|
|
8
|
+
"include": [],
|
|
9
|
+
"references": [
|
|
10
|
+
{
|
|
11
|
+
"path": "./tsconfig.lib.json"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "./tsconfig.spec.json"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"moduleResolution": "node10",
|
|
7
|
+
"types": ["jest", "node"]
|
|
8
|
+
},
|
|
9
|
+
"include": [
|
|
10
|
+
"jest.config.ts",
|
|
11
|
+
"src/**/*.test.ts",
|
|
12
|
+
"src/**/*.spec.ts",
|
|
13
|
+
"src/**/*.d.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
package/src/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
tslib_1.__exportStar(require("./lib/query-builder"), exports);
|
|
5
|
-
tslib_1.__exportStar(require("./lib/where-builder"), exports);
|
|
6
|
-
tslib_1.__exportStar(require("./lib/types"), exports);
|
package/src/lib/utils.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deepMerge = deepMerge;
|
|
4
|
-
function deepMerge(target, source) {
|
|
5
|
-
for (const key in source) {
|
|
6
|
-
if (source[key] instanceof Object && key in target) {
|
|
7
|
-
Object.assign(source[key], deepMerge(target[key], source[key]));
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
return Object.assign(Object.assign({}, target), source);
|
|
11
|
-
}
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":"5.8.3"}
|
|
File without changes
|