@builder.io/sdk-svelte 0.7.0 → 0.7.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/browser/blocks/custom-code/custom-code.svelte +35 -37
  2. package/lib/browser/components/content/components/enable-editor.svelte +82 -65
  3. package/lib/browser/components/content/components/enable-editor.svelte.d.ts +3 -3
  4. package/lib/browser/constants/sdk-version.d.ts +1 -1
  5. package/lib/browser/constants/sdk-version.js +1 -1
  6. package/lib/browser/functions/evaluate/choose-eval.d.ts +6 -0
  7. package/lib/browser/functions/evaluate/choose-eval.js +19 -0
  8. package/lib/browser/functions/evaluate/evaluate.d.ts +1 -5
  9. package/lib/browser/functions/evaluate/evaluate.js +7 -30
  10. package/lib/browser/functions/evaluate/helpers.d.ts +7 -1
  11. package/lib/browser/functions/evaluate/helpers.js +18 -0
  12. package/lib/browser/functions/evaluate/node-runtime/index.d.ts +1 -1
  13. package/lib/browser/functions/evaluate/node-runtime/index.js +1 -1
  14. package/lib/browser/functions/evaluate/node-runtime/node-runtime.d.ts +2 -0
  15. package/lib/browser/functions/evaluate/node-runtime/node-runtime.js +120 -0
  16. package/lib/browser/functions/evaluate/node-runtime/safeDynamicRequire.d.ts +13 -0
  17. package/lib/browser/functions/evaluate/node-runtime/safeDynamicRequire.js +18 -0
  18. package/lib/browser/functions/get-content/generate-content-url.js +27 -1
  19. package/lib/browser/functions/get-content/types.d.ts +94 -3
  20. package/lib/browser/functions/is-edge-runtime.js +2 -2
  21. package/lib/browser/functions/is-node-runtime.d.ts +4 -0
  22. package/lib/browser/functions/is-node-runtime.js +7 -0
  23. package/lib/edge/blocks/custom-code/custom-code.svelte +35 -37
  24. package/lib/edge/components/content/components/enable-editor.svelte +82 -65
  25. package/lib/edge/components/content/components/enable-editor.svelte.d.ts +3 -3
  26. package/lib/edge/constants/sdk-version.d.ts +1 -1
  27. package/lib/edge/constants/sdk-version.js +1 -1
  28. package/lib/edge/functions/evaluate/choose-eval.d.ts +6 -0
  29. package/lib/edge/functions/evaluate/choose-eval.js +19 -0
  30. package/lib/edge/functions/evaluate/evaluate.d.ts +1 -5
  31. package/lib/edge/functions/evaluate/evaluate.js +7 -30
  32. package/lib/edge/functions/evaluate/helpers.d.ts +7 -1
  33. package/lib/edge/functions/evaluate/helpers.js +18 -0
  34. package/lib/edge/functions/evaluate/node-runtime/index.d.ts +1 -1
  35. package/lib/edge/functions/evaluate/node-runtime/index.js +1 -1
  36. package/lib/edge/functions/evaluate/node-runtime/node-runtime.d.ts +2 -0
  37. package/lib/edge/functions/evaluate/node-runtime/node-runtime.js +120 -0
  38. package/lib/edge/functions/evaluate/node-runtime/safeDynamicRequire.d.ts +13 -0
  39. package/lib/edge/functions/evaluate/node-runtime/safeDynamicRequire.js +18 -0
  40. package/lib/edge/functions/get-content/generate-content-url.js +27 -1
  41. package/lib/edge/functions/get-content/types.d.ts +94 -3
  42. package/lib/edge/functions/is-edge-runtime.js +2 -2
  43. package/lib/edge/functions/is-node-runtime.d.ts +4 -0
  44. package/lib/edge/functions/is-node-runtime.js +7 -0
  45. package/lib/node/blocks/custom-code/custom-code.svelte +35 -37
  46. package/lib/node/components/content/components/enable-editor.svelte +82 -65
  47. package/lib/node/components/content/components/enable-editor.svelte.d.ts +3 -3
  48. package/lib/node/constants/sdk-version.d.ts +1 -1
  49. package/lib/node/constants/sdk-version.js +1 -1
  50. package/lib/node/functions/evaluate/choose-eval.d.ts +6 -0
  51. package/lib/node/functions/evaluate/choose-eval.js +19 -0
  52. package/lib/node/functions/evaluate/evaluate.d.ts +1 -5
  53. package/lib/node/functions/evaluate/evaluate.js +7 -30
  54. package/lib/node/functions/evaluate/helpers.d.ts +7 -1
  55. package/lib/node/functions/evaluate/helpers.js +18 -0
  56. package/lib/node/functions/evaluate/node-runtime/index.d.ts +1 -1
  57. package/lib/node/functions/evaluate/node-runtime/index.js +1 -1
  58. package/lib/node/functions/evaluate/node-runtime/node-runtime.d.ts +2 -0
  59. package/lib/node/functions/evaluate/node-runtime/node-runtime.js +120 -0
  60. package/lib/node/functions/evaluate/node-runtime/safeDynamicRequire.d.ts +13 -0
  61. package/lib/node/functions/evaluate/node-runtime/safeDynamicRequire.js +18 -0
  62. package/lib/node/functions/get-content/generate-content-url.js +27 -1
  63. package/lib/node/functions/get-content/types.d.ts +94 -3
  64. package/lib/node/functions/is-edge-runtime.js +2 -2
  65. package/lib/node/functions/is-node-runtime.d.ts +4 -0
  66. package/lib/node/functions/is-node-runtime.js +7 -0
  67. package/package.json +2 -1
  68. package/src/blocks/custom-code/custom-code.svelte +37 -40
  69. package/src/blocks/image/image.svelte +1 -1
  70. package/src/components/content/components/enable-editor.svelte +97 -79
  71. package/src/constants/sdk-version.ts +1 -1
  72. package/src/functions/evaluate/choose-eval.ts +22 -0
  73. package/src/functions/evaluate/evaluate.ts +8 -37
  74. package/src/functions/evaluate/helpers.ts +24 -1
  75. package/src/functions/evaluate/node-runtime/index.ts +1 -1
  76. package/src/functions/evaluate/node-runtime/node-runtime.ts +138 -0
  77. package/src/functions/evaluate/node-runtime/safeDynamicRequire.ts +18 -0
  78. package/src/functions/get-content/generate-content-url.ts +36 -1
  79. package/src/functions/get-content/types.ts +101 -3
  80. package/src/functions/is-edge-runtime.ts +2 -2
  81. package/src/functions/is-node-runtime.ts +8 -0
