@websolutespa/bom-next 3.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/CHANGELOG.md +7 -0
- package/README.md +11 -0
- package/dist/config.client-B8As5DY0.js +2 -0
- package/dist/config.edge-B8As5DY0.js +2 -0
- package/dist/config.server-B8As5DY0.js +2 -0
- package/dist/exports/client-xcFJNcG7.d.ts +979 -0
- package/dist/exports/client-xcFJNcG7.d.ts.map +1 -0
- package/dist/exports/client.js +5735 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/instrumentation-DBO6QH4d.d.ts +13 -0
- package/dist/exports/instrumentation-DBO6QH4d.d.ts.map +1 -0
- package/dist/exports/instrumentation.js +25 -0
- package/dist/exports/instrumentation.js.map +1 -0
- package/dist/exports/proxy-Cv7QFVci.d.ts +71 -0
- package/dist/exports/proxy-Cv7QFVci.d.ts.map +1 -0
- package/dist/exports/proxy.js +254 -0
- package/dist/exports/proxy.js.map +1 -0
- package/dist/exports/server-BHObJHJ6.d.ts +402 -0
- package/dist/exports/server-BHObJHJ6.d.ts.map +1 -0
- package/dist/exports/server.js +1974 -0
- package/dist/exports/server.js.map +1 -0
- package/dist/http-_2M-UFRx.js +61 -0
- package/dist/http-_2M-UFRx.js.map +1 -0
- package/dist/index-CdYcvudT.d.ts +4 -0
- package/dist/index-CtfAJ0y7.d.ts +499 -0
- package/dist/index-CtfAJ0y7.d.ts.map +1 -0
- package/dist/index.js +201 -0
- package/dist/index.js.map +1 -0
- package/dist/route-DzFwERQP.js +65 -0
- package/dist/route-DzFwERQP.js.map +1 -0
- package/dist/store-CJzqrHsl.js +73 -0
- package/dist/store-CJzqrHsl.js.map +1 -0
- package/dist/types-BatUTdbZ.d.ts +15 -0
- package/dist/types-BatUTdbZ.d.ts.map +1 -0
- package/dist/types-D29_snYY.d.ts +191 -0
- package/dist/types-D29_snYY.d.ts.map +1 -0
- package/dist/utils-Ca7xh5mm.js +78 -0
- package/dist/utils-Ca7xh5mm.js.map +1 -0
- package/dist/utils-YwteTbk7.js +23 -0
- package/dist/utils-YwteTbk7.js.map +1 -0
- package/package.json +156 -0
- package/src/client/client.tsx +18 -0
- package/src/client/extra.tsx +21 -0
- package/src/client/label.tsx +24 -0
- package/src/client/layout.tsx +22 -0
- package/src/client/local.collection.ts +33 -0
- package/src/client/metadata.tsx +217 -0
- package/src/client/page.tsx +22 -0
- package/src/client/useApi.test.tsx +203 -0
- package/src/client/useApi.ts +59 -0
- package/src/client/useCollectionApi.test.tsx +201 -0
- package/src/client/useCollectionApi.ts +66 -0
- package/src/client/useExtra.tsx +12 -0
- package/src/client/useHttp.ts +54 -0
- package/src/client/useLabel.test.tsx +76 -0
- package/src/client/useLabel.tsx +12 -0
- package/src/client/useLayout.tsx +13 -0
- package/src/client/useLink.tsx +13 -0
- package/src/client/useMetaData.tsx +12 -0
- package/src/client/usePage.tsx +12 -0
- package/src/client/useUser.tsx +12 -0
- package/src/client/user.tsx +43 -0
- package/src/exports/client.ts +20 -0
- package/src/exports/instrumentation.ts +1 -0
- package/src/exports/proxy.ts +5 -0
- package/src/exports/server.ts +19 -0
- package/src/global.d.ts +6 -0
- package/src/index.ts +17 -0
- package/src/instrumentation/client.ts +19 -0
- package/src/instrumentation/sentry/capture.requestError.ts +6 -0
- package/src/instrumentation/sentry/capture.routerTransitionStart.ts +4 -0
- package/src/instrumentation/sentry/config.client.ts +9 -0
- package/src/instrumentation/sentry/config.edge.ts +9 -0
- package/src/instrumentation/sentry/config.server.ts +9 -0
- package/src/instrumentation/server.ts +24 -0
- package/src/instrumentation/types.ts +12 -0
- package/src/lib/breadcrumb.ts +21 -0
- package/src/lib/const.ts +3 -0
- package/src/lib/http.test.ts +25 -0
- package/src/lib/http.ts +74 -0
- package/src/lib/label.ts +43 -0
- package/src/lib/layout.defaults.ts +167 -0
- package/src/lib/link.ts +19 -0
- package/src/lib/meta-data.ts +60 -0
- package/src/lib/open-graph.ts +165 -0
- package/src/lib/pagination.ts +78 -0
- package/src/lib/payload.config.ts +12 -0
- package/src/lib/route.ts +83 -0
- package/src/lib/store.ts +63 -0
- package/src/lib/structured-data.ts +24 -0
- package/src/lib/twitter-card.ts +49 -0
- package/src/lib/utils.ts +45 -0
- package/src/mock.ts +264 -0
- package/src/proxy/protected-route.test.ts +75 -0
- package/src/proxy/protected-route.ts +57 -0
- package/src/proxy/proxy.test.ts +48 -0
- package/src/proxy/proxy.ts +64 -0
- package/src/proxy/route-auto-detection.ts +126 -0
- package/src/proxy/route-proxy.ts +187 -0
- package/src/server/context.ts +147 -0
- package/src/server/handlers/auth.handler.ts +27 -0
- package/src/server/handlers/captions.handler.ts +32 -0
- package/src/server/handlers/collection.handler.test.ts +57 -0
- package/src/server/handlers/collection.handler.ts +212 -0
- package/src/server/handlers/error-response.ts +45 -0
- package/src/server/handlers/index.handler.ts +146 -0
- package/src/server/handlers/preview.handler.ts +63 -0
- package/src/server/handlers/registry.handler.test.ts +65 -0
- package/src/server/handlers/registry.handler.ts +259 -0
- package/src/server/handlers/route.handler.ts +161 -0
- package/src/server/handlers/sitemap.handler.ts +374 -0
- package/src/server/handlers/static-params.handler.ts +111 -0
- package/src/server/handlers/vars.handler.ts +167 -0
- package/src/server/layout.ts +120 -0
- package/src/server/lazy.ts +41 -0
- package/src/server/local-api.ts +63 -0
- package/src/server/metadata.ts +87 -0
- package/src/server/mixer-payload.ts +124 -0
- package/src/server/mixer-payload.type-test.ts +27 -0
- package/src/server/mixer.ts +439 -0
- package/src/server/mock/category.ts +34 -0
- package/src/server/mock/collection.ts +21 -0
- package/src/server/mock/page.ts +62 -0
- package/src/server/mock/route.ts +36 -0
- package/src/server/mock/store.ts +159 -0
- package/src/server/page.ts +86 -0
- package/src/server/session/session.ts +101 -0
- package/src/server/utils.ts +23 -0
- package/src/server/utmz.ts +11 -0
- package/src/types.ts +211 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { collectRoutes, defaultLocale, defaultMarket, ICategory, ILocale, IMarket, IMedia, IRoute, isDevelopment, isObject } from '@websolutespa/bom-core';
|
|
2
|
+
import type { Config, User } from '@websolutespa/payload-plugin-bowl';
|
|
3
|
+
import { InMemoryCache } from '@websolutespa/payload-plugin-bowl';
|
|
4
|
+
import { NextRequest } from 'next/server';
|
|
5
|
+
import { BasePayload, getPayload as getPayload_, SanitizedConfig, TypedFallbackLocale, TypedLocale } from 'payload';
|
|
6
|
+
import { cache } from 'react';
|
|
7
|
+
import { guest } from '../lib/const';
|
|
8
|
+
import type { BaseOptions, IContext, MixerContext, ReadonlyHeaders } from '../types';
|
|
9
|
+
import type { MixerPayload } from './mixer-payload';
|
|
10
|
+
|
|
11
|
+
// !!! todo check when to use in memory cache
|
|
12
|
+
|
|
13
|
+
const USE_CACHE = true || !(isDevelopment);
|
|
14
|
+
|
|
15
|
+
const getRouteCache = (): InMemoryCache<IRoute[]> => {
|
|
16
|
+
let cache: InMemoryCache<IRoute[]> | undefined = (globalThis as any)._route;
|
|
17
|
+
if (!cache) {
|
|
18
|
+
cache = new InMemoryCache<IRoute[]>();
|
|
19
|
+
(globalThis as any)._route = cache;
|
|
20
|
+
}
|
|
21
|
+
return cache;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getMixerCache = (): InMemoryCache<MixerContext> => {
|
|
25
|
+
let cache: InMemoryCache<MixerContext> | undefined = (globalThis as any)._mixer;
|
|
26
|
+
if (!cache) {
|
|
27
|
+
cache = new InMemoryCache<MixerContext>();
|
|
28
|
+
(globalThis as any)._mixer = cache;
|
|
29
|
+
}
|
|
30
|
+
return cache;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function getCacheKey({
|
|
34
|
+
market = 'all',
|
|
35
|
+
locale = 'all',
|
|
36
|
+
user = guest,
|
|
37
|
+
draft = 'false',
|
|
38
|
+
}: {
|
|
39
|
+
market?: string;
|
|
40
|
+
locale?: string;
|
|
41
|
+
user?: User & Config['user'];
|
|
42
|
+
draft?: string;
|
|
43
|
+
}) {
|
|
44
|
+
const roles = (user?.roles || ['guest']) as string[];
|
|
45
|
+
const tenants = ('tenants' in user && Array.isArray(user.tenants) ?
|
|
46
|
+
user.tenants.map((x: any) => isObject(x) ? x.id : x) :
|
|
47
|
+
[]
|
|
48
|
+
) as string[];
|
|
49
|
+
roles.sort();
|
|
50
|
+
tenants.sort();
|
|
51
|
+
const keys = [market, locale, draft === 'false' ? 'published' : 'draft', ...roles, ...tenants];
|
|
52
|
+
const key = `context-${keys.join('-')}`;
|
|
53
|
+
return key;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function getContext() {
|
|
57
|
+
let context: IContext | undefined = (globalThis as any)._context;
|
|
58
|
+
if (!context) {
|
|
59
|
+
context = {
|
|
60
|
+
payload: undefined,
|
|
61
|
+
context: undefined,
|
|
62
|
+
} as unknown as IContext;
|
|
63
|
+
(globalThis as any)._context = context;
|
|
64
|
+
}
|
|
65
|
+
return context;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function setContext(data: Partial<IContext>) {
|
|
69
|
+
const context = await getContext();
|
|
70
|
+
if (data.payload) {
|
|
71
|
+
context.payload = data.payload;
|
|
72
|
+
}
|
|
73
|
+
if (data.context) {
|
|
74
|
+
context.context = data.context;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getSearchParams(request: NextRequest) {
|
|
79
|
+
const searchParams = request.nextUrl.searchParams;
|
|
80
|
+
const params = Object.fromEntries(searchParams.entries());
|
|
81
|
+
return {
|
|
82
|
+
market: params.market || defaultMarket,
|
|
83
|
+
locale: params.locale || defaultLocale,
|
|
84
|
+
defaultMarket,
|
|
85
|
+
defaultLocale,
|
|
86
|
+
params,
|
|
87
|
+
searchParams: request.nextUrl.searchParams,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const getPayloadInstance = cache(
|
|
92
|
+
async (props: { config: SanitizedConfig | Promise<SanitizedConfig> }) => await getPayload_(props)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export function getOptions({
|
|
96
|
+
locale = 'all',
|
|
97
|
+
user = guest,
|
|
98
|
+
draft = false,
|
|
99
|
+
}: {
|
|
100
|
+
locale?: string;
|
|
101
|
+
user?: User & Config['user'];
|
|
102
|
+
draft?: boolean;
|
|
103
|
+
}) {
|
|
104
|
+
const localized: BaseOptions = {
|
|
105
|
+
locale: locale as TypedLocale,
|
|
106
|
+
fallbackLocale: defaultLocale as TypedLocale,
|
|
107
|
+
draft,
|
|
108
|
+
depth: 0,
|
|
109
|
+
pagination: false,
|
|
110
|
+
limit: 10000,
|
|
111
|
+
user,
|
|
112
|
+
overrideAccess: false,
|
|
113
|
+
};
|
|
114
|
+
const unlocalized: BaseOptions = {
|
|
115
|
+
locale: 'all',
|
|
116
|
+
fallbackLocale: defaultLocale as TypedLocale,
|
|
117
|
+
draft,
|
|
118
|
+
depth: 0,
|
|
119
|
+
pagination: false,
|
|
120
|
+
limit: 10000,
|
|
121
|
+
user,
|
|
122
|
+
overrideAccess: false,
|
|
123
|
+
};
|
|
124
|
+
return { localized, unlocalized };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function getRoutes({
|
|
128
|
+
payload,
|
|
129
|
+
market = 'all',
|
|
130
|
+
locale = 'all',
|
|
131
|
+
user = guest,
|
|
132
|
+
draft = false,
|
|
133
|
+
}: {
|
|
134
|
+
payload: BasePayload;
|
|
135
|
+
market?: string;
|
|
136
|
+
locale?: string;
|
|
137
|
+
user?: User & Config['user'];
|
|
138
|
+
draft?: boolean;
|
|
139
|
+
}): Promise<IRoute[]> {
|
|
140
|
+
const cacheKey = getCacheKey({ market, locale, user, draft: draft ? 'true' : 'false' });
|
|
141
|
+
|
|
142
|
+
const routeCache = getRouteCache();
|
|
143
|
+
if (USE_CACHE && routeCache.has(cacheKey)) {
|
|
144
|
+
console.log('getRoutes from cache', cacheKey);
|
|
145
|
+
return routeCache.get(cacheKey);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let routes: IRoute[] = [];
|
|
149
|
+
if (!user) {
|
|
150
|
+
return routes;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { localized, unlocalized } = getOptions({ locale, user, draft });
|
|
154
|
+
|
|
155
|
+
const marketRequest = await payload.find({
|
|
156
|
+
...localized,
|
|
157
|
+
collection: 'market',
|
|
158
|
+
where: { isActive: { equals: true } },
|
|
159
|
+
});
|
|
160
|
+
const markets = marketRequest.docs;
|
|
161
|
+
|
|
162
|
+
const localeResult = await payload.findGlobal({
|
|
163
|
+
...localized,
|
|
164
|
+
slug: 'locale',
|
|
165
|
+
}) as { items: ILocale[] };
|
|
166
|
+
const locales = localeResult.items.filter(x => x.isActive);
|
|
167
|
+
|
|
168
|
+
if (locales.length > 0 && markets.length > 0) {
|
|
169
|
+
const pageCollections = payload.config.collections.filter(x => {
|
|
170
|
+
return x.fields.find(x => 'name' in x && x.name === 'slug');
|
|
171
|
+
});
|
|
172
|
+
const keys = pageCollections.map(x => x.slug);
|
|
173
|
+
// console.log(payload.config.collections.find(x => x.slug === 'homepage'));
|
|
174
|
+
// const keys = Object.keys(store.pages) as CollectionSlug[];
|
|
175
|
+
const pages: Record<string, any[]> = Object.fromEntries(keys.map(x => [x, []]));
|
|
176
|
+
for (const key of keys) {
|
|
177
|
+
const pageResult = await payload.find({ ...unlocalized, collection: key });
|
|
178
|
+
pages[key] = pageResult.docs;
|
|
179
|
+
}
|
|
180
|
+
const categoryRequest = await payload.find({
|
|
181
|
+
...unlocalized,
|
|
182
|
+
collection: 'category',
|
|
183
|
+
depth: 1,
|
|
184
|
+
});
|
|
185
|
+
const categories = categoryRequest.docs;
|
|
186
|
+
const mediaRequest = await payload.find({
|
|
187
|
+
...unlocalized,
|
|
188
|
+
collection: 'media',
|
|
189
|
+
depth: 1,
|
|
190
|
+
});
|
|
191
|
+
const medias = mediaRequest.docs;
|
|
192
|
+
routes = collectRoutes(
|
|
193
|
+
pages,
|
|
194
|
+
categories as unknown as ICategory[],
|
|
195
|
+
markets as unknown as IMarket[],
|
|
196
|
+
locales,
|
|
197
|
+
medias as unknown as IMedia[]
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
routeCache.set(cacheKey, routes);
|
|
201
|
+
return routes;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function getMixerContext({
|
|
205
|
+
payload,
|
|
206
|
+
market,
|
|
207
|
+
locale,
|
|
208
|
+
user = guest,
|
|
209
|
+
draft = false,
|
|
210
|
+
}: {
|
|
211
|
+
payload: BasePayload;
|
|
212
|
+
market: string;
|
|
213
|
+
locale: string;
|
|
214
|
+
user?: User & Config['user'];
|
|
215
|
+
draft?: boolean;
|
|
216
|
+
}): Promise<MixerContext> {
|
|
217
|
+
const cacheKey = getCacheKey({ market, locale, user, draft: draft ? 'true' : 'false' });
|
|
218
|
+
|
|
219
|
+
const mixerCache = getMixerCache();
|
|
220
|
+
if (mixerCache.has(cacheKey) && USE_CACHE) {
|
|
221
|
+
console.log('getContext from cache', cacheKey);
|
|
222
|
+
return mixerCache.get(cacheKey);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { localized } = getOptions({ locale, user, draft });
|
|
226
|
+
const localeResult = await payload.findGlobal({
|
|
227
|
+
...localized,
|
|
228
|
+
slug: 'locale',
|
|
229
|
+
}) as { items: ILocale[] };
|
|
230
|
+
const locales = localeResult.items.filter(x => x.isActive) as unknown as ILocale[];
|
|
231
|
+
const localeIds = locales.map(x => x.id) as string[];
|
|
232
|
+
|
|
233
|
+
const marketRequest = await payload.find({
|
|
234
|
+
...localized,
|
|
235
|
+
collection: 'market',
|
|
236
|
+
});
|
|
237
|
+
const marketItems = marketRequest.docs;
|
|
238
|
+
const markets = marketItems.filter(x => x.isActive) as unknown as IMarket[];
|
|
239
|
+
const marketIds = markets.map(x => x.id);
|
|
240
|
+
|
|
241
|
+
const categoryRequest = await payload.find({
|
|
242
|
+
...localized,
|
|
243
|
+
collection: 'category',
|
|
244
|
+
depth: 2,
|
|
245
|
+
});
|
|
246
|
+
const categories = categoryRequest.docs;
|
|
247
|
+
|
|
248
|
+
const routes = await getRoutes({ payload, market, locale, user, draft });
|
|
249
|
+
const currentRoutes = filterRoutes(routes, market, locale);
|
|
250
|
+
|
|
251
|
+
const richText = false;
|
|
252
|
+
|
|
253
|
+
const context: MixerContext = {
|
|
254
|
+
locale,
|
|
255
|
+
localeIds,
|
|
256
|
+
locales,
|
|
257
|
+
market,
|
|
258
|
+
marketIds,
|
|
259
|
+
markets,
|
|
260
|
+
routes,
|
|
261
|
+
currentRoutes,
|
|
262
|
+
categories: categories as unknown as MixerContext['categories'],
|
|
263
|
+
richText,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
mixerCache.set(cacheKey, context);
|
|
267
|
+
await setContext({ context });
|
|
268
|
+
|
|
269
|
+
return context;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Runtime (request-time) payload accessor.
|
|
274
|
+
*
|
|
275
|
+
* By default, operates as the guest user for static generation. For auth:
|
|
276
|
+
* call getHeaders() directly in your page/route/layout, then pass it here.
|
|
277
|
+
*
|
|
278
|
+
* Example:
|
|
279
|
+
* const headers = await getHeaders();
|
|
280
|
+
* const { payload, user, getContext } = await getPayload({ config, headers });
|
|
281
|
+
*/
|
|
282
|
+
export async function getPayload({
|
|
283
|
+
config,
|
|
284
|
+
headers,
|
|
285
|
+
}: {
|
|
286
|
+
config: Promise<SanitizedConfig>;
|
|
287
|
+
headers?: ReadonlyHeaders;
|
|
288
|
+
}) {
|
|
289
|
+
const payload = await getPayload_({ config });
|
|
290
|
+
await setContext({ payload: payload as MixerPayload });
|
|
291
|
+
|
|
292
|
+
let user: User & Config['user'] = guest;
|
|
293
|
+
if (headers) {
|
|
294
|
+
try {
|
|
295
|
+
const auth = await payload.auth({ headers, canSetHeaders: false });
|
|
296
|
+
if (auth.user) {
|
|
297
|
+
user = auth.user as typeof user;
|
|
298
|
+
}
|
|
299
|
+
// console.log('getPayload', user, token);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.log('getPayload.error', error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const draft = false;
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
payload: payload as MixerPayload,
|
|
309
|
+
user,
|
|
310
|
+
getContext: (market: string, locale: string) => getMixerContext({ payload, market, locale, user, draft }),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function filterRoutes(routes: IRoute[], market: string, locale: string): IRoute[] {
|
|
315
|
+
market = market === 'all' ? defaultMarket : market;
|
|
316
|
+
locale = locale === 'all' ? defaultLocale : locale;
|
|
317
|
+
return routes.filter(x => x.market === market && x.locale === locale);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function getAuthHeaders(token: { value: string } | undefined): Headers {
|
|
321
|
+
if (token) {
|
|
322
|
+
return new Headers({ Authorization: `JWT ${token.value}` });
|
|
323
|
+
} else {
|
|
324
|
+
return new Headers({ Authorization: `users API-Key ${process.env.STORE_API_KEY}` });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Produces the common option block shared across all payload.find / payload.findGlobal calls.
|
|
330
|
+
* For payload.find, add pagination: false and limit: 10000 at the call site.
|
|
331
|
+
* For payload.findByID, spread directly — extra find-only fields are ignored.
|
|
332
|
+
*/
|
|
333
|
+
export function buildBaseOptions(user: any, locale: string, draft?: boolean) {
|
|
334
|
+
return {
|
|
335
|
+
locale: locale as TypedLocale,
|
|
336
|
+
fallbackLocale: defaultLocale as TypedFallbackLocale,
|
|
337
|
+
draft: draft ?? false,
|
|
338
|
+
depth: 0,
|
|
339
|
+
user,
|
|
340
|
+
overrideAccess: false,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Payload proxy example
|
|
347
|
+
*
|
|
348
|
+
*
|
|
349
|
+
* import type { Payload, PayloadRequest } from '../types/index.js'
|
|
350
|
+
|
|
351
|
+
import { commitTransaction } from './commitTransaction.js'
|
|
352
|
+
import { initTransaction } from './initTransaction.js'
|
|
353
|
+
import { killTransaction } from './killTransaction.js'
|
|
354
|
+
|
|
355
|
+
const localOperations = [
|
|
356
|
+
'count',
|
|
357
|
+
'countGlobalVersions',
|
|
358
|
+
'countVersions',
|
|
359
|
+
'create',
|
|
360
|
+
'delete',
|
|
361
|
+
'duplicate',
|
|
362
|
+
'find',
|
|
363
|
+
'findByID',
|
|
364
|
+
'delete',
|
|
365
|
+
'duplicate',
|
|
366
|
+
'find',
|
|
367
|
+
'findByID',
|
|
368
|
+
'findGlobal',
|
|
369
|
+
'findGlobalVersionByID',
|
|
370
|
+
'findGlobalVersions',
|
|
371
|
+
'findVersionByID',
|
|
372
|
+
'findVersionByID',
|
|
373
|
+
'login',
|
|
374
|
+
'update',
|
|
375
|
+
'updateGlobal',
|
|
376
|
+
'restoreVersion',
|
|
377
|
+
'restoreGlobalVersion',
|
|
378
|
+
] satisfies (keyof Payload)[]
|
|
379
|
+
|
|
380
|
+
type LocalOperation = (typeof localOperations)[number]
|
|
381
|
+
|
|
382
|
+
const isLocalOperation = (key: string): key is LocalOperation => {
|
|
383
|
+
return localOperations.includes(key as LocalOperation)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export const withTransaction = async <T>(
|
|
387
|
+
payload: Payload,
|
|
388
|
+
callback: (transaction: Payload) => Promise<T>,
|
|
389
|
+
) => {
|
|
390
|
+
const transactionReq = { payload } as PayloadRequest
|
|
391
|
+
await initTransaction(transactionReq)
|
|
392
|
+
|
|
393
|
+
const proxy = new Proxy(payload, {
|
|
394
|
+
get(target, property: keyof Payload, receiver) {
|
|
395
|
+
if (isLocalOperation(property)) {
|
|
396
|
+
const operation = Reflect.get(target, property, receiver)
|
|
397
|
+
|
|
398
|
+
return function (options) {
|
|
399
|
+
let req: Partial<PayloadRequest> = options.req
|
|
400
|
+
|
|
401
|
+
if (!req) {
|
|
402
|
+
req = {}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!req.transactionID) {
|
|
406
|
+
req = { ...req, transactionID: transactionReq.transactionID }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return (operation as (...args: unknown[]) => void)({
|
|
410
|
+
...options,
|
|
411
|
+
req,
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return Reflect.get(target, property, receiver)
|
|
417
|
+
},
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
const result = await callback(proxy)
|
|
422
|
+
await commitTransaction(transactionReq)
|
|
423
|
+
return result
|
|
424
|
+
} catch (e) {
|
|
425
|
+
await killTransaction(transactionReq)
|
|
426
|
+
throw e
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// -----------------
|
|
431
|
+
|
|
432
|
+
const { one, two } = await withTransaction(payload, async (transaction) => {
|
|
433
|
+
const one = await transaction.create({ collection: 'posts', data: { title: 'one' } })
|
|
434
|
+
const two = await transaction.create({ collection: 'posts', data: { title: 'two' } })
|
|
435
|
+
|
|
436
|
+
return { one, two }
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
*/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ICollectionDescription, IEntity, ILocalizedString, ISerializedCollection } from '@websolutespa/bom-core';
|
|
2
|
+
import { textToSlug } from './route';
|
|
3
|
+
|
|
4
|
+
export function toCategory(description: ICollectionDescription, collection: (IEntity & { category?: string })[]): ISerializedCollection {
|
|
5
|
+
const mapToCategory = (x: (IEntity & { category?: string }), i: number) => {
|
|
6
|
+
// !!! loose
|
|
7
|
+
if (typeof x.slug !== 'string' && x.category != null) {
|
|
8
|
+
if (x.title != null) {
|
|
9
|
+
if (typeof x.title === 'string') {
|
|
10
|
+
x.slug = textToSlug(x.title);
|
|
11
|
+
} else {
|
|
12
|
+
const slug: ILocalizedString = { ...x.title };
|
|
13
|
+
x.slug = Object.fromEntries(
|
|
14
|
+
Object.entries(slug).map(([k, v]) => [k, textToSlug(v)])
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
x.slug = '';
|
|
20
|
+
}
|
|
21
|
+
const data = {
|
|
22
|
+
...x,
|
|
23
|
+
};
|
|
24
|
+
if (!data.schema) {
|
|
25
|
+
data.schema = description.singularName;
|
|
26
|
+
}
|
|
27
|
+
return data;
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
...description,
|
|
31
|
+
items: collection.map((x, i) => mapToCategory(x, i)),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ICollectionDescription, IEntity, ISerializedCollection } from '@websolutespa/bom-core';
|
|
2
|
+
|
|
3
|
+
export function toCollection(description: ICollectionDescription, collection: IEntity[], createdAt?: string | Date, updatedAt?: string | Date): ISerializedCollection {
|
|
4
|
+
const mapToCollection = (x: IEntity, i: number) => {
|
|
5
|
+
const data = {
|
|
6
|
+
...((createdAt && createdAt) ? {
|
|
7
|
+
createdAt,
|
|
8
|
+
updatedAt,
|
|
9
|
+
} : {}),
|
|
10
|
+
...x,
|
|
11
|
+
};
|
|
12
|
+
if (!data.schema) {
|
|
13
|
+
data.schema = description.singularName;
|
|
14
|
+
}
|
|
15
|
+
return data;
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
...description,
|
|
19
|
+
items: collection.map((x, i) => mapToCollection(x, i)),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getCollectionDescription, ICategorized, ICollectionDescription, IEntity, ILocalizable, ILocalizedString, ISerializedCollection, ISerializedStore } from '@websolutespa/bom-core';
|
|
2
|
+
import { textToSlug } from './route';
|
|
3
|
+
|
|
4
|
+
export function createPageService(store: ISerializedStore, pageKeys: string[]): ISerializedCollection<ICategorized> {
|
|
5
|
+
const pages: ICategorized[] = [];
|
|
6
|
+
for (const key of pageKeys) {
|
|
7
|
+
const collection = store[key];
|
|
8
|
+
if (collection) {
|
|
9
|
+
const items = collection.items as ICategorized[];
|
|
10
|
+
pages.push(...items);
|
|
11
|
+
} else {
|
|
12
|
+
console.warn(`BomCli.createPageService.collection not found [${key}]`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const pageDescription = getCollectionDescription('page');
|
|
16
|
+
const pageService = {
|
|
17
|
+
...pageDescription,
|
|
18
|
+
items: pages,
|
|
19
|
+
};
|
|
20
|
+
return pageService;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function toPage(description: ICollectionDescription, collection: IEntity[], createdAt: string | Date, updatedAt: string | Date): ISerializedCollection {
|
|
24
|
+
const mapToPage = (x: IEntity, i: number) => {
|
|
25
|
+
if (typeof x.slug !== 'string') {
|
|
26
|
+
if (x.title != null) {
|
|
27
|
+
if (typeof x.title === 'string') {
|
|
28
|
+
x.slug = textToSlug(x.title);
|
|
29
|
+
} else {
|
|
30
|
+
const slug: ILocalizedString = { ...x.title };
|
|
31
|
+
x.slug = Object.fromEntries(
|
|
32
|
+
Object.entries(slug).map(([k, v]) => [k, textToSlug(v)])
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
throw `${description.singularName} error: invalid page missing title or slug`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!x.title) {
|
|
40
|
+
throw `${description.singularName} error: invalid page missing title`;
|
|
41
|
+
}
|
|
42
|
+
const meta: { [key: string]: ILocalizable } = x.meta || ({} as any);
|
|
43
|
+
meta.title = meta.title || (x.title as ILocalizable);
|
|
44
|
+
meta.description = meta.description || (x.abstract as ILocalizable);
|
|
45
|
+
meta.keywords = meta.keywords || 'keywords';
|
|
46
|
+
meta.robots = meta.robots || 'all';
|
|
47
|
+
x.meta = meta;
|
|
48
|
+
const page = {
|
|
49
|
+
createdAt,
|
|
50
|
+
updatedAt,
|
|
51
|
+
...x,
|
|
52
|
+
};
|
|
53
|
+
if (!page.schema) {
|
|
54
|
+
page.schema = description.singularName;
|
|
55
|
+
}
|
|
56
|
+
return page;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
...description,
|
|
60
|
+
items: collection.map((x, i) => mapToPage(x, i)),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { collectRoutes, getCollectionDescription, ICategorized, ICategory, ILocale, IMarket, IMemoryStore, IRoute, ISerializedCollection, ISerializedStore } from '@websolutespa/bom-core';
|
|
2
|
+
import { slugify } from 'transliteration';
|
|
3
|
+
|
|
4
|
+
export function createRouteService(store: ISerializedStore, pageKeys: string[]): ISerializedCollection {
|
|
5
|
+
const routes: IRoute[] = getRoutes(store, pageKeys);
|
|
6
|
+
const routeDescription = getCollectionDescription('route');
|
|
7
|
+
const routeService = {
|
|
8
|
+
...routeDescription,
|
|
9
|
+
items: routes,
|
|
10
|
+
};
|
|
11
|
+
return routeService;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getRoutes(store: ISerializedStore, pageKeys: string[]): IRoute[] {
|
|
15
|
+
let routes: IRoute[] = [];
|
|
16
|
+
const locales = store.locale!.items as ILocale[];
|
|
17
|
+
const markets = store.market!.items as IMarket[];
|
|
18
|
+
if (locales.length > 0 && markets.length > 0) {
|
|
19
|
+
const categories = store.category!.items as ICategory[];
|
|
20
|
+
const categorized: IMemoryStore<ICategorized> = {};
|
|
21
|
+
for (const key of pageKeys) {
|
|
22
|
+
const collection = store[key];
|
|
23
|
+
if (collection) {
|
|
24
|
+
categorized[key] = collection.items as ICategorized[];
|
|
25
|
+
} else {
|
|
26
|
+
console.warn('BomCli.getRoutes', `collection not found [${key}]`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
routes = collectRoutes(categorized, categories, markets, locales);
|
|
30
|
+
}
|
|
31
|
+
return routes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function textToSlug(value: string): string {
|
|
35
|
+
return slugify(value);
|
|
36
|
+
}
|