@builder.io/sdk-svelte 5.0.1 → 5.1.1

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 (45) hide show
  1. package/lib/browser/components/block/components/block-wrapper.svelte +6 -0
  2. package/lib/browser/components/block/components/interactive-element.svelte +6 -0
  3. package/lib/browser/constants/sdk-version.d.ts +1 -1
  4. package/lib/browser/constants/sdk-version.js +1 -1
  5. package/lib/browser/functions/evaluate/evaluate.d.ts +1 -1
  6. package/lib/browser/functions/evaluate/evaluate.js +2 -2
  7. package/lib/browser/functions/evaluate/helpers.d.ts +9 -1
  8. package/lib/browser/functions/evaluate/helpers.js +21 -2
  9. package/lib/browser/functions/evaluate/node-runtime/node-runtime.js +72 -67
  10. package/lib/browser/functions/get-block-actions-handler.d.ts +2 -0
  11. package/lib/browser/functions/get-block-actions-handler.js +2 -1
  12. package/lib/browser/functions/get-block-actions.d.ts +2 -0
  13. package/lib/edge/components/block/components/block-wrapper.svelte +6 -0
  14. package/lib/edge/components/block/components/interactive-element.svelte +6 -0
  15. package/lib/edge/constants/sdk-version.d.ts +1 -1
  16. package/lib/edge/constants/sdk-version.js +1 -1
  17. package/lib/edge/functions/evaluate/evaluate.d.ts +1 -1
  18. package/lib/edge/functions/evaluate/evaluate.js +2 -2
  19. package/lib/edge/functions/evaluate/helpers.d.ts +9 -1
  20. package/lib/edge/functions/evaluate/helpers.js +21 -2
  21. package/lib/edge/functions/evaluate/node-runtime/node-runtime.js +72 -67
  22. package/lib/edge/functions/get-block-actions-handler.d.ts +2 -0
  23. package/lib/edge/functions/get-block-actions-handler.js +2 -1
  24. package/lib/edge/functions/get-block-actions.d.ts +2 -0
  25. package/lib/node/components/block/components/block-wrapper.svelte +6 -0
  26. package/lib/node/components/block/components/interactive-element.svelte +6 -0
  27. package/lib/node/constants/sdk-version.d.ts +1 -1
  28. package/lib/node/constants/sdk-version.js +1 -1
  29. package/lib/node/functions/evaluate/evaluate.d.ts +1 -1
  30. package/lib/node/functions/evaluate/evaluate.js +2 -2
  31. package/lib/node/functions/evaluate/helpers.d.ts +9 -1
  32. package/lib/node/functions/evaluate/helpers.js +21 -2
  33. package/lib/node/functions/evaluate/node-runtime/node-runtime.js +72 -67
  34. package/lib/node/functions/get-block-actions-handler.d.ts +2 -0
  35. package/lib/node/functions/get-block-actions-handler.js +2 -1
  36. package/lib/node/functions/get-block-actions.d.ts +2 -0
  37. package/package.json +1 -1
  38. package/src/components/block/components/block-wrapper.svelte +6 -0
  39. package/src/components/block/components/interactive-element.svelte +6 -0
  40. package/src/constants/sdk-version.ts +1 -1
  41. package/src/functions/evaluate/evaluate.ts +3 -2
  42. package/src/functions/evaluate/helpers.ts +29 -2
  43. package/src/functions/evaluate/node-runtime/node-runtime.ts +73 -69
  44. package/src/functions/get-block-actions-handler.ts +4 -1
  45. package/src/functions/get-block-actions.ts +2 -0
@@ -24,5 +24,11 @@ export let context;
24
24
  localState: $context.localState,
25
25
  context: $context.context,
26
26
  stripPrefix: true,
27
+ trackingContext: {
28
+ apiKey: $context.apiKey,
29
+ canTrack: $context.canTrack ?? true,
30
+ contentId: $context.content?.id,
31
+ variationId: $context.content?.testVariationId,
32
+ },
27
33
  })}><slot /></DynamicRenderer