@@ -44,13 +44,12 @@
44
44
  export let context: BuilderEditorProps["context"] = undefined;
45
45
  export let canTrack: BuilderEditorProps["canTrack"] = undefined;
46
46
  export let apiKey: BuilderEditorProps["apiKey"];
47
- export let showContent: BuilderEditorProps["showContent"];
48
- export let classNameProp: BuilderEditorProps["classNameProp"];
49
-
50
- export let content: BuilderEditorProps["content"] = undefined;
51
47
  export let locale: BuilderEditorProps["locale"] = undefined;
52
48
  export let includeRefs: BuilderEditorProps["includeRefs"] = undefined;
53
49
  export let enrich: BuilderEditorProps["enrich"] = undefined;
50
+ export let content: BuilderEditorProps["content"] = undefined;
51
+ export let showContent: BuilderEditorProps["showContent"];
52
+ export let classNameProp: BuilderEditorProps["classNameProp"];
54
53
 
55
54
  function mergeNewContent(newContent: BuilderContent) {
56
55
  const newContentValue = {
@@ -196,6 +195,72 @@
196
195
  );
197
196
  }
198
197
  }
198
+ function elementRef_onIniteditingbldr(event) {
199
+ forceReRenderCount = forceReRenderCount + 1;
200
+ window.addEventListener("message", processMessage);
201
+ registerInsertMenu();
202
+ setupBrowserForEditing({
203
+ ...(locale
204
+ ? {
205
+ locale: locale,
206
+ }
207
+ : {}),
208
+ ...(includeRefs
209
+ ? {
210
+ includeRefs: includeRefs,
211
+ }
212
+ : {}),
213
+ ...(enrich
214
+ ? {
215
+ enrich: enrich,
216
+ }
217
+ : {}),
218
+ });
219
+ Object.values<ComponentInfo>($builderContextSignal.componentInfos).forEach(
220
+ (registeredComponent) => {
221
+ const message = createRegisterComponentMessage(registeredComponent);
222
+ window.parent?.postMessage(message, "*");
223
+ }
224
+ );
225
+ window.addEventListener(
226
+ "builder:component:stateChangeListenerActivated",
227
+ emitStateUpdate
228
+ );
229
+ }
230
+ function elementRef_onInitpreviewingbldr(event) {
231
+ const searchParams = new URL(location.href).searchParams;
232
+ const searchParamPreviewModel = searchParams.get("builder.preview");
233
+ const searchParamPreviewId = searchParams.get(
234
+ `builder.preview.${searchParamPreviewModel}`
235
+ );
236
+ const previewApiKey =
237
+ searchParams.get("apiKey") || searchParams.get("builder.space");
238
+
239
+ /**
240
+ * Make sure that:
241
+ * - the preview model name is the same as the one we're rendering, since there can be multiple models rendered
242
+ * at the same time, e.g. header/page/footer.
243
+ * - the API key is the same, since we don't want to preview content from other organizations.
244
+ * - if there is content, that the preview ID is the same as that of the one we receive.
245
+ *
246
+ * TO-DO: should we only update the state when there is a change?
247
+ **/
248
+ if (
249
+ searchParamPreviewModel === model &&
250
+ previewApiKey === apiKey &&
251
+ (!content || searchParamPreviewId === content.id)
252
+ ) {
253
+ fetchOneEntry({
254
+ model: model,
255
+ apiKey: apiKey,
256
+ apiVersion: $builderContextSignal.apiVersion,
257
+ }).then((content) => {
258
+ if (content) {
259
+ mergeNewContent(content);
260
+ }
261
+ });
262
+ }
263
+ }
199
264
 
