@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.
Files changed (170) hide show
  1. package/BindingsResolver.d.ts +4 -1
  2. package/BindingsResolver.js.map +1 -1
  3. package/ComponentInputTraverser.js +4 -0
  4. package/ComponentInputTraverser.js.map +1 -1
  5. package/ComponentResolver.d.ts +6 -3
  6. package/ComponentResolver.js +9 -4
  7. package/ComponentResolver.js.map +1 -1
  8. package/ContentSdk.d.ts +67 -1
  9. package/ContentSdk.js +90 -8
  10. package/ContentSdk.js.map +1 -1
  11. package/DocumentStoreManager.d.ts +9 -2
  12. package/DocumentStoreManager.js +24 -5
  13. package/DocumentStoreManager.js.map +1 -1
  14. package/EditingSdk.d.ts +7 -1
  15. package/EditingSdk.js +5 -3
  16. package/EditingSdk.js.map +1 -1
  17. package/ElementFactory.js +7 -2
  18. package/ElementFactory.js.map +1 -1
  19. package/ElementFactory.test.js +102 -0
  20. package/ElementFactory.test.js.map +1 -1
  21. package/LiveSdk.d.ts +3 -1
  22. package/LiveSdk.js +3 -2
  23. package/LiveSdk.js.map +1 -1
  24. package/PreviewDocument.d.ts +3 -1
  25. package/PreviewDocument.js +5 -2
  26. package/PreviewDocument.js.map +1 -1
  27. package/PreviewSdk.d.ts +4 -1
  28. package/PreviewSdk.js +6 -2
  29. package/PreviewSdk.js.map +1 -1
  30. package/ResizeObserver.js +1 -1
  31. package/ResizeObserver.js.map +1 -1
  32. package/StylesBindingsProcessor.js +3 -2
  33. package/StylesBindingsProcessor.js.map +1 -1
  34. package/Theme.js +2 -1
  35. package/Theme.js.map +1 -1
  36. package/asset/deliveryUrl.d.ts +41 -0
  37. package/asset/deliveryUrl.js +34 -0
  38. package/asset/deliveryUrl.js.map +1 -0
  39. package/asset/deliveryUrl.test.d.ts +1 -0
  40. package/asset/deliveryUrl.test.js +127 -0
  41. package/asset/deliveryUrl.test.js.map +1 -0
  42. package/asset/geometry.d.ts +7 -0
  43. package/asset/geometry.js +10 -0
  44. package/asset/geometry.js.map +1 -0
  45. package/asset/imageSrcSet.d.ts +63 -0
  46. package/asset/imageSrcSet.js +98 -0
  47. package/asset/imageSrcSet.js.map +1 -0
  48. package/asset/imageSrcSet.test.d.ts +1 -0
  49. package/asset/imageSrcSet.test.js +103 -0
  50. package/asset/imageSrcSet.test.js.map +1 -0
  51. package/asset/index.d.ts +5 -0
  52. package/asset/index.js +5 -0
  53. package/asset/normalize.d.ts +24 -0
  54. package/asset/normalize.js +106 -0
  55. package/asset/normalize.js.map +1 -0
  56. package/asset/normalize.test.d.ts +1 -0
  57. package/asset/normalize.test.js +263 -0
  58. package/asset/normalize.test.js.map +1 -0
  59. package/asset/types.d.ts +2 -0
  60. package/asset/types.js +0 -0
  61. package/componentLoader/ComponentLoader.d.ts +16 -0
  62. package/componentLoader/ComponentLoader.js +0 -0
  63. package/componentLoader/RegistryOnlyComponentLoader.d.ts +9 -0
  64. package/componentLoader/RegistryOnlyComponentLoader.js +8 -0
  65. package/componentLoader/RegistryOnlyComponentLoader.js.map +1 -0
  66. package/componentLoader/createComponentLoader.d.ts +7 -0
  67. package/componentLoader/createComponentLoader.js +10 -0
  68. package/componentLoader/createComponentLoader.js.map +1 -0
  69. package/componentLoader/defaultComponentLoader.d.ts +7 -0
  70. package/componentLoader/defaultComponentLoader.js +8 -0
  71. package/componentLoader/defaultComponentLoader.js.map +1 -0
  72. package/componentLoader/index.d.ts +3 -0
  73. package/componentLoader/index.js +3 -0
  74. package/contentClientState.test.d.ts +1 -0
  75. package/contentClientState.test.js +384 -0
  76. package/contentClientState.test.js.map +1 -0
  77. package/contentEntry/ContentEntryBindingsResolver.d.ts +33 -0
  78. package/contentEntry/ContentEntryBindingsResolver.js +52 -0
  79. package/contentEntry/ContentEntryBindingsResolver.js.map +1 -0
  80. package/contentEntry/ContentEntryCache.d.ts +49 -0
  81. package/contentEntry/ContentEntryCache.js +51 -0
  82. package/contentEntry/ContentEntryCache.js.map +1 -0
  83. package/contentEntry/resolveContentEntry.d.ts +74 -0
  84. package/contentEntry/resolveContentEntry.js +53 -0
  85. package/contentEntry/resolveContentEntry.js.map +1 -0
  86. package/contentEntry/resolveContentEntry.test.d.ts +1 -0
  87. package/contentEntry/resolveContentEntry.test.js +213 -0
  88. package/contentEntry/resolveContentEntry.test.js.map +1 -0
  89. package/createComponent.d.ts +5 -0
  90. package/createComponent.js +32 -0
  91. package/createComponent.js.map +1 -0
  92. package/createContentClient.d.ts +17 -0
  93. package/createContentClient.js +16 -0
  94. package/createContentClient.js.map +1 -0
  95. package/createContentClient.test.d.ts +1 -0
  96. package/createContentClient.test.js +165 -0
  97. package/createContentClient.test.js.map +1 -0
  98. package/createInput.d.ts +10 -2
  99. package/createInput.js +8 -1
  100. package/createInput.js.map +1 -1
  101. package/dataProviders/ApiClient.d.ts +10 -5
  102. package/dataProviders/ApiClient.js +10 -10
  103. package/dataProviders/ApiClient.js.map +1 -1
  104. package/dataProviders/DefaultDataProvider.d.ts +4 -0
  105. package/dataProviders/DefaultDataProvider.js +35 -0
  106. package/dataProviders/DefaultDataProvider.js.map +1 -1
  107. package/dataProviders/GET_EXPERIMENT_PAUSED.d.ts +1 -0
  108. package/dataProviders/GET_EXPERIMENT_PAUSED.js +17 -0
  109. package/dataProviders/GET_EXPERIMENT_PAUSED.js.map +1 -0
  110. package/dataProviders/GET_PAGE_BY_ID.d.ts +1 -1
  111. package/dataProviders/GET_PAGE_BY_ID.js +2 -0
  112. package/dataProviders/GET_PAGE_BY_ID.js.map +1 -1
  113. package/dataProviders/GET_PAGE_BY_PATH.d.ts +1 -1
  114. package/dataProviders/GET_PAGE_BY_PATH.js +2 -0
  115. package/dataProviders/GET_PAGE_BY_PATH.js.map +1 -1
  116. package/dataProviders/GET_PAGE_EXPERIMENT.d.ts +1 -0
  117. package/dataProviders/GET_PAGE_EXPERIMENT.js +32 -0
  118. package/dataProviders/GET_PAGE_EXPERIMENT.js.map +1 -0
  119. package/dataProviders/GET_VARIANT_CONTENT.d.ts +1 -0
  120. package/dataProviders/GET_VARIANT_CONTENT.js +24 -0
  121. package/dataProviders/GET_VARIANT_CONTENT.js.map +1 -0
  122. package/dataProviders/NullDataProvider.d.ts +4 -0
  123. package/dataProviders/NullDataProvider.js +9 -0
  124. package/dataProviders/NullDataProvider.js.map +1 -1
  125. package/dataProviders/queries.test.d.ts +1 -0
  126. package/dataProviders/queries.test.js +45 -0
  127. package/dataProviders/queries.test.js.map +1 -0
  128. package/defaultBreakpoints.js +23 -3
  129. package/defaultBreakpoints.js.map +1 -1
  130. package/experiments/PostHogAnalyticsProvider.d.ts +31 -0
  131. package/experiments/PostHogAnalyticsProvider.js +28 -0
  132. package/experiments/PostHogAnalyticsProvider.js.map +1 -0
  133. package/experiments/analyticsProvider.d.ts +4 -0
  134. package/experiments/analyticsProvider.js +11 -0
  135. package/experiments/analyticsProvider.js.map +1 -0
  136. package/experiments/bucketing.d.ts +27 -0
  137. package/experiments/bucketing.js +91 -0
  138. package/experiments/bucketing.js.map +1 -0
  139. package/experiments/bucketing.test.d.ts +1 -0
  140. package/experiments/bucketing.test.js +183 -0
  141. package/experiments/bucketing.test.js.map +1 -0
  142. package/experiments/index.d.ts +5 -0
  143. package/experiments/index.js +5 -0
  144. package/experiments/render.d.ts +82 -0
  145. package/experiments/render.js +149 -0
  146. package/experiments/render.js.map +1 -0
  147. package/experiments/types.d.ts +108 -0
  148. package/experiments/types.js +4 -0
  149. package/experiments/types.js.map +1 -0
  150. package/headersProvider.d.ts +1 -2
  151. package/headersProvider.js.map +1 -1
  152. package/image/geometry.d.ts +54 -0
  153. package/image/geometry.js +112 -0
  154. package/image/geometry.js.map +1 -0
  155. package/image/geometry.test.d.ts +1 -0
  156. package/image/geometry.test.js +190 -0
  157. package/image/geometry.test.js.map +1 -0
  158. package/image/index.d.ts +3 -0
  159. package/image/index.js +3 -0
  160. package/image/presets.d.ts +6 -0
  161. package/image/presets.js +20 -0
  162. package/image/presets.js.map +1 -0
  163. package/image/types.d.ts +10 -0
  164. package/image/types.js +0 -0
  165. package/index.d.ts +12 -0
  166. package/index.js +12 -0
  167. package/jsonPatch.js +3 -3
  168. package/package.json +7 -6
  169. package/types/WebsiteBuilderTheme.d.ts +5 -0
  170. package/types.d.ts +75 -1