28
34
  >
@@ -24,6 +24,12 @@ $: attributes = () => {
24
24
  rootSetState: $context.rootSetState,
25
25
  localState: $context.localState,
26
26
  context: $context.context,
27
+ trackingContext: {
28
+ apiKey: $context.apiKey,
29
+ canTrack: $context.canTrack ?? true,
30
+ contentId: $context.content?.id,
31
+ variationId: $context.content?.testVariationId,
32
+ },
27
33
  }),
28
34
  }
29
35
  : {};
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "5.0.1";
1
+ export declare const SDK_VERSION = "5.1.1";
@@ -1 +1 @@
1
- export const SDK_VERSION = "5.0.1";
1
+ export const SDK_VERSION = "5.1.1";
@@ -1,5 +1,5 @@
1
1
  import type { EvaluatorArgs } from './helpers';
2
2
  type EvalValue = unknown;
3
3
  export declare const getSimpleExpressionGetPath: (code: string) => string | undefined;
4
- export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression }: EvaluatorArgs): EvalValue;
4
+ export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression, trackingContext }: EvaluatorArgs): EvalValue;
5
5
  export {};
@@ -16,7 +16,7 @@ const VIRTUAL_INDEX_REGEX = /(\s)*var(\s)+_virtual_index(\s)*=(\s)*state(?<getPa
16
16
  export const getSimpleExpressionGetPath = (code) => {
17
17
  return STATE_GETTER_REGEX.exec(code.trim())?.groups?.getPath?.slice(1) || VIRTUAL_INDEX_REGEX.exec(code.trim())?.groups?.getPath?.slice(1);
18
18
  };
19
- export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true }) {
19
+ export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true, trackingContext }) {
20
20
  if (code.trim() === '') {
21
21
  return undefined;
22
22
  }
@@ -37,7 +37,7 @@ export function evaluate({ code, context, localState, rootState, rootSetState, e
37
37
  code: parseCode(code, {
38
38
  isExpression
39
39
  }),
40
- builder: getBuilderGlobals(),
40
+ builder: getBuilderGlobals(trackingContext),
41
41
  context,
42
42
  event,
43
43
  rootSetState,
@@ -1,14 +1,22 @@
1
1
  import type { BuilderContextInterface, BuilderRenderState } from '../../context/types';
2
2
  import { getUserAttributes } from '../track/helpers';
3
+ export type TrackingContext = {
4
+ apiKey: string | null;
5
+ canTrack?: boolean;
6
+ contentId?: string;
7
+ variationId?: string;
8
+ };
3
9
  export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
4
10
  event?: Event;
5
11
  isExpression?: boolean;
12
+ trackingContext?: TrackingContext;
6
13
  };
7
14
  export type BuilderGlobals = {
8
15
  isEditing: boolean | undefined;
9
16
  isBrowser: boolean | undefined;
10
17
  isServer: boolean | undefined;
11
18
  getUserAttributes: typeof getUserAttributes;
19
+ trackConversion: (amount?: number, customProperties?: Record<string, any>) => void;
12
20
  };
13
21
  export type ExecutorArgs = Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'> & {
14
22
  code: string;
@@ -20,7 +28,7 @@ export type FunctionArguments = ReturnType<typeof getFunctionArguments>;
20
28
  export declare const getFunctionArguments: ({ builder, context, event, state }: Pick<ExecutorArgs, "context" | "builder" | "event"> & {
21
29
  state: BuilderRenderState;
22
30
  }) => [string, Event | BuilderRenderState | import("../../context/types").BuilderRenderContext | BuilderGlobals | undefined][];
23
- export declare const getBuilderGlobals: () => BuilderGlobals;
31
+ export declare const getBuilderGlobals: (trackingContext?: TrackingContext) => BuilderGlobals;
24
32
  export declare const parseCode: (code: string, { isExpression }: Pick<EvaluatorArgs, 'isExpression'>) => string;
25
33
  export declare function flattenState({ rootState, localState, rootSetState }: {
26
34
  rootState: Record<string | symbol, any>;
@@ -1,6 +1,7 @@
1
1
  import { isBrowser } from '../is-browser';
2
2
  import { isEditing } from '../is-editing';
3
3
  import { getUserAttributes } from '../track/helpers';
4
+ import { _track } from '../track/index';
4
5
  export const getFunctionArguments = ({ builder, context, event, state }) => {
5
6
  return Object.entries({
6
7
  state,
@@ -11,11 +12,29 @@ export const getFunctionArguments = ({ builder, context, event, state }) => {
11
12
  event
12
13
  });
13
14
  };
14
- export const getBuilderGlobals = () => ({
15
+ export const getBuilderGlobals = (trackingContext) => ({
15
16
  isEditing: isEditing(),
16
17
  isBrowser: isBrowser(),
17
18
  isServer: !isBrowser(),
18
- getUserAttributes: () => getUserAttributes()
19
+ getUserAttributes: () => getUserAttributes(),
20
+ trackConversion: (amount, customProperties) => {
21
+ if (!trackingContext?.apiKey || trackingContext?.canTrack === false) {
22
+ return;
23
+ }
24
+ _track({
25
+ type: 'conversion',
26
+ apiKey: trackingContext.apiKey,
27
+ canTrack: trackingContext.canTrack ?? true,
28
+ contentId: trackingContext.contentId,
29
+ variationId: trackingContext.variationId !== trackingContext.contentId ? trackingContext.variationId : undefined,
30
+ metadata: {
31
+ ...(customProperties || {}),
32
+ ...(amount !== undefined ? {
33
+ amount
34
+ } : {})
35
+ }
36
+ });
37
+ }
19
38
  });
20
39
  export const parseCode = (code, { isExpression = true }) => {
21
40
  // Be able to handle simple expressions like "state.foo" or "1 + 1"
@@ -56,17 +56,23 @@ if (typeof output === 'object' && output !== null) {
56
56
  `;
57
57
  };
58
58
  let IVM_INSTANCE = null;
59
- let IVM_CONTEXT = null;
59
+ // Create fresh isolates per request to prevent memory leaks
60
+ let IVM_OPTIONS = {
61
+ memoryLimit: 128
62
+ };
60
63
  /**
61
64
  * Set the `isolated-vm` instance to be used by the node runtime.
62
65
  * This is useful for environments that are not able to rely on our
63
66
  * `safeDynamicRequire` trick to import the `isolated-vm` package.
64
67
  */
65
- export const setIvm = (ivm, options = {}) => {
68
+ export const setIvm = (ivm, options) => {
66
69
  if (IVM_INSTANCE)
67
70
  return;
68
71
  IVM_INSTANCE = ivm;
69
- setIsolateContext(options);
72
+ // Store options to be used per-request in runInNode
73
+ if (options) {
74
+ IVM_OPTIONS = options;
75
+ }
70
76
  };
71
77
  // only mention the script for SDKs that have it.
72
78
  const SHOULD_MENTION_INITIALIZE_SCRIPT = SDK_NAME === '@builder.io/sdk-react-nextjs' || SDK_NAME === '@builder.io/sdk-react' || SDK_NAME === '@builder.io/sdk-qwik' || SDK_NAME === '@builder.io/sdk-vue';
@@ -91,73 +97,72 @@ const getIvm = () => {
91
97
  For more information, visit https://builder.io/c/docs/integration-tips#enabling-data-bindings-in-node-environments`;
92
98
  throw new Error(ERROR_MESSAGE);
93
99
  };
94
- function setIsolateContext(options = {
95
- memoryLimit: 128
96
- }) {
97
- if (IVM_CONTEXT)
98
- return IVM_CONTEXT;
99
- const ivm = getIvm();
100
- const isolate = new ivm.Isolate(options);
101
- const context = isolate.createContextSync();
102
- const jail = context.global;
103
- // This makes the global object available in the context as `global`. We use `derefInto()` here
104
- // because otherwise `global` would actually be a Reference{} object in the new isolate.
105
- jail.setSync('global', jail.derefInto());
106
- // We will create a basic `log` function for the new isolate to use.
107
- jail.setSync('log', function (...logArgs) {
108
- console.log(...logArgs);
109
- });
110
- jail.setSync(INJECTED_IVM_GLOBAL, ivm);
111
- IVM_CONTEXT = context;
112
- return context;
113
- }
114
- const getIsolateContext = () => {
115
- return setIsolateContext();
116
- };
117
100
  export const runInNode = ({ code, builder, context, event, localState, rootSetState, rootState }) => {
118
101
  const ivm = getIvm();
119
- const state = fastClone({
120
- ...rootState,
121
- ...localState
122
- });
123
- const args = getFunctionArguments({
124
- builder,
125
- context,
126
- event,
127
- state
128
- });
129
- const isolateContext = getIsolateContext();
130
- const jail = isolateContext.global;
131
- /**
132
- * Propagate state changes back to the reactive root state.
133
- */
134
- jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
135
- // mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
136
- // like Qwik.
137
- set(rootState, key, value);
138
- // call the `rootSetState` function if it exists
139
- rootSetState?.(rootState);
140
- });
141
- args.forEach(([key, arg]) => {
142
- const val = typeof arg === 'object' ? new ivm.Reference(
143
- // workaround: methods with default values for arguments is not being cloned over
144
- key === 'builder' ? {
145
- ...arg,
146
- getUserAttributes: () => arg.getUserAttributes()
147
- } : arg) : null;
148
- jail.setSync(getSyncValName(key), val);
149
- });
150
- const evalStr = processCode({
151
- code,
152
- args
153
- });
154
- const resultStr = isolateContext.evalClosureSync(evalStr);
102
+ // Use stored options from setIvm, or default to { memoryLimit: 128 }
103
+ let isolate;
155
104
  try {
156
- // returning objects throw errors in isolated vm, so we stringify it and parse it back
157
- const res = JSON.parse(resultStr);
158
- return res;
105
+ isolate = new ivm.Isolate(IVM_OPTIONS);
106
+ const isolateContext = isolate.createContextSync();
107
+ const jail = isolateContext.global;
108
+ // Setup the isolate
109
+ jail.setSync('global', jail.derefInto());
110
+ jail.setSync('log', function (...logArgs) {
111
+ console.log(...logArgs);
112
+ });
113
+ jail.setSync(INJECTED_IVM_GLOBAL, ivm);
114
+ const state = fastClone({
115
+ ...rootState,
116
+ ...localState
117
+ });
118
+ const args = getFunctionArguments({
119
+ builder,
120
+ context,
121
+ event,
122
+ state
123
+ });
124
+ /**
125
+ * Propagate state changes back to the reactive root state.
126
+ */
127
+ jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
128
+ // mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
129
+ // like Qwik.
130
+ set(rootState, key, value);
131
+ // call the `rootSetState` function if it exists
132
+ rootSetState?.(rootState);
133
+ });
134
+ args.forEach(([key, arg]) => {
135
+ const val = typeof arg === 'object' ? new ivm.Reference(
136
+ // workaround: methods with default values for arguments is not being cloned over
137
+ key === 'builder' ? {
138
+ ...arg,
139
+ getUserAttributes: () => arg.getUserAttributes()
140
+ } : arg) : null;
141
+ jail.setSync(getSyncValName(key), val);
142
+ });
143
+ const evalStr = processCode({
144
+ code,
145
+ args
146
+ });
147
+ const resultStr = isolateContext.evalClosureSync(evalStr);
148
+ try {
149
+ // returning objects throw errors in isolated vm, so we stringify it and parse it back
150
+ const res = JSON.parse(resultStr);
151
+ return res;
152
+ }
153
+ catch (_error) {
154
+ return resultStr;
155
+ }
159
156
  }
160
- catch (_error) {
161
- return resultStr;
157
+ finally {
158
+ // Destroy the entire VM, this frees ALL memory at the C++ level.
159
+ if (isolate) {
160
+ try {
161
+ isolate.dispose();
162
+ }
163
+ catch (e) {
164
+ // Ignore disposal errors
165
+ }
166
+ }
162
167
  }
163
168
  };
@@ -1,7 +1,9 @@
1
1
  import type { BuilderContextInterface } from '../context/types';
2
2
  import type { BuilderBlock } from '../types/builder-block';
3
+ import type { TrackingContext } from './evaluate/helpers';
3
4
  type Options = {
4
5
  block: BuilderBlock;
6
+ trackingContext?: TrackingContext;
5
7
  } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>;
6
8
  type EventHandler = (event: Event) => any;
7
9
  export declare const createEventHandler: (value: string, options: Options) => EventHandler;
@@ -6,5 +6,6 @@ export const createEventHandler = (value, options) => event => evaluate({
6
6
  rootState: options.rootState,
7
7
  rootSetState: options.rootSetState,
8
8
  event,
9
- isExpression: false
9
+ isExpression: false,
10
+ trackingContext: options.trackingContext
10
11
  });
@@ -1,10 +1,12 @@
1
1
  import type { BuilderContextInterface } from '../context/types';
2
2
  import type { BuilderBlock } from '../types/builder-block';
3
+ import type { TrackingContext } from './evaluate/helpers';
3
4
  type Actions = {
4
5
  [index: string]: (event: Event) => any;
5
6
  };
6
7
  export declare function getBlockActions(options: {
7
8
  block: BuilderBlock;
8
9
  stripPrefix?: boolean;
10
+ trackingContext?: TrackingContext;
9
11
  } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): Actions;
10
12
  export {};
@@ -24,5 +24,11 @@ export let context;
24
24
  localState: $context.localState,
25
25
  context: $context.context,
26
26
  stripPrefix: true,
27
+ trackingContext: {
28
+ apiKey: $context.apiKey,
29
+ canTrack: $context.canTrack ?? true,
30
+ contentId: $context.content?.id,
31
+ variationId: $context.content?.testVariationId,
32
+ },
27
33
  })}><slot /></DynamicRenderer
28
34
  >
@@ -24,6 +24,12 @@ $: attributes = () => {
24
24
  rootSetState: $context.rootSetState,
25
25
  localState: $context.localState,
26
26
  context: $context.context,
27
+ trackingContext: {
28
+ apiKey: $context.apiKey,
29
+ canTrack: $context.canTrack ?? true,
30
+ contentId: $context.content?.id,
31
+ variationId: $context.content?.testVariationId,
32
+ },
27
33
  }),
28
34
  }