200
265
  let elementRef;
201
266
 
@@ -206,95 +271,41 @@
206
271
  let clicked = false;
207
272
 
208
273
  onMount(() => {
209
- if (!apiKey) {
210
- logger.error(
211
- "No API key provided to `RenderContent` component. This can cause issues. Please provide an API key using the `apiKey` prop."
212
- );
213
- }
214
274
  if (isBrowser()) {
215
- if (isEditing()) {
216
- forceReRenderCount = forceReRenderCount + 1;
217
- window.addEventListener("message", processMessage);
218
- registerInsertMenu();
219
- setupBrowserForEditing({
220
- ...(locale
221
- ? {
222
- locale: locale,
223
- }
224
- : {}),
225
- ...(includeRefs
226
- ? {
227
- includeRefs: includeRefs,
228
- }
229
- : {}),
230
- ...(enrich
231
- ? {
232
- enrich: enrich,
233
- }
234
- : {}),
235
- });
236
- Object.values<ComponentInfo>(
237
- $builderContextSignal.componentInfos
238
- ).forEach((registeredComponent) => {
239
- const message = createRegisterComponentMessage(registeredComponent);
240
- window.parent?.postMessage(message, "*");
241
- });
242
- window.addEventListener(
243
- "builder:component:stateChangeListenerActivated",
244
- emitStateUpdate
245
- );
275
+ if (isEditing() && elementRef) {
276
+ elementRef.dispatchEvent(new CustomEvent("initeditingbldr"));
246
277
  }
247
- if ($builderContextSignal.content) {
278
+ const shouldTrackImpression =
279
+ $builderContextSignal.content && getDefaultCanTrack(canTrack);
280
+ if (shouldTrackImpression) {
248
281
  const variationId = $builderContextSignal.content?.testVariationId;
249
282
  const contentId = $builderContextSignal.content?.id;
283
+ const apiKeyProp = apiKey;
250
284
  _track({
251
285
  type: "impression",
252
- canTrack: getDefaultCanTrack(canTrack),
286
+ canTrack: true,
253
287
  contentId,
254
- apiKey: apiKey,
288
+ apiKey: apiKeyProp!,
255
289
  variationId: variationId !== contentId ? variationId : undefined,
256
290
  });
257
291
  }
258
- // override normal content in preview mode
259
- if (isPreviewing()) {
260
- const searchParams = new URL(location.href).searchParams;
261
- const searchParamPreviewModel = searchParams.get("builder.preview");
262
- const searchParamPreviewId = searchParams.get(
263
- `builder.preview.${searchParamPreviewModel}`
264
- );
265
- const previewApiKey =
266
- searchParams.get("apiKey") || searchParams.get("builder.space");
267
292
 
268
- /**
269
- * Make sure that:
270
- * - the preview model name is the same as the one we're rendering, since there can be multiple models rendered
271
- * at the same time, e.g. header/page/footer.
272
- * - the API key is the same, since we don't want to preview content from other organizations.
273
- * - if there is content, that the preview ID is the same as that of the one we receive.
274
- *
275
- * TO-DO: should we only update the state when there is a change?
276
- **/
277
- if (
278
- searchParamPreviewModel === model &&
279
- previewApiKey === apiKey &&
280
- (!content || searchParamPreviewId === content.id)
281
- ) {
282
- fetchOneEntry({
283
- model: model,
284
- apiKey: apiKey,
285
- apiVersion: $builderContextSignal.apiVersion,
286
- }).then((content) => {
287
- if (content) {
288
- mergeNewContent(content);
289
- }
290
- });
291
- }
293
+ // override normal content in preview mode
294
+ if (isPreviewing() && elementRef) {
295
+ elementRef.dispatchEvent(new CustomEvent("initpreviewingbldr"));
292
296
  }
293
- evaluateJsCode();
294
- runHttpRequests();
295
- emitStateUpdate();
296
297
  }
297
298
  });
299
+ onMount(() => {
300
+ if (!apiKey) {
301
+ logger.error(
302
+ "No API key provided to `RenderContent` component. This can cause issues. Please provide an API key using the `apiKey` prop."
303
+ );
304
+ }
305
+ evaluateJsCode();
306
+ runHttpRequests();
307
+ emitStateUpdate();
308
+ });
298
309
 
299
310
  function onUpdateFn_0(..._args: any[]) {
300
311
  if (content) {
@@ -337,6 +348,7 @@
337
348
 
338
349
  {#if $builderContextSignal.content}
339
350
  <div
351
+ {...{}}
340
352
  key={forceReRenderCount}
341
353
  bind:this={elementRef}
342
354
  on:click={(event) => {
@@ -352,6 +364,12 @@
352
364
  "aria-hidden": true,
353
365
  }}
354
366
  class={classNameProp}
367
+ on:initeditingbldr={(event) => {
368
+ elementRef_onIniteditingbldr(event);
369
+ }}
370
+ on:initpreviewingbldr={(event) => {
371
+ elementRef_onInitpreviewingbldr(event);
372
+ }}
355
373
  >
356
374
  <slot />
357
375
  </div>
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.7.0"
1
+ export const SDK_VERSION = "0.7.1-0"
@@ -0,0 +1,22 @@
1
+ import { runInBrowser } from './browser-runtime/browser.js';
2
+ import type { ExecutorArgs } from './helpers.js';
3
+ import { isBrowser } from '../is-browser.js';
4
+
5
+ /**
6
+ * THIS IS A MAGICAL IMPORT. It is aliased by the build process of every SDK configuration, so that
7
+ * it points to the correct runtime for that configuration, which are expected to live exactly at:
8
+ * - ./browser-runtime/index.js
9
+ * - ./node-runtime/index.js
10
+ * - ./edge-runtime/index.js
11
+ *
12
+ * We have code in `/output-generation` that does this aliasing, and is re-used by each SDK.
13
+ * Also, each individual `tsconfig.json` aliases this import to the browser runtime so that the
14
+ * types can be resolved correctly.
15
+ */
16
+ import { evaluator } from 'placeholder-runtime';
17
+
18
+ /**
19
+ * Even though we have separate runtimes for browser/node/edge, sometimes frameworks will
20
+ * end up sending the server runtime code to the browser (most notably in dev mode).
21
+ */
22
+ export const chooseBrowserOrServerEval = (args: ExecutorArgs) => isBrowser() ? runInBrowser(args) : evaluator(args)
@@ -1,25 +1,7 @@
1
1
  import { logger } from '../../helpers/logger.js';
2
- import { isBrowser } from '../is-browser.js';
3
- import { isEditing } from '../is-editing.js';
4
- import type { BuilderGlobals, ExecutorArgs } from './helpers.js';
5
- import { getUserAttributes } from '../track/helpers.js';
6
-
7
- /**
8
- * THIS IS A MAGICAL IMPORT. It is aliased by the build process of every SDK configuration, so that
9
- * it points to the correct runtime for that configuration, which are expected to live exactly at:
10
- * - ./browser-runtime/index.js
11
- * - ./node-runtime/index.js
12
- * - ./edge-runtime/index.js
13
- *
14
- * We have code in `/output-generation` that does this aliasing, and is re-used by each SDK.
15
- * Also, each individual `tsconfig.json` aliases this import to the browser runtime so that the
16
- * types can be resolved correctly.
17
- */
18
- import { evaluator } from 'placeholder-runtime';
19
- export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
20
- event?: Event;
21
- isExpression?: boolean;
22
- };
2
+ import type { EvaluatorArgs, ExecutorArgs } from './helpers.js';
3
+ import { getBuilderGlobals, parseCode } from './helpers.js';
4
+ import { chooseBrowserOrServerEval } from './choose-eval.js';
23
5
  export function evaluate({
24
6
  code,
25
7
  context,
@@ -33,22 +15,11 @@ export function evaluate({
33
15
  logger.warn('Skipping evaluation of empty code block.');
34
16
  return;
35
17
  }
36
- const builder: BuilderGlobals = {
37
- isEditing: isEditing(),
38
- isBrowser: isBrowser(),
39
- isServer: !isBrowser(),
40
- getUserAttributes: () => getUserAttributes()
41
- };
42
-
43
- // Be able to handle simple expressions like "state.foo" or "1 + 1"
44
- // as well as full blocks like "var foo = "bar"; return foo"
45
- const useReturn =
46
- // we disable this for cases where we definitely don't want a return
47
- isExpression && !(code.includes(';') || code.includes(' return ') || code.trim().startsWith('return '));
48
- const useCode = useReturn ? `return (${code});` : code;
49
18
  const args: ExecutorArgs = {
50
- code: useCode,
51
- builder,
19
+ code: parseCode(code, {
20
+ isExpression
21
+ }),
22
+ builder: getBuilderGlobals(),
52
23
  context,
53
24
  event,
54
25
  rootSetState,
@@ -56,7 +27,7 @@ export function evaluate({
56
27
  localState
57
28
  };
58
29
  try {
59
- return evaluator(args);
30
+ return chooseBrowserOrServerEval(args);
60
31
  } catch (e: any) {
61
32
  logger.error('Failed code evaluation: ' + e.message, {
62
33
  code
@@ -1,5 +1,11 @@
1
1
  import type { BuilderContextInterface, BuilderRenderState } from '../../context/types.js';
2
- import type { getUserAttributes } from '../track/helpers.js';
2
+ import { isBrowser } from '../is-browser.js';
3
+ import { isEditing } from '../is-editing.js';
4
+ import { getUserAttributes } from '../track/helpers.js';
5
+ export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
6
+ event?: Event;
7
+ isExpression?: boolean;
8
+ };
3
9
  export type BuilderGlobals = {
4
10
  isEditing: boolean | undefined;
5
11
  isBrowser: boolean | undefined;
@@ -29,4 +35,21 @@ export const getFunctionArguments = ({
29
35
  context,
30
36
  event
31
37
  });
38
+ };
39
+ export const getBuilderGlobals = (): BuilderGlobals => ({
40
+ isEditing: isEditing(),
41
+ isBrowser: isBrowser(),
42
+ isServer: !isBrowser(),
43
+ getUserAttributes: () => getUserAttributes()
44
+ });
45
+ export const parseCode = (code: string, {
46
+ isExpression = true
47
+ }: Pick<EvaluatorArgs, 'isExpression'>) => {
48
+ // Be able to handle simple expressions like "state.foo" or "1 + 1"
49
+ // as well as full blocks like "var foo = "bar"; return foo"
50
+ const useReturn =
51
+ // we disable this for cases where we definitely don't want a return
52
+ isExpression && !(code.includes(';') || code.includes(' return ') || code.trim().startsWith('return '));
53
+ const useCode = useReturn ? `return (${code});` : code;
54
+ return useCode;
32
55
  }
@@ -1 +1 @@
1
- export { evaluator } from '../browser-runtime/index.js'
1
+ export { runInNode as evaluator } from './node-runtime.js'
@@ -0,0 +1,138 @@
1
+ import type { FunctionArguments, ExecutorArgs, BuilderGlobals } from '../helpers.js';
2
+ import { getFunctionArguments } from '../helpers.js';
3
+ import { safeDynamicRequire } from './safeDynamicRequire.js';
4
+ import { fastClone } from '../../fast-clone.js';
5
+ import { set } from '../../set.js';
6
+ const ivm: typeof import('isolated-vm') = safeDynamicRequire('isolated-vm');
7
+ const getSyncValName = (key: string) => `bldr_${key}_sync`;
8
+ const BUILDER_SET_STATE_NAME = 'BUILDER_SET_STATE';
9
+ const INJECTED_IVM_GLOBAL = 'BUILDER_IVM';
10
+
11
+ // Convert all argument references to proxies, and pass `copySync` method to target object, to return a copy of the original JS object
12
+ // https://github.com/laverdet/isolated-vm#referencecopysync
13
+ const REF_TO_PROXY_FN = `
14
+ var refToProxy = (obj) => {
15
+ if (typeof obj !== 'object' || obj === null) {
16
+ return obj;
17
+ }
18
+ return new Proxy({}, {
19
+ get(target, key) {
20
+ if (key === 'copySync') {
21
+ return () => obj.copySync();
22
+ }
23
+ const val = obj.getSync(key);
24
+ if (typeof val?.getSync === 'function') {
25
+ return refToProxy(val);
26
+ }
27
+ return val;
28
+ },
29
+ set(target, key, value) {
30
+ const v = typeof value === 'object' ? new ${INJECTED_IVM_GLOBAL}.Reference(value) : value;
31
+ obj.setSync(key, v);
32
+ ${BUILDER_SET_STATE_NAME}(key, value)
33
+ },
34
+ deleteProperty(target, key) {
35
+ obj.deleteSync(key);
36
+ }
37
+ })
38
+ }
39
+ `;
40
+ const processCode = ({
41
+ code,
42
+ args
43
+ }: {
44
+ code: string;
45
+ args: FunctionArguments;
46
+ }) => {
47
+ const fnArgs = args.map(([name]) => `var ${name} = refToProxy(${getSyncValName(name)}); `).join('');
48
+
49
+ // the output is stringified and parsed back to the parent isolate if needed (when it's an `object`)
50
+ return `
51
+ ${REF_TO_PROXY_FN}
52
+ ${fnArgs}
53
+ function theFunction() {
54
+ ${code}
55
+ }
56
+
57
+ let output = theFunction()
58
+
59
+ if (typeof output === 'object' && output !== null) {
60
+ output = JSON.stringify(output.copySync ? output.copySync() : output);
61
+ }
62
+
63
+ output;
64
+ `;
65
+ };
66
+ const getIsolateContext = () => {
67
+ // if (Builder.serverContext) {
68
+ // return Builder.serverContext;
69
+ // }
70
+ // Builder.setServerContext(isolate.createContextSync());
71
+ const isolate = new ivm.Isolate({
72
+ memoryLimit: 128
73
+ });
74
+ return isolate.createContextSync();
75
+ };
76
+ export const runInNode = ({
77
+ code,
78
+ builder,
79
+ context,
80
+ event,
81
+ localState,
82
+ rootSetState,
83
+ rootState
84
+ }: ExecutorArgs) => {
85
+ const state = fastClone({
86
+ ...rootState,
87
+ ...localState
88
+ });
89
+ const args = getFunctionArguments({
90
+ builder,
91
+ context,
92
+ event,
93
+ state
94
+ });
95
+ const isolateContext = getIsolateContext();
96
+ const jail = isolateContext.global;
97
+ // This makes the global object available in the context as `global`. We use `derefInto()` here
98
+ // because otherwise `global` would actually be a Reference{} object in the new isolate.
99
+ jail.setSync('global', jail.derefInto());
100
+
101
+ // We will create a basic `log` function for the new isolate to use.
102
+ jail.setSync('log', function (...logArgs: any[]) {
103
+ console.log(...logArgs);
104
+ });
105
+
106
+ /**
107
+ * Propagate state changes back to the reactive root state.
108
+ */
109
+ jail.setSync(BUILDER_SET_STATE_NAME, function (key: string, value: any) {
110
+ // mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
111
+ // like Qwik.
112
+ set(rootState, key, value);
113
+ // call the `rootSetState` function if it exists
114
+ rootSetState?.(rootState);
115
+ });
116
+ args.forEach(([key, arg]) => {
117
+ const val = typeof arg === 'object' ? new ivm.Reference(
118
+ // workaround: methods with default values for arguments is not being cloned over
119
+ key === 'builder' ? {
120
+ ...arg,
121
+ getUserAttributes: () => (arg as BuilderGlobals).getUserAttributes()
122
+ } : arg) : null;
123
+ jail.setSync(getSyncValName(key), val);
124
+ });
125
+ jail.setSync(INJECTED_IVM_GLOBAL, ivm);
126
+ const evalStr = processCode({
127
+ code,
128
+ args
129
+ });
130
+ const resultStr = isolateContext.evalSync(evalStr);
131
+ try {
132
+ // returning objects throw errors in isolated vm, so we stringify it and parse it back
133
+ const res = JSON.parse(resultStr);
134
+ return res;
135
+ } catch (_error: any) {
136
+ return resultStr;
137
+ }
138
+ }
@@ -0,0 +1,18 @@
1
+ const noop = () => null;
2
+
3
+ /**
4
+ * Allows us to require node-specific code safely within bundlers.
5
+ *
6
+ * https://github.com/laverdet/isolated-vm/issues/423
7
+ *
8
+ * NOTE: this code should only ever run in `node`, and never in an `edge` runtime.
9
+ * This is guaranteed by the fact that each SDK has separate bundles for each runtime.
10
+ * However, it might still end up running in the `browser` if the `node` bundle is loaded in the
11
+ * browser (which happens in many apps' dev mode, and certain other scenarios like Qwik's prod mode).
12
+ */
13
+ export let safeDynamicRequire: typeof require = ((noop as unknown) as typeof require);
14
+ try {
15
+ safeDynamicRequire = eval('require');
16
+ } catch (error) {
17
+ /* empty */
18
+ }
@@ -2,6 +2,7 @@ import { flatten } from '../../helpers/flatten.js';
2
2
  import { getBuilderSearchParamsFromWindow, normalizeSearchParams } from '../get-builder-search-params/index.js';
3
3
  import type { GetContentOptions } from './types.js';
4
4
  import { DEFAULT_API_VERSION } from '../../types/api-version.js';
5
+ const isPositiveNumber = (thing: unknown) => typeof thing === 'number' && !isNaN(thing) && thing >= 0;
5
6
  export const generateContentUrl = (options: GetContentOptions): URL => {
6
7
  let {
7
8
  noTraverse = false
@@ -15,7 +16,14 @@ export const generateContentUrl = (options: GetContentOptions): URL => {
15
16
  includeRefs = true,
16
17
  enrich,
17
18
  locale,
18
- apiVersion = DEFAULT_API_VERSION
19
+ apiVersion = DEFAULT_API_VERSION,
20
+ fields,
21
+ omit,
22
+ offset,
23
+ cacheSeconds,
24
+ staleCacheSeconds,
25
+ sort,
26
+ includeUnpublished
19
27
  } = options;
20
28
  if (!apiKey) {
21
29
  throw new Error('Missing API key');
@@ -29,6 +37,33 @@ export const generateContentUrl = (options: GetContentOptions): URL => {
29
37
  noTraverse = true;
30
38
  }
31
39
  const url = new URL(`https://cdn.builder.io/api/${apiVersion}/content/${model}?apiKey=${apiKey}&limit=${limit}&noTraverse=${noTraverse}&includeRefs=${includeRefs}${locale ? `&locale=${locale}` : ''}${enrich ? `&enrich=${enrich}` : ''}`);
40
+ url.searchParams.set('omit', omit || 'meta.componentsUsed');
41
+ if (fields) {
42
+ url.searchParams.set('fields', fields);
43
+ }
44
+ if (Number.isFinite(offset) && offset! > -1) {
45
+ url.searchParams.set('offset', String(Math.floor(offset!)));
46
+ }
47
+ if (typeof includeUnpublished === 'boolean') {
48
+ url.searchParams.set('includeUnpublished', String(includeUnpublished));
49
+ }
50
+ if (cacheSeconds && isPositiveNumber(cacheSeconds)) {
51
+ url.searchParams.set('cacheSeconds', String(cacheSeconds));
52
+ }
53
+ if (staleCacheSeconds && isPositiveNumber(staleCacheSeconds)) {
54
+ url.searchParams.set('staleCacheSeconds', String(staleCacheSeconds));
55
+ }
56
+ if (sort) {
57
+ const flattened = flatten({
58
+ sort
59
+ });
60
+ for (const key in flattened) {
61
+ url.searchParams.set(key, JSON.stringify((flattened as any)[key]));
62
+ }
63
+ }
64
+
65
+ // TODO: how to express 'offset' in the url - as direct queryparam or as flattened in options[key] ?
66
+
32
67
  const queryOptions = {
33
68
  ...getBuilderSearchParamsFromWindow(),
34
69
  ...normalizeSearchParams(options.options || {})