@rytass/wms-module-graphql 0.5.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/README.md +31 -5
- package/adapters/member-base.d.ts +11 -0
- package/adapters/member-base.js +17 -1
- package/docs/api-reference.md +38 -18
- package/docs/events.md +263 -67
- package/docs/integration-guide.md +35 -7
- package/index.cjs.js +112 -10
- package/index.d.ts +2 -2
- package/lib/events/index.d.ts +2 -0
- package/lib/events/operation-result-map.d.ts +77 -0
- package/lib/events/typed-maps.d.ts +73 -0
- package/lib/events/wms-mutation.interceptor.d.ts +4 -2
- package/lib/events/wms-mutation.interceptor.js +24 -6
- package/lib/graphql.module.d.ts +13 -4
- package/lib/graphql.module.js +11 -3
- package/lib/helpers/loader.service.d.ts +22 -0
- package/lib/helpers/loader.service.js +64 -2
- package/lib/resolvers/location.resolver.d.ts +10 -1
- package/lib/resolvers/location.resolver.js +18 -4
- package/package.json +2 -2
|
@@ -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.
|
|
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.
|
|
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",
|