29
35
  : {};
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "5.0.1";
1
+ export declare const SDK_VERSION = "5.1.1";
@@ -1 +1 @@
1
- export const SDK_VERSION = "5.0.1";
1
+ export const SDK_VERSION = "5.1.1";
@@ -1,5 +1,5 @@
1
1
  import type { EvaluatorArgs } from './helpers';
2
2
  type EvalValue = unknown;
3
3
  export declare const getSimpleExpressionGetPath: (code: string) => string | undefined;
4
- export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression }: EvaluatorArgs): EvalValue;
4
+ export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression, trackingContext }: EvaluatorArgs): EvalValue;
5
5
  export {};
@@ -16,7 +16,7 @@ const VIRTUAL_INDEX_REGEX = /(\s)*var(\s)+_virtual_index(\s)*=(\s)*state(?<getPa
16
16
  export const getSimpleExpressionGetPath = (code) => {
17
17
  return STATE_GETTER_REGEX.exec(code.trim())?.groups?.getPath?.slice(1) || VIRTUAL_INDEX_REGEX.exec(code.trim())?.groups?.getPath?.slice(1);
18
18
  };
19
- export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true }) {
19
+ export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true, trackingContext }) {
20
20
  if (code.trim() === '') {
21
21
  return undefined;
22
22
  }
@@ -37,7 +37,7 @@ export function evaluate({ code, context, localState, rootState, rootSetState, e
37
37
  code: parseCode(code, {
38
38
  isExpression
39
39
  }),
40
- builder: getBuilderGlobals(),
40
+ builder: getBuilderGlobals(trackingContext),
41
41
  context,
42
42
  event,
43
43
  rootSetState,
@@ -1,14 +1,22 @@
1
1
  import type { BuilderContextInterface, BuilderRenderState } from '../../context/types';
2
2
  import { getUserAttributes } from '../track/helpers';
3
+ export type TrackingContext = {
4
+ apiKey: string | null;
5
+ canTrack?: boolean;
6
+ contentId?: string;
7
+ variationId?: string;
8
+ };
3
9
  export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
4
10
  event?: Event;
5
11
  isExpression?: boolean;
12
+ trackingContext?: TrackingContext;
6
13
  };
7
14
  export type BuilderGlobals = {
8
15
  isEditing: boolean | undefined;
9
16
  isBrowser: boolean | undefined;
10
17
  isServer: boolean | undefined;
11
18
  getUserAttributes: typeof getUserAttributes;
19
+ trackConversion: (amount?: number, customProperties?: Record<string, any>) => void;
12
20
  };
13
21
  export type ExecutorArgs = Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'> & {
14
22
  code: string;
@@ -20,7 +28,7 @@ export type FunctionArguments = ReturnType<typeof getFunctionArguments>;
20
28
  export declare const getFunctionArguments: ({ builder, context, event, state }: Pick<ExecutorArgs, "context" | "builder" | "event"> & {
21
29
  state: BuilderRenderState;
22
30
  }) => [string, Event | BuilderRenderState | import("../../context/types").BuilderRenderContext | BuilderGlobals | undefined][];
23
- export declare const getBuilderGlobals: () => BuilderGlobals;
31
+ export declare const getBuilderGlobals: (trackingContext?: TrackingContext) => BuilderGlobals;
24
32
  export declare const parseCode: (code: string, { isExpression }: Pick<EvaluatorArgs, 'isExpression'>) => string;
25
33
  export declare function flattenState({ rootState, localState, rootSetState }: {
26
34
  rootState: Record<string | symbol, any>;
@@ -1,6 +1,7 @@
1
1
  import { isBrowser } from '../is-browser';
2
2
  import { isEditing } from '../is-editing';
3
3
  import { getUserAttributes } from '../track/helpers';
4
+ import { _track } from '../track/index';
4
5
  export const getFunctionArguments = ({ builder, context, event, state }) => {
5
6
  return Object.entries({
6
7
  state,
@@ -11,11 +12,29 @@ export const getFunctionArguments = ({ builder, context, event, state }) => {
11
12
  event
12
13
  });
13
14
  };
14
- export const getBuilderGlobals = () => ({
15
+ export const getBuilderGlobals = (trackingContext) => ({
15
16
  isEditing: isEditing(),
16
17
  isBrowser: isBrowser(),
17
18
  isServer: !isBrowser(),
18
- getUserAttributes: () => getUserAttributes()
19
+ getUserAttributes: () => getUserAttributes(),
20
+ trackConversion: (amount, customProperties) => {
21
+ if (!trackingContext?.apiKey || trackingContext?.canTrack === false) {
22
+ return;
23
+ }
24
+ _track({
25
+ type: 'conversion',
26
+ apiKey: trackingContext.apiKey,
27
+ canTrack: trackingContext.canTrack ?? true,
28
+ contentId: trackingContext.contentId,
29
+ variationId: trackingContext.variationId !== trackingContext.contentId ? trackingContext.variationId : undefined,
30
+ metadata: {
31
+ ...(customProperties || {}),
32
+ ...(amount !== undefined ? {
33
+ amount
34
+ } : {})
35
+ }
36
+ });
37
+ }
19
38
  });
20
39
  export const parseCode = (code, { isExpression = true }) => {
21
40
  // Be able to handle simple expressions like "state.foo" or "1 + 1"
@@ -56,17 +56,23 @@ if (typeof output === 'object' && output !== null) {
56
56
  `;
57
57
  };
58
58
  let IVM_INSTANCE = null;
59
- let IVM_CONTEXT = null;
59
+ // Create fresh isolates per request to prevent memory leaks
60
+ let IVM_OPTIONS = {
61
+ memoryLimit: 128
62
+ };
60
63
  /**
61
64
  * Set the `isolated-vm` instance to be used by the node runtime.
62
65
  * This is useful for environments that are not able to rely on our
63
66
  * `safeDynamicRequire` trick to import the `isolated-vm` package.
64
67
  */
65
- export const setIvm = (ivm, options = {}) => {
68
+ export const setIvm = (ivm, options) => {
66
69
  if (IVM_INSTANCE)
67
70
  return;
68
71
  IVM_INSTANCE = ivm;
69
- setIsolateContext(options);
72
+ // Store options to be used per-request in runInNode
73
+ if (options) {
74
+ IVM_OPTIONS = options;
75
+ }
70
76
  };
71
77
  // only mention the script for SDKs that have it.
72
78
  const SHOULD_MENTION_INITIALIZE_SCRIPT = SDK_NAME === '@builder.io/sdk-react-nextjs' || SDK_NAME === '@builder.io/sdk-react' || SDK_NAME === '@builder.io/sdk-qwik' || SDK_NAME === '@builder.io/sdk-vue';
@@ -91,73 +97,72 @@ const getIvm = () => {
91
97
  For more information, visit https://builder.io/c/docs/integration-tips#enabling-data-bindings-in-node-environments`;
92
98
  throw new Error(ERROR_MESSAGE);
93
99
  };
94
- function setIsolateContext(options = {
95
- memoryLimit: 128
96
- }) {
97
- if (IVM_CONTEXT)
98
- return IVM_CONTEXT;
99
- const ivm = getIvm();
100
- const isolate = new ivm.Isolate(options);
101
- const context = isolate.createContextSync();
102
- const jail = context.global;
103
- // This makes the global object available in the context as `global`. We use `derefInto()` here
104
- // because otherwise `global` would actually be a Reference{} object in the new isolate.
105
- jail.setSync('global', jail.derefInto());
106
- // We will create a basic `log` function for the new isolate to use.
107
- jail.setSync('log', function (...logArgs) {
108
- console.log(...logArgs);
109
- });
110
- jail.setSync(INJECTED_IVM_GLOBAL, ivm);
111
- IVM_CONTEXT = context;
112
- return context;
113
- }
114
- const getIsolateContext = () => {
115
- return setIsolateContext();
116
- };
117
100
  export const runInNode = ({ code, builder, context, event, localState, rootSetState, rootState }) => {
118
101
  const ivm = getIvm();
119
- const state = fastClone({
120
- ...rootState,
121
- ...localState
122
- });
123
- const args = getFunctionArguments({
124
- builder,
125
- context,
126
- event,
127
- state
128
- });
129
- const isolateContext = getIsolateContext();
130
- const jail = isolateContext.global;
131
- /**
132
- * Propagate state changes back to the reactive root state.
133
- */
134
- jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
135
- // mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
136
- // like Qwik.
137
- set(rootState, key, value);
138
- // call the `rootSetState` function if it exists
139
- rootSetState?.(rootState);
140
- });
141
- args.forEach(([key, arg]) => {
142
- const val = typeof arg === 'object' ? new ivm.Reference(
143
- // workaround: methods with default values for arguments is not being cloned over
144
- key === 'builder' ? {
145
- ...arg,
146
- getUserAttributes: () => arg.getUserAttributes()
147
- } : arg) : null;
148
- jail.setSync(getSyncValName(key), val);
149
- });
150
- const evalStr = processCode({
151
- code,
152
- args
153
- });
154
- const resultStr = isolateContext.evalClosureSync(evalStr);
102
+ // Use stored options from setIvm, or default to { memoryLimit: 128 }
103
+ let isolate;
155
104
  try {
156
- // returning objects throw errors in isolated vm, so we stringify it and parse it back
157
- const res = JSON.parse(resultStr);
158
- return res;
105
+ isolate = new ivm.Isolate(IVM_OPTIONS);
106
+ const isolateContext = isolate.createContextSync();
107
+ const jail = isolateContext.global;
108
+ // Setup the isolate
109
+ jail.setSync('global', jail.derefInto());
110
+ jail.setSync('log', function (...logArgs) {
111
+ console.log(...logArgs);
112
+ });
113
+ jail.setSync(INJECTED_IVM_GLOBAL, ivm);
114
+ const state = fastClone({
115
+ ...rootState,
116
+ ...localState
117
+ });
118
+ const args = getFunctionArguments({
119
+ builder,
120
+ context,
121
+ event,
122
+ state
123
+ });
124
+ /**
125
+ * Propagate state changes back to the reactive root state.
126
+ */
127
+ jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
128
+ // mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
129
+ // like Qwik.
130
+ set(rootState, key, value);
131
+ // call the `rootSetState` function if it exists
132
+ rootSetState?.(rootState);
133
+ });
134
+ args.forEach(([key, arg]) => {
135
+ const val = typeof arg === 'object' ? new ivm.Reference(
136
+ // workaround: methods with default values for arguments is not being cloned over
137
+ key === 'builder' ? {
138
+ ...arg,
139
+ getUserAttributes: () => arg.getUserAttributes()
140
+ } : arg) : null;
141
+ jail.setSync(getSyncValName(key), val);
142
+ });
143
+ const evalStr = processCode({
144
+ code,
145
+ args
146
+ });
147
+ const resultStr = isolateContext.evalClosureSync(evalStr);
148
+ try {
149
+ // returning objects throw errors in isolated vm, so we stringify it and parse it back
150
+ const res = JSON.parse(resultStr);
151
+ return res;
152
+ }
153
+ catch (_error) {
154
+ return resultStr;
155
+ }
159
156
  }
160
- catch (_error) {
161
- return resultStr;
157
+ finally {
158
+ // Destroy the entire VM, this frees ALL memory at the C++ level.
159
+ if (isolate) {
160
+ try {
161
+ isolate.dispose();
162
+ }
163
+ catch (e) {
164
+ // Ignore disposal errors
165
+ }
166
+ }
162
167
  }
163
168
  };
@@ -1,7 +1,9 @@
1
1
  import type { BuilderContextInterface } from '../context/types';
2
2
  import type { BuilderBlock } from '../types/builder-block';
3
+ import type { TrackingContext } from './evaluate/helpers';
3
4
  type Options = {
4
5
  block: BuilderBlock;
6
+ trackingContext?: TrackingContext;
5
7
  } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>;
6
8
  type EventHandler = (event: Event) => any;
7
9
  export declare const createEventHandler: (value: string, options: Options) => EventHandler;
@@ -6,5 +6,6 @@ export const createEventHandler = (value, options) => event => evaluate({
6
6
  rootState: options.rootState,
7
7
  rootSetState: options.rootSetState,
8
8
  event,
9
- isExpression: false
9
+ isExpression: false,
10
+ trackingContext: options.trackingContext
10
11
  });
@@ -1,10 +1,12 @@
1
1
  import type { BuilderContextInterface } from '../context/types';
2
2
  import type { BuilderBlock } from '../types/builder-block';
3
+ import type { TrackingContext } from './evaluate/helpers';
3
4
  type Actions = {
4
5
  [index: string]: (event: Event) => any;
5
6
  };
6
7
  export declare function getBlockActions(options: {
7
8
  block: BuilderBlock;
8
9
  stripPrefix?: boolean;
10
+ trackingContext?: TrackingContext;
9
11
  } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): Actions;
10
12
  export {};