@rentman/rm-core 1.0.0
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/.editorconfig +23 -0
- package/package.json +33 -0
- package/src/classes/ApiFormatter.ts +338 -0
- package/src/classes/BatchStatus.ts +86 -0
- package/src/classes/ChangesCompiler.ts +161 -0
- package/src/classes/ChangesProcessor.ts +475 -0
- package/src/classes/CollectionCache.ts +234 -0
- package/src/classes/CollectionEntryService.ts +83 -0
- package/src/classes/CollectionHandler.ts +619 -0
- package/src/classes/CollectionResourceService.ts +38 -0
- package/src/classes/ColumnCache.ts +114 -0
- package/src/classes/ColumnExpansion.ts +74 -0
- package/src/classes/ColumnHandler.ts +90 -0
- package/src/classes/ColumnSet.ts +181 -0
- package/src/classes/ColumnUtil.ts +589 -0
- package/src/classes/CreateRequest.ts +158 -0
- package/src/classes/ItemAdministrator.ts +571 -0
- package/src/classes/ItemCache.ts +158 -0
- package/src/classes/ItemEntryService.ts +488 -0
- package/src/classes/ItemHandler.ts +592 -0
- package/src/classes/ItemRequestService.ts +229 -0
- package/src/classes/ItemResourceService.ts +36 -0
- package/src/classes/KeyService.ts +76 -0
- package/src/classes/LockService.ts +184 -0
- package/src/classes/ModuleChangesCompiler.ts +123 -0
- package/src/classes/PromiseService.ts +19 -0
- package/src/classes/QueryMatcher.ts +135 -0
- package/src/classes/QueryParser.ts +209 -0
- package/src/classes/QueryService.ts +35 -0
- package/src/classes/RequestColumnExpansion.ts +31 -0
- package/src/classes/ResourceCache.ts +289 -0
- package/src/classes/Util.ts +422 -0
- package/src/constants.ts +11 -0
- package/src/factories/BatchStatusFactory.ts +18 -0
- package/src/factories/ChangesCompilerFactory.ts +20 -0
- package/src/factories/ChangesProcessorFactory.ts +53 -0
- package/src/factories/CollectionCacheFactory.ts +15 -0
- package/src/factories/CollectionEntryFactory.ts +35 -0
- package/src/factories/CollectionResourceFactory.ts +36 -0
- package/src/factories/ColumnExpansionFactory.ts +25 -0
- package/src/factories/ColumnSetFactory.ts +24 -0
- package/src/factories/CreateRequestFactory.ts +28 -0
- package/src/factories/ItemCacheFactory.ts +18 -0
- package/src/factories/ItemEntryFactory.ts +18 -0
- package/src/factories/ItemResourceFactory.ts +28 -0
- package/src/factories/ModuleChangesCompilerFactory.ts +21 -0
- package/src/factories/QueryMatcherFactory.ts +20 -0
- package/src/factories/QueryParserFactory.ts +10 -0
- package/src/factories/RequestColumnExpansionFactory.ts +15 -0
- package/src/factories/ResourceCacheFactory.ts +55 -0
- package/src/index.ts +188 -0
- package/src/interfaces/ApiRequestServiceInterface.ts +17 -0
- package/src/interfaces/AuthInterface.ts +5 -0
- package/src/interfaces/ErrorHandlerInterface.ts +3 -0
- package/src/interfaces/ResourceCacheInterface.ts +9 -0
- package/src/models/CollectionEditResource.ts +139 -0
- package/src/models/CollectionEntry.ts +448 -0
- package/src/models/CollectionResource.ts +223 -0
- package/src/models/CollectionViewResource.ts +23 -0
- package/src/models/EditResource.ts +15 -0
- package/src/models/ItemEntry.ts +24 -0
- package/src/models/ItemResource.ts +35 -0
- package/src/models/ViewResource.ts +14 -0
- package/tsconfig.json +12 -0
- package/tslint.json +63 -0
- package/webpack.common.js +35 -0
- package/webpack.dev.js +10 -0
- package/webpack.prod.js +17 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import ColumnExpansionFactory from "../factories/ColumnExpansionFactory";
|
|
2
|
+
import ColumnSetFactory from "../factories/ColumnSetFactory";
|
|
3
|
+
import ApiRequestServiceInterface from '../interfaces/ApiRequestServiceInterface';
|
|
4
|
+
import ErrorHandlerInterface from '../interfaces/ErrorHandlerInterface';
|
|
5
|
+
import CollectionEntry from "../models/CollectionEntry";
|
|
6
|
+
import ItemEntry from "../models/ItemEntry";
|
|
7
|
+
import ColumnExpansion from "./ColumnExpansion";
|
|
8
|
+
import ColumnHandler from "./ColumnHandler";
|
|
9
|
+
import ItemEntryService from "./ItemEntryService";
|
|
10
|
+
import ItemHandler from "./ItemHandler";
|
|
11
|
+
import PromiseService from "./PromiseService";
|
|
12
|
+
import QueryService from "./QueryService";
|
|
13
|
+
|
|
14
|
+
import { pathExists } from "./Util";
|
|
15
|
+
|
|
16
|
+
export default class ItemRequestService {
|
|
17
|
+
constructor (
|
|
18
|
+
private apiRequestService: ApiRequestServiceInterface,
|
|
19
|
+
private errorHandler: ErrorHandlerInterface,
|
|
20
|
+
private itemHandler: ItemHandler,
|
|
21
|
+
private columnHandler: ColumnHandler,
|
|
22
|
+
private columnExpansionFactory: ColumnExpansionFactory,
|
|
23
|
+
private columnSetFactory: ColumnSetFactory,
|
|
24
|
+
private itemEntryService: ItemEntryService,
|
|
25
|
+
private queryService: QueryService,
|
|
26
|
+
private promiseService: PromiseService
|
|
27
|
+
) {
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
requestItem (itemType, itemId, columns) {
|
|
31
|
+
const requestColumns = {
|
|
32
|
+
[itemType]: columns
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return this.apiRequestService.query(
|
|
36
|
+
itemType,
|
|
37
|
+
{id: itemId},
|
|
38
|
+
requestColumns
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
requestItems (collectionEntry: CollectionEntry, query = collectionEntry.query.object, columns = collectionEntry.options.columns) {
|
|
43
|
+
const options = collectionEntry.options;
|
|
44
|
+
|
|
45
|
+
const requestColumns = {
|
|
46
|
+
[collectionEntry.itemType]: columns
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const params = {
|
|
50
|
+
requestType: 'query',
|
|
51
|
+
columns: requestColumns,
|
|
52
|
+
query: query,
|
|
53
|
+
itemType: '',
|
|
54
|
+
allLinkIds: false,
|
|
55
|
+
limit: null
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (options.queryItemType === null)
|
|
59
|
+
params.itemType = collectionEntry.itemType;
|
|
60
|
+
else {
|
|
61
|
+
params.itemType = options.queryItemType;
|
|
62
|
+
params.allLinkIds = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (options.limit !== null)
|
|
66
|
+
params.limit = options.limit;
|
|
67
|
+
|
|
68
|
+
return this.apiRequestService.requestSkeleton(params);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
requestLinkItems (parentType, parentIdArray, itemType, query, columns): Promise<any> {
|
|
72
|
+
const requestColumns = {
|
|
73
|
+
[itemType]: columns
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return query === null ?
|
|
77
|
+
this.apiRequestService.query(
|
|
78
|
+
parentType,
|
|
79
|
+
{id: parentIdArray},
|
|
80
|
+
requestColumns
|
|
81
|
+
) :
|
|
82
|
+
this.apiRequestService.query(
|
|
83
|
+
itemType,
|
|
84
|
+
this.queryService.addAndCondition(
|
|
85
|
+
query,
|
|
86
|
+
{linkedTo: parentType, query: {id: parentIdArray}}
|
|
87
|
+
),
|
|
88
|
+
requestColumns // should contain a derived column that refers to the parent
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
processExpansion (requestColumns, response, itemType: string, itemEntries: ItemEntry[]) {
|
|
93
|
+
const columnExpansion = this.columnExpansionFactory.construct(
|
|
94
|
+
itemType,
|
|
95
|
+
requestColumns,
|
|
96
|
+
response.fetchColumnsObject()
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
for (const itemEntry of itemEntries) {
|
|
100
|
+
if (this.itemEntryService.isLocal(itemEntry))
|
|
101
|
+
continue;
|
|
102
|
+
|
|
103
|
+
this.handleCachedItem(
|
|
104
|
+
itemEntry,
|
|
105
|
+
response,
|
|
106
|
+
columnExpansion
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return columnExpansion;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
processResponse (response, idArray, parent, columnInfo, columnExpansion?): Promise<ItemEntry[]> {
|
|
114
|
+
if (columnExpansion === undefined)
|
|
115
|
+
columnExpansion = this.columnExpansionFactory.construct(
|
|
116
|
+
columnInfo.itemType,
|
|
117
|
+
columnInfo.true ? true : columnInfo.request,
|
|
118
|
+
columnInfo.response
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const itemType = columnInfo.itemType;
|
|
122
|
+
const rootColumns = columnInfo.root;
|
|
123
|
+
const itemEntries = [];
|
|
124
|
+
const deferredEntries = [];
|
|
125
|
+
|
|
126
|
+
// should be fast...
|
|
127
|
+
for (const itemId of idArray) {
|
|
128
|
+
let itemEntry = this.itemHandler.entries.get(itemType, itemId);
|
|
129
|
+
|
|
130
|
+
if (itemEntry === undefined) {
|
|
131
|
+
itemEntry = this.itemHandler.createEntry(
|
|
132
|
+
itemId,
|
|
133
|
+
response.fetchObject(itemType, itemId),
|
|
134
|
+
response.fetchDerivedObject(itemType, itemId, rootColumns),
|
|
135
|
+
columnInfo
|
|
136
|
+
);
|
|
137
|
+
itemEntries.push(itemEntry);
|
|
138
|
+
} else {
|
|
139
|
+
if (itemEntry.deferred !== undefined) {
|
|
140
|
+
if (itemEntry.deferRequest) {
|
|
141
|
+
itemEntries.push(
|
|
142
|
+
this.itemHandler.resolveDeferredEntry(
|
|
143
|
+
itemEntry,
|
|
144
|
+
response.fetchObject(itemType, itemId),
|
|
145
|
+
response.fetchDerivedObject(itemType, itemId, rootColumns),
|
|
146
|
+
columnInfo
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
} else
|
|
150
|
+
deferredEntries.push(itemEntry.deferred.promise);
|
|
151
|
+
} else {
|
|
152
|
+
itemEntries.push(
|
|
153
|
+
this.handleCachedItem(
|
|
154
|
+
itemEntry,
|
|
155
|
+
response,
|
|
156
|
+
columnExpansion
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (parent !== null)
|
|
163
|
+
itemEntry.parent = parent;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return this.promiseService.all(deferredEntries).then(
|
|
167
|
+
resolvedEntries => {
|
|
168
|
+
for (const resolvedEntry of resolvedEntries)
|
|
169
|
+
itemEntries.push(
|
|
170
|
+
this.handleCachedItem(
|
|
171
|
+
resolvedEntry,
|
|
172
|
+
response,
|
|
173
|
+
columnExpansion
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
return itemEntries;
|
|
178
|
+
},
|
|
179
|
+
this.errorHandler.reject
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
handleCachedItem (itemEntry, response, columnExpansion: ColumnExpansion) {
|
|
184
|
+
const expansionResult = columnExpansion.run(itemEntry.columns, true);
|
|
185
|
+
|
|
186
|
+
return expansionResult === null ?
|
|
187
|
+
itemEntry :
|
|
188
|
+
this.itemEntryService.expand(
|
|
189
|
+
itemEntry,
|
|
190
|
+
response.fetchObject(itemEntry.itemType, itemEntry.itemId),
|
|
191
|
+
response.fetchDerivedObject(
|
|
192
|
+
itemEntry.itemType,
|
|
193
|
+
itemEntry.itemId,
|
|
194
|
+
expansionResult.difference.root
|
|
195
|
+
),
|
|
196
|
+
expansionResult
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
processLinkResponse (response, parentType, parentIdArray, itemType, options) {
|
|
201
|
+
const responseColumns = response.fetchColumnsObject();
|
|
202
|
+
const columnInfo = this.columnSetFactory.construct(itemType, options.columns, responseColumns);
|
|
203
|
+
const columnExpansion = this.columnExpansionFactory.construct(itemType, options.columns, responseColumns);
|
|
204
|
+
const parentItems = response.items[parentType];
|
|
205
|
+
const promises = {};
|
|
206
|
+
|
|
207
|
+
for (const parentId of parentIdArray) {
|
|
208
|
+
promises[parentId] = pathExists(parentItems, [parentId, 'links', itemType]) ?
|
|
209
|
+
this.processResponse(
|
|
210
|
+
response,
|
|
211
|
+
parentItems[parentId].links[itemType],
|
|
212
|
+
{itemType: parentType, itemId: parentId},
|
|
213
|
+
columnInfo,
|
|
214
|
+
columnExpansion.run
|
|
215
|
+
) :
|
|
216
|
+
[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return this.promiseService.all(promises).then(resolved => {
|
|
220
|
+
return {
|
|
221
|
+
items: resolved,
|
|
222
|
+
columns: columnInfo,
|
|
223
|
+
columnExpansion: columnExpansion.run
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
this.errorHandler.reject
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { noop } from '../index';
|
|
2
|
+
import ItemEntryService from "./ItemEntryService";
|
|
3
|
+
import { shallowMerge } from "./Util";
|
|
4
|
+
|
|
5
|
+
export default class ItemResourceService {
|
|
6
|
+
constructor (
|
|
7
|
+
private itemEntryService: ItemEntryService
|
|
8
|
+
) {
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
processOptions (options) {
|
|
13
|
+
return shallowMerge(
|
|
14
|
+
options,
|
|
15
|
+
{
|
|
16
|
+
edit: false,
|
|
17
|
+
onChange: noop,
|
|
18
|
+
onDeactivate: noop
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
compareOptions (first, second) {
|
|
24
|
+
for (const key in first) {
|
|
25
|
+
if (first[key] !== second[key])
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isNew (resource) {
|
|
33
|
+
return this.itemEntryService.isLocal(resource.entry) &&
|
|
34
|
+
resource.transactions === undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export default class KeyService {
|
|
2
|
+
NEW_PREFIX: string = '-1';
|
|
3
|
+
DUPLICATE_PREFIX: string = '-2';
|
|
4
|
+
|
|
5
|
+
NEW_KEYWORD: string = 'new-';
|
|
6
|
+
DUPLICATE_KEYWORD: string = 'dup-';
|
|
7
|
+
|
|
8
|
+
seperator: string = ':';
|
|
9
|
+
|
|
10
|
+
constructor () {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getSingleKey (itemType: string, itemId: number | string) {
|
|
14
|
+
return itemType.concat(this.seperator, String(itemId));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getCollectionCacheKey (parentType: string, parentId: number, childType: string) {
|
|
18
|
+
return parentType.concat(this.seperator, String(parentId), this.seperator, childType);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getCssKey () {
|
|
22
|
+
return Array.prototype.join.call(arguments, '_');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
parseStateKey (stateKey: string) {
|
|
26
|
+
stateKey = stateKey.toString();
|
|
27
|
+
|
|
28
|
+
if (stateKey.indexOf(this.NEW_KEYWORD) === 0)
|
|
29
|
+
return {
|
|
30
|
+
type: 'new',
|
|
31
|
+
template: stateKey.substring(this.NEW_KEYWORD.length)
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (stateKey.indexOf(this.DUPLICATE_KEYWORD) === 0)
|
|
35
|
+
return {
|
|
36
|
+
type: 'duplicate',
|
|
37
|
+
itemId: stateKey.substring(this.DUPLICATE_KEYWORD.length)
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getDuplicateStateKey (itemId: string | number) {
|
|
44
|
+
return this.DUPLICATE_KEYWORD.concat(String(itemId));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getNewStateKey (templateType: string) {
|
|
48
|
+
return this.NEW_KEYWORD.concat(templateType);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getLocalId (): number {
|
|
52
|
+
return parseInt(this.NEW_PREFIX.concat(this.getRandomString()), 10);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getLocalDuplicateId (): number {
|
|
56
|
+
return parseInt(this.DUPLICATE_PREFIX.concat(this.getRandomString()), 10);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getRandomString () {
|
|
60
|
+
return Math.random().toString().split('.')[1].substr(0, 8);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// note: the changes compiler uses this
|
|
64
|
+
// might be called with undefined (itemHandler.createRequest)
|
|
65
|
+
isLocalId (itemId: number | string) {
|
|
66
|
+
return itemId !== undefined && this.isNewId(itemId) || this.isDuplicateId(itemId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
isNewId (itemId: number | string) {
|
|
70
|
+
return itemId.toString().indexOf(this.NEW_PREFIX) === 0 || itemId === this.NEW_KEYWORD;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isDuplicateId (itemId: number | string) {
|
|
74
|
+
return itemId.toString().indexOf(this.DUPLICATE_PREFIX) === 0 || itemId === this.DUPLICATE_KEYWORD;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import ApiRequestServiceInterface from '../interfaces/ApiRequestServiceInterface';
|
|
2
|
+
import ErrorHandlerInterface from '../interfaces/ErrorHandlerInterface';
|
|
3
|
+
import ItemEntry from "../models/ItemEntry";
|
|
4
|
+
import KeyService from "./KeyService";
|
|
5
|
+
import PromiseService from "./PromiseService";
|
|
6
|
+
import { createPath } from "./Util";
|
|
7
|
+
|
|
8
|
+
export default class LockService {
|
|
9
|
+
lockItems = {};
|
|
10
|
+
promises = {};
|
|
11
|
+
|
|
12
|
+
constructor (
|
|
13
|
+
private apiRequestService: ApiRequestServiceInterface,
|
|
14
|
+
private errorHandler: ErrorHandlerInterface,
|
|
15
|
+
private keyService: KeyService,
|
|
16
|
+
private promiseService: PromiseService
|
|
17
|
+
) {
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getValue (itemEntry: ItemEntry, map) {
|
|
22
|
+
const itemType = itemEntry.itemType;
|
|
23
|
+
|
|
24
|
+
if (map[itemType] === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
|
|
27
|
+
return map[itemType][itemEntry.itemId];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setValue (itemEntry: ItemEntry, value, map) {
|
|
31
|
+
return createPath(
|
|
32
|
+
map,
|
|
33
|
+
[itemEntry.itemType, itemEntry.itemId],
|
|
34
|
+
[{}, value]
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getLocks () {
|
|
39
|
+
return this.lockItems;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
hasLock (lockTokenId) {
|
|
43
|
+
for (const itemType in this.lockItems) {
|
|
44
|
+
for (const itemId in this.lockItems[itemType]) {
|
|
45
|
+
const lockItem = this.lockItems[itemType][itemId];
|
|
46
|
+
|
|
47
|
+
// tslint:disable:triple-equals
|
|
48
|
+
if (!lockItem.isForeign && lockItem.id == lockTokenId)
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// should be fast (watched)
|
|
57
|
+
isLocked (itemEntry: ItemEntry) {
|
|
58
|
+
const lockItem = this.getValue(itemEntry, this.lockItems);
|
|
59
|
+
|
|
60
|
+
if (lockItem === undefined)
|
|
61
|
+
return false;
|
|
62
|
+
|
|
63
|
+
return lockItem.isForeign;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getMessage (itemEntry) {
|
|
67
|
+
const lockItem = this.getValue(itemEntry, this.lockItems);
|
|
68
|
+
|
|
69
|
+
if (lockItem === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
|
|
72
|
+
if (!lockItem.isForeign)
|
|
73
|
+
return undefined;
|
|
74
|
+
|
|
75
|
+
return lockItem.message;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// does not reject when a foreign lock is encountered
|
|
79
|
+
// is also used by deleteLock
|
|
80
|
+
fetchCachedLock (itemEntry: ItemEntry) {
|
|
81
|
+
const lock = this.getValue(itemEntry, this.lockItems);
|
|
82
|
+
|
|
83
|
+
if (lock !== undefined)
|
|
84
|
+
return this.promiseService.when(lock);
|
|
85
|
+
|
|
86
|
+
const promise = this.getValue(itemEntry, this.promises);
|
|
87
|
+
|
|
88
|
+
if (promise !== undefined)
|
|
89
|
+
return promise;
|
|
90
|
+
|
|
91
|
+
return this.promiseService.when();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fetchLock (itemEntry: ItemEntry) {
|
|
95
|
+
return this.fetchCachedLock(itemEntry).then(lockItem => {
|
|
96
|
+
if (lockItem !== undefined)
|
|
97
|
+
return lockItem;
|
|
98
|
+
|
|
99
|
+
return this.requestLock(itemEntry);
|
|
100
|
+
},
|
|
101
|
+
this.errorHandler.reject
|
|
102
|
+
).then(lockItem => {
|
|
103
|
+
return lockItem.isForeign ?
|
|
104
|
+
this.errorHandler.reject(lockItem.message) :
|
|
105
|
+
lockItem.token;
|
|
106
|
+
},
|
|
107
|
+
this.errorHandler.reject
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
requestLock (itemEntry: ItemEntry) {
|
|
112
|
+
const itemType = itemEntry.itemType;
|
|
113
|
+
const itemId = itemEntry.itemId;
|
|
114
|
+
|
|
115
|
+
const promise = this.apiRequestService.create(
|
|
116
|
+
'Locktoken',
|
|
117
|
+
this.keyService.getLocalId(),
|
|
118
|
+
{itemtype: itemType, item: itemId}
|
|
119
|
+
).then(
|
|
120
|
+
response => {
|
|
121
|
+
delete this.promises[itemType][itemId];
|
|
122
|
+
|
|
123
|
+
return this.setValue(
|
|
124
|
+
itemEntry,
|
|
125
|
+
response.fetchObject(
|
|
126
|
+
'Locktoken',
|
|
127
|
+
response.links.Locktoken[0]
|
|
128
|
+
),
|
|
129
|
+
this.lockItems
|
|
130
|
+
);
|
|
131
|
+
}, error => {
|
|
132
|
+
delete this.promises[itemType][itemId];
|
|
133
|
+
|
|
134
|
+
if (error.code === 301) {
|
|
135
|
+
return this.setValue(
|
|
136
|
+
itemEntry,
|
|
137
|
+
{
|
|
138
|
+
isForeign: true,
|
|
139
|
+
message: error.message
|
|
140
|
+
},
|
|
141
|
+
this.lockItems
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this.errorHandler.reject(error);
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return this.setValue(itemEntry, promise, this.promises);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
deleteLock (itemEntry) {
|
|
153
|
+
return this.fetchCachedLock(itemEntry).then(cachedLock => {
|
|
154
|
+
// undefined: not requested yet
|
|
155
|
+
|
|
156
|
+
if (cachedLock === undefined)
|
|
157
|
+
return;
|
|
158
|
+
|
|
159
|
+
if (cachedLock.isForeign) {
|
|
160
|
+
this.clearLock(itemEntry);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return this.apiRequestService.delete('Locktoken', cachedLock.id)
|
|
165
|
+
.then(
|
|
166
|
+
() => {
|
|
167
|
+
this.clearLock(itemEntry);
|
|
168
|
+
},
|
|
169
|
+
this.errorHandler.reject
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
this.errorHandler.reject
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
clearLock (itemEntry) {
|
|
177
|
+
const itemType = itemEntry.itemType;
|
|
178
|
+
|
|
179
|
+
if (this.lockItems[itemType] === undefined)
|
|
180
|
+
return false;
|
|
181
|
+
|
|
182
|
+
return delete this.lockItems[itemType][itemEntry.itemId];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import ItemEntry from "../models/ItemEntry";
|
|
2
|
+
import CollectionCache from "./CollectionCache";
|
|
3
|
+
import ItemCache from "./ItemCache";
|
|
4
|
+
import ItemEntryService from "./ItemEntryService";
|
|
5
|
+
import { hasProperties } from "./Util";
|
|
6
|
+
|
|
7
|
+
const EMPTY_ENTRY = {};
|
|
8
|
+
|
|
9
|
+
export default class ModuleChangesCompiler {
|
|
10
|
+
parentMap;
|
|
11
|
+
destination;
|
|
12
|
+
|
|
13
|
+
constructor (
|
|
14
|
+
private itemEntryService: ItemEntryService,
|
|
15
|
+
private rootItemEntry: ItemEntry,
|
|
16
|
+
private singleItemCache: ItemCache,
|
|
17
|
+
private collectionCache: CollectionCache
|
|
18
|
+
) {
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process () {
|
|
22
|
+
this.parentMap = this.createParentMap(this.singleItemCache);
|
|
23
|
+
this.processItems(this.rootItemEntry.itemType, [this.rootItemEntry], {});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private createParentMap (itemCache) {
|
|
27
|
+
const result = {};
|
|
28
|
+
|
|
29
|
+
itemCache.forEach(itemEntry => {
|
|
30
|
+
const itemType = itemEntry.itemType;
|
|
31
|
+
const parent = itemEntry.parent;
|
|
32
|
+
|
|
33
|
+
if (parent === undefined) // we don't care for items that are not linked to the root item
|
|
34
|
+
return;
|
|
35
|
+
|
|
36
|
+
const parentType = parent.itemType;
|
|
37
|
+
const parentId = parent.itemId;
|
|
38
|
+
|
|
39
|
+
if (result[parentType] === undefined)
|
|
40
|
+
result[parentType] = {};
|
|
41
|
+
|
|
42
|
+
let mapEntry = result[parentType];
|
|
43
|
+
|
|
44
|
+
if (mapEntry[parentId] === undefined)
|
|
45
|
+
mapEntry[parentId] = {};
|
|
46
|
+
|
|
47
|
+
mapEntry = mapEntry[parentId];
|
|
48
|
+
|
|
49
|
+
if (mapEntry[itemType] === undefined)
|
|
50
|
+
mapEntry[itemType] = [];
|
|
51
|
+
|
|
52
|
+
mapEntry[itemType].push(itemEntry);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private processItems (itemType, itemEntries: ItemEntry[], linkAccumulator) {
|
|
60
|
+
if (this.destination[itemType] === undefined)
|
|
61
|
+
this.destination[itemType] = {};
|
|
62
|
+
|
|
63
|
+
if (linkAccumulator[itemType] === undefined)
|
|
64
|
+
linkAccumulator[itemType] = [];
|
|
65
|
+
|
|
66
|
+
for (const itemEntry of itemEntries) {
|
|
67
|
+
const itemId = itemEntry.itemId;
|
|
68
|
+
|
|
69
|
+
// each item only has a single parent, so we can safely do this
|
|
70
|
+
if (this.destination[itemType][itemId] !== undefined)
|
|
71
|
+
continue;
|
|
72
|
+
|
|
73
|
+
linkAccumulator[itemType].push(itemId);
|
|
74
|
+
|
|
75
|
+
this.destination[itemType][itemId] = this.processItem(itemEntry);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private processItem (itemEntry) {
|
|
80
|
+
const itemType = itemEntry.itemType;
|
|
81
|
+
const itemId = itemEntry.itemId;
|
|
82
|
+
const childItems = this.getItemsByParent(itemType, itemId);
|
|
83
|
+
const childCollections = this.collectionCache.getCollectionsByParent(itemType, itemId);
|
|
84
|
+
const values = this.itemEntryService.formatRequest(itemEntry);
|
|
85
|
+
const links = {};
|
|
86
|
+
|
|
87
|
+
for (const childType in childItems)
|
|
88
|
+
this.processItems(childType, childItems[childType], links);
|
|
89
|
+
|
|
90
|
+
for (const collectionEntry of childCollections) {
|
|
91
|
+
if (collectionEntry.options.query !== null)
|
|
92
|
+
continue;
|
|
93
|
+
|
|
94
|
+
this.processItems(collectionEntry.itemType, collectionEntry.items, links);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const hasValues = hasProperties(values);
|
|
98
|
+
const hasLinks = hasProperties(links);
|
|
99
|
+
|
|
100
|
+
if (hasValues && hasLinks)
|
|
101
|
+
return {values: values, links: links};
|
|
102
|
+
|
|
103
|
+
if (hasValues)
|
|
104
|
+
return {values: values};
|
|
105
|
+
|
|
106
|
+
if (hasLinks)
|
|
107
|
+
return {links: links};
|
|
108
|
+
|
|
109
|
+
return EMPTY_ENTRY;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private getItemsByParent (parentType, parentId) {
|
|
113
|
+
const mapEntry = this.parentMap[parentType];
|
|
114
|
+
|
|
115
|
+
if (mapEntry === undefined)
|
|
116
|
+
return {};
|
|
117
|
+
|
|
118
|
+
if (mapEntry[parentId] === undefined)
|
|
119
|
+
return {};
|
|
120
|
+
|
|
121
|
+
return mapEntry[parentId];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { all, defer, reject, when } from 'q';
|
|
2
|
+
|
|
3
|
+
export default class PromiseService {
|
|
4
|
+
all (iterable): Promise<any> {
|
|
5
|
+
return all(iterable);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
when (value?): Promise<any> {
|
|
9
|
+
return when(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
reject (value?): Promise<any> {
|
|
13
|
+
return reject(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
defer (): { resolve (value?); reject (value?); promise: Promise<any>; } {
|
|
17
|
+
return defer();
|
|
18
|
+
}
|
|
19
|
+
}
|