@websolutespa/bom-mixer-store 1.4.2 → 1.4.3
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/CHANGELOG.md +121 -115
- package/dist/index.d.ts +2 -1
- package/dist/index.js +123 -105
- package/dist/index.mjs +118 -101
- package/package.json +1 -1
- package/src/cached/cached.service.ts +26 -8
- package/src/decorators/href.ts +0 -4
- package/src/decorators/menu.ts +0 -1
- package/src/decorators/nav.ts +0 -1
- package/src/local/local.api.ts +2 -2
- package/src/mock/mock.api.ts +2 -1
- package/src/mock/mock.store.ts +1 -1
- package/src/remote/remote.api.ts +2 -1
- package/src/remote/remote.store.ts +4 -4
- package/src/store/store.handler.ts +116 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,115 +1,121 @@
|
|
|
1
|
-
# @websolutespa/bom-mixer-store
|
|
2
|
-
|
|
3
|
-
## 1.4.
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
## 1.
|
|
27
|
-
|
|
28
|
-
### Minor Changes
|
|
29
|
-
|
|
30
|
-
- Added:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Modified: MockMenuService
|
|
38
|
-
|
|
39
|
-
## 1.2.
|
|
40
|
-
|
|
41
|
-
### Patch Changes
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
## 1.2.
|
|
46
|
-
|
|
47
|
-
###
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
## 1.
|
|
52
|
-
|
|
53
|
-
###
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
## 1.1.
|
|
58
|
-
|
|
59
|
-
###
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
## 1.0
|
|
64
|
-
|
|
65
|
-
###
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
## 1.0.
|
|
70
|
-
|
|
71
|
-
###
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
## 0.
|
|
76
|
-
|
|
77
|
-
###
|
|
78
|
-
|
|
79
|
-
- Modified
|
|
80
|
-
|
|
81
|
-
## 0.3.
|
|
82
|
-
|
|
83
|
-
### Patch Changes
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
## 0.3.
|
|
88
|
-
|
|
89
|
-
###
|
|
90
|
-
|
|
91
|
-
- Added:
|
|
92
|
-
|
|
93
|
-
## 0.
|
|
94
|
-
|
|
95
|
-
### Minor Changes
|
|
96
|
-
|
|
97
|
-
- Added:
|
|
98
|
-
|
|
99
|
-
## 0.
|
|
100
|
-
|
|
101
|
-
###
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
## 0.1.
|
|
106
|
-
|
|
107
|
-
###
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
|
|
111
|
-
## 0.0
|
|
112
|
-
|
|
113
|
-
###
|
|
114
|
-
|
|
115
|
-
-
|
|
1
|
+
# @websolutespa/bom-mixer-store
|
|
2
|
+
|
|
3
|
+
## 1.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Modified: CachedService, Decorators, LocalApi, MockApi, MockStore, RemoteApi, RemoteStore, StoreHandler
|
|
8
|
+
|
|
9
|
+
## 1.4.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Added: Decorators, CachedService, RemotePageService, RemoteMenuService, StoreStrategy.DecoratedApi, nextPhase, storeCacheDuration
|
|
14
|
+
- Modified: RemoteStore
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @websolutespa/bom-core@0.6.4
|
|
17
|
+
|
|
18
|
+
## 1.4.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Modified: MockPageService
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- @websolutespa/bom-core@0.6.3
|
|
25
|
+
|
|
26
|
+
## 1.4.0
|
|
27
|
+
|
|
28
|
+
### Minor Changes
|
|
29
|
+
|
|
30
|
+
- Added: BuildStrategy
|
|
31
|
+
|
|
32
|
+
## 1.3.0
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- Added: storeIndexHandler
|
|
37
|
+
- Modified: Store, StoreHandler, RemoteService, MockService, MockPageService, MockMenuService, StaticService
|
|
38
|
+
|
|
39
|
+
## 1.2.2
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- Modified: MockMenuService IPageRelationResult
|
|
44
|
+
|
|
45
|
+
## 1.2.1
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- Fixing: MockMenuService collectPages
|
|
50
|
+
|
|
51
|
+
## 1.2.0
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- Updating: next 13
|
|
56
|
+
|
|
57
|
+
## 1.1.1
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- Fixing: check redirects by pattern
|
|
62
|
+
|
|
63
|
+
## 1.1.0
|
|
64
|
+
|
|
65
|
+
### Minor Changes
|
|
66
|
+
|
|
67
|
+
- Modified: store refactor
|
|
68
|
+
|
|
69
|
+
## 1.0.1
|
|
70
|
+
|
|
71
|
+
### Patch Changes
|
|
72
|
+
|
|
73
|
+
- Fixing: localhost url
|
|
74
|
+
|
|
75
|
+
## 1.0.0
|
|
76
|
+
|
|
77
|
+
### Minor Changes
|
|
78
|
+
|
|
79
|
+
- Modified routes, added redirects
|
|
80
|
+
|
|
81
|
+
## 0.3.2
|
|
82
|
+
|
|
83
|
+
### Patch Changes
|
|
84
|
+
|
|
85
|
+
- Modified: MockMenuService
|
|
86
|
+
|
|
87
|
+
## 0.3.1
|
|
88
|
+
|
|
89
|
+
### Patch Changes
|
|
90
|
+
|
|
91
|
+
- Added: source files
|
|
92
|
+
|
|
93
|
+
## 0.3.0
|
|
94
|
+
|
|
95
|
+
### Minor Changes
|
|
96
|
+
|
|
97
|
+
- Added: splat routes
|
|
98
|
+
|
|
99
|
+
## 0.2.0
|
|
100
|
+
|
|
101
|
+
### Minor Changes
|
|
102
|
+
|
|
103
|
+
- Added: @websolutespa/bom-core
|
|
104
|
+
|
|
105
|
+
## 0.1.1
|
|
106
|
+
|
|
107
|
+
### Patch Changes
|
|
108
|
+
|
|
109
|
+
- Modified: store service
|
|
110
|
+
|
|
111
|
+
## 0.1.0
|
|
112
|
+
|
|
113
|
+
### Minor Changes
|
|
114
|
+
|
|
115
|
+
- mock refactor
|
|
116
|
+
|
|
117
|
+
## 0.0.2
|
|
118
|
+
|
|
119
|
+
### Patch Changes
|
|
120
|
+
|
|
121
|
+
- Added: first release
|
package/dist/index.d.ts
CHANGED
|
@@ -206,7 +206,8 @@ declare function collectionPatch<T extends IStore>(storePromise: () => Promise<T
|
|
|
206
206
|
declare function collectionDelete<T extends IStore>(storePromise: () => Promise<T>): (request: NextApiRequest, response: NextApiResponse) => Promise<void>;
|
|
207
207
|
declare function routePost(store: IStore, request: NextApiRequest, response: NextApiResponse): Promise<void>;
|
|
208
208
|
declare function storeIndexHandler(): any;
|
|
209
|
+
declare function findRoute(store: IStore, pathname: string, href: string): Promise<IRoute | undefined>;
|
|
209
210
|
|
|
210
211
|
declare function getStore<T extends IStore>(): Promise<T>;
|
|
211
212
|
|
|
212
|
-
export { ApiRequestOptions, BuildStrategy, COLLECTIONS, IApiHandler, ICrudMethod, IField, IHandler, KNOWN_COLLECTIONS, MAX_INT, MOCK_COLLECTIONS, MapTo, MockMenuService, MockPageService, MockService, NextPhase, RemoteMenuService, RemotePageService, RemoteService, SERVICE_DEFAULT_DEPTH, STATIC_COLLECTIONS, StaticService, StaticServiceConfig, StaticServiceSrc, StoreStrategy, apiHandler, buildStrategy, collectionDelete, collectionGet, collectionPatch, collectionPost, decorateHref_, decorateHrefs_, decorateMenu_, decorateMenus_, decorateNav_, decorateNavs_, decorateRelation_, decorateRelations_, decorateStaticRelation_, decorateStaticRelations_, defaultApiRequestOptions, errorHandler, fromCache, getDefaultApiRequestOptions, getLocalApiUrl, getMockStore, getMockStoreFromData_, getRemoteStore, getStore, isApiRequest, isNextRequest, isStaticRequest, localApiDelete, localApiFetch, localApiGet, localApiPatch, localApiPost, localApiPut, localizeItem_, localizeItems_, mockApiDelete, mockApiFetch, mockApiGet, mockApiPatch, mockApiPost, mockApiPut, mockHandler, nextPhase, remoteApiDelete, remoteApiFetch, remoteApiGet, remoteApiPatch, remoteApiPost, remoteApiPut, routePost, staticCollections, storeApiDelete, storeApiGet, storeApiPatch, storeApiPost, storeApiPut, storeCacheDuration, storeHandler, storeIndexHandler, storeStrategy };
|
|
213
|
+
export { ApiRequestOptions, BuildStrategy, COLLECTIONS, IApiHandler, ICrudMethod, IField, IHandler, KNOWN_COLLECTIONS, MAX_INT, MOCK_COLLECTIONS, MapTo, MockMenuService, MockPageService, MockService, NextPhase, RemoteMenuService, RemotePageService, RemoteService, SERVICE_DEFAULT_DEPTH, STATIC_COLLECTIONS, StaticService, StaticServiceConfig, StaticServiceSrc, StoreStrategy, apiHandler, buildStrategy, collectionDelete, collectionGet, collectionPatch, collectionPost, decorateHref_, decorateHrefs_, decorateMenu_, decorateMenus_, decorateNav_, decorateNavs_, decorateRelation_, decorateRelations_, decorateStaticRelation_, decorateStaticRelations_, defaultApiRequestOptions, errorHandler, findRoute, fromCache, getDefaultApiRequestOptions, getLocalApiUrl, getMockStore, getMockStoreFromData_, getRemoteStore, getStore, isApiRequest, isNextRequest, isStaticRequest, localApiDelete, localApiFetch, localApiGet, localApiPatch, localApiPost, localApiPut, localizeItem_, localizeItems_, mockApiDelete, mockApiFetch, mockApiGet, mockApiPatch, mockApiPost, mockApiPut, mockHandler, nextPhase, remoteApiDelete, remoteApiFetch, remoteApiGet, remoteApiPatch, remoteApiPost, remoteApiPut, routePost, staticCollections, storeApiDelete, storeApiGet, storeApiPatch, storeApiPost, storeApiPut, storeCacheDuration, storeHandler, storeIndexHandler, storeStrategy };
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,7 @@ __export(src_exports, {
|
|
|
64
64
|
decorateStaticRelations_: () => decorateStaticRelations_,
|
|
65
65
|
defaultApiRequestOptions: () => defaultApiRequestOptions,
|
|
66
66
|
errorHandler: () => errorHandler,
|
|
67
|
+
findRoute: () => findRoute,
|
|
67
68
|
fromCache: () => fromCache,
|
|
68
69
|
getDefaultApiRequestOptions: () => getDefaultApiRequestOptions,
|
|
69
70
|
getLocalApiUrl: () => getLocalApiUrl,
|
|
@@ -203,10 +204,6 @@ async function decorateHrefs_(store, items, params = {}) {
|
|
|
203
204
|
locale: {
|
|
204
205
|
equals: params.locale
|
|
205
206
|
}
|
|
206
|
-
/*
|
|
207
|
-
...(params.market ? { market: { equals: params.market } } : {}),
|
|
208
|
-
...(params.locale ? { locale: { equals: params.locale } } : {}),
|
|
209
|
-
*/
|
|
210
207
|
}
|
|
211
208
|
});
|
|
212
209
|
return items.map((item) => {
|
|
@@ -252,7 +249,6 @@ async function decorateMenu_(store, item, params) {
|
|
|
252
249
|
const undecoratedPages = collectUndecoratedPages(items);
|
|
253
250
|
if (undecoratedCategories.length > 0 || undecoratedPages.length > 0) {
|
|
254
251
|
const routes = await store.route.findMany({
|
|
255
|
-
...params,
|
|
256
252
|
where: {
|
|
257
253
|
...market ? { market: { equals: market } } : {},
|
|
258
254
|
...locale ? { locale: { equals: locale } } : {}
|
|
@@ -419,7 +415,6 @@ async function decorateNav_(store, item, params = {}, routes) {
|
|
|
419
415
|
}
|
|
420
416
|
async function decorateNavs_(store, items, params = {}) {
|
|
421
417
|
const routes = await store.route.findMany({
|
|
422
|
-
...params,
|
|
423
418
|
where: {
|
|
424
419
|
...params.market ? { market: { equals: params.market } } : {},
|
|
425
420
|
...params.locale ? { locale: { equals: params.locale } } : {}
|
|
@@ -521,7 +516,6 @@ var API_BASE = process.env.NEXT_PUBLIC_API || "/api";
|
|
|
521
516
|
function getLocalApiUrl(pathname) {
|
|
522
517
|
let url = `${import_bom_core5.isBrowser ? window.location.origin : API_URL}${API_BASE}${pathname}`;
|
|
523
518
|
url = import_bom_core5.isBrowser ? url : url.replace("://localhost", "://127.0.0.1");
|
|
524
|
-
console.log("getLocalApiUrl", url);
|
|
525
519
|
return url;
|
|
526
520
|
}
|
|
527
521
|
async function localApiFetch(pathname, options = {}, serviceOptions) {
|
|
@@ -531,6 +525,7 @@ async function localApiFetch(pathname, options = {}, serviceOptions) {
|
|
|
531
525
|
url.searchParams.append("market", serviceOptions.market);
|
|
532
526
|
}
|
|
533
527
|
const apiOptions = (0, import_bom_core5.merge)({ ...getDefaultApiRequestOptions() }, options);
|
|
528
|
+
console.log("localApiFetch", apiOptions.method, url.href);
|
|
534
529
|
const apiResponse = await (0, import_bom_core5.httpFetch)(url.href, apiOptions);
|
|
535
530
|
return apiResponse;
|
|
536
531
|
}
|
|
@@ -665,12 +660,12 @@ var API_BASE3 = process.env.NEXT_PUBLIC_API || "/api";
|
|
|
665
660
|
function getMockApiUrl(pathname) {
|
|
666
661
|
let url = `${API_URL2}${API_BASE3}/_mock${pathname}`;
|
|
667
662
|
url = import_bom_core7.isBrowser ? url : url.replace("://localhost", "://127.0.0.1");
|
|
668
|
-
console.log("getMockApiUrl", url);
|
|
669
663
|
return url;
|
|
670
664
|
}
|
|
671
665
|
async function mockApiFetch(pathname, options = {}) {
|
|
672
666
|
const url = getMockApiUrl(pathname);
|
|
673
667
|
const apiOptions = (0, import_bom_core7.merge)({ ...getDefaultApiRequestOptions() }, options);
|
|
668
|
+
console.log("mockApiFetch", apiOptions.method, url);
|
|
674
669
|
const apiResponse = await (0, import_bom_core7.httpFetch)(url, apiOptions);
|
|
675
670
|
return apiResponse;
|
|
676
671
|
}
|
|
@@ -691,7 +686,7 @@ async function mockApiDelete(url, options = {}) {
|
|
|
691
686
|
}
|
|
692
687
|
|
|
693
688
|
// src/store/store.handler.ts
|
|
694
|
-
var
|
|
689
|
+
var import_bom_core13 = require("@websolutespa/bom-core");
|
|
695
690
|
|
|
696
691
|
// src/static/static.ts
|
|
697
692
|
var staticCollections = [{
|
|
@@ -1128,7 +1123,7 @@ function getMockStoreFromData_(data) {
|
|
|
1128
1123
|
}
|
|
1129
1124
|
}
|
|
1130
1125
|
} else {
|
|
1131
|
-
console.
|
|
1126
|
+
console.warn("mock store not found");
|
|
1132
1127
|
}
|
|
1133
1128
|
staticCollections.forEach((config) => {
|
|
1134
1129
|
store[config.slug] = new StaticService(config, store);
|
|
@@ -1160,13 +1155,11 @@ async function getMockStore() {
|
|
|
1160
1155
|
}, import_bom_core9.isDevelopment ? void 0 : "STORE_MOCK_");
|
|
1161
1156
|
}
|
|
1162
1157
|
|
|
1163
|
-
// src/remote/remote.store.ts
|
|
1164
|
-
var import_bom_core13 = require("@websolutespa/bom-core");
|
|
1165
|
-
|
|
1166
1158
|
// src/cached/cached.service.ts
|
|
1167
1159
|
var import_bom_core10 = require("@websolutespa/bom-core");
|
|
1168
|
-
var CachedService = class {
|
|
1160
|
+
var CachedService = class extends import_bom_core10.JsonService {
|
|
1169
1161
|
constructor(service, options = {}) {
|
|
1162
|
+
super([]);
|
|
1170
1163
|
this.cache_ = /* @__PURE__ */ new Map();
|
|
1171
1164
|
this.duration_ = storeCacheDuration;
|
|
1172
1165
|
if (!service) {
|
|
@@ -1210,11 +1203,27 @@ var CachedService = class {
|
|
|
1210
1203
|
}, key);
|
|
1211
1204
|
}
|
|
1212
1205
|
async findMany(params = {}) {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1206
|
+
if (storeStrategy === "decoratedApi" /* DecoratedApi */) {
|
|
1207
|
+
const { market, locale, ...otherParams } = params;
|
|
1208
|
+
const serializedParams = (0, import_bom_core10.qsSerialize)({ market, locale });
|
|
1209
|
+
const key = `findMany?${serializedParams}`;
|
|
1210
|
+
return await this.fromCache_(async () => {
|
|
1211
|
+
let items = await this.service_.findMany({ market, locale });
|
|
1212
|
+
if (otherParams.where) {
|
|
1213
|
+
items = this.whereItems_(items, otherParams.where);
|
|
1214
|
+
}
|
|
1215
|
+
if (otherParams.sort) {
|
|
1216
|
+
items = this.sortCollection_(items, otherParams.sort);
|
|
1217
|
+
}
|
|
1218
|
+
return items;
|
|
1219
|
+
}, key);
|
|
1220
|
+
} else {
|
|
1221
|
+
const serializedParams = (0, import_bom_core10.qsSerialize)(params);
|
|
1222
|
+
const key = `findMany?${serializedParams}`;
|
|
1223
|
+
return await this.fromCache_(async () => {
|
|
1224
|
+
return await this.service_.findMany(params);
|
|
1225
|
+
}, key);
|
|
1226
|
+
}
|
|
1218
1227
|
}
|
|
1219
1228
|
async findPaged(params = {}) {
|
|
1220
1229
|
const serializedParams = (0, import_bom_core10.qsSerialize)(params);
|
|
@@ -1244,12 +1253,12 @@ var API_BASE4 = process.env.STORE_API || "/api";
|
|
|
1244
1253
|
function getRemoteApiUrl(pathname) {
|
|
1245
1254
|
let url = `${API_URL3}${API_BASE4}${pathname}`;
|
|
1246
1255
|
url = import_bom_core11.isBrowser ? url : url.replace("://localhost", "://127.0.0.1");
|
|
1247
|
-
console.log("getRemoteApiUrl", url);
|
|
1248
1256
|
return url;
|
|
1249
1257
|
}
|
|
1250
1258
|
async function remoteApiFetch(pathname, options = {}) {
|
|
1251
1259
|
const url = getRemoteApiUrl(pathname);
|
|
1252
1260
|
const apiOptions = (0, import_bom_core11.merge)({ ...getDefaultApiRequestOptions() }, options);
|
|
1261
|
+
console.log("remoteApiFetch", apiOptions.method, url);
|
|
1253
1262
|
const apiResponse = await (0, import_bom_core11.httpFetch)(url, apiOptions);
|
|
1254
1263
|
return apiResponse;
|
|
1255
1264
|
}
|
|
@@ -1406,7 +1415,6 @@ var RemotePageService = class extends RemoteService {
|
|
|
1406
1415
|
var PAGES2 = process.env.PAGES ? process.env.PAGES.split(",") : [];
|
|
1407
1416
|
async function getRemoteStore_() {
|
|
1408
1417
|
return fromCache(async () => {
|
|
1409
|
-
console.log("getRemoteStore_");
|
|
1410
1418
|
if (COLLECTIONS.length === 0) {
|
|
1411
1419
|
console.error("RemoteStore.getRemoteStore_ no collections");
|
|
1412
1420
|
throw "RemoteStore.getRemoteStore_ no collections";
|
|
@@ -1426,7 +1434,7 @@ async function getRemoteStore_() {
|
|
|
1426
1434
|
store[key] = new CachedService(
|
|
1427
1435
|
new RemoteMenuService(key, store)
|
|
1428
1436
|
);
|
|
1429
|
-
} else if (KNOWN_COLLECTIONS.includes(key)
|
|
1437
|
+
} else if (KNOWN_COLLECTIONS.includes(key)) {
|
|
1430
1438
|
store[key] = new CachedService(
|
|
1431
1439
|
new RemoteService(key, store)
|
|
1432
1440
|
);
|
|
@@ -1508,7 +1516,7 @@ function collectionGet(storePromise) {
|
|
|
1508
1516
|
const collection = collections[0];
|
|
1509
1517
|
const id = collections[1];
|
|
1510
1518
|
const qs = Object.entries(query).map((kv) => kv.join("=")).join("&");
|
|
1511
|
-
const params = (0,
|
|
1519
|
+
const params = (0, import_bom_core13.qsDeserialize)(qs);
|
|
1512
1520
|
if (!COLLECTIONS.includes(collection)) {
|
|
1513
1521
|
console.error("storeHandler.collectionGet", "method not allowed", collection);
|
|
1514
1522
|
return response.status(405).send("method not allowed");
|
|
@@ -1520,7 +1528,7 @@ function collectionGet(storePromise) {
|
|
|
1520
1528
|
return response.status(405).send("method not allowed");
|
|
1521
1529
|
}
|
|
1522
1530
|
if (id) {
|
|
1523
|
-
const item = await service.findOne((0,
|
|
1531
|
+
const item = await service.findOne((0, import_bom_core13.asEquatable)(id), params);
|
|
1524
1532
|
if (item) {
|
|
1525
1533
|
return response.status(200).json(item);
|
|
1526
1534
|
} else {
|
|
@@ -1612,87 +1620,20 @@ async function routePost(store, request, response) {
|
|
|
1612
1620
|
if (request.body.pathname && request.body.href) {
|
|
1613
1621
|
try {
|
|
1614
1622
|
const { pathname, href } = request.body;
|
|
1615
|
-
const
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
return response.status(200).json(routes[0]);
|
|
1624
|
-
} else {
|
|
1625
|
-
const splatRoutes = await store.route.findMany({
|
|
1626
|
-
where: {
|
|
1627
|
-
useSplat: {
|
|
1628
|
-
equals: true
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
const splatRoute = splatRoutes.reduce((p, c) => {
|
|
1633
|
-
const match = pathname.indexOf(c.id) === 0;
|
|
1634
|
-
if (match && (!p || p.id.length < c.id.length)) {
|
|
1635
|
-
return { ...c, splat: pathname.substring(c.id.length, pathname.length) };
|
|
1636
|
-
} else {
|
|
1637
|
-
return p;
|
|
1638
|
-
}
|
|
1639
|
-
}, void 0);
|
|
1640
|
-
if (splatRoute) {
|
|
1641
|
-
return response.status(200).json(splatRoute);
|
|
1642
|
-
}
|
|
1643
|
-
const redirects = await store.redirect.findMany({
|
|
1644
|
-
where: {
|
|
1645
|
-
isActive: {
|
|
1646
|
-
equals: true
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
});
|
|
1650
|
-
const exactMatchResolver = (redirect) => {
|
|
1651
|
-
const status = parseInt(redirect.status) || 307;
|
|
1652
|
-
if (status < 400) {
|
|
1653
|
-
const redirectUrl = redirect.to;
|
|
1654
|
-
return response.status(status).send({
|
|
1655
|
-
redirectUrl
|
|
1656
|
-
});
|
|
1657
|
-
} else {
|
|
1658
|
-
return response.status(status).send("gone");
|
|
1659
|
-
}
|
|
1660
|
-
};
|
|
1661
|
-
const exactMatchRedirect = redirects.find((x) => x.from === href);
|
|
1662
|
-
if (exactMatchRedirect !== void 0) {
|
|
1663
|
-
return exactMatchResolver(exactMatchRedirect);
|
|
1623
|
+
const route = await findRoute(store, pathname, href);
|
|
1624
|
+
return response.status(200).json(route);
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
if (error.status < 500) {
|
|
1627
|
+
if (error.status < 400) {
|
|
1628
|
+
return response.status(error.status).send({
|
|
1629
|
+
redirectUrl: error.redirectUrl
|
|
1630
|
+
});
|
|
1664
1631
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
let i = 0;
|
|
1670
|
-
let redirectUrl = redirect.to.replace(/\*/g, (...rest) => {
|
|
1671
|
-
return `$${++i}`;
|
|
1672
|
-
});
|
|
1673
|
-
redirectUrl = href.replace(regexp, redirectUrl);
|
|
1674
|
-
return response.status(status).send({
|
|
1675
|
-
redirectUrl
|
|
1676
|
-
});
|
|
1677
|
-
} else {
|
|
1678
|
-
return response.status(status).send("gone");
|
|
1679
|
-
}
|
|
1680
|
-
};
|
|
1681
|
-
redirects.forEach((x) => {
|
|
1682
|
-
let expression = x.from;
|
|
1683
|
-
expression = expression.replace(/\*(?!\))/g, "(.*)");
|
|
1684
|
-
expression = `^${expression.replace(/(^\^)|(\$$)/g, "")}$`;
|
|
1685
|
-
x.from = expression;
|
|
1686
|
-
});
|
|
1687
|
-
const firstMatch = redirects.find((x) => href.match(x.from));
|
|
1688
|
-
if (firstMatch) {
|
|
1689
|
-
return patternMatchResolver(firstMatch);
|
|
1632
|
+
if (error.status === 404) {
|
|
1633
|
+
return response.status(404).send("not found");
|
|
1634
|
+
} else {
|
|
1635
|
+
return response.status(error.status).send("gone");
|
|
1690
1636
|
}
|
|
1691
|
-
return response.status(404).send("not found");
|
|
1692
|
-
}
|
|
1693
|
-
} catch (error) {
|
|
1694
|
-
if (error.status === 404) {
|
|
1695
|
-
return response.status(404).send("not found");
|
|
1696
1637
|
} else {
|
|
1697
1638
|
console.error("storeHandler.error.500", error);
|
|
1698
1639
|
throw error;
|
|
@@ -1706,8 +1647,8 @@ function storeIndexHandler() {
|
|
|
1706
1647
|
return apiHandler({
|
|
1707
1648
|
get: async (request, response) => {
|
|
1708
1649
|
try {
|
|
1709
|
-
const market =
|
|
1710
|
-
const locale =
|
|
1650
|
+
const market = import_bom_core13.defaultMarket;
|
|
1651
|
+
const locale = import_bom_core13.defaultLocale;
|
|
1711
1652
|
if (!Array.isArray(COLLECTIONS) || COLLECTIONS?.length === 0) {
|
|
1712
1653
|
console.error("apiHome.collectionGet", "collections not found", COLLECTIONS);
|
|
1713
1654
|
return response.status(405).send("method not allowed");
|
|
@@ -1833,6 +1774,82 @@ function storeIndexHandler() {
|
|
|
1833
1774
|
}
|
|
1834
1775
|
});
|
|
1835
1776
|
}
|
|
1777
|
+
async function findRoute(store, pathname, href) {
|
|
1778
|
+
const routes = await store.route.findMany({
|
|
1779
|
+
where: {
|
|
1780
|
+
id: {
|
|
1781
|
+
equals: pathname
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
if (routes.length > 0) {
|
|
1786
|
+
return routes[0];
|
|
1787
|
+
} else {
|
|
1788
|
+
const splatRoutes = await store.route.findMany({
|
|
1789
|
+
where: {
|
|
1790
|
+
useSplat: {
|
|
1791
|
+
equals: true
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
const splatRoute = splatRoutes.reduce((p, c) => {
|
|
1796
|
+
const match = pathname.indexOf(c.id) === 0;
|
|
1797
|
+
if (match && (!p || p.id.length < c.id.length)) {
|
|
1798
|
+
return { ...c, splat: pathname.substring(c.id.length, pathname.length) };
|
|
1799
|
+
} else {
|
|
1800
|
+
return p;
|
|
1801
|
+
}
|
|
1802
|
+
}, void 0);
|
|
1803
|
+
if (splatRoute) {
|
|
1804
|
+
return splatRoute;
|
|
1805
|
+
}
|
|
1806
|
+
const redirects = await store.redirect.findMany({
|
|
1807
|
+
where: {
|
|
1808
|
+
isActive: {
|
|
1809
|
+
equals: true
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
const exactMatchResolver = (redirect) => {
|
|
1814
|
+
const status = parseInt(redirect.status) || 307;
|
|
1815
|
+
if (status < 400) {
|
|
1816
|
+
const redirectUrl = redirect.to;
|
|
1817
|
+
throw { status, redirectUrl };
|
|
1818
|
+
} else {
|
|
1819
|
+
throw { status };
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
const exactMatchRedirect = redirects.find((x) => x.from === href);
|
|
1823
|
+
if (exactMatchRedirect !== void 0) {
|
|
1824
|
+
return exactMatchResolver(exactMatchRedirect);
|
|
1825
|
+
}
|
|
1826
|
+
const patternMatchResolver = (redirect) => {
|
|
1827
|
+
const status = parseInt(redirect.status) || 307;
|
|
1828
|
+
if (status < 400) {
|
|
1829
|
+
const regexp = new RegExp(redirect.from);
|
|
1830
|
+
let i = 0;
|
|
1831
|
+
let redirectUrl = redirect.to.replace(/\*/g, (...rest) => {
|
|
1832
|
+
return `$${++i}`;
|
|
1833
|
+
});
|
|
1834
|
+
redirectUrl = href.replace(regexp, redirectUrl);
|
|
1835
|
+
throw { status, redirectUrl };
|
|
1836
|
+
} else {
|
|
1837
|
+
throw { status };
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
redirects.forEach((x) => {
|
|
1841
|
+
let expression = x.from;
|
|
1842
|
+
expression = expression.replace(/\*(?!\))/g, "(.*)");
|
|
1843
|
+
expression = `^${expression.replace(/(^\^)|(\$$)/g, "")}$`;
|
|
1844
|
+
x.from = expression;
|
|
1845
|
+
});
|
|
1846
|
+
const firstMatch = redirects.find((x) => href.match(x.from));
|
|
1847
|
+
if (firstMatch) {
|
|
1848
|
+
return patternMatchResolver(firstMatch);
|
|
1849
|
+
}
|
|
1850
|
+
throw { status: 404 };
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1836
1853
|
|
|
1837
1854
|
// src/mock/mock.handler.ts
|
|
1838
1855
|
function mockHandler() {
|
|
@@ -1892,6 +1909,7 @@ async function storeApiDelete(url, options = {}) {
|
|
|
1892
1909
|
decorateStaticRelations_,
|
|
1893
1910
|
defaultApiRequestOptions,
|
|
1894
1911
|
errorHandler,
|
|
1912
|
+
findRoute,
|
|
1895
1913
|
fromCache,
|
|
1896
1914
|
getDefaultApiRequestOptions,
|
|
1897
1915
|
getLocalApiUrl,
|
package/dist/index.mjs
CHANGED
|
@@ -99,10 +99,6 @@ async function decorateHrefs_(store, items, params = {}) {
|
|
|
99
99
|
locale: {
|
|
100
100
|
equals: params.locale
|
|
101
101
|
}
|
|
102
|
-
/*
|
|
103
|
-
...(params.market ? { market: { equals: params.market } } : {}),
|
|
104
|
-
...(params.locale ? { locale: { equals: params.locale } } : {}),
|
|
105
|
-
*/
|
|
106
102
|
}
|
|
107
103
|
});
|
|
108
104
|
return items.map((item) => {
|
|
@@ -152,7 +148,6 @@ async function decorateMenu_(store, item, params) {
|
|
|
152
148
|
const undecoratedPages = collectUndecoratedPages(items);
|
|
153
149
|
if (undecoratedCategories.length > 0 || undecoratedPages.length > 0) {
|
|
154
150
|
const routes = await store.route.findMany({
|
|
155
|
-
...params,
|
|
156
151
|
where: {
|
|
157
152
|
...market ? { market: { equals: market } } : {},
|
|
158
153
|
...locale ? { locale: { equals: locale } } : {}
|
|
@@ -319,7 +314,6 @@ async function decorateNav_(store, item, params = {}, routes) {
|
|
|
319
314
|
}
|
|
320
315
|
async function decorateNavs_(store, items, params = {}) {
|
|
321
316
|
const routes = await store.route.findMany({
|
|
322
|
-
...params,
|
|
323
317
|
where: {
|
|
324
318
|
...params.market ? { market: { equals: params.market } } : {},
|
|
325
319
|
...params.locale ? { locale: { equals: params.locale } } : {}
|
|
@@ -421,7 +415,6 @@ var API_BASE = process.env.NEXT_PUBLIC_API || "/api";
|
|
|
421
415
|
function getLocalApiUrl(pathname) {
|
|
422
416
|
let url = `${isBrowser ? window.location.origin : API_URL}${API_BASE}${pathname}`;
|
|
423
417
|
url = isBrowser ? url : url.replace("://localhost", "://127.0.0.1");
|
|
424
|
-
console.log("getLocalApiUrl", url);
|
|
425
418
|
return url;
|
|
426
419
|
}
|
|
427
420
|
async function localApiFetch(pathname, options = {}, serviceOptions) {
|
|
@@ -431,6 +424,7 @@ async function localApiFetch(pathname, options = {}, serviceOptions) {
|
|
|
431
424
|
url.searchParams.append("market", serviceOptions.market);
|
|
432
425
|
}
|
|
433
426
|
const apiOptions = merge({ ...getDefaultApiRequestOptions() }, options);
|
|
427
|
+
console.log("localApiFetch", apiOptions.method, url.href);
|
|
434
428
|
const apiResponse = await httpFetch(url.href, apiOptions);
|
|
435
429
|
return apiResponse;
|
|
436
430
|
}
|
|
@@ -565,12 +559,12 @@ var API_BASE3 = process.env.NEXT_PUBLIC_API || "/api";
|
|
|
565
559
|
function getMockApiUrl(pathname) {
|
|
566
560
|
let url = `${API_URL2}${API_BASE3}/_mock${pathname}`;
|
|
567
561
|
url = isBrowser2 ? url : url.replace("://localhost", "://127.0.0.1");
|
|
568
|
-
console.log("getMockApiUrl", url);
|
|
569
562
|
return url;
|
|
570
563
|
}
|
|
571
564
|
async function mockApiFetch(pathname, options = {}) {
|
|
572
565
|
const url = getMockApiUrl(pathname);
|
|
573
566
|
const apiOptions = merge2({ ...getDefaultApiRequestOptions() }, options);
|
|
567
|
+
console.log("mockApiFetch", apiOptions.method, url);
|
|
574
568
|
const apiResponse = await httpFetch2(url, apiOptions);
|
|
575
569
|
return apiResponse;
|
|
576
570
|
}
|
|
@@ -1028,7 +1022,7 @@ function getMockStoreFromData_(data) {
|
|
|
1028
1022
|
}
|
|
1029
1023
|
}
|
|
1030
1024
|
} else {
|
|
1031
|
-
console.
|
|
1025
|
+
console.warn("mock store not found");
|
|
1032
1026
|
}
|
|
1033
1027
|
staticCollections.forEach((config) => {
|
|
1034
1028
|
store[config.slug] = new StaticService(config, store);
|
|
@@ -1060,13 +1054,11 @@ async function getMockStore() {
|
|
|
1060
1054
|
}, isDevelopment ? void 0 : "STORE_MOCK_");
|
|
1061
1055
|
}
|
|
1062
1056
|
|
|
1063
|
-
// src/remote/remote.store.ts
|
|
1064
|
-
import { isDevelopment as isDevelopment2 } from "@websolutespa/bom-core";
|
|
1065
|
-
|
|
1066
1057
|
// src/cached/cached.service.ts
|
|
1067
|
-
import { qsSerialize } from "@websolutespa/bom-core";
|
|
1068
|
-
var CachedService = class {
|
|
1058
|
+
import { JsonService as JsonService3, qsSerialize } from "@websolutespa/bom-core";
|
|
1059
|
+
var CachedService = class extends JsonService3 {
|
|
1069
1060
|
constructor(service, options = {}) {
|
|
1061
|
+
super([]);
|
|
1070
1062
|
this.cache_ = /* @__PURE__ */ new Map();
|
|
1071
1063
|
this.duration_ = storeCacheDuration;
|
|
1072
1064
|
if (!service) {
|
|
@@ -1110,11 +1102,27 @@ var CachedService = class {
|
|
|
1110
1102
|
}, key);
|
|
1111
1103
|
}
|
|
1112
1104
|
async findMany(params = {}) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1105
|
+
if (storeStrategy === "decoratedApi" /* DecoratedApi */) {
|
|
1106
|
+
const { market, locale, ...otherParams } = params;
|
|
1107
|
+
const serializedParams = qsSerialize({ market, locale });
|
|
1108
|
+
const key = `findMany?${serializedParams}`;
|
|
1109
|
+
return await this.fromCache_(async () => {
|
|
1110
|
+
let items = await this.service_.findMany({ market, locale });
|
|
1111
|
+
if (otherParams.where) {
|
|
1112
|
+
items = this.whereItems_(items, otherParams.where);
|
|
1113
|
+
}
|
|
1114
|
+
if (otherParams.sort) {
|
|
1115
|
+
items = this.sortCollection_(items, otherParams.sort);
|
|
1116
|
+
}
|
|
1117
|
+
return items;
|
|
1118
|
+
}, key);
|
|
1119
|
+
} else {
|
|
1120
|
+
const serializedParams = qsSerialize(params);
|
|
1121
|
+
const key = `findMany?${serializedParams}`;
|
|
1122
|
+
return await this.fromCache_(async () => {
|
|
1123
|
+
return await this.service_.findMany(params);
|
|
1124
|
+
}, key);
|
|
1125
|
+
}
|
|
1118
1126
|
}
|
|
1119
1127
|
async findPaged(params = {}) {
|
|
1120
1128
|
const serializedParams = qsSerialize(params);
|
|
@@ -1144,12 +1152,12 @@ var API_BASE4 = process.env.STORE_API || "/api";
|
|
|
1144
1152
|
function getRemoteApiUrl(pathname) {
|
|
1145
1153
|
let url = `${API_URL3}${API_BASE4}${pathname}`;
|
|
1146
1154
|
url = isBrowser3 ? url : url.replace("://localhost", "://127.0.0.1");
|
|
1147
|
-
console.log("getRemoteApiUrl", url);
|
|
1148
1155
|
return url;
|
|
1149
1156
|
}
|
|
1150
1157
|
async function remoteApiFetch(pathname, options = {}) {
|
|
1151
1158
|
const url = getRemoteApiUrl(pathname);
|
|
1152
1159
|
const apiOptions = merge3({ ...getDefaultApiRequestOptions() }, options);
|
|
1160
|
+
console.log("remoteApiFetch", apiOptions.method, url);
|
|
1153
1161
|
const apiResponse = await httpFetch3(url, apiOptions);
|
|
1154
1162
|
return apiResponse;
|
|
1155
1163
|
}
|
|
@@ -1306,7 +1314,6 @@ var RemotePageService = class extends RemoteService {
|
|
|
1306
1314
|
var PAGES2 = process.env.PAGES ? process.env.PAGES.split(",") : [];
|
|
1307
1315
|
async function getRemoteStore_() {
|
|
1308
1316
|
return fromCache(async () => {
|
|
1309
|
-
console.log("getRemoteStore_");
|
|
1310
1317
|
if (COLLECTIONS.length === 0) {
|
|
1311
1318
|
console.error("RemoteStore.getRemoteStore_ no collections");
|
|
1312
1319
|
throw "RemoteStore.getRemoteStore_ no collections";
|
|
@@ -1326,7 +1333,7 @@ async function getRemoteStore_() {
|
|
|
1326
1333
|
store[key] = new CachedService(
|
|
1327
1334
|
new RemoteMenuService(key, store)
|
|
1328
1335
|
);
|
|
1329
|
-
} else if (KNOWN_COLLECTIONS.includes(key)
|
|
1336
|
+
} else if (KNOWN_COLLECTIONS.includes(key)) {
|
|
1330
1337
|
store[key] = new CachedService(
|
|
1331
1338
|
new RemoteService(key, store)
|
|
1332
1339
|
);
|
|
@@ -1512,87 +1519,20 @@ async function routePost(store, request, response) {
|
|
|
1512
1519
|
if (request.body.pathname && request.body.href) {
|
|
1513
1520
|
try {
|
|
1514
1521
|
const { pathname, href } = request.body;
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
return response.status(200).json(routes[0]);
|
|
1524
|
-
} else {
|
|
1525
|
-
const splatRoutes = await store.route.findMany({
|
|
1526
|
-
where: {
|
|
1527
|
-
useSplat: {
|
|
1528
|
-
equals: true
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
});
|
|
1532
|
-
const splatRoute = splatRoutes.reduce((p, c) => {
|
|
1533
|
-
const match = pathname.indexOf(c.id) === 0;
|
|
1534
|
-
if (match && (!p || p.id.length < c.id.length)) {
|
|
1535
|
-
return { ...c, splat: pathname.substring(c.id.length, pathname.length) };
|
|
1536
|
-
} else {
|
|
1537
|
-
return p;
|
|
1538
|
-
}
|
|
1539
|
-
}, void 0);
|
|
1540
|
-
if (splatRoute) {
|
|
1541
|
-
return response.status(200).json(splatRoute);
|
|
1542
|
-
}
|
|
1543
|
-
const redirects = await store.redirect.findMany({
|
|
1544
|
-
where: {
|
|
1545
|
-
isActive: {
|
|
1546
|
-
equals: true
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
});
|
|
1550
|
-
const exactMatchResolver = (redirect) => {
|
|
1551
|
-
const status = parseInt(redirect.status) || 307;
|
|
1552
|
-
if (status < 400) {
|
|
1553
|
-
const redirectUrl = redirect.to;
|
|
1554
|
-
return response.status(status).send({
|
|
1555
|
-
redirectUrl
|
|
1556
|
-
});
|
|
1557
|
-
} else {
|
|
1558
|
-
return response.status(status).send("gone");
|
|
1559
|
-
}
|
|
1560
|
-
};
|
|
1561
|
-
const exactMatchRedirect = redirects.find((x) => x.from === href);
|
|
1562
|
-
if (exactMatchRedirect !== void 0) {
|
|
1563
|
-
return exactMatchResolver(exactMatchRedirect);
|
|
1522
|
+
const route = await findRoute(store, pathname, href);
|
|
1523
|
+
return response.status(200).json(route);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
if (error.status < 500) {
|
|
1526
|
+
if (error.status < 400) {
|
|
1527
|
+
return response.status(error.status).send({
|
|
1528
|
+
redirectUrl: error.redirectUrl
|
|
1529
|
+
});
|
|
1564
1530
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
let i = 0;
|
|
1570
|
-
let redirectUrl = redirect.to.replace(/\*/g, (...rest) => {
|
|
1571
|
-
return `$${++i}`;
|
|
1572
|
-
});
|
|
1573
|
-
redirectUrl = href.replace(regexp, redirectUrl);
|
|
1574
|
-
return response.status(status).send({
|
|
1575
|
-
redirectUrl
|
|
1576
|
-
});
|
|
1577
|
-
} else {
|
|
1578
|
-
return response.status(status).send("gone");
|
|
1579
|
-
}
|
|
1580
|
-
};
|
|
1581
|
-
redirects.forEach((x) => {
|
|
1582
|
-
let expression = x.from;
|
|
1583
|
-
expression = expression.replace(/\*(?!\))/g, "(.*)");
|
|
1584
|
-
expression = `^${expression.replace(/(^\^)|(\$$)/g, "")}$`;
|
|
1585
|
-
x.from = expression;
|
|
1586
|
-
});
|
|
1587
|
-
const firstMatch = redirects.find((x) => href.match(x.from));
|
|
1588
|
-
if (firstMatch) {
|
|
1589
|
-
return patternMatchResolver(firstMatch);
|
|
1531
|
+
if (error.status === 404) {
|
|
1532
|
+
return response.status(404).send("not found");
|
|
1533
|
+
} else {
|
|
1534
|
+
return response.status(error.status).send("gone");
|
|
1590
1535
|
}
|
|
1591
|
-
return response.status(404).send("not found");
|
|
1592
|
-
}
|
|
1593
|
-
} catch (error) {
|
|
1594
|
-
if (error.status === 404) {
|
|
1595
|
-
return response.status(404).send("not found");
|
|
1596
1536
|
} else {
|
|
1597
1537
|
console.error("storeHandler.error.500", error);
|
|
1598
1538
|
throw error;
|
|
@@ -1733,6 +1673,82 @@ function storeIndexHandler() {
|
|
|
1733
1673
|
}
|
|
1734
1674
|
});
|
|
1735
1675
|
}
|
|
1676
|
+
async function findRoute(store, pathname, href) {
|
|
1677
|
+
const routes = await store.route.findMany({
|
|
1678
|
+
where: {
|
|
1679
|
+
id: {
|
|
1680
|
+
equals: pathname
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
if (routes.length > 0) {
|
|
1685
|
+
return routes[0];
|
|
1686
|
+
} else {
|
|
1687
|
+
const splatRoutes = await store.route.findMany({
|
|
1688
|
+
where: {
|
|
1689
|
+
useSplat: {
|
|
1690
|
+
equals: true
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
const splatRoute = splatRoutes.reduce((p, c) => {
|
|
1695
|
+
const match = pathname.indexOf(c.id) === 0;
|
|
1696
|
+
if (match && (!p || p.id.length < c.id.length)) {
|
|
1697
|
+
return { ...c, splat: pathname.substring(c.id.length, pathname.length) };
|
|
1698
|
+
} else {
|
|
1699
|
+
return p;
|
|
1700
|
+
}
|
|
1701
|
+
}, void 0);
|
|
1702
|
+
if (splatRoute) {
|
|
1703
|
+
return splatRoute;
|
|
1704
|
+
}
|
|
1705
|
+
const redirects = await store.redirect.findMany({
|
|
1706
|
+
where: {
|
|
1707
|
+
isActive: {
|
|
1708
|
+
equals: true
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
const exactMatchResolver = (redirect) => {
|
|
1713
|
+
const status = parseInt(redirect.status) || 307;
|
|
1714
|
+
if (status < 400) {
|
|
1715
|
+
const redirectUrl = redirect.to;
|
|
1716
|
+
throw { status, redirectUrl };
|
|
1717
|
+
} else {
|
|
1718
|
+
throw { status };
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
const exactMatchRedirect = redirects.find((x) => x.from === href);
|
|
1722
|
+
if (exactMatchRedirect !== void 0) {
|
|
1723
|
+
return exactMatchResolver(exactMatchRedirect);
|
|
1724
|
+
}
|
|
1725
|
+
const patternMatchResolver = (redirect) => {
|
|
1726
|
+
const status = parseInt(redirect.status) || 307;
|
|
1727
|
+
if (status < 400) {
|
|
1728
|
+
const regexp = new RegExp(redirect.from);
|
|
1729
|
+
let i = 0;
|
|
1730
|
+
let redirectUrl = redirect.to.replace(/\*/g, (...rest) => {
|
|
1731
|
+
return `$${++i}`;
|
|
1732
|
+
});
|
|
1733
|
+
redirectUrl = href.replace(regexp, redirectUrl);
|
|
1734
|
+
throw { status, redirectUrl };
|
|
1735
|
+
} else {
|
|
1736
|
+
throw { status };
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
redirects.forEach((x) => {
|
|
1740
|
+
let expression = x.from;
|
|
1741
|
+
expression = expression.replace(/\*(?!\))/g, "(.*)");
|
|
1742
|
+
expression = `^${expression.replace(/(^\^)|(\$$)/g, "")}$`;
|
|
1743
|
+
x.from = expression;
|
|
1744
|
+
});
|
|
1745
|
+
const firstMatch = redirects.find((x) => href.match(x.from));
|
|
1746
|
+
if (firstMatch) {
|
|
1747
|
+
return patternMatchResolver(firstMatch);
|
|
1748
|
+
}
|
|
1749
|
+
throw { status: 404 };
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1736
1752
|
|
|
1737
1753
|
// src/mock/mock.handler.ts
|
|
1738
1754
|
function mockHandler() {
|
|
@@ -1791,6 +1807,7 @@ export {
|
|
|
1791
1807
|
decorateStaticRelations_,
|
|
1792
1808
|
defaultApiRequestOptions,
|
|
1793
1809
|
errorHandler,
|
|
1810
|
+
findRoute,
|
|
1794
1811
|
fromCache,
|
|
1795
1812
|
getDefaultApiRequestOptions,
|
|
1796
1813
|
getLocalApiUrl,
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IEntity, IEquatable, IPagedResult, IQuerable, QueryParams, qsSerialize } from '@websolutespa/bom-core';
|
|
2
|
-
import { storeCacheDuration } from '../store/store';
|
|
1
|
+
import { IEntity, IEquatable, IPagedResult, IQuerable, JsonService, QueryParams, qsSerialize } from '@websolutespa/bom-core';
|
|
2
|
+
import { StoreStrategy, storeCacheDuration, storeStrategy } from '../store/store';
|
|
3
3
|
|
|
4
4
|
export type ICacheResult<T extends IEntity = IEntity> = T | T[] | IPagedResult<T> | undefined;
|
|
5
5
|
|
|
@@ -12,13 +12,15 @@ export type InMemoryCacheOptions = {
|
|
|
12
12
|
duration?: number | false;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export class CachedService<T extends IEntity = IEntity> implements IQuerable<T> {
|
|
15
|
+
// export class CachedService<T extends IEntity = IEntity> implements IQuerable<T> {
|
|
16
|
+
export class CachedService<T extends IEntity = IEntity> extends JsonService<T> {
|
|
16
17
|
|
|
17
18
|
private service_: IQuerable<T>;
|
|
18
19
|
private cache_: Map<string, ICache<T>> = new Map<string, ICache<T>>();
|
|
19
20
|
private duration_: number | false = storeCacheDuration;
|
|
20
21
|
|
|
21
22
|
constructor(service: IQuerable<T>, options: InMemoryCacheOptions = {}) {
|
|
23
|
+
super([]);
|
|
22
24
|
if (!service) {
|
|
23
25
|
throw new Error('CachedService: service is required');
|
|
24
26
|
}
|
|
@@ -67,11 +69,27 @@ export class CachedService<T extends IEntity = IEntity> implements IQuerable<T>
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
async findMany<B extends T = T>(params: QueryParams = {}): Promise<B[]> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (storeStrategy === StoreStrategy.DecoratedApi) {
|
|
73
|
+
const { market, locale, ...otherParams } = params;
|
|
74
|
+
const serializedParams = qsSerialize({ market, locale });
|
|
75
|
+
const key = `findMany?${serializedParams}`;
|
|
76
|
+
return await this.fromCache_(async () => {
|
|
77
|
+
let items = await this.service_.findMany({ market, locale });
|
|
78
|
+
if (otherParams.where) {
|
|
79
|
+
items = this.whereItems_(items, otherParams.where);
|
|
80
|
+
}
|
|
81
|
+
if (otherParams.sort) {
|
|
82
|
+
items = this.sortCollection_(items, otherParams.sort);
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}, key) as B[];
|
|
86
|
+
} else {
|
|
87
|
+
const serializedParams = qsSerialize(params);
|
|
88
|
+
const key = `findMany?${serializedParams}`;
|
|
89
|
+
return await this.fromCache_(async () => {
|
|
90
|
+
return await this.service_.findMany(params);
|
|
91
|
+
}, key) as B[];
|
|
92
|
+
}
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
async findPaged<B extends T = T>(params: QueryParams = {}): Promise<IPagedResult<B>> {
|
package/src/decorators/href.ts
CHANGED
|
@@ -35,10 +35,6 @@ export async function decorateHrefs_<T extends ISchema = ISchema>(store: IStore,
|
|
|
35
35
|
locale: {
|
|
36
36
|
equals: params.locale,
|
|
37
37
|
},
|
|
38
|
-
/*
|
|
39
|
-
...(params.market ? { market: { equals: params.market } } : {}),
|
|
40
|
-
...(params.locale ? { locale: { equals: params.locale } } : {}),
|
|
41
|
-
*/
|
|
42
38
|
},
|
|
43
39
|
});
|
|
44
40
|
return items.map(item => {
|
package/src/decorators/menu.ts
CHANGED
|
@@ -22,7 +22,6 @@ export async function decorateMenu_(store: IStore, item: IUndecoratedMenu, param
|
|
|
22
22
|
undecoratedPages.length > 0
|
|
23
23
|
) {
|
|
24
24
|
const routes = await store.route.findMany<IRoute>({
|
|
25
|
-
...params,
|
|
26
25
|
where: {
|
|
27
26
|
...(market ? { market: { equals: market } } : {}),
|
|
28
27
|
...(locale ? { locale: { equals: locale } } : {}),
|
package/src/decorators/nav.ts
CHANGED
|
@@ -19,7 +19,6 @@ export async function decorateNav_<T extends ISchema = ISchema>(store: IStore, i
|
|
|
19
19
|
|
|
20
20
|
export async function decorateNavs_<T extends ISchema = ISchema>(store: IStore, items: T[], params: QueryParams = {}): Promise<T[]> {
|
|
21
21
|
const routes = await store.route.findMany<IRoute>({
|
|
22
|
-
...params,
|
|
23
22
|
where: {
|
|
24
23
|
...(params.market ? { market: { equals: params.market } } : {}),
|
|
25
24
|
...(params.locale ? { locale: { equals: params.locale } } : {}),
|
package/src/local/local.api.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function getLocalApiUrl(pathname: string): string {
|
|
|
16
16
|
let url = `${isBrowser ? window.location.origin : API_URL}${API_BASE}${pathname}`;
|
|
17
17
|
// !!! node 18 issue -> require('dns').setDefaultResultOrder('ipv4first');
|
|
18
18
|
url = isBrowser ? url : url.replace('://localhost', '://127.0.0.1');
|
|
19
|
-
console.log('getLocalApiUrl', url);
|
|
19
|
+
// console.log('getLocalApiUrl', url);
|
|
20
20
|
return url;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ export async function localApiFetch<T = unknown>(pathname: string, options: ApiR
|
|
|
28
28
|
url.searchParams.append('market', serviceOptions.market);
|
|
29
29
|
}
|
|
30
30
|
const apiOptions = merge({ ...getDefaultApiRequestOptions() }, options);
|
|
31
|
-
|
|
31
|
+
console.log('localApiFetch', apiOptions.method, url.href);
|
|
32
32
|
const apiResponse = await httpFetch(url.href, apiOptions);
|
|
33
33
|
// console.log('localApiFetch', pathname, apiResponse);
|
|
34
34
|
return apiResponse;
|
package/src/mock/mock.api.ts
CHANGED
|
@@ -12,13 +12,14 @@ function getMockApiUrl(pathname: string): string {
|
|
|
12
12
|
let url = `${API_URL}${API_BASE}/_mock${pathname}`;
|
|
13
13
|
// !!! node 18 issue -> require('dns').setDefaultResultOrder('ipv4first');
|
|
14
14
|
url = isBrowser ? url : url.replace('://localhost', '://127.0.0.1');
|
|
15
|
-
console.log('getMockApiUrl', url);
|
|
15
|
+
// console.log('getMockApiUrl', url);
|
|
16
16
|
return url;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export async function mockApiFetch<T = unknown>(pathname: string, options: ApiRequestOptions = {}): Promise<T> {
|
|
20
20
|
const url = getMockApiUrl(pathname);
|
|
21
21
|
const apiOptions = merge({ ...getDefaultApiRequestOptions() }, options);
|
|
22
|
+
console.log('mockApiFetch', apiOptions.method, url);
|
|
22
23
|
const apiResponse = await httpFetch(url, apiOptions);
|
|
23
24
|
return apiResponse;
|
|
24
25
|
}
|
package/src/mock/mock.store.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function getMockStoreFromData_<T extends IStore>(data?: IMemoryStore): T
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
} else {
|
|
42
|
-
console.
|
|
42
|
+
console.warn('mock store not found');
|
|
43
43
|
}
|
|
44
44
|
staticCollections.forEach(config => {
|
|
45
45
|
store[config.slug] = new StaticService(config, store);
|
package/src/remote/remote.api.ts
CHANGED
|
@@ -12,13 +12,14 @@ function getRemoteApiUrl(pathname: string): string {
|
|
|
12
12
|
let url = `${API_URL}${API_BASE}${pathname}`;
|
|
13
13
|
// !!! node 18 issue -> require('dns').setDefaultResultOrder('ipv4first');
|
|
14
14
|
url = isBrowser ? url : url.replace('://localhost', '://127.0.0.1');
|
|
15
|
-
console.log('getRemoteApiUrl', url);
|
|
15
|
+
// console.log('getRemoteApiUrl', url);
|
|
16
16
|
return url;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export async function remoteApiFetch<T = unknown>(pathname: string, options: ApiRequestOptions = {}): Promise<T> {
|
|
20
20
|
const url = getRemoteApiUrl(pathname);
|
|
21
21
|
const apiOptions = merge({ ...getDefaultApiRequestOptions() }, options);
|
|
22
|
+
console.log('remoteApiFetch', apiOptions.method, url);
|
|
22
23
|
const apiResponse = await httpFetch(url, apiOptions);
|
|
23
24
|
return apiResponse;
|
|
24
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IEntity, IMemoryStore, IQuerable, IStore
|
|
1
|
+
import { IEntity, IMemoryStore, IQuerable, IStore } from '@websolutespa/bom-core';
|
|
2
2
|
import { fromCache } from '../cache/cache.service';
|
|
3
3
|
import { CachedService } from '../cached/cached.service';
|
|
4
4
|
import { MockService } from '../mock/mock.service';
|
|
@@ -13,7 +13,7 @@ const PAGES: string[] = process.env.PAGES ? process.env.PAGES.split(',') : [];
|
|
|
13
13
|
|
|
14
14
|
async function getRemoteStore_<T extends IStore>(): Promise<T> {
|
|
15
15
|
return fromCache<T>(async () => {
|
|
16
|
-
console.log('getRemoteStore_');
|
|
16
|
+
// console.log('getRemoteStore_');
|
|
17
17
|
if (COLLECTIONS.length === 0) {
|
|
18
18
|
console.error('RemoteStore.getRemoteStore_ no collections');
|
|
19
19
|
throw ('RemoteStore.getRemoteStore_ no collections');
|
|
@@ -33,7 +33,7 @@ async function getRemoteStore_<T extends IStore>(): Promise<T> {
|
|
|
33
33
|
store[key] = new CachedService(
|
|
34
34
|
new RemoteMenuService(key, store)
|
|
35
35
|
);
|
|
36
|
-
} else if (KNOWN_COLLECTIONS.includes(key)
|
|
36
|
+
} else if (KNOWN_COLLECTIONS.includes(key)) {
|
|
37
37
|
store[key] = new CachedService(
|
|
38
38
|
new RemoteService(key, store)
|
|
39
39
|
);
|
|
@@ -42,8 +42,8 @@ async function getRemoteStore_<T extends IStore>(): Promise<T> {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
return store as T;
|
|
45
|
-
// }, isDevelopment ? undefined : 'STORE_RUNTIME_');
|
|
46
45
|
}, 'STORE_RUNTIME_');
|
|
46
|
+
// }, isDevelopment ? undefined : 'STORE_RUNTIME_');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function getRemoteStoreStaticBuild_<T extends IStore>(): Promise<T> {
|
|
@@ -155,6 +155,9 @@ export async function routePost(store: IStore, request: NextApiRequest, response
|
|
|
155
155
|
if (request.body.pathname && request.body.href) {
|
|
156
156
|
try {
|
|
157
157
|
const { pathname, href } = request.body;
|
|
158
|
+
const route = await findRoute(store, pathname, href);
|
|
159
|
+
return response.status(200).json(route);
|
|
160
|
+
/*
|
|
158
161
|
// here we use findMany because route id would break api url
|
|
159
162
|
const routes = await store.route.findMany<IRoute>({
|
|
160
163
|
where: {
|
|
@@ -221,7 +224,7 @@ export async function routePost(store: IStore, request: NextApiRequest, response
|
|
|
221
224
|
if (status < 400) {
|
|
222
225
|
const regexp = new RegExp(redirect.from);
|
|
223
226
|
let i = 0;
|
|
224
|
-
let redirectUrl = (redirect.to as string).replace(
|
|
227
|
+
let redirectUrl = (redirect.to as string).replace(//g, (...rest) => {
|
|
225
228
|
return `$${++i}`;
|
|
226
229
|
});
|
|
227
230
|
redirectUrl = href.replace(regexp, redirectUrl);
|
|
@@ -249,7 +252,24 @@ export async function routePost(store: IStore, request: NextApiRequest, response
|
|
|
249
252
|
// console.error('not found', collection);
|
|
250
253
|
return response.status(404).send('not found');
|
|
251
254
|
}
|
|
255
|
+
*/
|
|
252
256
|
} catch (error: any) {
|
|
257
|
+
if (error.status < 500) {
|
|
258
|
+
if (error.status < 400) {
|
|
259
|
+
return response.status(error.status).send({
|
|
260
|
+
redirectUrl: error.redirectUrl,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (error.status === 404) {
|
|
264
|
+
return response.status(404).send('not found');
|
|
265
|
+
} else {
|
|
266
|
+
return response.status(error.status).send('gone');
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
console.error('storeHandler.error.500', error);
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
/*
|
|
253
273
|
if (error.status === 404) {
|
|
254
274
|
// console.error('not found', collection);
|
|
255
275
|
return response.status(404).send('not found');
|
|
@@ -257,6 +277,7 @@ export async function routePost(store: IStore, request: NextApiRequest, response
|
|
|
257
277
|
console.error('storeHandler.error.500', error);
|
|
258
278
|
throw error;
|
|
259
279
|
}
|
|
280
|
+
*/
|
|
260
281
|
}
|
|
261
282
|
} else {
|
|
262
283
|
return response.status(400).send('bad request');
|
|
@@ -397,3 +418,97 @@ export function storeIndexHandler() {
|
|
|
397
418
|
},
|
|
398
419
|
});
|
|
399
420
|
}
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
export async function findRoute(store: IStore, pathname: string, href: string): Promise<IRoute | undefined> {
|
|
425
|
+
// here we use findMany because route id would break api url
|
|
426
|
+
const routes = await store.route.findMany<IRoute>({
|
|
427
|
+
where: {
|
|
428
|
+
id: {
|
|
429
|
+
equals: pathname,
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
// console.log('pathname', pathname);
|
|
434
|
+
if (routes.length > 0) {
|
|
435
|
+
return routes[0];
|
|
436
|
+
} else {
|
|
437
|
+
// exact route match not found
|
|
438
|
+
// let's try if match splat routes
|
|
439
|
+
const splatRoutes = await store.route.findMany<IRoute>({
|
|
440
|
+
where: {
|
|
441
|
+
useSplat: {
|
|
442
|
+
equals: true,
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
// console.log('splatRoutes', splatRoutes);
|
|
447
|
+
// find best match splat route
|
|
448
|
+
const splatRoute = splatRoutes.reduce((p: (IRoute & { splat: string }) | undefined, c: IRoute) => {
|
|
449
|
+
const match = pathname.indexOf(c.id) === 0;
|
|
450
|
+
if (match && (!p || p.id.length < c.id.length)) {
|
|
451
|
+
return { ...c, splat: pathname.substring(c.id.length, pathname.length) };
|
|
452
|
+
} else {
|
|
453
|
+
return p;
|
|
454
|
+
}
|
|
455
|
+
}, undefined);
|
|
456
|
+
if (splatRoute) {
|
|
457
|
+
return splatRoute;
|
|
458
|
+
}
|
|
459
|
+
// collect redirects
|
|
460
|
+
const redirects = await store.redirect.findMany<IRedirect>({
|
|
461
|
+
where: {
|
|
462
|
+
isActive: {
|
|
463
|
+
equals: true,
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
// redirects.sort((a, b) => a.order - b.order);
|
|
468
|
+
// exact match redirect resolver
|
|
469
|
+
const exactMatchResolver = (redirect: IRedirect) => {
|
|
470
|
+
const status: number = parseInt(redirect.status) || 307;
|
|
471
|
+
if (status < 400) {
|
|
472
|
+
const redirectUrl = redirect.to;
|
|
473
|
+
throw { status, redirectUrl };
|
|
474
|
+
} else {
|
|
475
|
+
throw { status };
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
// check exact match redirects
|
|
479
|
+
const exactMatchRedirect = redirects.find(x => x.from === href);
|
|
480
|
+
if (exactMatchRedirect !== undefined) {
|
|
481
|
+
return exactMatchResolver(exactMatchRedirect);
|
|
482
|
+
}
|
|
483
|
+
// pattern match redirect resolver
|
|
484
|
+
const patternMatchResolver = (redirect: IRedirect) => {
|
|
485
|
+
const status: number = parseInt(redirect.status) || 307;
|
|
486
|
+
if (status < 400) {
|
|
487
|
+
const regexp = new RegExp(redirect.from);
|
|
488
|
+
let i = 0;
|
|
489
|
+
let redirectUrl = (redirect.to as string).replace(/\*/g, (...rest) => {
|
|
490
|
+
return `$${++i}`;
|
|
491
|
+
});
|
|
492
|
+
redirectUrl = href.replace(regexp, redirectUrl);
|
|
493
|
+
throw { status, redirectUrl };
|
|
494
|
+
} else {
|
|
495
|
+
throw { status };
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
// check redirects by pattern
|
|
499
|
+
redirects.forEach(x => {
|
|
500
|
+
let expression = x.from;
|
|
501
|
+
// wrapping catchall in (.*)
|
|
502
|
+
expression = expression.replace(/\*(?!\))/g, '(.*)');
|
|
503
|
+
// wrapping expression in ^...$
|
|
504
|
+
expression = `^${expression.replace(/(^\^)|(\$$)/g, '')}$`;
|
|
505
|
+
x.from = expression;
|
|
506
|
+
});
|
|
507
|
+
// check if a regex pattern redirect does match
|
|
508
|
+
const firstMatch = redirects.find(x => href.match(x.from));
|
|
509
|
+
if (firstMatch) {
|
|
510
|
+
return patternMatchResolver(firstMatch);
|
|
511
|
+
}
|
|
512
|
+
throw { status: 404 };
|
|
513
|
+
}
|
|
514
|
+
}
|