@rytass/wms-module-graphql 0.6.0 → 0.6.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/index.cjs.js
CHANGED
|
@@ -55,6 +55,65 @@ exports.LoaderService = class LoaderService {
|
|
|
55
55
|
const fields = ___default.default(Object.keys(__from)).sort();
|
|
56
56
|
return `${__hash}_${fields.map((field) => data[field]).join('_')}`;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* resolve a `__from` property name to the physical column it is stored in.
|
|
60
|
+
*
|
|
61
|
+
* TypeORM's `FindOptionsWhere` only accepts real column properties. A
|
|
62
|
+
* `@RelationId()` property — e.g. `LocationEntityClass.parentId`, which mirrors
|
|
63
|
+
* the `parent` tree relation — is virtual and is rejected with
|
|
64
|
+
* `EntityPropertyNotFoundError`, even though it is backed by the relation's
|
|
65
|
+
* join column. Map those back to their join column so they stay batchable.
|
|
66
|
+
* @param EntityMetadata
|
|
67
|
+
* @param string
|
|
68
|
+
* @returns string | null
|
|
69
|
+
*/
|
|
70
|
+
resolveColumnName(metadata, propertyName) {
|
|
71
|
+
const column = metadata.findColumnWithPropertyName(propertyName);
|
|
72
|
+
if (column)
|
|
73
|
+
return column.databaseName;
|
|
74
|
+
const relationId = metadata.relationIds.find((candidate) => candidate.propertyName === propertyName);
|
|
75
|
+
const joinColumns = relationId?.relation.joinColumns ?? [];
|
|
76
|
+
return joinColumns.length === 1 ? joinColumns[0].databaseName : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* build the where clause for `__from` keys that TypeORM's find options cannot
|
|
80
|
+
* express (see {@link resolveColumnName}), by addressing the columns directly.
|
|
81
|
+
* @param SelectQueryBuilder
|
|
82
|
+
* @param EntityMetadata
|
|
83
|
+
* @param LoaderOptions[]
|
|
84
|
+
* @param (keyof T)[]
|
|
85
|
+
*/
|
|
86
|
+
applyColumnWhere(qb, metadata, options, primaryKeys) {
|
|
87
|
+
const columns = primaryKeys.map((primaryKey) => {
|
|
88
|
+
const columnName = this.resolveColumnName(metadata, String(primaryKey));
|
|
89
|
+
if (!columnName)
|
|
90
|
+
throw new Error(`Loader cannot resolve property "${String(primaryKey)}" to a column of "${metadata.name}".`);
|
|
91
|
+
return {
|
|
92
|
+
primaryKey,
|
|
93
|
+
column: `entity.${this.dataSource.driver.escape(columnName)}`,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
if (columns.length === 1) {
|
|
97
|
+
const [{ primaryKey, column }] = columns;
|
|
98
|
+
qb.andWhere(`${column} IN (:...wmsLoaderValues)`, {
|
|
99
|
+
wmsLoaderValues: options.map(({ __from }) => __from[primaryKey]),
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
qb.andWhere(new typeorm.Brackets((where) => {
|
|
104
|
+
options.forEach(({ __from }, optionIndex) => {
|
|
105
|
+
const parameters = {};
|
|
106
|
+
const predicate = columns
|
|
107
|
+
.map(({ primaryKey, column }, columnIndex) => {
|
|
108
|
+
const parameter = `wmsLoaderValue_${optionIndex}_${columnIndex}`;
|
|
109
|
+
parameters[parameter] = __from[primaryKey];
|
|
110
|
+
return `${column} = :${parameter}`;
|
|
111
|
+
})
|
|
112
|
+
.join(' AND ');
|
|
113
|
+
where.orWhere(`(${predicate})`, parameters);
|
|
114
|
+
});
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
58
117
|
/**
|
|
59
118
|
* query data by collecting primary keys from options with same `__entity`
|
|
60
119
|
* @param LoaderOptions[]
|
|
@@ -65,7 +124,10 @@ exports.LoaderService = class LoaderService {
|
|
|
65
124
|
const repo = this.dataSource.getRepository(__entity);
|
|
66
125
|
let qb = repo.createQueryBuilder('entity');
|
|
67
126
|
const primaryKeys = Object.keys(__from);
|
|
68
|
-
|
|
127
|
+
const hasVirtualKey = primaryKeys.some((primaryKey) => !repo.metadata.findColumnWithPropertyName(String(primaryKey)));
|
|
128
|
+
if (hasVirtualKey)
|
|
129
|
+
this.applyColumnWhere(qb, repo.metadata, options, primaryKeys);
|
|
130
|
+
else if (primaryKeys.length > 1)
|
|
69
131
|
qb.setFindOptions({ where: options.map(({ __from }) => __from) });
|
|
70
132
|
else {
|
|
71
133
|
const [primaryKey] = primaryKeys;
|
|
@@ -3611,8 +3673,21 @@ let LocationResolver = class LocationResolver {
|
|
|
3611
3673
|
__from: { id: parentId },
|
|
3612
3674
|
});
|
|
3613
3675
|
}
|
|
3614
|
-
|
|
3615
|
-
|
|
3676
|
+
/**
|
|
3677
|
+
* `children` must never be read off the parent entity: only
|
|
3678
|
+
* `LocationQuery.location` and `LocationService.findMany` eager-load the tree
|
|
3679
|
+
* relation. Every other producer of a `LocationDto` (stock/loader/change-log/
|
|
3680
|
+
* transfer/ship/receive/warehouse-map resolvers, the `parent` field above and
|
|
3681
|
+
* the create/update mutations) hands back an entity whose `children` is
|
|
3682
|
+
* `undefined`, which breaks the non-nullable list. Always resolve through the
|
|
3683
|
+
* request-scoped loader instead.
|
|
3684
|
+
*/
|
|
3685
|
+
children({ id }, { loaders }) {
|
|
3686
|
+
return loaders.batchMany.load({
|
|
3687
|
+
__entity: wmsModuleCore.LocationEntityClass,
|
|
3688
|
+
__from: { parentId: id },
|
|
3689
|
+
__conditions: (qb) => qb.orderBy('entity."key"', 'ASC'),
|
|
3690
|
+
});
|
|
3616
3691
|
}
|
|
3617
3692
|
};
|
|
3618
3693
|
tslib.__decorate([
|
|
@@ -3634,9 +3709,10 @@ tslib.__decorate([
|
|
|
3634
3709
|
tslib.__decorate([
|
|
3635
3710
|
graphql.ResolveField(() => [LocationDto]),
|
|
3636
3711
|
tslib.__param(0, graphql.Parent()),
|
|
3712
|
+
tslib.__param(1, graphql.Context()),
|
|
3637
3713
|
tslib.__metadata("design:type", Function),
|
|
3638
|
-
tslib.__metadata("design:paramtypes", [wmsModuleCore.LocationEntityClass]),
|
|
3639
|
-
tslib.__metadata("design:returntype",
|
|
3714
|
+
tslib.__metadata("design:paramtypes", [wmsModuleCore.LocationEntityClass, Object]),
|
|
3715
|
+
tslib.__metadata("design:returntype", Promise)
|
|
3640
3716
|
], LocationResolver.prototype, "children", null);
|
|
3641
3717
|
LocationResolver = tslib.__decorate([
|
|
3642
3718
|
graphql.Resolver(() => LocationDto)
|
|
@@ -33,6 +33,28 @@ export declare class LoaderService {
|
|
|
33
33
|
* @returns Map<string, data>
|
|
34
34
|
*/
|
|
35
35
|
private useIndex;
|
|
36
|
+
/**
|
|
37
|
+
* resolve a `__from` property name to the physical column it is stored in.
|
|
38
|
+
*
|
|
39
|
+
* TypeORM's `FindOptionsWhere` only accepts real column properties. A
|
|
40
|
+
* `@RelationId()` property — e.g. `LocationEntityClass.parentId`, which mirrors
|
|
41
|
+
* the `parent` tree relation — is virtual and is rejected with
|
|
42
|
+
* `EntityPropertyNotFoundError`, even though it is backed by the relation's
|
|
43
|
+
* join column. Map those back to their join column so they stay batchable.
|
|
44
|
+
* @param EntityMetadata
|
|
45
|
+
* @param string
|
|
46
|
+
* @returns string | null
|
|
47
|
+
*/
|
|
48
|
+
private resolveColumnName;
|
|
49
|
+
/**
|
|
50
|
+
* build the where clause for `__from` keys that TypeORM's find options cannot
|
|
51
|
+
* express (see {@link resolveColumnName}), by addressing the columns directly.
|
|
52
|
+
* @param SelectQueryBuilder
|
|
53
|
+
* @param EntityMetadata
|
|
54
|
+
* @param LoaderOptions[]
|
|
55
|
+
* @param (keyof T)[]
|
|
56
|
+
*/
|
|
57
|
+
private applyColumnWhere;
|
|
36
58
|
/**
|
|
37
59
|
* query data by collecting primary keys from options with same `__entity`
|
|
38
60
|
* @param LoaderOptions[]
|
|
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
import _ from 'lodash';
|
|
5
5
|
import { LRUCache } from 'lru-cache';
|
|
6
|
-
import { DataSource, In } from 'typeorm';
|
|
6
|
+
import { DataSource, Brackets, In } from 'typeorm';
|
|
7
7
|
import DataLoader from 'dataloader';
|
|
8
8
|
|
|
9
9
|
let LoaderService = class LoaderService {
|
|
@@ -42,6 +42,65 @@ let LoaderService = class LoaderService {
|
|
|
42
42
|
const fields = _(Object.keys(__from)).sort();
|
|
43
43
|
return `${__hash}_${fields.map((field) => data[field]).join('_')}`;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* resolve a `__from` property name to the physical column it is stored in.
|
|
47
|
+
*
|
|
48
|
+
* TypeORM's `FindOptionsWhere` only accepts real column properties. A
|
|
49
|
+
* `@RelationId()` property — e.g. `LocationEntityClass.parentId`, which mirrors
|
|
50
|
+
* the `parent` tree relation — is virtual and is rejected with
|
|
51
|
+
* `EntityPropertyNotFoundError`, even though it is backed by the relation's
|
|
52
|
+
* join column. Map those back to their join column so they stay batchable.
|
|
53
|
+
* @param EntityMetadata
|
|
54
|
+
* @param string
|
|
55
|
+
* @returns string | null
|
|
56
|
+
*/
|
|
57
|
+
resolveColumnName(metadata, propertyName) {
|
|
58
|
+
const column = metadata.findColumnWithPropertyName(propertyName);
|
|
59
|
+
if (column)
|
|
60
|
+
return column.databaseName;
|
|
61
|
+
const relationId = metadata.relationIds.find((candidate) => candidate.propertyName === propertyName);
|
|
62
|
+
const joinColumns = relationId?.relation.joinColumns ?? [];
|
|
63
|
+
return joinColumns.length === 1 ? joinColumns[0].databaseName : null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* build the where clause for `__from` keys that TypeORM's find options cannot
|
|
67
|
+
* express (see {@link resolveColumnName}), by addressing the columns directly.
|
|
68
|
+
* @param SelectQueryBuilder
|
|
69
|
+
* @param EntityMetadata
|
|
70
|
+
* @param LoaderOptions[]
|
|
71
|
+
* @param (keyof T)[]
|
|
72
|
+
*/
|
|
73
|
+
applyColumnWhere(qb, metadata, options, primaryKeys) {
|
|
74
|
+
const columns = primaryKeys.map((primaryKey) => {
|
|
75
|
+
const columnName = this.resolveColumnName(metadata, String(primaryKey));
|
|
76
|
+
if (!columnName)
|
|
77
|
+
throw new Error(`Loader cannot resolve property "${String(primaryKey)}" to a column of "${metadata.name}".`);
|
|
78
|
+
return {
|
|
79
|
+
primaryKey,
|
|
80
|
+
column: `entity.${this.dataSource.driver.escape(columnName)}`,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
if (columns.length === 1) {
|
|
84
|
+
const [{ primaryKey, column }] = columns;
|
|
85
|
+
qb.andWhere(`${column} IN (:...wmsLoaderValues)`, {
|
|
86
|
+
wmsLoaderValues: options.map(({ __from }) => __from[primaryKey]),
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
qb.andWhere(new Brackets((where) => {
|
|
91
|
+
options.forEach(({ __from }, optionIndex) => {
|
|
92
|
+
const parameters = {};
|
|
93
|
+
const predicate = columns
|
|
94
|
+
.map(({ primaryKey, column }, columnIndex) => {
|
|
95
|
+
const parameter = `wmsLoaderValue_${optionIndex}_${columnIndex}`;
|
|
96
|
+
parameters[parameter] = __from[primaryKey];
|
|
97
|
+
return `${column} = :${parameter}`;
|
|
98
|
+
})
|
|
99
|
+
.join(' AND ');
|
|
100
|
+
where.orWhere(`(${predicate})`, parameters);
|
|
101
|
+
});
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
45
104
|
/**
|
|
46
105
|
* query data by collecting primary keys from options with same `__entity`
|
|
47
106
|
* @param LoaderOptions[]
|
|
@@ -52,7 +111,10 @@ let LoaderService = class LoaderService {
|
|
|
52
111
|
const repo = this.dataSource.getRepository(__entity);
|
|
53
112
|
let qb = repo.createQueryBuilder('entity');
|
|
54
113
|
const primaryKeys = Object.keys(__from);
|
|
55
|
-
|
|
114
|
+
const hasVirtualKey = primaryKeys.some((primaryKey) => !repo.metadata.findColumnWithPropertyName(String(primaryKey)));
|
|
115
|
+
if (hasVirtualKey)
|
|
116
|
+
this.applyColumnWhere(qb, repo.metadata, options, primaryKeys);
|
|
117
|
+
else if (primaryKeys.length > 1)
|
|
56
118
|
qb.setFindOptions({ where: options.map(({ __from }) => __from) });
|
|
57
119
|
else {
|
|
58
120
|
const [primaryKey] = primaryKeys;
|
|
@@ -6,5 +6,14 @@ import { LocationEntityClass } from '@rytass/wms-module-core';
|
|
|
6
6
|
export declare class LocationResolver {
|
|
7
7
|
mapData({ id }: LocationDto, { loaders }: LoaderType<WarehouseMapEntity>): Promise<MapDataDto | null>;
|
|
8
8
|
parent({ parentId }: LocationEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationEntityClass> | null;
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `children` must never be read off the parent entity: only
|
|
11
|
+
* `LocationQuery.location` and `LocationService.findMany` eager-load the tree
|
|
12
|
+
* relation. Every other producer of a `LocationDto` (stock/loader/change-log/
|
|
13
|
+
* transfer/ship/receive/warehouse-map resolvers, the `parent` field above and
|
|
14
|
+
* the create/update mutations) hands back an entity whose `children` is
|
|
15
|
+
* `undefined`, which breaks the non-nullable list. Always resolve through the
|
|
16
|
+
* request-scoped loader instead.
|
|
17
|
+
*/
|
|
18
|
+
children({ id }: LocationEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationDto[]>;
|
|
10
19
|
}
|
|
@@ -21,8 +21,21 @@ let LocationResolver = class LocationResolver {
|
|
|
21
21
|
__from: { id: parentId },
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* `children` must never be read off the parent entity: only
|
|
26
|
+
* `LocationQuery.location` and `LocationService.findMany` eager-load the tree
|
|
27
|
+
* relation. Every other producer of a `LocationDto` (stock/loader/change-log/
|
|
28
|
+
* transfer/ship/receive/warehouse-map resolvers, the `parent` field above and
|
|
29
|
+
* the create/update mutations) hands back an entity whose `children` is
|
|
30
|
+
* `undefined`, which breaks the non-nullable list. Always resolve through the
|
|
31
|
+
* request-scoped loader instead.
|
|
32
|
+
*/
|
|
33
|
+
children({ id }, { loaders }) {
|
|
34
|
+
return loaders.batchMany.load({
|
|
35
|
+
__entity: LocationEntityClass,
|
|
36
|
+
__from: { parentId: id },
|
|
37
|
+
__conditions: (qb) => qb.orderBy('entity."key"', 'ASC'),
|
|
38
|
+
});
|
|
26
39
|
}
|
|
27
40
|
};
|
|
28
41
|
__decorate([
|
|
@@ -44,9 +57,10 @@ __decorate([
|
|
|
44
57
|
__decorate([
|
|
45
58
|
ResolveField(() => [LocationDto]),
|
|
46
59
|
__param(0, Parent()),
|
|
60
|
+
__param(1, Context()),
|
|
47
61
|
__metadata("design:type", Function),
|
|
48
|
-
__metadata("design:paramtypes", [LocationEntityClass]),
|
|
49
|
-
__metadata("design:returntype",
|
|
62
|
+
__metadata("design:paramtypes", [LocationEntityClass, Object]),
|
|
63
|
+
__metadata("design:returntype", Promise)
|
|
50
64
|
], LocationResolver.prototype, "children", null);
|
|
51
65
|
LocationResolver = __decorate([
|
|
52
66
|
Resolver(() => LocationDto)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rytass/wms-module-graphql",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Rytass WMS Module — GraphQL schema, resolvers and DTOs for warehouse management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rytass",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@nestjs/apollo": ">=13.0.0 <13.2.0",
|
|
58
58
|
"@nestjs/common": "^11.0.0",
|
|
59
59
|
"@nestjs/graphql": "^13.0.0",
|
|
60
|
-
"@rytass/wms-module-core": "^0.3.
|
|
60
|
+
"@rytass/wms-module-core": "^0.3.1",
|
|
61
61
|
"graphql": "^16.0.0",
|
|
62
62
|
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
63
63
|
"rxjs": "^7.8.0",
|