@webiny/website-builder-sdk 0.0.0-unstable.7be00a75a9 → 0.0.0-unstable.7c2def32a7
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/BindingsResolver.d.ts +4 -1
- package/BindingsResolver.js.map +1 -1
- package/ComponentInputTraverser.js +4 -0
- package/ComponentInputTraverser.js.map +1 -1
- package/ComponentResolver.d.ts +6 -3
- package/ComponentResolver.js +9 -4
- package/ComponentResolver.js.map +1 -1
- package/ContentSdk.d.ts +67 -1
- package/ContentSdk.js +90 -8
- package/ContentSdk.js.map +1 -1
- package/DocumentStoreManager.d.ts +9 -2
- package/DocumentStoreManager.js +24 -5
- package/DocumentStoreManager.js.map +1 -1
- package/EditingSdk.d.ts +7 -1
- package/EditingSdk.js +5 -3
- package/EditingSdk.js.map +1 -1
- package/ElementFactory.js +7 -2
- package/ElementFactory.js.map +1 -1
- package/ElementFactory.test.js +102 -0
- package/ElementFactory.test.js.map +1 -1
- package/LiveSdk.d.ts +3 -1
- package/LiveSdk.js +3 -2
- package/LiveSdk.js.map +1 -1
- package/PreviewDocument.d.ts +3 -1
- package/PreviewDocument.js +5 -2
- package/PreviewDocument.js.map +1 -1
- package/PreviewSdk.d.ts +4 -1
- package/PreviewSdk.js +6 -2
- package/PreviewSdk.js.map +1 -1
- package/ResizeObserver.js +1 -1
- package/ResizeObserver.js.map +1 -1
- package/StylesBindingsProcessor.js +3 -2
- package/StylesBindingsProcessor.js.map +1 -1
- package/Theme.js +2 -1
- package/Theme.js.map +1 -1
- package/asset/deliveryUrl.d.ts +41 -0
- package/asset/deliveryUrl.js +34 -0
- package/asset/deliveryUrl.js.map +1 -0
- package/asset/deliveryUrl.test.d.ts +1 -0
- package/asset/deliveryUrl.test.js +127 -0
- package/asset/deliveryUrl.test.js.map +1 -0
- package/asset/geometry.d.ts +7 -0
- package/asset/geometry.js +10 -0
- package/asset/geometry.js.map +1 -0
- package/asset/imageSrcSet.d.ts +63 -0
- package/asset/imageSrcSet.js +98 -0
- package/asset/imageSrcSet.js.map +1 -0
- package/asset/imageSrcSet.test.d.ts +1 -0
- package/asset/imageSrcSet.test.js +103 -0
- package/asset/imageSrcSet.test.js.map +1 -0
- package/asset/index.d.ts +5 -0
- package/asset/index.js +5 -0
- package/asset/normalize.d.ts +24 -0
- package/asset/normalize.js +106 -0
- package/asset/normalize.js.map +1 -0
- package/asset/normalize.test.d.ts +1 -0
- package/asset/normalize.test.js +263 -0
- package/asset/normalize.test.js.map +1 -0
- package/asset/types.d.ts +2 -0
- package/asset/types.js +0 -0
- package/componentLoader/ComponentLoader.d.ts +16 -0
- package/componentLoader/ComponentLoader.js +0 -0
- package/componentLoader/RegistryOnlyComponentLoader.d.ts +9 -0
- package/componentLoader/RegistryOnlyComponentLoader.js +8 -0
- package/componentLoader/RegistryOnlyComponentLoader.js.map +1 -0
- package/componentLoader/createComponentLoader.d.ts +7 -0
- package/componentLoader/createComponentLoader.js +10 -0
- package/componentLoader/createComponentLoader.js.map +1 -0
- package/componentLoader/defaultComponentLoader.d.ts +7 -0
- package/componentLoader/defaultComponentLoader.js +8 -0
- package/componentLoader/defaultComponentLoader.js.map +1 -0
- package/componentLoader/index.d.ts +3 -0
- package/componentLoader/index.js +3 -0
- package/contentClientState.test.d.ts +1 -0
- package/contentClientState.test.js +384 -0
- package/contentClientState.test.js.map +1 -0
- package/contentEntry/ContentEntryBindingsResolver.d.ts +33 -0
- package/contentEntry/ContentEntryBindingsResolver.js +52 -0
- package/contentEntry/ContentEntryBindingsResolver.js.map +1 -0
- package/contentEntry/ContentEntryCache.d.ts +49 -0
- package/contentEntry/ContentEntryCache.js +51 -0
- package/contentEntry/ContentEntryCache.js.map +1 -0
- package/contentEntry/resolveContentEntry.d.ts +74 -0
- package/contentEntry/resolveContentEntry.js +53 -0
- package/contentEntry/resolveContentEntry.js.map +1 -0
- package/contentEntry/resolveContentEntry.test.d.ts +1 -0
- package/contentEntry/resolveContentEntry.test.js +213 -0
- package/contentEntry/resolveContentEntry.test.js.map +1 -0
- package/createComponent.d.ts +5 -0
- package/createComponent.js +32 -0
- package/createComponent.js.map +1 -0
- package/createContentClient.d.ts +17 -0
- package/createContentClient.js +16 -0
- package/createContentClient.js.map +1 -0
- package/createContentClient.test.d.ts +1 -0
- package/createContentClient.test.js +165 -0
- package/createContentClient.test.js.map +1 -0
- package/createInput.d.ts +10 -2
- package/createInput.js +8 -1
- package/createInput.js.map +1 -1
- package/dataProviders/ApiClient.d.ts +10 -5
- package/dataProviders/ApiClient.js +10 -10
- package/dataProviders/ApiClient.js.map +1 -1
- package/dataProviders/DefaultDataProvider.d.ts +4 -0
- package/dataProviders/DefaultDataProvider.js +35 -0
- package/dataProviders/DefaultDataProvider.js.map +1 -1
- package/dataProviders/GET_EXPERIMENT_PAUSED.d.ts +1 -0
- package/dataProviders/GET_EXPERIMENT_PAUSED.js +17 -0
- package/dataProviders/GET_EXPERIMENT_PAUSED.js.map +1 -0
- package/dataProviders/GET_PAGE_BY_ID.d.ts +1 -1
- package/dataProviders/GET_PAGE_BY_ID.js +2 -0
- package/dataProviders/GET_PAGE_BY_ID.js.map +1 -1
- package/dataProviders/GET_PAGE_BY_PATH.d.ts +1 -1
- package/dataProviders/GET_PAGE_BY_PATH.js +2 -0
- package/dataProviders/GET_PAGE_BY_PATH.js.map +1 -1
- package/dataProviders/GET_PAGE_EXPERIMENT.d.ts +1 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.js +32 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.js.map +1 -0
- package/dataProviders/GET_VARIANT_CONTENT.d.ts +1 -0
- package/dataProviders/GET_VARIANT_CONTENT.js +24 -0
- package/dataProviders/GET_VARIANT_CONTENT.js.map +1 -0
- package/dataProviders/NullDataProvider.d.ts +4 -0
- package/dataProviders/NullDataProvider.js +9 -0
- package/dataProviders/NullDataProvider.js.map +1 -1
- package/dataProviders/queries.test.d.ts +1 -0
- package/dataProviders/queries.test.js +45 -0
- package/dataProviders/queries.test.js.map +1 -0
- package/defaultBreakpoints.js +23 -3
- package/defaultBreakpoints.js.map +1 -1
- package/experiments/PostHogAnalyticsProvider.d.ts +31 -0
- package/experiments/PostHogAnalyticsProvider.js +28 -0
- package/experiments/PostHogAnalyticsProvider.js.map +1 -0
- package/experiments/analyticsProvider.d.ts +4 -0
- package/experiments/analyticsProvider.js +11 -0
- package/experiments/analyticsProvider.js.map +1 -0
- package/experiments/bucketing.d.ts +27 -0
- package/experiments/bucketing.js +91 -0
- package/experiments/bucketing.js.map +1 -0
- package/experiments/bucketing.test.d.ts +1 -0
- package/experiments/bucketing.test.js +183 -0
- package/experiments/bucketing.test.js.map +1 -0
- package/experiments/index.d.ts +5 -0
- package/experiments/index.js +5 -0
- package/experiments/render.d.ts +82 -0
- package/experiments/render.js +149 -0
- package/experiments/render.js.map +1 -0
- package/experiments/types.d.ts +108 -0
- package/experiments/types.js +4 -0
- package/experiments/types.js.map +1 -0
- package/headersProvider.d.ts +1 -2
- package/headersProvider.js.map +1 -1
- package/image/geometry.d.ts +54 -0
- package/image/geometry.js +112 -0
- package/image/geometry.js.map +1 -0
- package/image/geometry.test.d.ts +1 -0
- package/image/geometry.test.js +190 -0
- package/image/geometry.test.js.map +1 -0
- package/image/index.d.ts +3 -0
- package/image/index.js +3 -0
- package/image/presets.d.ts +6 -0
- package/image/presets.js +20 -0
- package/image/presets.js.map +1 -0
- package/image/types.d.ts +10 -0
- package/image/types.js +0 -0
- package/index.d.ts +12 -0
- package/index.js +12 -0
- package/jsonPatch.js +3 -3
- package/package.json +7 -6
- package/types/WebsiteBuilderTheme.d.ts +5 -0
- package/types.d.ts +75 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createContentClient } from "./createContentClient.js";
|
|
3
|
+
import { ContentSdk, contentSdk } from "./ContentSdk.js";
|
|
4
|
+
import { componentRegistry } from "./ComponentRegistry.js";
|
|
5
|
+
import { contentEntryCache } from "./contentEntry/ContentEntryCache.js";
|
|
6
|
+
import { DEFAULT_MAX_DOCUMENT_STORES, DocumentStoreManager, documentStoreManager } from "./DocumentStoreManager.js";
|
|
7
|
+
import { resolveVisitorContext } from "./experiments/render.js";
|
|
8
|
+
import { setHeadersProvider } from "./headersProvider.js";
|
|
9
|
+
import { setDefaultComponentLoader } from "./componentLoader/defaultComponentLoader.js";
|
|
10
|
+
const componentLabels = {
|
|
11
|
+
"tenant-a": "Hero from tenant A",
|
|
12
|
+
"tenant-b": "Hero from tenant B"
|
|
13
|
+
};
|
|
14
|
+
const configFor = (tenant)=>({
|
|
15
|
+
apiHost: `https://${tenant}.example.com`,
|
|
16
|
+
apiKey: `key-${tenant}`,
|
|
17
|
+
apiTenant: tenant
|
|
18
|
+
});
|
|
19
|
+
const createBlueprint = (name, label)=>({
|
|
20
|
+
component: ()=>label,
|
|
21
|
+
manifest: {
|
|
22
|
+
name,
|
|
23
|
+
label,
|
|
24
|
+
inputs: [
|
|
25
|
+
{
|
|
26
|
+
type: "contentEntry",
|
|
27
|
+
name: "entry",
|
|
28
|
+
renderer: "Webiny/ContentEntry",
|
|
29
|
+
models: [
|
|
30
|
+
"article"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
tags: []
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const createDocument = (componentName)=>({
|
|
38
|
+
id: "page-1",
|
|
39
|
+
version: 1,
|
|
40
|
+
state: {},
|
|
41
|
+
properties: {
|
|
42
|
+
id: "page-1",
|
|
43
|
+
path: "/"
|
|
44
|
+
},
|
|
45
|
+
extensions: {},
|
|
46
|
+
metadata: {},
|
|
47
|
+
bindings: {},
|
|
48
|
+
elements: {
|
|
49
|
+
root: {
|
|
50
|
+
type: "Webiny/Element",
|
|
51
|
+
id: "root",
|
|
52
|
+
component: {
|
|
53
|
+
name: componentName
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const tenantComponentLoader = ({ apiClient })=>({
|
|
59
|
+
async loadComponent (ref) {
|
|
60
|
+
const data = await apiClient.query({
|
|
61
|
+
query: "query LoadComponent($name: String!) { component(name: $name) { label } }",
|
|
62
|
+
variables: {
|
|
63
|
+
name: ref.name
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return createBlueprint(ref.name, data.component.label);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const resolveLabel = (client, document)=>{
|
|
70
|
+
const resolved = client.resolveElement({
|
|
71
|
+
element: document.elements.root,
|
|
72
|
+
elementBindings: {},
|
|
73
|
+
state: document.state
|
|
74
|
+
});
|
|
75
|
+
if (!resolved) return null;
|
|
76
|
+
return {
|
|
77
|
+
label: resolved[0].manifest.label,
|
|
78
|
+
entry: resolved[0].inputs.entry
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
describe("content client state", ()=>{
|
|
82
|
+
let requestedTenants;
|
|
83
|
+
let requestedPageIds;
|
|
84
|
+
beforeEach(()=>{
|
|
85
|
+
requestedTenants = [];
|
|
86
|
+
requestedPageIds = {};
|
|
87
|
+
const fetchMock = vi.fn(async (_url, init)=>{
|
|
88
|
+
const headers = init.headers;
|
|
89
|
+
const tenant = headers["X-Tenant"];
|
|
90
|
+
const body = JSON.parse(init.body);
|
|
91
|
+
requestedTenants.push(tenant);
|
|
92
|
+
const delay = "tenant-a" === tenant ? 20 : 0;
|
|
93
|
+
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
94
|
+
if (body.query.includes("getPageById")) {
|
|
95
|
+
const id = body.variables.id;
|
|
96
|
+
requestedPageIds[tenant] = [
|
|
97
|
+
...requestedPageIds[tenant] ?? [],
|
|
98
|
+
id
|
|
99
|
+
];
|
|
100
|
+
const page = {
|
|
101
|
+
id,
|
|
102
|
+
properties: {
|
|
103
|
+
id,
|
|
104
|
+
path: "/",
|
|
105
|
+
title: `Draft of ${tenant}`
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
json: async ()=>({
|
|
110
|
+
data: {
|
|
111
|
+
websiteBuilder: {
|
|
112
|
+
getPageById: {
|
|
113
|
+
data: page,
|
|
114
|
+
error: null
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
json: async ()=>({
|
|
123
|
+
data: {
|
|
124
|
+
component: {
|
|
125
|
+
label: componentLabels[tenant]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
132
|
+
});
|
|
133
|
+
afterEach(()=>{
|
|
134
|
+
vi.unstubAllGlobals();
|
|
135
|
+
});
|
|
136
|
+
it("resolves same-name components and entries per client, concurrently", async ()=>{
|
|
137
|
+
const name = "Acme/Hero";
|
|
138
|
+
const document = createDocument(name);
|
|
139
|
+
const clientA = createContentClient({
|
|
140
|
+
...configFor("tenant-a"),
|
|
141
|
+
componentLoader: tenantComponentLoader
|
|
142
|
+
});
|
|
143
|
+
const clientB = createContentClient({
|
|
144
|
+
...configFor("tenant-b"),
|
|
145
|
+
componentLoader: tenantComponentLoader
|
|
146
|
+
});
|
|
147
|
+
clientA.getContentEntryCache().set("root:entry", {
|
|
148
|
+
id: "entry-a"
|
|
149
|
+
});
|
|
150
|
+
clientB.getContentEntryCache().set("root:entry", {
|
|
151
|
+
id: "entry-b"
|
|
152
|
+
});
|
|
153
|
+
const render = async (client)=>{
|
|
154
|
+
await client.loadDocumentComponents(document);
|
|
155
|
+
return resolveLabel(client, document);
|
|
156
|
+
};
|
|
157
|
+
const [resultA, resultB] = await Promise.all([
|
|
158
|
+
render(clientA),
|
|
159
|
+
render(clientB)
|
|
160
|
+
]);
|
|
161
|
+
expect(requestedTenants.sort()).toEqual([
|
|
162
|
+
"tenant-a",
|
|
163
|
+
"tenant-b"
|
|
164
|
+
]);
|
|
165
|
+
expect(resultA).toEqual({
|
|
166
|
+
label: "Hero from tenant A",
|
|
167
|
+
entry: {
|
|
168
|
+
id: "entry-a"
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
expect(resultB).toEqual({
|
|
172
|
+
label: "Hero from tenant B",
|
|
173
|
+
entry: {
|
|
174
|
+
id: "entry-b"
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
expect(componentRegistry.get(name)).toBeUndefined();
|
|
178
|
+
expect(contentEntryCache.has("root:entry")).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
it("keeps the singleton and clients apart, and init() doesn't touch clients", ()=>{
|
|
181
|
+
const name = "Acme/SingletonOnly";
|
|
182
|
+
const client = createContentClient(configFor("tenant-a"));
|
|
183
|
+
contentSdk.init(configFor("tenant-root"));
|
|
184
|
+
contentSdk.registerComponent(createBlueprint(name, "Singleton component"));
|
|
185
|
+
expect(componentRegistry.get(name)).toBeDefined();
|
|
186
|
+
expect(client.getComponentRegistry().get(name)).toBeUndefined();
|
|
187
|
+
expect(resolveLabel(client, createDocument(name))).toBeNull();
|
|
188
|
+
client.registerComponent(createBlueprint("Acme/ClientOnly", "Client component"));
|
|
189
|
+
expect(componentRegistry.get("Acme/ClientOnly")).toBeUndefined();
|
|
190
|
+
contentSdk.init(configFor("tenant-other"));
|
|
191
|
+
expect(client.getComponentRegistry().get("Acme/ClientOnly")).toBeDefined();
|
|
192
|
+
expect(resolveLabel(contentSdk, createDocument(name))?.label).toBe("Singleton component");
|
|
193
|
+
});
|
|
194
|
+
it("uses the module-level state for contentSdk and a plain new ContentSdk(), as before", ()=>{
|
|
195
|
+
const sdk = new ContentSdk();
|
|
196
|
+
expect(contentSdk.getComponentRegistry()).toBe(componentRegistry);
|
|
197
|
+
expect(contentSdk.getContentEntryCache()).toBe(contentEntryCache);
|
|
198
|
+
expect(contentSdk.getDocumentStoreManager()).toBe(documentStoreManager);
|
|
199
|
+
expect(sdk.getComponentRegistry()).toBe(componentRegistry);
|
|
200
|
+
expect(sdk.getContentEntryCache()).toBe(contentEntryCache);
|
|
201
|
+
expect(sdk.getDocumentStoreManager()).toBe(documentStoreManager);
|
|
202
|
+
const client = createContentClient(configFor("tenant-a"));
|
|
203
|
+
expect(client.getComponentRegistry()).not.toBe(componentRegistry);
|
|
204
|
+
expect(client.getContentEntryCache()).not.toBe(contentEntryCache);
|
|
205
|
+
expect(client.getDocumentStoreManager()).not.toBe(documentStoreManager);
|
|
206
|
+
});
|
|
207
|
+
it("still requires init() on the singleton path", ()=>{
|
|
208
|
+
const sdk = new ContentSdk();
|
|
209
|
+
expect(()=>sdk.registerComponent(createBlueprint("Acme/X", "X"))).toThrow("ContentSdk has not been initialized!");
|
|
210
|
+
});
|
|
211
|
+
it("calls a custom loader with the refs of unregistered components only", async ()=>{
|
|
212
|
+
const refs = [];
|
|
213
|
+
const loader = {
|
|
214
|
+
async loadComponent (ref) {
|
|
215
|
+
refs.push(ref);
|
|
216
|
+
return createBlueprint(ref.name, `Loaded ${ref.name}`);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const client = createContentClient({
|
|
220
|
+
...configFor("tenant-a"),
|
|
221
|
+
componentLoader: ()=>loader
|
|
222
|
+
});
|
|
223
|
+
client.registerComponent(createBlueprint("Acme/Registered", "Registered"));
|
|
224
|
+
const document = createDocument("Acme/Remote");
|
|
225
|
+
document.elements.second = {
|
|
226
|
+
type: "Webiny/Element",
|
|
227
|
+
id: "second",
|
|
228
|
+
component: {
|
|
229
|
+
name: "Acme/Registered"
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
document.elements.third = {
|
|
233
|
+
type: "Webiny/Element",
|
|
234
|
+
id: "third",
|
|
235
|
+
component: {
|
|
236
|
+
name: "Acme/Remote"
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
await client.loadDocumentComponents(document);
|
|
240
|
+
expect(refs).toEqual([
|
|
241
|
+
{
|
|
242
|
+
name: "Acme/Remote"
|
|
243
|
+
}
|
|
244
|
+
]);
|
|
245
|
+
expect(resolveLabel(client, document)?.label).toBe("Loaded Acme/Remote");
|
|
246
|
+
await client.loadDocumentComponents(document);
|
|
247
|
+
expect(refs).toHaveLength(1);
|
|
248
|
+
});
|
|
249
|
+
it("uses the default loader, created per client with its own config", async ()=>{
|
|
250
|
+
const tenants = [];
|
|
251
|
+
setDefaultComponentLoader(({ config })=>{
|
|
252
|
+
tenants.push(config.apiTenant);
|
|
253
|
+
return {
|
|
254
|
+
async loadComponent (ref) {
|
|
255
|
+
return createBlueprint(ref.name, `Default for ${config.apiTenant}`);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
try {
|
|
260
|
+
const document = createDocument("Acme/FromDefault");
|
|
261
|
+
const clientA = createContentClient(configFor("tenant-a"));
|
|
262
|
+
const clientB = createContentClient(configFor("tenant-b"));
|
|
263
|
+
await Promise.all([
|
|
264
|
+
clientA.loadDocumentComponents(document),
|
|
265
|
+
clientB.loadDocumentComponents(document)
|
|
266
|
+
]);
|
|
267
|
+
expect(tenants.sort()).toEqual([
|
|
268
|
+
"tenant-a",
|
|
269
|
+
"tenant-b"
|
|
270
|
+
]);
|
|
271
|
+
expect(resolveLabel(clientA, document)?.label).toBe("Default for tenant-a");
|
|
272
|
+
expect(resolveLabel(clientB, document)?.label).toBe("Default for tenant-b");
|
|
273
|
+
} finally{
|
|
274
|
+
setDefaultComponentLoader(void 0);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
it("prefers the client's own loader over the default", async ()=>{
|
|
278
|
+
const defaultLoad = vi.fn();
|
|
279
|
+
setDefaultComponentLoader(()=>({
|
|
280
|
+
loadComponent: defaultLoad
|
|
281
|
+
}));
|
|
282
|
+
try {
|
|
283
|
+
const client = createContentClient({
|
|
284
|
+
...configFor("tenant-a"),
|
|
285
|
+
componentLoader: ()=>({
|
|
286
|
+
async loadComponent (ref) {
|
|
287
|
+
return createBlueprint(ref.name, "Own loader");
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
const document = createDocument("Acme/Own");
|
|
292
|
+
await client.loadDocumentComponents(document);
|
|
293
|
+
expect(defaultLoad).not.toHaveBeenCalled();
|
|
294
|
+
expect(resolveLabel(client, document)?.label).toBe("Own loader");
|
|
295
|
+
} finally{
|
|
296
|
+
setDefaultComponentLoader(void 0);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
it("fetches preview pages through each client, with the client's preview headers", async ()=>{
|
|
300
|
+
const previewClient = (tenant, pageId)=>createContentClient({
|
|
301
|
+
...configFor(tenant),
|
|
302
|
+
preview: true,
|
|
303
|
+
headersProvider: async ()=>new Headers({
|
|
304
|
+
"X-Preview-Params": `wb.type=page&wb.path=/&wb.id=${pageId}`
|
|
305
|
+
})
|
|
306
|
+
});
|
|
307
|
+
const clientA = previewClient("tenant-a", "draft-a");
|
|
308
|
+
const clientB = previewClient("tenant-b", "draft-b");
|
|
309
|
+
const [pageA, pageB] = await Promise.all([
|
|
310
|
+
clientA.getPage("/"),
|
|
311
|
+
clientB.getPage("/")
|
|
312
|
+
]);
|
|
313
|
+
expect(clientA.isPreviewing()).toBe(true);
|
|
314
|
+
expect(requestedPageIds).toEqual({
|
|
315
|
+
"tenant-a": [
|
|
316
|
+
"draft-a"
|
|
317
|
+
],
|
|
318
|
+
"tenant-b": [
|
|
319
|
+
"draft-b"
|
|
320
|
+
]
|
|
321
|
+
});
|
|
322
|
+
expect(pageA?.properties.title).toBe("Draft of tenant-a");
|
|
323
|
+
expect(pageB?.properties.title).toBe("Draft of tenant-b");
|
|
324
|
+
});
|
|
325
|
+
it("falls back to the global headers provider for preview, as before", async ()=>{
|
|
326
|
+
setHeadersProvider(async ()=>new Headers({
|
|
327
|
+
"X-Preview-Params": "wb.type=page&wb.path=/&wb.id=draft-global"
|
|
328
|
+
}));
|
|
329
|
+
const client = createContentClient({
|
|
330
|
+
...configFor("tenant-b"),
|
|
331
|
+
preview: true
|
|
332
|
+
});
|
|
333
|
+
const page = await client.getPage("/");
|
|
334
|
+
expect(requestedPageIds).toEqual({
|
|
335
|
+
"tenant-b": [
|
|
336
|
+
"draft-global"
|
|
337
|
+
]
|
|
338
|
+
});
|
|
339
|
+
expect(page?.properties.title).toBe("Draft of tenant-b");
|
|
340
|
+
});
|
|
341
|
+
it("loads nothing without a loader or a default", async ()=>{
|
|
342
|
+
const client = createContentClient(configFor("tenant-a"));
|
|
343
|
+
const document = createDocument("Acme/Unknown");
|
|
344
|
+
await client.loadDocumentComponents(document);
|
|
345
|
+
expect(requestedTenants).toEqual([]);
|
|
346
|
+
expect(resolveLabel(client, document)).toBeNull();
|
|
347
|
+
});
|
|
348
|
+
it("bounds the document stores and evicts the least recently used one", ()=>{
|
|
349
|
+
const stores = new DocumentStoreManager({
|
|
350
|
+
maxEntries: 2
|
|
351
|
+
});
|
|
352
|
+
const first = stores.getStore("a");
|
|
353
|
+
stores.getStore("b");
|
|
354
|
+
expect(stores.getStore("a")).toBe(first);
|
|
355
|
+
stores.getStore("c");
|
|
356
|
+
expect(stores.hasStore("a")).toBe(true);
|
|
357
|
+
expect(stores.hasStore("b")).toBe(false);
|
|
358
|
+
expect(stores.hasStore("c")).toBe(true);
|
|
359
|
+
expect(stores.getStores().size).toBe(2);
|
|
360
|
+
stores.clear();
|
|
361
|
+
expect(stores.getStores().size).toBe(0);
|
|
362
|
+
});
|
|
363
|
+
it("keeps every store below the default bound", ()=>{
|
|
364
|
+
const stores = new DocumentStoreManager();
|
|
365
|
+
for(let i = 0; i < 1000; i++)stores.getStore(`page-${i}`);
|
|
366
|
+
expect(stores.getStores().size).toBe(1000);
|
|
367
|
+
expect(DEFAULT_MAX_DOCUMENT_STORES).toBe(10000);
|
|
368
|
+
});
|
|
369
|
+
it("reads request headers from the client's provider, else the global one", async ()=>{
|
|
370
|
+
setHeadersProvider(async ()=>new Headers({
|
|
371
|
+
cookie: "wb_ab_vid=global-visitor"
|
|
372
|
+
}));
|
|
373
|
+
const fromGlobal = await resolveVisitorContext();
|
|
374
|
+
expect(fromGlobal.context.visitorId).toBe("global-visitor");
|
|
375
|
+
const fromOption = await resolveVisitorContext({
|
|
376
|
+
headersProvider: async ()=>new Headers({
|
|
377
|
+
cookie: "wb_ab_vid=client-visitor"
|
|
378
|
+
})
|
|
379
|
+
});
|
|
380
|
+
expect(fromOption.context.visitorId).toBe("client-visitor");
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
//# sourceMappingURL=contentClientState.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentClientState.test.js","sources":["../src/contentClientState.test.ts"],"sourcesContent":["import { describe, it, expect, beforeEach, afterEach, vi } from \"vitest\";\nimport { createContentClient } from \"./createContentClient.js\";\nimport { ContentSdk, contentSdk } from \"./ContentSdk.js\";\nimport type { ContentSDKConfig } from \"./ContentSdk.js\";\nimport { componentRegistry } from \"./ComponentRegistry.js\";\nimport { contentEntryCache } from \"./contentEntry/ContentEntryCache.js\";\nimport { documentStoreManager } from \"./DocumentStoreManager.js\";\nimport { DocumentStoreManager } from \"./DocumentStoreManager.js\";\nimport { DEFAULT_MAX_DOCUMENT_STORES } from \"./DocumentStoreManager.js\";\nimport type { ComponentLoader } from \"./componentLoader/ComponentLoader.js\";\nimport type { Component, Document, ElementComponent } from \"./types.js\";\nimport { resolveVisitorContext } from \"./experiments/render.js\";\nimport { setHeadersProvider } from \"./headersProvider.js\";\nimport { setDefaultComponentLoader } from \"./componentLoader/defaultComponentLoader.js\";\n\n// What the stubbed API returns for a component, per tenant.\nconst componentLabels: Record<string, string> = {\n \"tenant-a\": \"Hero from tenant A\",\n \"tenant-b\": \"Hero from tenant B\"\n};\n\nconst configFor = (tenant: string): ContentSDKConfig => {\n return {\n apiHost: `https://${tenant}.example.com`,\n apiKey: `key-${tenant}`,\n apiTenant: tenant\n };\n};\n\nconst createBlueprint = (name: string, label: string): Component => {\n return {\n component: () => label,\n manifest: {\n name,\n label,\n inputs: [\n {\n type: \"contentEntry\",\n name: \"entry\",\n renderer: \"Webiny/ContentEntry\",\n models: [\"article\"]\n }\n ],\n tags: []\n }\n };\n};\n\nconst createDocument = (componentName: string): Document => {\n return {\n id: \"page-1\",\n version: 1,\n state: {},\n properties: { id: \"page-1\", path: \"/\" },\n extensions: {},\n metadata: {} as Document[\"metadata\"],\n bindings: {},\n elements: {\n root: {\n type: \"Webiny/Element\",\n id: \"root\",\n component: { name: componentName }\n }\n }\n };\n};\n\n// A loader that fetches the component through the client's own API client.\nconst tenantComponentLoader: NonNullable<ContentSDKConfig[\"componentLoader\"]> = ({ apiClient }) => {\n return {\n async loadComponent(ref: ElementComponent) {\n const data = await apiClient.query({\n query: \"query LoadComponent($name: String!) { component(name: $name) { label } }\",\n variables: { name: ref.name }\n });\n return createBlueprint(ref.name, data.component.label);\n }\n };\n};\n\nconst resolveLabel = (client: Pick<ContentSdk, \"resolveElement\">, document: Document) => {\n const resolved = client.resolveElement({\n element: document.elements.root,\n elementBindings: {},\n state: document.state\n });\n if (!resolved) {\n return null;\n }\n return { label: resolved[0].manifest.label, entry: resolved[0].inputs.entry };\n};\n\ndescribe(\"content client state\", () => {\n let requestedTenants: string[];\n let requestedPageIds: Record<string, string[]>;\n\n beforeEach(() => {\n requestedTenants = [];\n requestedPageIds = {};\n\n const fetchMock = vi.fn(async (_url: string, init: RequestInit) => {\n const headers = init.headers as Record<string, string>;\n const tenant = headers[\"X-Tenant\"];\n const body = JSON.parse(init.body as string);\n requestedTenants.push(tenant);\n\n // Tenant A answers last, so the two loads interleave.\n const delay = tenant === \"tenant-a\" ? 20 : 0;\n await new Promise(resolve => setTimeout(resolve, delay));\n\n if (body.query.includes(\"getPageById\")) {\n const id: string = body.variables.id;\n requestedPageIds[tenant] = [...(requestedPageIds[tenant] ?? []), id];\n const page = { id, properties: { id, path: \"/\", title: `Draft of ${tenant}` } };\n return {\n json: async () => ({\n data: { websiteBuilder: { getPageById: { data: page, error: null } } }\n })\n };\n }\n\n return {\n json: async () => ({ data: { component: { label: componentLabels[tenant] } } })\n };\n });\n\n vi.stubGlobal(\"fetch\", fetchMock);\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"resolves same-name components and entries per client, concurrently\", async () => {\n const name = \"Acme/Hero\";\n const document = createDocument(name);\n\n const clientA = createContentClient({\n ...configFor(\"tenant-a\"),\n componentLoader: tenantComponentLoader\n });\n const clientB = createContentClient({\n ...configFor(\"tenant-b\"),\n componentLoader: tenantComponentLoader\n });\n\n clientA.getContentEntryCache().set(\"root:entry\", { id: \"entry-a\" });\n clientB.getContentEntryCache().set(\"root:entry\", { id: \"entry-b\" });\n\n const render = async (client: typeof clientA) => {\n await client.loadDocumentComponents(document);\n return resolveLabel(client, document);\n };\n\n const [resultA, resultB] = await Promise.all([render(clientA), render(clientB)]);\n\n expect(requestedTenants.sort()).toEqual([\"tenant-a\", \"tenant-b\"]);\n expect(resultA).toEqual({ label: \"Hero from tenant A\", entry: { id: \"entry-a\" } });\n expect(resultB).toEqual({ label: \"Hero from tenant B\", entry: { id: \"entry-b\" } });\n\n // Nothing reached the singleton's state.\n expect(componentRegistry.get(name)).toBeUndefined();\n expect(contentEntryCache.has(\"root:entry\")).toBe(false);\n });\n\n it(\"keeps the singleton and clients apart, and init() doesn't touch clients\", () => {\n const name = \"Acme/SingletonOnly\";\n const client = createContentClient(configFor(\"tenant-a\"));\n\n contentSdk.init(configFor(\"tenant-root\"));\n contentSdk.registerComponent(createBlueprint(name, \"Singleton component\"));\n\n expect(componentRegistry.get(name)).toBeDefined();\n expect(client.getComponentRegistry().get(name)).toBeUndefined();\n expect(resolveLabel(client, createDocument(name))).toBeNull();\n\n client.registerComponent(createBlueprint(\"Acme/ClientOnly\", \"Client component\"));\n expect(componentRegistry.get(\"Acme/ClientOnly\")).toBeUndefined();\n\n contentSdk.init(configFor(\"tenant-other\"));\n expect(client.getComponentRegistry().get(\"Acme/ClientOnly\")).toBeDefined();\n expect(resolveLabel(contentSdk, createDocument(name))?.label).toBe(\"Singleton component\");\n });\n\n it(\"uses the module-level state for contentSdk and a plain new ContentSdk(), as before\", () => {\n const sdk = new ContentSdk();\n\n expect(contentSdk.getComponentRegistry()).toBe(componentRegistry);\n expect(contentSdk.getContentEntryCache()).toBe(contentEntryCache);\n expect(contentSdk.getDocumentStoreManager()).toBe(documentStoreManager);\n expect(sdk.getComponentRegistry()).toBe(componentRegistry);\n expect(sdk.getContentEntryCache()).toBe(contentEntryCache);\n expect(sdk.getDocumentStoreManager()).toBe(documentStoreManager);\n\n const client = createContentClient(configFor(\"tenant-a\"));\n expect(client.getComponentRegistry()).not.toBe(componentRegistry);\n expect(client.getContentEntryCache()).not.toBe(contentEntryCache);\n expect(client.getDocumentStoreManager()).not.toBe(documentStoreManager);\n });\n\n it(\"still requires init() on the singleton path\", () => {\n const sdk = new ContentSdk();\n expect(() => sdk.registerComponent(createBlueprint(\"Acme/X\", \"X\"))).toThrow(\n \"ContentSdk has not been initialized!\"\n );\n });\n\n it(\"calls a custom loader with the refs of unregistered components only\", async () => {\n const refs: ElementComponent[] = [];\n const loader: ComponentLoader = {\n async loadComponent(ref) {\n refs.push(ref);\n return createBlueprint(ref.name, `Loaded ${ref.name}`);\n }\n };\n\n const client = createContentClient({\n ...configFor(\"tenant-a\"),\n componentLoader: () => loader\n });\n client.registerComponent(createBlueprint(\"Acme/Registered\", \"Registered\"));\n\n const document = createDocument(\"Acme/Remote\");\n document.elements.second = {\n type: \"Webiny/Element\",\n id: \"second\",\n component: { name: \"Acme/Registered\" }\n };\n document.elements.third = {\n type: \"Webiny/Element\",\n id: \"third\",\n component: { name: \"Acme/Remote\" }\n };\n\n await client.loadDocumentComponents(document);\n expect(refs).toEqual([{ name: \"Acme/Remote\" }]);\n expect(resolveLabel(client, document)?.label).toBe(\"Loaded Acme/Remote\");\n\n // Registered now, so the loader isn't asked again.\n await client.loadDocumentComponents(document);\n expect(refs).toHaveLength(1);\n });\n\n it(\"uses the default loader, created per client with its own config\", async () => {\n const tenants: string[] = [];\n setDefaultComponentLoader(({ config }) => {\n tenants.push(config.apiTenant);\n return {\n async loadComponent(ref) {\n return createBlueprint(ref.name, `Default for ${config.apiTenant}`);\n }\n };\n });\n\n try {\n const document = createDocument(\"Acme/FromDefault\");\n const clientA = createContentClient(configFor(\"tenant-a\"));\n const clientB = createContentClient(configFor(\"tenant-b\"));\n\n await Promise.all([\n clientA.loadDocumentComponents(document),\n clientB.loadDocumentComponents(document)\n ]);\n\n expect(tenants.sort()).toEqual([\"tenant-a\", \"tenant-b\"]);\n expect(resolveLabel(clientA, document)?.label).toBe(\"Default for tenant-a\");\n expect(resolveLabel(clientB, document)?.label).toBe(\"Default for tenant-b\");\n } finally {\n setDefaultComponentLoader(undefined);\n }\n });\n\n it(\"prefers the client's own loader over the default\", async () => {\n const defaultLoad = vi.fn();\n setDefaultComponentLoader(() => ({ loadComponent: defaultLoad }));\n\n try {\n const client = createContentClient({\n ...configFor(\"tenant-a\"),\n componentLoader: () => ({\n async loadComponent(ref) {\n return createBlueprint(ref.name, \"Own loader\");\n }\n })\n });\n const document = createDocument(\"Acme/Own\");\n\n await client.loadDocumentComponents(document);\n\n expect(defaultLoad).not.toHaveBeenCalled();\n expect(resolveLabel(client, document)?.label).toBe(\"Own loader\");\n } finally {\n setDefaultComponentLoader(undefined);\n }\n });\n\n it(\"fetches preview pages through each client, with the client's preview headers\", async () => {\n const previewClient = (tenant: string, pageId: string) => {\n return createContentClient({\n ...configFor(tenant),\n preview: true,\n headersProvider: async () =>\n new Headers({ \"X-Preview-Params\": `wb.type=page&wb.path=/&wb.id=${pageId}` })\n });\n };\n const clientA = previewClient(\"tenant-a\", \"draft-a\");\n const clientB = previewClient(\"tenant-b\", \"draft-b\");\n\n const [pageA, pageB] = await Promise.all([clientA.getPage(\"/\"), clientB.getPage(\"/\")]);\n\n expect(clientA.isPreviewing()).toBe(true);\n expect(requestedPageIds).toEqual({ \"tenant-a\": [\"draft-a\"], \"tenant-b\": [\"draft-b\"] });\n expect(pageA?.properties.title).toBe(\"Draft of tenant-a\");\n expect(pageB?.properties.title).toBe(\"Draft of tenant-b\");\n });\n\n it(\"falls back to the global headers provider for preview, as before\", async () => {\n setHeadersProvider(\n async () =>\n new Headers({ \"X-Preview-Params\": \"wb.type=page&wb.path=/&wb.id=draft-global\" })\n );\n const client = createContentClient({ ...configFor(\"tenant-b\"), preview: true });\n\n const page = await client.getPage(\"/\");\n\n expect(requestedPageIds).toEqual({ \"tenant-b\": [\"draft-global\"] });\n expect(page?.properties.title).toBe(\"Draft of tenant-b\");\n });\n\n it(\"loads nothing without a loader or a default\", async () => {\n const client = createContentClient(configFor(\"tenant-a\"));\n const document = createDocument(\"Acme/Unknown\");\n\n await client.loadDocumentComponents(document);\n\n expect(requestedTenants).toEqual([]);\n expect(resolveLabel(client, document)).toBeNull();\n });\n\n it(\"bounds the document stores and evicts the least recently used one\", () => {\n const stores = new DocumentStoreManager({ maxEntries: 2 });\n const first = stores.getStore(\"a\");\n stores.getStore(\"b\");\n\n // Touch \"a\", so \"b\" is now the least recently used.\n expect(stores.getStore(\"a\")).toBe(first);\n stores.getStore(\"c\");\n\n expect(stores.hasStore(\"a\")).toBe(true);\n expect(stores.hasStore(\"b\")).toBe(false);\n expect(stores.hasStore(\"c\")).toBe(true);\n expect(stores.getStores().size).toBe(2);\n\n stores.clear();\n expect(stores.getStores().size).toBe(0);\n });\n\n it(\"keeps every store below the default bound\", () => {\n const stores = new DocumentStoreManager();\n for (let i = 0; i < 1000; i++) {\n stores.getStore(`page-${i}`);\n }\n expect(stores.getStores().size).toBe(1000);\n expect(DEFAULT_MAX_DOCUMENT_STORES).toBe(10_000);\n });\n\n it(\"reads request headers from the client's provider, else the global one\", async () => {\n setHeadersProvider(async () => new Headers({ cookie: \"wb_ab_vid=global-visitor\" }));\n\n const fromGlobal = await resolveVisitorContext();\n expect(fromGlobal.context.visitorId).toBe(\"global-visitor\");\n\n const fromOption = await resolveVisitorContext({\n headersProvider: async () => new Headers({ cookie: \"wb_ab_vid=client-visitor\" })\n });\n expect(fromOption.context.visitorId).toBe(\"client-visitor\");\n });\n});\n"],"names":["componentLabels","configFor","tenant","createBlueprint","name","label","createDocument","componentName","tenantComponentLoader","apiClient","ref","data","resolveLabel","client","document","resolved","describe","requestedTenants","requestedPageIds","beforeEach","fetchMock","vi","_url","init","headers","body","JSON","delay","Promise","resolve","setTimeout","id","page","afterEach","it","clientA","createContentClient","clientB","render","resultA","resultB","expect","componentRegistry","contentEntryCache","contentSdk","sdk","ContentSdk","documentStoreManager","refs","loader","tenants","setDefaultComponentLoader","config","undefined","defaultLoad","previewClient","pageId","Headers","pageA","pageB","setHeadersProvider","stores","DocumentStoreManager","first","i","DEFAULT_MAX_DOCUMENT_STORES","fromGlobal","resolveVisitorContext","fromOption"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ResolvedElement } from "../types.js";
|
|
2
|
+
import type { IBindingsResolver, ResolveElementParams } from "../BindingsResolver.js";
|
|
3
|
+
import type { ContentEntryCache } from "./ContentEntryCache.js";
|
|
4
|
+
/**
|
|
5
|
+
* Decorator for `IBindingsResolver` that resolves `contentEntry` inputs.
|
|
6
|
+
*
|
|
7
|
+
* The inner resolver evaluates binding expressions against the document state,
|
|
8
|
+
* producing raw values (`{ id, modelId }` references or query params). This
|
|
9
|
+
* decorator then replaces those raw values with pre-resolved CMS entries from
|
|
10
|
+
* a supplied map, or (on the client) triggers async resolution via a
|
|
11
|
+
* `ContentEntryCache`. The cache defaults to the `contentEntryCache` singleton.
|
|
12
|
+
*
|
|
13
|
+
* **Server path**: `resolveContentEntries()` pre-fetches all entries, builds
|
|
14
|
+
* the `resolvedEntries` map, and passes it here. The decorator synchronously
|
|
15
|
+
* swaps raw values for resolved entries during render.
|
|
16
|
+
*
|
|
17
|
+
* **Client path (editor preview)**: `resolvedEntries` may be empty. The
|
|
18
|
+
* decorator falls back to the cache's `resolve()`, which kicks off an
|
|
19
|
+
* async fetch; the mobx-observable cache triggers re-render when it lands.
|
|
20
|
+
*/
|
|
21
|
+
export declare class ContentEntryBindingsResolver implements IBindingsResolver {
|
|
22
|
+
private readonly inner;
|
|
23
|
+
private readonly resolvedEntries;
|
|
24
|
+
private readonly cache;
|
|
25
|
+
constructor(inner: IBindingsResolver, resolvedEntries?: ReadonlyMap<string, unknown>, cache?: ContentEntryCache);
|
|
26
|
+
resolveElement(params: ResolveElementParams): ResolvedElement[];
|
|
27
|
+
private isUnsafePropertyKey;
|
|
28
|
+
/**
|
|
29
|
+
* Walk the input AST and replace raw content-entry values with resolved
|
|
30
|
+
* entries from the map or the cache.
|
|
31
|
+
*/
|
|
32
|
+
private enrichContentEntryInputs;
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { contentEntryCache } from "./ContentEntryCache.js";
|
|
2
|
+
import { environment } from "../Environment.js";
|
|
3
|
+
class ContentEntryBindingsResolver {
|
|
4
|
+
constructor(inner, resolvedEntries = new Map(), cache = contentEntryCache){
|
|
5
|
+
this.inner = inner;
|
|
6
|
+
this.resolvedEntries = resolvedEntries;
|
|
7
|
+
this.cache = cache;
|
|
8
|
+
}
|
|
9
|
+
resolveElement(params) {
|
|
10
|
+
const results = this.inner.resolveElement(params);
|
|
11
|
+
for (const instance of results)this.enrichContentEntryInputs(params.element.id, params.inputAst, instance.inputs);
|
|
12
|
+
return results;
|
|
13
|
+
}
|
|
14
|
+
isUnsafePropertyKey(key) {
|
|
15
|
+
return "__proto__" === key || "constructor" === key || "prototype" === key;
|
|
16
|
+
}
|
|
17
|
+
enrichContentEntryInputs(elementId, ast, inputs) {
|
|
18
|
+
for (const node of ast){
|
|
19
|
+
const inputName = node.name;
|
|
20
|
+
if (!this.isUnsafePropertyKey(inputName)) {
|
|
21
|
+
if ("contentEntry" === node.input.type) {
|
|
22
|
+
const key = `${elementId}:${inputName}`;
|
|
23
|
+
const rawValue = inputs[inputName];
|
|
24
|
+
const list = node.input.list ?? false;
|
|
25
|
+
if (this.resolvedEntries.has(key)) {
|
|
26
|
+
inputs[inputName] = this.resolvedEntries.get(key);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (this.cache.has(key)) {
|
|
30
|
+
inputs[inputName] = this.cache.get(key);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (environment.isClient() && this.cache.getLoader()) {
|
|
34
|
+
const cacheKey = `${key}:${JSON.stringify(rawValue ?? null)}`;
|
|
35
|
+
this.cache.resolve(cacheKey, rawValue, list);
|
|
36
|
+
const cached = this.cache.get(cacheKey);
|
|
37
|
+
if (void 0 !== cached) inputs[inputName] = cached;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (node.children.length > 0) {
|
|
41
|
+
const value = inputs[inputName];
|
|
42
|
+
if (node.list && Array.isArray(value)) {
|
|
43
|
+
for (const item of value)if ("object" == typeof item && null !== item) this.enrichContentEntryInputs(elementId, node.children, item);
|
|
44
|
+
} else if ("object" == typeof value && null !== value && !Array.isArray(value)) this.enrichContentEntryInputs(elementId, node.children, value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export { ContentEntryBindingsResolver };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=ContentEntryBindingsResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentEntry/ContentEntryBindingsResolver.js","sources":["../../src/contentEntry/ContentEntryBindingsResolver.ts"],"sourcesContent":["import type { ResolvedElement } from \"~/types.js\";\nimport type { IBindingsResolver, ResolveElementParams } from \"~/BindingsResolver.js\";\nimport type { InputAstNode } from \"~/ComponentManifestToAstConverter.js\";\nimport type { ContentEntryCache } from \"./ContentEntryCache.js\";\nimport { contentEntryCache } from \"./ContentEntryCache.js\";\nimport { environment } from \"~/Environment.js\";\n\n/**\n * Decorator for `IBindingsResolver` that resolves `contentEntry` inputs.\n *\n * The inner resolver evaluates binding expressions against the document state,\n * producing raw values (`{ id, modelId }` references or query params). This\n * decorator then replaces those raw values with pre-resolved CMS entries from\n * a supplied map, or (on the client) triggers async resolution via a\n * `ContentEntryCache`. The cache defaults to the `contentEntryCache` singleton.\n *\n * **Server path**: `resolveContentEntries()` pre-fetches all entries, builds\n * the `resolvedEntries` map, and passes it here. The decorator synchronously\n * swaps raw values for resolved entries during render.\n *\n * **Client path (editor preview)**: `resolvedEntries` may be empty. The\n * decorator falls back to the cache's `resolve()`, which kicks off an\n * async fetch; the mobx-observable cache triggers re-render when it lands.\n */\nexport class ContentEntryBindingsResolver implements IBindingsResolver {\n constructor(\n private readonly inner: IBindingsResolver,\n private readonly resolvedEntries: ReadonlyMap<string, unknown> = new Map(),\n private readonly cache: ContentEntryCache = contentEntryCache\n ) {}\n\n resolveElement(params: ResolveElementParams): ResolvedElement[] {\n const results = this.inner.resolveElement(params);\n\n for (const instance of results) {\n this.enrichContentEntryInputs(params.element.id, params.inputAst, instance.inputs);\n }\n\n return results;\n }\n\n private isUnsafePropertyKey(key: string): boolean {\n return key === \"__proto__\" || key === \"constructor\" || key === \"prototype\";\n }\n\n /**\n * Walk the input AST and replace raw content-entry values with resolved\n * entries from the map or the cache.\n */\n private enrichContentEntryInputs(\n elementId: string,\n ast: InputAstNode[],\n inputs: Record<string, any>\n ): void {\n for (const node of ast) {\n const inputName = node.name;\n if (this.isUnsafePropertyKey(inputName)) {\n continue;\n }\n\n if (node.input.type === \"contentEntry\") {\n const key = `${elementId}:${inputName}`;\n const rawValue = inputs[inputName];\n const list = node.input.list ?? false;\n\n // 1. Pre-resolved entries map (seeded by server pre-pass).\n if (this.resolvedEntries.has(key)) {\n inputs[inputName] = this.resolvedEntries.get(key);\n continue;\n }\n\n // 2. Cache (populated by previous renders or async resolves on\n // the client).\n if (this.cache.has(key)) {\n inputs[inputName] = this.cache.get(key);\n continue;\n }\n\n // 3. Client-side async resolution: trigger a background fetch.\n // The observable cache write will cause a re-render.\n if (environment.isClient() && this.cache.getLoader()) {\n const cacheKey = `${key}:${JSON.stringify(rawValue ?? null)}`;\n this.cache.resolve(cacheKey, rawValue, list);\n const cached = this.cache.get(cacheKey);\n if (cached !== undefined) {\n inputs[inputName] = cached;\n }\n }\n }\n\n // Recurse into children (object / object-list nodes).\n if (node.children.length > 0) {\n const value = inputs[inputName];\n if (node.list && Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === \"object\" && item !== null) {\n this.enrichContentEntryInputs(elementId, node.children, item);\n }\n }\n } else if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n this.enrichContentEntryInputs(elementId, node.children, value);\n }\n }\n }\n }\n}\n"],"names":["ContentEntryBindingsResolver","inner","resolvedEntries","Map","cache","contentEntryCache","params","results","instance","key","elementId","ast","inputs","node","inputName","rawValue","list","environment","cacheKey","JSON","cached","undefined","value","Array","item"],"mappings":";;AAwBO,MAAMA;IACT,YACqBC,KAAwB,EACxBC,kBAAgD,IAAIC,KAAK,EACzDC,QAA2BC,iBAAiB,CAC/D;aAHmBJ,KAAK,GAALA;aACAC,eAAe,GAAfA;aACAE,KAAK,GAALA;IAClB;IAEH,eAAeE,MAA4B,EAAqB;QAC5D,MAAMC,UAAU,IAAI,CAAC,KAAK,CAAC,cAAc,CAACD;QAE1C,KAAK,MAAME,YAAYD,QACnB,IAAI,CAAC,wBAAwB,CAACD,OAAO,OAAO,CAAC,EAAE,EAAEA,OAAO,QAAQ,EAAEE,SAAS,MAAM;QAGrF,OAAOD;IACX;IAEQ,oBAAoBE,GAAW,EAAW;QAC9C,OAAOA,AAAQ,gBAARA,OAAuBA,AAAQ,kBAARA,OAAyBA,AAAQ,gBAARA;IAC3D;IAMQ,yBACJC,SAAiB,EACjBC,GAAmB,EACnBC,MAA2B,EACvB;QACJ,KAAK,MAAMC,QAAQF,IAAK;YACpB,MAAMG,YAAYD,KAAK,IAAI;YAC3B,KAAI,IAAI,CAAC,mBAAmB,CAACC;gBAI7B,IAAID,AAAoB,mBAApBA,KAAK,KAAK,CAAC,IAAI,EAAqB;oBACpC,MAAMJ,MAAM,GAAGC,UAAU,CAAC,EAAEI,WAAW;oBACvC,MAAMC,WAAWH,MAAM,CAACE,UAAU;oBAClC,MAAME,OAAOH,KAAK,KAAK,CAAC,IAAI,IAAI;oBAGhC,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAACJ,MAAM;wBAC/BG,MAAM,CAACE,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAACL;wBAC7C;oBACJ;oBAIA,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAACA,MAAM;wBACrBG,MAAM,CAACE,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAACL;wBACnC;oBACJ;oBAIA,IAAIQ,YAAY,QAAQ,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI;wBAClD,MAAMC,WAAW,GAAGT,IAAI,CAAC,EAAEU,KAAK,SAAS,CAACJ,YAAY,OAAO;wBAC7D,IAAI,CAAC,KAAK,CAAC,OAAO,CAACG,UAAUH,UAAUC;wBACvC,MAAMI,SAAS,IAAI,CAAC,KAAK,CAAC,GAAG,CAACF;wBAC9B,IAAIE,AAAWC,WAAXD,QACAR,MAAM,CAACE,UAAU,GAAGM;oBAE5B;gBACJ;gBAGA,IAAIP,KAAK,QAAQ,CAAC,MAAM,GAAG,GAAG;oBAC1B,MAAMS,QAAQV,MAAM,CAACE,UAAU;oBAC/B,IAAID,KAAK,IAAI,IAAIU,MAAM,OAAO,CAACD,QAC3B;wBAAA,KAAK,MAAME,QAAQF,MACf,IAAI,AAAgB,YAAhB,OAAOE,QAAqBA,AAAS,SAATA,MAC5B,IAAI,CAAC,wBAAwB,CAACd,WAAWG,KAAK,QAAQ,EAAEW;oBAEhE,OACG,IAAI,AAAiB,YAAjB,OAAOF,SAAsBA,AAAU,SAAVA,SAAkB,CAACC,MAAM,OAAO,CAACD,QACrE,IAAI,CAAC,wBAAwB,CAACZ,WAAWG,KAAK,QAAQ,EAAES;gBAEhE;;QACJ;IACJ;AACJ"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type ContentEntryLoader, type ResolvedContentEntry } from "./resolveContentEntry.js";
|
|
2
|
+
type RawValue = unknown | unknown[] | null | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* SDK-level mobx cache for resolved content-entry inputs. Mirrors the CMS
|
|
5
|
+
* `RefCache` pattern: an observable store that both the server pre-pass
|
|
6
|
+
* (`resolveAutoLoad`) and the editor preview path can seed / read. The
|
|
7
|
+
* `contentEntryCache` singleton is used by `contentSdk`; each content client
|
|
8
|
+
* created with `createContentClient` holds its own instance.
|
|
9
|
+
*
|
|
10
|
+
* - **Server / SSR**: `resolveAutoLoad` calls `set(key, resolved)` for every
|
|
11
|
+
* entry it pre-resolves. `BindingsResolver.resolveSingleInstance` reads via
|
|
12
|
+
* `get(key)` synchronously during render — no React context needed.
|
|
13
|
+
*
|
|
14
|
+
* - **Editor preview**: `resolve(key, input, value, loader)` triggers an async
|
|
15
|
+
* fetch whose result is written inside a mobx action, so `observer`
|
|
16
|
+
* components re-render once it lands. Keyed by a signature of the raw value,
|
|
17
|
+
* so when the editor changes the selection/query the key changes and the
|
|
18
|
+
* entry is re-resolved.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ContentEntryCache {
|
|
21
|
+
private cache;
|
|
22
|
+
private loader;
|
|
23
|
+
private pending;
|
|
24
|
+
constructor();
|
|
25
|
+
get(key: string): ResolvedContentEntry | undefined;
|
|
26
|
+
has(key: string): boolean;
|
|
27
|
+
set(key: string, value: ResolvedContentEntry): void;
|
|
28
|
+
/**
|
|
29
|
+
* Register the CMS data loader that `resolve()` will use for async fetches.
|
|
30
|
+
* Called once during initialisation (e.g. by `resolveAutoLoad`). The loader
|
|
31
|
+
* is **not** mobx-observable — it's a plain service reference.
|
|
32
|
+
*/
|
|
33
|
+
setLoader(loader: ContentEntryLoader): void;
|
|
34
|
+
/**
|
|
35
|
+
* Return the registered loader, if any.
|
|
36
|
+
*/
|
|
37
|
+
getLoader(): ContentEntryLoader | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Lazily resolve `value` under `key`. Idempotent and safe to call during
|
|
40
|
+
* render: the observable write happens later, inside an action.
|
|
41
|
+
*
|
|
42
|
+
* Uses the loader registered via `setLoader()`. If no loader is registered,
|
|
43
|
+
* the call is a no-op (the raw value will be used instead).
|
|
44
|
+
*/
|
|
45
|
+
resolve(key: string, value: RawValue, list: boolean): void;
|
|
46
|
+
clear(): void;
|
|
47
|
+
}
|
|
48
|
+
export declare const contentEntryCache: ContentEntryCache;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { makeAutoObservable, runInAction } from "mobx";
|
|
2
|
+
import { resolveContentEntryValue } from "./resolveContentEntry.js";
|
|
3
|
+
class ContentEntryCache {
|
|
4
|
+
constructor(){
|
|
5
|
+
this.cache = new Map();
|
|
6
|
+
this.pending = new Set();
|
|
7
|
+
makeAutoObservable(this, {
|
|
8
|
+
setLoader: false,
|
|
9
|
+
getLoader: false,
|
|
10
|
+
pending: false
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
get(key) {
|
|
14
|
+
return this.cache.get(key);
|
|
15
|
+
}
|
|
16
|
+
has(key) {
|
|
17
|
+
return this.cache.has(key);
|
|
18
|
+
}
|
|
19
|
+
set(key, value) {
|
|
20
|
+
runInAction(()=>{
|
|
21
|
+
this.cache.set(key, value);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
setLoader(loader) {
|
|
25
|
+
this.loader = loader;
|
|
26
|
+
}
|
|
27
|
+
getLoader() {
|
|
28
|
+
return this.loader;
|
|
29
|
+
}
|
|
30
|
+
resolve(key, value, list) {
|
|
31
|
+
if (this.cache.has(key) || this.pending.has(key) || !this.loader) return;
|
|
32
|
+
this.pending.add(key);
|
|
33
|
+
resolveContentEntryValue(value, list, this.loader).then((resolved)=>{
|
|
34
|
+
runInAction(()=>{
|
|
35
|
+
this.cache.set(key, resolved);
|
|
36
|
+
});
|
|
37
|
+
this.pending.delete(key);
|
|
38
|
+
}).catch(()=>{
|
|
39
|
+
this.pending.delete(key);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
clear() {
|
|
43
|
+
runInAction(()=>{
|
|
44
|
+
this.cache.clear();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const contentEntryCache = new ContentEntryCache();
|
|
49
|
+
export { ContentEntryCache, contentEntryCache };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=ContentEntryCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentEntry/ContentEntryCache.js","sources":["../../src/contentEntry/ContentEntryCache.ts"],"sourcesContent":["import { makeAutoObservable, runInAction } from \"mobx\";\nimport {\n resolveContentEntryValue,\n type ContentEntryLoader,\n type ResolvedContentEntry\n} from \"./resolveContentEntry.js\";\n\ntype RawValue = unknown | unknown[] | null | undefined;\n\n/**\n * SDK-level mobx cache for resolved content-entry inputs. Mirrors the CMS\n * `RefCache` pattern: an observable store that both the server pre-pass\n * (`resolveAutoLoad`) and the editor preview path can seed / read. The\n * `contentEntryCache` singleton is used by `contentSdk`; each content client\n * created with `createContentClient` holds its own instance.\n *\n * - **Server / SSR**: `resolveAutoLoad` calls `set(key, resolved)` for every\n * entry it pre-resolves. `BindingsResolver.resolveSingleInstance` reads via\n * `get(key)` synchronously during render — no React context needed.\n *\n * - **Editor preview**: `resolve(key, input, value, loader)` triggers an async\n * fetch whose result is written inside a mobx action, so `observer`\n * components re-render once it lands. Keyed by a signature of the raw value,\n * so when the editor changes the selection/query the key changes and the\n * entry is re-resolved.\n */\nexport class ContentEntryCache {\n private cache = new Map<string, ResolvedContentEntry>();\n private loader: ContentEntryLoader | undefined;\n // Non-observable dedupe set, so touching it during render never counts as a mobx\n // mutation-in-render.\n private pending = new Set<string>();\n\n constructor() {\n makeAutoObservable<ContentEntryCache, \"pending\">(this, {\n // The loader is a plain service reference, not an observable value.\n setLoader: false,\n getLoader: false,\n pending: false\n });\n }\n\n get(key: string): ResolvedContentEntry | undefined {\n return this.cache.get(key);\n }\n\n has(key: string): boolean {\n return this.cache.has(key);\n }\n\n set(key: string, value: ResolvedContentEntry): void {\n runInAction(() => {\n this.cache.set(key, value);\n });\n }\n\n /**\n * Register the CMS data loader that `resolve()` will use for async fetches.\n * Called once during initialisation (e.g. by `resolveAutoLoad`). The loader\n * is **not** mobx-observable — it's a plain service reference.\n */\n setLoader(loader: ContentEntryLoader): void {\n this.loader = loader;\n }\n\n /**\n * Return the registered loader, if any.\n */\n getLoader(): ContentEntryLoader | undefined {\n return this.loader;\n }\n\n /**\n * Lazily resolve `value` under `key`. Idempotent and safe to call during\n * render: the observable write happens later, inside an action.\n *\n * Uses the loader registered via `setLoader()`. If no loader is registered,\n * the call is a no-op (the raw value will be used instead).\n */\n resolve(key: string, value: RawValue, list: boolean): void {\n if (this.cache.has(key) || this.pending.has(key) || !this.loader) {\n return;\n }\n this.pending.add(key);\n resolveContentEntryValue(value as any, list, this.loader)\n .then(resolved => {\n runInAction(() => {\n this.cache.set(key, resolved);\n });\n this.pending.delete(key);\n })\n .catch(() => {\n this.pending.delete(key);\n });\n }\n\n clear(): void {\n runInAction(() => {\n this.cache.clear();\n });\n }\n}\n\nexport const contentEntryCache = new ContentEntryCache();\n"],"names":["ContentEntryCache","Map","Set","makeAutoObservable","key","value","runInAction","loader","list","resolveContentEntryValue","resolved","contentEntryCache"],"mappings":";;AA0BO,MAAMA;IAOT,aAAc;aANN,KAAK,GAAG,IAAIC;aAIZ,OAAO,GAAG,IAAIC;QAGlBC,mBAAiD,IAAI,EAAE;YAEnD,WAAW;YACX,WAAW;YACX,SAAS;QACb;IACJ;IAEA,IAAIC,GAAW,EAAoC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAACA;IAC1B;IAEA,IAAIA,GAAW,EAAW;QACtB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAACA;IAC1B;IAEA,IAAIA,GAAW,EAAEC,KAA2B,EAAQ;QAChDC,YAAY;YACR,IAAI,CAAC,KAAK,CAAC,GAAG,CAACF,KAAKC;QACxB;IACJ;IAOA,UAAUE,MAA0B,EAAQ;QACxC,IAAI,CAAC,MAAM,GAAGA;IAClB;IAKA,YAA4C;QACxC,OAAO,IAAI,CAAC,MAAM;IACtB;IASA,QAAQH,GAAW,EAAEC,KAAe,EAAEG,IAAa,EAAQ;QACvD,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAACJ,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,CAACA,QAAQ,CAAC,IAAI,CAAC,MAAM,EAC5D;QAEJ,IAAI,CAAC,OAAO,CAAC,GAAG,CAACA;QACjBK,yBAAyBJ,OAAcG,MAAM,IAAI,CAAC,MAAM,EACnD,IAAI,CAACE,CAAAA;YACFJ,YAAY;gBACR,IAAI,CAAC,KAAK,CAAC,GAAG,CAACF,KAAKM;YACxB;YACA,IAAI,CAAC,OAAO,CAAC,MAAM,CAACN;QACxB,GACC,KAAK,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,MAAM,CAACA;QACxB;IACR;IAEA,QAAc;QACVE,YAAY;YACR,IAAI,CAAC,KAAK,CAAC,KAAK;QACpB;IACJ;AACJ;AAEO,MAAMK,oBAAoB,IAAIX"}
|