@@ -0,0 +1,74 @@
1
+ import type { ContentEntryReference, ContentEntryQueryValue } from "../types.js";
2
+ /**
3
+ * Minimal CMS data loader the content-entry resolver depends on. Injected by the
4
+ * caller (the render layer passes the CMS `contentSdk`) so this module stays
5
+ * decoupled from `@webiny/cms-sdk`.
6
+ */
7
+ export interface ContentEntryLoader {
8
+ getEntry(params: {
9
+ modelId: string;
10
+ entryId: string;
11
+ }): Promise<unknown | null>;
12
+ listEntries(params: {
13
+ modelId: string;
14
+ sort?: Record<string, "asc" | "desc">;
15
+ limit?: number;
16
+ search?: string;
17
+ after?: string;
18
+ }): Promise<{
19
+ data: unknown[];
20
+ meta: {
21
+ cursor: string | null;
22
+ hasMoreItems: boolean;
23
+ totalCount: number;
24
+ };
25
+ }>;
26
+ }
27
+ /**
28
+ * The query params used to produce a page. Embedded in the query result so the
29
+ * client can fetch further pages (loadMore) from `pageInfo.cursor`.
30
+ */
31
+ export interface ContentEntryQuerySpec {
32
+ modelId: string;
33
+ sort?: Record<string, "asc" | "desc">;
34
+ limit?: number;
35
+ search?: string;
36
+ }
37
+ export interface ResolvedContentEntryQuery<T = unknown> {
38
+ items: T[];
39
+ pageInfo: {
40
+ cursor: string | null;
41
+ hasMore: boolean;
42
+ totalCount: number;
43
+ };
44
+ /** Continuation params for loadMore. */
45
+ query?: ContentEntryQuerySpec;
46
+ }
47
+ /**
48
+ * The shape passed to a component for a content-entry input:
49
+ * - manual, single -> the resolved entry (or null)
50
+ * - manual, list -> an array of resolved entries
51
+ * - query -> `{ items, pageInfo, query? }`
52
+ */
53
+ export type ResolvedContentEntry = unknown | unknown[] | ResolvedContentEntryQuery | null;
54
+ /**
55
+ * Detect whether a raw binding value is a `ContentEntryQueryValue` (query mode)
56
+ * rather than a manual `ContentEntryReference`.
57
+ *
58
+ * Query values carry `modelId` but NOT `id`; manual references carry both.
59
+ */
60
+ export declare function isQueryValue(value: unknown): value is ContentEntryQueryValue;
61
+ /**
62
+ * Detect whether a content-entry input's value is already a resolved CMS entry
63
+ * rather than a bare reference that needs fetching.
64
+ */
65
+ export declare function isAlreadyResolved(value: unknown, list: boolean): boolean;
66
+ /**
67
+ * Resolve a content-entry binding value into CMS entries. Works entirely from
68
+ * the value shape — no component manifest needed.
69
+ *
70
+ * - `{ id, modelId }` → fetch single entry
71
+ * - `[{ id, modelId }, ...]` → fetch multiple entries
72
+ * - `{ modelId, sort?, limit?, search? }` → list query
73
+ */
74
+ export declare function resolveContentEntryValue(value: ContentEntryReference | ContentEntryReference[] | ContentEntryQueryValue | null | undefined, list: boolean, loader: ContentEntryLoader): Promise<ResolvedContentEntry>;
@@ -0,0 +1,53 @@
1
+ function isQueryValue(value) {
2
+ return "object" == typeof value && null !== value && "modelId" in value && !("id" in value) && !Array.isArray(value);
3
+ }
4
+ function isAlreadyResolved(value, list) {
5
+ if (null == value) return false;
6
+ if ("object" == typeof value && "items" in value) return true;
7
+ if (list && Array.isArray(value) && value.length > 0) {
8
+ const first = value[0];
9
+ return "object" == typeof first && null !== first && "values" in first;
10
+ }
11
+ if (!list && "object" == typeof value && "values" in value) return true;
12
+ return false;
13
+ }
14
+ async function resolveContentEntryValue(value, list, loader) {
15
+ if (null == value) return list ? [] : null;
16
+ if (isQueryValue(value)) {
17
+ const query = value;
18
+ const listParams = {
19
+ modelId: query.modelId,
20
+ sort: query.sort ? {
21
+ [query.sort.field]: query.sort.order
22
+ } : void 0,
23
+ limit: query.limit,
24
+ search: query.search
25
+ };
26
+ const result = await loader.listEntries(listParams);
27
+ return {
28
+ items: result.data,
29
+ pageInfo: {
30
+ cursor: result.meta.cursor,
31
+ hasMore: result.meta.hasMoreItems,
32
+ totalCount: result.meta.totalCount
33
+ },
34
+ query: listParams
35
+ };
36
+ }
37
+ if (Array.isArray(value)) {
38
+ const refs = value;
39
+ const entries = await Promise.all(refs.map((ref)=>loader.getEntry({
40
+ modelId: ref.modelId,
41
+ entryId: ref.id
42
+ })));
43
+ return entries.filter((entry)=>null !== entry);
44
+ }
45
+ const ref = value;
46
+ return loader.getEntry({
47
+ modelId: ref.modelId,
48
+ entryId: ref.id
49
+ });
50
+ }
51
+ export { isAlreadyResolved, isQueryValue, resolveContentEntryValue };
52
+
53
+ //# sourceMappingURL=resolveContentEntry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentEntry/resolveContentEntry.js","sources":["../../src/contentEntry/resolveContentEntry.ts"],"sourcesContent":["import type { ContentEntryReference, ContentEntryQueryValue } from \"~/types.js\";\n\n/**\n * Minimal CMS data loader the content-entry resolver depends on. Injected by the\n * caller (the render layer passes the CMS `contentSdk`) so this module stays\n * decoupled from `@webiny/cms-sdk`.\n */\nexport interface ContentEntryLoader {\n getEntry(params: { modelId: string; entryId: string }): Promise<unknown | null>;\n listEntries(params: {\n modelId: string;\n sort?: Record<string, \"asc\" | \"desc\">;\n limit?: number;\n search?: string;\n after?: string;\n }): Promise<{\n data: unknown[];\n meta: { cursor: string | null; hasMoreItems: boolean; totalCount: number };\n }>;\n}\n\n/**\n * The query params used to produce a page. Embedded in the query result so the\n * client can fetch further pages (loadMore) from `pageInfo.cursor`.\n */\nexport interface ContentEntryQuerySpec {\n modelId: string;\n sort?: Record<string, \"asc\" | \"desc\">;\n limit?: number;\n search?: string;\n}\n\nexport interface ResolvedContentEntryQuery<T = unknown> {\n items: T[];\n pageInfo: { cursor: string | null; hasMore: boolean; totalCount: number };\n /** Continuation params for loadMore. */\n query?: ContentEntryQuerySpec;\n}\n\n/**\n * The shape passed to a component for a content-entry input:\n * - manual, single -> the resolved entry (or null)\n * - manual, list -> an array of resolved entries\n * - query -> `{ items, pageInfo, query? }`\n */\nexport type ResolvedContentEntry = unknown | unknown[] | ResolvedContentEntryQuery | null;\n\n/**\n * Detect whether a raw binding value is a `ContentEntryQueryValue` (query mode)\n * rather than a manual `ContentEntryReference`.\n *\n * Query values carry `modelId` but NOT `id`; manual references carry both.\n */\nexport function isQueryValue(value: unknown): value is ContentEntryQueryValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"modelId\" in value &&\n !(\"id\" in value) &&\n !Array.isArray(value)\n );\n}\n\n/**\n * Detect whether a content-entry input's value is already a resolved CMS entry\n * rather than a bare reference that needs fetching.\n */\nexport function isAlreadyResolved(value: unknown, list: boolean): boolean {\n if (value == null) {\n return false;\n }\n // Query result — already resolved if it has `items`.\n if (typeof value === \"object\" && \"items\" in (value as any)) {\n return true;\n }\n // List of resolved entries — each has `values` (CMS entry shape).\n if (list && Array.isArray(value) && value.length > 0) {\n const first = value[0];\n return typeof first === \"object\" && first !== null && \"values\" in first;\n }\n // Single resolved entry.\n if (!list && typeof value === \"object\" && \"values\" in (value as any)) {\n return true;\n }\n return false;\n}\n\n/**\n * Resolve a content-entry binding value into CMS entries. Works entirely from\n * the value shape — no component manifest needed.\n *\n * - `{ id, modelId }` → fetch single entry\n * - `[{ id, modelId }, ...]` → fetch multiple entries\n * - `{ modelId, sort?, limit?, search? }` → list query\n */\nexport async function resolveContentEntryValue(\n value:\n | ContentEntryReference\n | ContentEntryReference[]\n | ContentEntryQueryValue\n | null\n | undefined,\n list: boolean,\n loader: ContentEntryLoader\n): Promise<ResolvedContentEntry> {\n if (value == null) {\n return list ? [] : null;\n }\n\n // Query mode: value is `{ modelId, sort?, limit?, search? }`.\n if (isQueryValue(value)) {\n const query = value;\n const listParams: ContentEntryQuerySpec = {\n modelId: query.modelId,\n sort: query.sort ? { [query.sort.field]: query.sort.order } : undefined,\n limit: query.limit,\n search: query.search\n };\n const result = await loader.listEntries(listParams);\n return {\n items: result.data,\n pageInfo: {\n cursor: result.meta.cursor,\n hasMore: result.meta.hasMoreItems,\n totalCount: result.meta.totalCount\n },\n query: listParams\n };\n }\n\n // Manual list: value is `[{ id, modelId }, ...]`.\n if (Array.isArray(value)) {\n const refs = value as ContentEntryReference[];\n const entries = await Promise.all(\n refs.map(ref => loader.getEntry({ modelId: ref.modelId, entryId: ref.id }))\n );\n return entries.filter(entry => entry !== null);\n }\n\n // Manual single: value is `{ id, modelId }`.\n const ref = value as ContentEntryReference;\n return loader.getEntry({ modelId: ref.modelId, entryId: ref.id });\n}\n"],"names":["isQueryValue","value","Array","isAlreadyResolved","list","first","resolveContentEntryValue","loader","query","listParams","undefined","result","refs","entries","Promise","ref","entry"],"mappings":"AAqDO,SAASA,aAAaC,KAAc;IACvC,OACI,AAAiB,YAAjB,OAAOA,SACPA,AAAU,SAAVA,SACA,aAAaA,SACb,CAAE,SAAQA,KAAI,KACd,CAACC,MAAM,OAAO,CAACD;AAEvB;AAMO,SAASE,kBAAkBF,KAAc,EAAEG,IAAa;IAC3D,IAAIH,AAAS,QAATA,OACA,OAAO;IAGX,IAAI,AAAiB,YAAjB,OAAOA,SAAsB,WAAYA,OACzC,OAAO;IAGX,IAAIG,QAAQF,MAAM,OAAO,CAACD,UAAUA,MAAM,MAAM,GAAG,GAAG;QAClD,MAAMI,QAAQJ,KAAK,CAAC,EAAE;QACtB,OAAO,AAAiB,YAAjB,OAAOI,SAAsBA,AAAU,SAAVA,SAAkB,YAAYA;IACtE;IAEA,IAAI,CAACD,QAAQ,AAAiB,YAAjB,OAAOH,SAAsB,YAAaA,OACnD,OAAO;IAEX,OAAO;AACX;AAUO,eAAeK,yBAClBL,KAKe,EACfG,IAAa,EACbG,MAA0B;IAE1B,IAAIN,AAAS,QAATA,OACA,OAAOG,OAAO,EAAE,GAAG;IAIvB,IAAIJ,aAAaC,QAAQ;QACrB,MAAMO,QAAQP;QACd,MAAMQ,aAAoC;YACtC,SAASD,MAAM,OAAO;YACtB,MAAMA,MAAM,IAAI,GAAG;gBAAE,CAACA,MAAM,IAAI,CAAC,KAAK,CAAC,EAAEA,MAAM,IAAI,CAAC,KAAK;YAAC,IAAIE;YAC9D,OAAOF,MAAM,KAAK;YAClB,QAAQA,MAAM,MAAM;QACxB;QACA,MAAMG,SAAS,MAAMJ,OAAO,WAAW,CAACE;QACxC,OAAO;YACH,OAAOE,OAAO,IAAI;YAClB,UAAU;gBACN,QAAQA,OAAO,IAAI,CAAC,MAAM;gBAC1B,SAASA,OAAO,IAAI,CAAC,YAAY;gBACjC,YAAYA,OAAO,IAAI,CAAC,UAAU;YACtC;YACA,OAAOF;QACX;IACJ;IAGA,IAAIP,MAAM,OAAO,CAACD,QAAQ;QACtB,MAAMW,OAAOX;QACb,MAAMY,UAAU,MAAMC,QAAQ,GAAG,CAC7BF,KAAK,GAAG,CAACG,CAAAA,MAAOR,OAAO,QAAQ,CAAC;gBAAE,SAASQ,IAAI,OAAO;gBAAE,SAASA,IAAI,EAAE;YAAC;QAE5E,OAAOF,QAAQ,MAAM,CAACG,CAAAA,QAASA,AAAU,SAAVA;IACnC;IAGA,MAAMD,MAAMd;IACZ,OAAOM,OAAO,QAAQ,CAAC;QAAE,SAASQ,IAAI,OAAO;QAAE,SAASA,IAAI,EAAE;IAAC;AACnE"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,213 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { isAlreadyResolved, isQueryValue, resolveContentEntryValue } from "./resolveContentEntry.js";
3
+ const entry = (id)=>({
4
+ id,
5
+ entryId: id,
6
+ values: {
7
+ title: `T${id}`
8
+ }
9
+ });
10
+ function makeLoader(over = {}) {
11
+ return {
12
+ getEntry: vi.fn(async ({ entryId })=>entry(entryId)),
13
+ listEntries: vi.fn(async ()=>({
14
+ data: [
15
+ entry("1"),
16
+ entry("2")
17
+ ],
18
+ meta: {
19
+ cursor: "c1",
20
+ hasMoreItems: true,
21
+ totalCount: 5
22
+ }
23
+ })),
24
+ ...over
25
+ };
26
+ }
27
+ describe("resolveContentEntryValue — manual mode", ()=>{
28
+ it("resolves a single reference", async ()=>{
29
+ const loader = makeLoader();
30
+ const result = await resolveContentEntryValue({
31
+ id: "42",
32
+ modelId: "blog"
33
+ }, false, loader);
34
+ expect(loader.getEntry).toHaveBeenCalledWith({
35
+ modelId: "blog",
36
+ entryId: "42"
37
+ });
38
+ expect(result).toEqual(entry("42"));
39
+ });
40
+ it("returns null for a null single value", async ()=>{
41
+ const loader = makeLoader();
42
+ const result = await resolveContentEntryValue(null, false, loader);
43
+ expect(result).toBeNull();
44
+ expect(loader.getEntry).not.toHaveBeenCalled();
45
+ });
46
+ it("resolves a list of references, preserving order", async ()=>{
47
+ const loader = makeLoader();
48
+ const result = await resolveContentEntryValue([
49
+ {
50
+ id: "1",
51
+ modelId: "blog"
52
+ },
53
+ {
54
+ id: "2",
55
+ modelId: "blog"
56
+ }
57
+ ], true, loader);
58
+ expect(loader.getEntry).toHaveBeenCalledTimes(2);
59
+ expect(result).toEqual([
60
+ entry("1"),
61
+ entry("2")
62
+ ]);
63
+ });
64
+ it("filters out unresolved (deleted) references in a list", async ()=>{
65
+ const loader = makeLoader({
66
+ getEntry: vi.fn(async ({ entryId })=>"2" === entryId ? null : entry(entryId))
67
+ });
68
+ const result = await resolveContentEntryValue([
69
+ {
70
+ id: "1",
71
+ modelId: "blog"
72
+ },
73
+ {
74
+ id: "2",
75
+ modelId: "blog"
76
+ }
77
+ ], true, loader);
78
+ expect(result).toEqual([
79
+ entry("1")
80
+ ]);
81
+ });
82
+ it("returns an empty array for a null list value", async ()=>{
83
+ const loader = makeLoader();
84
+ const result = await resolveContentEntryValue(null, true, loader);
85
+ expect(result).toEqual([]);
86
+ expect(loader.getEntry).not.toHaveBeenCalled();
87
+ });
88
+ });
89
+ describe("resolveContentEntryValue — query mode", ()=>{
90
+ it("runs the query and returns items + pageInfo", async ()=>{
91
+ const loader = makeLoader();
92
+ const result = await resolveContentEntryValue({
93
+ modelId: "blog",
94
+ sort: {
95
+ field: "values_title",
96
+ order: "asc"
97
+ },
98
+ limit: 5,
99
+ search: "cat"
100
+ }, false, loader);
101
+ expect(loader.listEntries).toHaveBeenCalledWith({
102
+ modelId: "blog",
103
+ sort: {
104
+ values_title: "asc"
105
+ },
106
+ limit: 5,
107
+ search: "cat"
108
+ });
109
+ expect(result).toMatchObject({
110
+ items: [
111
+ entry("1"),
112
+ entry("2")
113
+ ],
114
+ pageInfo: {
115
+ cursor: "c1",
116
+ hasMore: true,
117
+ totalCount: 5
118
+ }
119
+ });
120
+ });
121
+ it("always embeds the continuation query", async ()=>{
122
+ const loader = makeLoader();
123
+ const result = await resolveContentEntryValue({
124
+ modelId: "blog",
125
+ limit: 4
126
+ }, false, loader);
127
+ expect(result.query).toEqual({
128
+ modelId: "blog",
129
+ sort: void 0,
130
+ limit: 4,
131
+ search: void 0
132
+ });
133
+ });
134
+ it("runs a minimal query (modelId only)", async ()=>{
135
+ const loader = makeLoader();
136
+ await resolveContentEntryValue({
137
+ modelId: "blog"
138
+ }, false, loader);
139
+ expect(loader.listEntries).toHaveBeenCalledWith({
140
+ modelId: "blog",
141
+ sort: void 0,
142
+ limit: void 0,
143
+ search: void 0
144
+ });
145
+ });
146
+ });
147
+ describe("isQueryValue", ()=>{
148
+ it("returns true for { modelId } without id", ()=>{
149
+ expect(isQueryValue({
150
+ modelId: "blog"
151
+ })).toBe(true);
152
+ expect(isQueryValue({
153
+ modelId: "blog",
154
+ sort: {
155
+ field: "a",
156
+ order: "asc"
157
+ }
158
+ })).toBe(true);
159
+ });
160
+ it("returns false for a manual reference { id, modelId }", ()=>{
161
+ expect(isQueryValue({
162
+ id: "42",
163
+ modelId: "blog"
164
+ })).toBe(false);
165
+ });
166
+ it("returns false for arrays and non-objects", ()=>{
167
+ expect(isQueryValue([
168
+ {
169
+ modelId: "blog"
170
+ }
171
+ ])).toBe(false);
172
+ expect(isQueryValue(null)).toBe(false);
173
+ expect(isQueryValue("string")).toBe(false);
174
+ });
175
+ });
176
+ describe("isAlreadyResolved", ()=>{
177
+ it("returns true for a query result with items", ()=>{
178
+ expect(isAlreadyResolved({
179
+ items: [],
180
+ pageInfo: {}
181
+ }, false)).toBe(true);
182
+ });
183
+ it("returns true for a list of resolved entries", ()=>{
184
+ expect(isAlreadyResolved([
185
+ {
186
+ values: {
187
+ title: "x"
188
+ }
189
+ }
190
+ ], true)).toBe(true);
191
+ });
192
+ it("returns false for a list of bare references", ()=>{
193
+ expect(isAlreadyResolved([
194
+ {
195
+ id: "1",
196
+ modelId: "blog"
197
+ }
198
+ ], true)).toBe(false);
199
+ });
200
+ it("returns true for a single resolved entry", ()=>{
201
+ expect(isAlreadyResolved({
202
+ values: {
203
+ title: "x"
204
+ }
205
+ }, false)).toBe(true);
206
+ });
207
+ it("returns false for null", ()=>{
208
+ expect(isAlreadyResolved(null, false)).toBe(false);
209
+ expect(isAlreadyResolved(null, true)).toBe(false);
210
+ });
211
+ });
212
+
213
+ //# sourceMappingURL=resolveContentEntry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentEntry/resolveContentEntry.test.js","sources":["../../src/contentEntry/resolveContentEntry.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from \"vitest\";\nimport {\n resolveContentEntryValue,\n isQueryValue,\n isAlreadyResolved,\n type ContentEntryLoader\n} from \"./resolveContentEntry.js\";\n\nconst entry = (id: string) => ({ id, entryId: id, values: { title: `T${id}` } });\n\nfunction makeLoader(over: Partial<ContentEntryLoader> = {}): ContentEntryLoader {\n return {\n getEntry: vi.fn(async ({ entryId }: { entryId: string }) => entry(entryId)),\n listEntries: vi.fn(async () => ({\n data: [entry(\"1\"), entry(\"2\")],\n meta: { cursor: \"c1\", hasMoreItems: true, totalCount: 5 }\n })),\n ...over\n };\n}\n\ndescribe(\"resolveContentEntryValue — manual mode\", () => {\n it(\"resolves a single reference\", async () => {\n const loader = makeLoader();\n const result = await resolveContentEntryValue({ id: \"42\", modelId: \"blog\" }, false, loader);\n expect(loader.getEntry).toHaveBeenCalledWith({ modelId: \"blog\", entryId: \"42\" });\n expect(result).toEqual(entry(\"42\"));\n });\n\n it(\"returns null for a null single value\", async () => {\n const loader = makeLoader();\n const result = await resolveContentEntryValue(null, false, loader);\n expect(result).toBeNull();\n expect(loader.getEntry).not.toHaveBeenCalled();\n });\n\n it(\"resolves a list of references, preserving order\", async () => {\n const loader = makeLoader();\n const result = await resolveContentEntryValue(\n [\n { id: \"1\", modelId: \"blog\" },\n { id: \"2\", modelId: \"blog\" }\n ],\n true,\n loader\n );\n expect(loader.getEntry).toHaveBeenCalledTimes(2);\n expect(result).toEqual([entry(\"1\"), entry(\"2\")]);\n });\n\n it(\"filters out unresolved (deleted) references in a list\", async () => {\n const loader = makeLoader({\n getEntry: vi.fn(async ({ entryId }: { entryId: string }) =>\n entryId === \"2\" ? null : entry(entryId)\n )\n });\n const result = await resolveContentEntryValue(\n [\n { id: \"1\", modelId: \"blog\" },\n { id: \"2\", modelId: \"blog\" }\n ],\n true,\n loader\n );\n expect(result).toEqual([entry(\"1\")]);\n });\n\n it(\"returns an empty array for a null list value\", async () => {\n const loader = makeLoader();\n const result = await resolveContentEntryValue(null, true, loader);\n expect(result).toEqual([]);\n expect(loader.getEntry).not.toHaveBeenCalled();\n });\n});\n\ndescribe(\"resolveContentEntryValue — query mode\", () => {\n it(\"runs the query and returns items + pageInfo\", async () => {\n const loader = makeLoader();\n const result = await resolveContentEntryValue(\n {\n modelId: \"blog\",\n sort: { field: \"values_title\", order: \"asc\" },\n limit: 5,\n search: \"cat\"\n },\n false,\n loader\n );\n expect(loader.listEntries).toHaveBeenCalledWith({\n modelId: \"blog\",\n sort: { values_title: \"asc\" },\n limit: 5,\n search: \"cat\"\n });\n expect(result).toMatchObject({\n items: [entry(\"1\"), entry(\"2\")],\n pageInfo: { cursor: \"c1\", hasMore: true, totalCount: 5 }\n });\n });\n\n it(\"always embeds the continuation query\", async () => {\n const loader = makeLoader();\n const result = (await resolveContentEntryValue(\n { modelId: \"blog\", limit: 4 },\n false,\n loader\n )) as Record<string, unknown>;\n expect(result.query).toEqual({\n modelId: \"blog\",\n sort: undefined,\n limit: 4,\n search: undefined\n });\n });\n\n it(\"runs a minimal query (modelId only)\", async () => {\n const loader = makeLoader();\n await resolveContentEntryValue({ modelId: \"blog\" }, false, loader);\n expect(loader.listEntries).toHaveBeenCalledWith({\n modelId: \"blog\",\n sort: undefined,\n limit: undefined,\n search: undefined\n });\n });\n});\n\ndescribe(\"isQueryValue\", () => {\n it(\"returns true for { modelId } without id\", () => {\n expect(isQueryValue({ modelId: \"blog\" })).toBe(true);\n expect(isQueryValue({ modelId: \"blog\", sort: { field: \"a\", order: \"asc\" } })).toBe(true);\n });\n\n it(\"returns false for a manual reference { id, modelId }\", () => {\n expect(isQueryValue({ id: \"42\", modelId: \"blog\" })).toBe(false);\n });\n\n it(\"returns false for arrays and non-objects\", () => {\n expect(isQueryValue([{ modelId: \"blog\" }])).toBe(false);\n expect(isQueryValue(null)).toBe(false);\n expect(isQueryValue(\"string\")).toBe(false);\n });\n});\n\ndescribe(\"isAlreadyResolved\", () => {\n it(\"returns true for a query result with items\", () => {\n expect(isAlreadyResolved({ items: [], pageInfo: {} }, false)).toBe(true);\n });\n\n it(\"returns true for a list of resolved entries\", () => {\n expect(isAlreadyResolved([{ values: { title: \"x\" } }], true)).toBe(true);\n });\n\n it(\"returns false for a list of bare references\", () => {\n expect(isAlreadyResolved([{ id: \"1\", modelId: \"blog\" }], true)).toBe(false);\n });\n\n it(\"returns true for a single resolved entry\", () => {\n expect(isAlreadyResolved({ values: { title: \"x\" } }, false)).toBe(true);\n });\n\n it(\"returns false for null\", () => {\n expect(isAlreadyResolved(null, false)).toBe(false);\n expect(isAlreadyResolved(null, true)).toBe(false);\n });\n});\n"],"names":["entry","id","makeLoader","over","vi","entryId","describe","it","loader","result","resolveContentEntryValue","expect","undefined","isQueryValue","isAlreadyResolved"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Component, ComponentManifestInput } from "./types.js";
2
+ export type ExtractInputs<T> = T extends {
3
+ inputs: infer I;
4
+ } ? I : never;
5
+ export declare function createComponent<TComponent extends (props: any) => any, TInputs extends ExtractInputs<Parameters<TComponent>[0]>>(component: TComponent, manifest: ComponentManifestInput<TInputs>): Component;
@@ -0,0 +1,32 @@
1
+ import { createSlotInput } from "./createInput.js";
2
+ function createComponent(component, manifest) {
3
+ const inputs = [];
4
+ if (Array.isArray(manifest.inputs)) inputs.push(...manifest.inputs);
5
+ else {
6
+ const inputsObject = manifest.inputs ?? {};
7
+ Object.keys(inputsObject).forEach((name)=>{
8
+ inputs.push({
9
+ ...inputsObject[name],
10
+ name
11
+ });
12
+ });
13
+ }
14
+ const acceptsChildren = manifest.acceptsChildren;
15
+ if (acceptsChildren) {
16
+ const hasChildren = inputs.some((input)=>"children" === input.name);
17
+ if (!hasChildren) inputs.push(createSlotInput({
18
+ name: "children"
19
+ }));
20
+ }
21
+ return {
22
+ component,
23
+ manifest: {
24
+ ...manifest,
25
+ tags: manifest.tags ?? [],
26
+ inputs
27
+ }
28
+ };
29
+ }
30
+ export { createComponent };
31
+
32
+ //# sourceMappingURL=createComponent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createComponent.js","sources":["../src/createComponent.ts"],"sourcesContent":["import type {\n Component,\n ComponentInput,\n ComponentManifest,\n ComponentManifestInput\n} from \"./types.js\";\nimport type { InputFactory } from \"./createInput.js\";\nimport { createSlotInput } from \"./createInput.js\";\n\nexport type ExtractInputs<T> = T extends { inputs: infer I } ? I : never;\n\nexport function createComponent<\n TComponent extends (props: any) => any,\n TInputs extends ExtractInputs<Parameters<TComponent>[0]>\n>(component: TComponent, manifest: ComponentManifestInput<TInputs>): Component {\n const inputs: ComponentInput[] = [];\n\n if (Array.isArray(manifest.inputs)) {\n inputs.push(...manifest.inputs);\n } else {\n const inputsObject: Record<string, InputFactory<any>> = manifest.inputs ?? {};\n Object.keys(inputsObject).forEach((name: string) => {\n inputs.push({ ...inputsObject[name], name });\n });\n }\n\n const acceptsChildren = manifest.acceptsChildren;\n\n if (acceptsChildren) {\n const hasChildren = inputs.some(input => input.name === \"children\");\n if (!hasChildren) {\n inputs.push(createSlotInput({ name: \"children\" }));\n }\n }\n\n return {\n component,\n manifest: { ...manifest, tags: manifest.tags ?? [], inputs } as ComponentManifest\n };\n}\n"],"names":["createComponent","component","manifest","inputs","Array","inputsObject","Object","name","acceptsChildren","hasChildren","input","createSlotInput"],"mappings":";AAWO,SAASA,gBAGdC,SAAqB,EAAEC,QAAyC;IAC9D,MAAMC,SAA2B,EAAE;IAEnC,IAAIC,MAAM,OAAO,CAACF,SAAS,MAAM,GAC7BC,OAAO,IAAI,IAAID,SAAS,MAAM;SAC3B;QACH,MAAMG,eAAkDH,SAAS,MAAM,IAAI,CAAC;QAC5EI,OAAO,IAAI,CAACD,cAAc,OAAO,CAAC,CAACE;YAC/BJ,OAAO,IAAI,CAAC;gBAAE,GAAGE,YAAY,CAACE,KAAK;gBAAEA;YAAK;QAC9C;IACJ;IAEA,MAAMC,kBAAkBN,SAAS,eAAe;IAEhD,IAAIM,iBAAiB;QACjB,MAAMC,cAAcN,OAAO,IAAI,CAACO,CAAAA,QAASA,AAAe,eAAfA,MAAM,IAAI;QACnD,IAAI,CAACD,aACDN,OAAO,IAAI,CAACQ,gBAAgB;YAAE,MAAM;QAAW;IAEvD;IAEA,OAAO;QACHV;QACA,UAAU;YAAE,GAAGC,QAAQ;YAAE,MAAMA,SAAS,IAAI,IAAI,EAAE;YAAEC;QAAO;IAC/D;AACJ"}
@@ -0,0 +1,17 @@
1
+ import { ContentSdk } from "./ContentSdk.js";
2
+ import type { ContentSDKConfig } from "./ContentSdk.js";
3
+ /**
4
+ * A configured content client. It exposes the same methods as `contentSdk`,
5
+ * except `init()`: a client's configuration is fixed when it is created.
6
+ */
7
+ export type ContentClient = Omit<ContentSdk, "init">;
8
+ /**
9
+ * Create a content client with its own configuration and its own API client, separate
10
+ * from the `contentSdk` singleton and from other clients. Use it when one process serves
11
+ * requests for different tenants or API hosts.
12
+ *
13
+ * Data fetching is per client, and so are the component registry, the content entry cache
14
+ * and the document store. The editing environment is module-level and shared by all
15
+ * clients and `contentSdk`.
16
+ */
17
+ export declare function createContentClient(config: ContentSDKConfig): ContentClient;
@@ -0,0 +1,16 @@
1
+ import { ContentSdk } from "./ContentSdk.js";
2
+ import { ComponentRegistry } from "./ComponentRegistry.js";
3
+ import { ContentEntryCache } from "./contentEntry/ContentEntryCache.js";
4
+ import { DocumentStoreManager } from "./DocumentStoreManager.js";
5
+ function createContentClient(config) {
6
+ const client = new ContentSdk({
7
+ componentRegistry: new ComponentRegistry(),
8
+ contentEntryCache: new ContentEntryCache(),
9
+ documentStoreManager: new DocumentStoreManager()
10
+ });
11
+ client.init(config);
12
+ return client;
13
+ }
14
+ export { createContentClient };
15
+
16
+ //# sourceMappingURL=createContentClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createContentClient.js","sources":["../src/createContentClient.ts"],"sourcesContent":["import { 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\";\n\n/**\n * A configured content client. It exposes the same methods as `contentSdk`,\n * except `init()`: a client's configuration is fixed when it is created.\n */\nexport type ContentClient = Omit<ContentSdk, \"init\">;\n\n/**\n * Create a content client with its own configuration and its own API client, separate\n * from the `contentSdk` singleton and from other clients. Use it when one process serves\n * requests for different tenants or API hosts.\n *\n * Data fetching is per client, and so are the component registry, the content entry cache\n * and the document store. The editing environment is module-level and shared by all\n * clients and `contentSdk`.\n */\nexport function createContentClient(config: ContentSDKConfig): ContentClient {\n const client = new ContentSdk({\n componentRegistry: new ComponentRegistry(),\n contentEntryCache: new ContentEntryCache(),\n documentStoreManager: new DocumentStoreManager()\n });\n client.init(config);\n return client;\n}\n"],"names":["createContentClient","config","client","ContentSdk","ComponentRegistry","ContentEntryCache","DocumentStoreManager"],"mappings":";;;;AAqBO,SAASA,oBAAoBC,MAAwB;IACxD,MAAMC,SAAS,IAAIC,WAAW;QAC1B,mBAAmB,IAAIC;QACvB,mBAAmB,IAAIC;QACvB,sBAAsB,IAAIC;IAC9B;IACAJ,OAAO,IAAI,CAACD;IACZ,OAAOC;AACX"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,165 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { createContentClient } from "./createContentClient.js";
3
+ import { contentSdk } from "./ContentSdk.js";
4
+ const pageResponse = (path)=>({
5
+ data: {
6
+ websiteBuilder: {
7
+ getPageByPath: {
8
+ data: {
9
+ id: `page:${path}`,
10
+ properties: {
11
+ id: `page:${path}`,
12
+ path
13
+ }
14
+ },
15
+ error: null
16
+ }
17
+ }
18
+ }
19
+ });
20
+ describe("createContentClient", ()=>{
21
+ let requests;
22
+ let delays;
23
+ beforeEach(()=>{
24
+ requests = [];
25
+ delays = {};
26
+ const fetchMock = vi.fn(async (url, init)=>{
27
+ const headers = init.headers;
28
+ const body = JSON.parse(init.body);
29
+ const path = body.variables.path;
30
+ requests.push({
31
+ url,
32
+ tenant: headers["X-Tenant"],
33
+ authorization: headers["Authorization"],
34
+ path
35
+ });
36
+ const delay = delays[path] ?? 0;
37
+ await new Promise((resolve)=>setTimeout(resolve, delay));
38
+ return {
39
+ json: async ()=>pageResponse(path)
40
+ };
41
+ });
42
+ vi.stubGlobal("fetch", fetchMock);
43
+ });
44
+ afterEach(()=>{
45
+ vi.unstubAllGlobals();
46
+ });
47
+ it("sends each client's own tenant, host and key", async ()=>{
48
+ const clientA = createContentClient({
49
+ apiHost: "https://a.example.com",
50
+ apiKey: "key-a",
51
+ apiTenant: "tenant-a"
52
+ });
53
+ const clientB = createContentClient({
54
+ apiHost: "https://b.example.com",
55
+ apiKey: "key-b",
56
+ apiTenant: "tenant-b"
57
+ });
58
+ await clientA.getPage("/a");
59
+ await clientB.getPage("/b");
60
+ expect(requests).toEqual([
61
+ {
62
+ url: "https://a.example.com/graphql",
63
+ tenant: "tenant-a",
64
+ authorization: "Bearer key-a",
65
+ path: "/a"
66
+ },
67
+ {
68
+ url: "https://b.example.com/graphql",
69
+ tenant: "tenant-b",
70
+ authorization: "Bearer key-b",
71
+ path: "/b"
72
+ }
73
+ ]);
74
+ });
75
+ it("keeps clients isolated when their calls interleave", async ()=>{
76
+ const clientA = createContentClient({
77
+ apiHost: "https://a.example.com",
78
+ apiKey: "key-a",
79
+ apiTenant: "tenant-a"
80
+ });
81
+ const clientB = createContentClient({
82
+ apiHost: "https://b.example.com",
83
+ apiKey: "key-b",
84
+ apiTenant: "tenant-b"
85
+ });
86
+ delays["/a"] = 20;
87
+ delays["/b"] = 0;
88
+ const [pageA, pageB] = await Promise.all([
89
+ clientA.getPage("/a"),
90
+ clientB.getPage("/b")
91
+ ]);
92
+ expect(pageA?.properties.path).toBe("/a");
93
+ expect(pageB?.properties.path).toBe("/b");
94
+ const requestA = requests.find((request)=>"/a" === request.path);
95
+ const requestB = requests.find((request)=>"/b" === request.path);
96
+ expect(requestA).toMatchObject({
97
+ url: "https://a.example.com/graphql",
98
+ tenant: "tenant-a",
99
+ authorization: "Bearer key-a"
100
+ });
101
+ expect(requestB).toMatchObject({
102
+ url: "https://b.example.com/graphql",
103
+ tenant: "tenant-b",
104
+ authorization: "Bearer key-b"
105
+ });
106
+ });
107
+ it("does not change what the contentSdk singleton sends, and init() does not change a client", async ()=>{
108
+ contentSdk.init({
109
+ apiHost: "https://singleton.example.com",
110
+ apiKey: "key-singleton",
111
+ apiTenant: "root"
112
+ });
113
+ const client = createContentClient({
114
+ apiHost: "https://client.example.com",
115
+ apiKey: "key-client",
116
+ apiTenant: "tenant-client"
117
+ });
118
+ await contentSdk.getPage("/singleton");
119
+ contentSdk.init({
120
+ apiHost: "https://singleton-2.example.com",
121
+ apiKey: "key-singleton-2",
122
+ apiTenant: "root-2"
123
+ });
124
+ await client.getPage("/client");
125
+ await contentSdk.getPage("/singleton-2");
126
+ expect(requests).toEqual([
127
+ {
128
+ url: "https://singleton.example.com/graphql",
129
+ tenant: "root",
130
+ authorization: "Bearer key-singleton",
131
+ path: "/singleton"
132
+ },
133
+ {
134
+ url: "https://client.example.com/graphql",
135
+ tenant: "tenant-client",
136
+ authorization: "Bearer key-client",
137
+ path: "/client"
138
+ },
139
+ {
140
+ url: "https://singleton-2.example.com/graphql",
141
+ tenant: "root-2",
142
+ authorization: "Bearer key-singleton-2",
143
+ path: "/singleton-2"
144
+ }
145
+ ]);
146
+ });
147
+ it("keeps the preview flag per client", ()=>{
148
+ const live = createContentClient({
149
+ apiHost: "https://a.example.com",
150
+ apiKey: "key-a",
151
+ apiTenant: "tenant-a"
152
+ });
153
+ const preview = createContentClient({
154
+ apiHost: "https://a.example.com",
155
+ apiKey: "key-a",
156
+ apiTenant: "tenant-a",
157
+ preview: true,
158
+ previewParams: "wb.type=page&wb.path=/a&wb.id=page-1"
159
+ });
160
+ expect(live.isPreviewing()).toBe(false);
161
+ expect(preview.isPreviewing()).toBe(true);
162
+ });
163
+ });
164
+
165
+ //# sourceMappingURL=createContentClient.test.js.map