diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 97fe75ad92..a4dcd70afd 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1154,7 +1154,7 @@ Cypress.Commands.add("ValidatePaginationInputDataV2", () => { Cypress.Commands.add("CheckForPageSaveError", () => { cy.get("body").then(($ele) => { if ($ele.find(commonlocators.saveStatusError).length) { - cy.reload() + cy.reload(); } }); }); diff --git a/app/client/src/actions/evaluationActions.ts b/app/client/src/actions/evaluationActions.ts index aca42ec15b..1b90227884 100644 --- a/app/client/src/actions/evaluationActions.ts +++ b/app/client/src/actions/evaluationActions.ts @@ -65,8 +65,7 @@ export const EVALUATE_REDUX_ACTIONS = [ // App Data ReduxActionTypes.SET_APP_MODE, ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS, - ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE, - ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE, + ReduxActionTypes.UPDATE_APP_STORE, ReduxActionTypes.SET_USER_CURRENT_GEO_LOCATION, // Widgets ReduxActionTypes.UPDATE_LAYOUT, diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index 77310045ea..4262e4200c 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -24,7 +24,6 @@ import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsRe import { GenerateTemplatePageRequest } from "api/PageApi"; import { ENTITY_TYPE } from "entities/AppsmithConsole"; import { Replayable } from "entities/Replay/ReplayEntity/ReplayEditor"; -import { StoreValueActionDescription } from "@appsmith/entities/DataTree/actionTriggers"; export interface FetchPageListPayload { applicationId: string; @@ -348,30 +347,12 @@ export const setAppMode = (payload: APP_MODE): ReduxAction => { }; }; -export const updateAppStoreEvaluated = ( - storeValueAction?: StoreValueActionDescription["payload"], -) => ({ - type: ReduxActionTypes.UPDATE_APP_STORE_EVALUATED, - payload: storeValueAction, -}); - -export const updateAppTransientStore = ( +export const updateAppStore = ( payload: Record, - storeValueAction?: StoreValueActionDescription["payload"], -): EvaluationReduxAction> => ({ - type: ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE, - payload, - postEvalActions: [updateAppStoreEvaluated(storeValueAction)], -}); - -export const updateAppPersistentStore = ( - payload: Record, - storeValueAction?: StoreValueActionDescription["payload"], ): EvaluationReduxAction> => { return { - type: ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE, + type: ReduxActionTypes.UPDATE_APP_STORE, payload, - postEvalActions: [updateAppStoreEvaluated(storeValueAction)], }; }; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 102c0b61ac..5ff049dd09 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -477,9 +477,7 @@ export const ReduxActionTypes = { TOGGLE_PROPERTY_PANE_WIDGET_NAME_EDIT: "TOGGLE_PROPERTY_PANE_WIDGET_NAME_EDIT", SET_PROPERTY_PANE_WIDTH: "SET_PROPERTY_PANE_WIDTH", - UPDATE_APP_PERSISTENT_STORE: "UPDATE_APP_PERSISTENT_STORE", - UPDATE_APP_TRANSIENT_STORE: "UPDATE_APP_TRANSIENT_STORE", - UPDATE_APP_STORE_EVALUATED: "UPDATE_APP_STORE_EVALUATED", + UPDATE_APP_STORE: "UPDATE_APP_STORE", SET_ACTION_TO_EXECUTE_ON_PAGELOAD: "SET_ACTION_TO_EXECUTE_ON_PAGELOAD", TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS: "TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS", diff --git a/app/client/src/ce/entities/DataTree/actionTriggers.ts b/app/client/src/ce/entities/DataTree/actionTriggers.ts index 4e60d198da..0cbdf552f5 100644 --- a/app/client/src/ce/entities/DataTree/actionTriggers.ts +++ b/app/client/src/ce/entities/DataTree/actionTriggers.ts @@ -106,7 +106,6 @@ export type StoreValueActionDescription = ActionDescriptionInterface< key: string; value: string; persist: boolean; - uniqueActionRequestId: string; }, "STORE_VALUE" >; diff --git a/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts b/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts index 9fd13baee8..868cb91863 100644 --- a/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts +++ b/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts @@ -16,10 +16,6 @@ import { setAppVersionOnWorkerSaga, } from "sagas/EvaluationsSaga"; import navigateActionSaga from "sagas/ActionExecution/NavigateActionSaga"; -import storeValueLocally, { - clearLocalStore, - removeLocalValue, -} from "sagas/ActionExecution/StoreActionSaga"; import downloadSaga from "sagas/ActionExecution/DownloadActionSaga"; import copySaga from "sagas/ActionExecution/CopyActionSaga"; import resetWidgetActionSaga from "sagas/ActionExecution/ResetWidgetActionSaga"; @@ -93,15 +89,6 @@ export function* executeActionTriggers( case "CLOSE_MODAL": yield call(closeModalSaga, trigger); break; - case "STORE_VALUE": - yield call(storeValueLocally, trigger.payload); - break; - case "REMOVE_VALUE": - yield call(removeLocalValue, trigger.payload); - break; - case "CLEAR_STORE": - yield call(clearLocalStore); - break; case "DOWNLOAD": yield call(downloadSaga, trigger.payload); break; diff --git a/app/client/src/ce/workers/Evaluation/Actions.ts b/app/client/src/ce/workers/Evaluation/Actions.ts index f365cad44b..2aed33c157 100644 --- a/app/client/src/ce/workers/Evaluation/Actions.ts +++ b/app/client/src/ce/workers/Evaluation/Actions.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-types */ import { DataTree, DataTreeEntity } from "entities/DataTree/dataTreeFactory"; -import { set } from "lodash"; +import set from "lodash/set"; import { ActionDescription, ActionTriggerFunctionNames, @@ -10,12 +10,12 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { isAction, isAppsmithEntity, isTrueObject } from "./evaluationUtils"; import { EvalContext } from "workers/Evaluation/evaluate"; import { ActionCalledInSyncFieldError } from "workers/Evaluation/errorModifier"; +import { initStoreFns } from "workers/Evaluation/fns/storeFns"; import { ActionDescriptionWithExecutionType, ActionDispatcherWithExecutionType, PLATFORM_FUNCTIONS, } from "@appsmith/workers/Evaluation/PlatformFunctions"; - declare global { /** All identifiers added to the worker global scope should also * be included in the DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS in @@ -226,8 +226,14 @@ export const addDataTreeToContext = (args: { export const addPlatformFunctionsToEvalContext = (context: any) => { for (const [funcName, fn] of platformFunctionEntries) { - context[funcName] = pusher.bind({}, fn); + Object.defineProperty(context, funcName, { + value: pusher.bind({}, fn), + enumerable: false, + writable: true, + configurable: true, + }); } + initStoreFns(context); }; export const getAllAsyncFunctions = (dataTree: DataTree) => { @@ -242,7 +248,7 @@ export const getAllAsyncFunctions = (dataTree: DataTree) => { } } - for (const [name] of platformFunctionEntries) { + for (const name of Object.values(ActionTriggerFunctionNames)) { asyncFunctionNameMap[name] = true; } diff --git a/app/client/src/ce/workers/Evaluation/evalWorkerActions.ts b/app/client/src/ce/workers/Evaluation/evalWorkerActions.ts index 248400e34f..5e024c274d 100644 --- a/app/client/src/ce/workers/Evaluation/evalWorkerActions.ts +++ b/app/client/src/ce/workers/Evaluation/evalWorkerActions.ts @@ -26,9 +26,10 @@ export const EVAL_WORKER_ACTIONS = { ...EVAL_WORKER_ASYNC_ACTION, }; -export const MAIN_THREAD_ACTION = { - PROCESS_TRIGGER: "PROCESS_TRIGGER", - PROCESS_LOGS: "PROCESS_LOGS", - LINT_TREE: "LINT_TREE", - PROCESS_JS_FUNCTION_EXECUTION: "PROCESS_JS_FUNCTION_EXECUTION", -}; +export enum MAIN_THREAD_ACTION { + PROCESS_TRIGGER = "PROCESS_TRIGGER", + PROCESS_STORE_UPDATES = "PROCESS_STORE_UPDATES", + PROCESS_LOGS = "PROCESS_LOGS", + LINT_TREE = "LINT_TREE", + PROCESS_JS_FUNCTION_EXECUTION = "PROCESS_JS_FUNCTION_EXECUTION", +} diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index 03300d50df..fb03d218dd 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -154,7 +154,7 @@ export class DataTreeFactory { ...appData, // combine both persistent and transient state with the transient state // taking precedence in case the key is the same - store: { ...appData.store.persistent, ...appData.store.transient }, + store: appData.store, theme, } as DataTreeAppsmith; (dataTree.appsmith as DataTreeAppsmith).ENTITY_TYPE = ENTITY_TYPE.APPSMITH; diff --git a/app/client/src/entities/Engine/index.ts b/app/client/src/entities/Engine/index.ts index 7508a53a05..d32e66416a 100644 --- a/app/client/src/entities/Engine/index.ts +++ b/app/client/src/entities/Engine/index.ts @@ -1,5 +1,5 @@ import { fetchApplication } from "actions/applicationActions"; -import { setAppMode, updateAppPersistentStore } from "actions/pageActions"; +import { setAppMode, updateAppStore } from "actions/pageActions"; import { ApplicationPayload, ReduxActionErrorTypes, @@ -69,9 +69,7 @@ export default abstract class AppEngine { if (!apiCalls) throw new PageNotFoundError(`Cannot find page with id: ${pageId}`); const application: ApplicationPayload = yield select(getCurrentApplication); - yield put( - updateAppPersistentStore(getPersistentAppStore(application.id, branch)), - ); + yield put(updateAppStore(getPersistentAppStore(application.id, branch))); const toLoadPageId: string = pageId || (yield select(getDefaultPageId)); this._urlRedirect = URLGeneratorFactory.create( application.applicationVersion, diff --git a/app/client/src/reducers/entityReducers/appReducer.ts b/app/client/src/reducers/entityReducers/appReducer.ts index acff92c3f2..8d9256b949 100644 --- a/app/client/src/reducers/entityReducers/appReducer.ts +++ b/app/client/src/reducers/entityReducers/appReducer.ts @@ -23,10 +23,7 @@ export type UrlDataState = { fullPath: string; }; -export type AppStoreState = { - transient: Record; - persistent: Record; -}; +export type AppStoreState = Record; export type AppDataState = { mode?: APP_MODE; @@ -55,10 +52,7 @@ const initialState: AppDataState = { hash: "", fullPath: "", }, - store: { - transient: {}, - persistent: {}, - }, + store: {}, geolocation: { canBeRequested: "geolocation" in navigator, currentPosition: {}, @@ -93,28 +87,13 @@ const appReducer = createReducer(initialState, { URL: action.payload, }; }, - [ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE]: ( + [ReduxActionTypes.UPDATE_APP_STORE]: ( state: AppDataState, action: ReduxAction>, ) => { return { ...state, - store: { - ...state.store, - transient: action.payload, - }, - }; - }, - [ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE]: ( - state: AppDataState, - action: ReduxAction>, - ) => { - return { - ...state, - store: { - ...state.store, - persistent: action.payload, - }, + store: action.payload, }; }, [ReduxActionTypes.SET_USER_CURRENT_GEO_LOCATION]: ( diff --git a/app/client/src/sagas/ActionExecution/StoreActionSaga.ts b/app/client/src/sagas/ActionExecution/StoreActionSaga.ts index d898a5b688..cf83c36fdd 100644 --- a/app/client/src/sagas/ActionExecution/StoreActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/StoreActionSaga.ts @@ -1,94 +1,62 @@ -import { put, select, take } from "redux-saga/effects"; +import { put, select } from "redux-saga/effects"; import { getAppStoreName } from "constants/AppConstants"; import localStorage from "utils/localStorage"; -import { - updateAppPersistentStore, - updateAppTransientStore, -} from "actions/pageActions"; +import { updateAppStore } from "actions/pageActions"; import AppsmithConsole from "utils/AppsmithConsole"; import { getAppStoreData } from "selectors/entitiesSelector"; import { + ClearStoreActionDescription, RemoveValueActionDescription, StoreValueActionDescription, } from "@appsmith/entities/DataTree/actionTriggers"; import { getCurrentGitBranch } from "selectors/gitSyncSelectors"; import { getCurrentApplicationId } from "selectors/editorSelectors"; import { AppStoreState } from "reducers/entityReducers/appReducer"; -import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { Severity, LOG_CATEGORY } from "entities/AppsmithConsole"; +import moment from "moment"; -export default function* storeValueLocally( - action: StoreValueActionDescription["payload"], -) { - if (action.persist) { - const applicationId: string = yield select(getCurrentApplicationId); - const branch: string | undefined = yield select(getCurrentGitBranch); - const appStoreName = getAppStoreName(applicationId, branch); - const existingStore = localStorage.getItem(appStoreName) || "{}"; - const parsedStore = JSON.parse(existingStore); - parsedStore[action.key] = action.value; - const storeString = JSON.stringify(parsedStore); - localStorage.setItem(appStoreName, storeString); - yield put(updateAppPersistentStore(parsedStore, action)); - AppsmithConsole.info({ - text: `store('${action.key}', '${action.value}', true)`, - }); - } else { - const existingStore: AppStoreState = yield select(getAppStoreData); - const newTransientStore = { - ...existingStore.transient, - [action.key]: action.value, - }; - yield put(updateAppTransientStore(newTransientStore, action)); - AppsmithConsole.info({ - text: `store('${action.key}', '${action.value}', false)`, - }); - } - /* It is possible that user calls multiple storeValue function together, in such case we need to track completion of each action separately - We use uniqueActionRequestId to differentiate each storeValueAction here. - */ - while (true) { - const returnedAction: StoreValueActionDescription = yield take( - ReduxActionTypes.UPDATE_APP_STORE_EVALUATED, - ); - if (!returnedAction?.payload?.uniqueActionRequestId) { - break; - } +type StoreOperation = + | StoreValueActionDescription + | ClearStoreActionDescription + | RemoveValueActionDescription; - const { uniqueActionRequestId } = returnedAction.payload; - if (uniqueActionRequestId === action.uniqueActionRequestId) { - break; - } - } -} - -export function* removeLocalValue( - action: RemoveValueActionDescription["payload"], -) { +export function* handleStoreOperations(triggers: StoreOperation[]) { const applicationId: string = yield select(getCurrentApplicationId); const branch: string | undefined = yield select(getCurrentGitBranch); const appStoreName = getAppStoreName(applicationId, branch); - const existingStore = localStorage.getItem(appStoreName) || "{}"; - const parsedStore = JSON.parse(existingStore); - delete parsedStore[action.key]; - const storeString = JSON.stringify(parsedStore); + const existingLocalStore = localStorage.getItem(appStoreName) || "{}"; + let parsedLocalStore = JSON.parse(existingLocalStore); + let currentStore: AppStoreState = yield select(getAppStoreData); + const logs: string[] = []; + for (const t of triggers) { + const { type } = t; + if (type === "STORE_VALUE") { + const { key, persist, value } = t.payload; + if (persist) { + parsedLocalStore[key] = value; + } + currentStore[key] = value; + logs.push(`storeValue('${key}', '${value}', ${persist})`); + } else if (type === "REMOVE_VALUE") { + const { key } = t.payload; + delete parsedLocalStore[key]; + delete currentStore[key]; + logs.push(`removeValue('${key}')`); + } else if (type === "CLEAR_STORE") { + parsedLocalStore = {}; + currentStore = {}; + logs.push(`clearStore()`); + } + } + yield put(updateAppStore(currentStore)); + const storeString = JSON.stringify(parsedLocalStore); localStorage.setItem(appStoreName, storeString); - yield put(updateAppPersistentStore(parsedStore)); - const existingTransientStore: AppStoreState = yield select(getAppStoreData); - delete existingTransientStore.transient?.[action.key]; - yield put(updateAppTransientStore(existingTransientStore.transient)); - AppsmithConsole.info({ - text: `remove('${action.key}')`, - }); -} - -export function* clearLocalStore() { - const applicationId: string = yield select(getCurrentApplicationId); - const branch: string | undefined = yield select(getCurrentGitBranch); - const appStoreName = getAppStoreName(applicationId, branch); - localStorage.setItem(appStoreName, "{}"); - yield put(updateAppPersistentStore({})); - yield put(updateAppTransientStore({})); - AppsmithConsole.info({ - text: `clear()`, - }); + AppsmithConsole.addLogs( + logs.map((text) => ({ + text, + severity: Severity.INFO, + category: LOG_CATEGORY.USER_GENERATED, + timestamp: moment().format("hh:mm:ss"), + })), + ); } diff --git a/app/client/src/sagas/DebuggerSagas.ts b/app/client/src/sagas/DebuggerSagas.ts index e1688479ce..9d440d39c6 100644 --- a/app/client/src/sagas/DebuggerSagas.ts +++ b/app/client/src/sagas/DebuggerSagas.ts @@ -617,8 +617,8 @@ export function* storeLogs( entityId: string, ) { AppsmithConsole.addLogs( - logs.reduce((acc: Log[], log: LogObject) => { - acc.push({ + logs.map((log: LogObject) => { + return { text: createLogTitleString(log.data), logData: log.data, source: { @@ -629,9 +629,8 @@ export function* storeLogs( severity: log.severity, timestamp: log.timestamp, category: LOG_CATEGORY.USER_GENERATED, - }); - return acc; - }, []), + }; + }), ); } diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index ea34a081a1..8b361c28ef 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -113,6 +113,7 @@ import { import { BatchedJSExecutionData } from "reducers/entityReducers/jsActionsReducer"; import { sortJSExecutionDataByCollectionId } from "workers/Evaluation/JSObject/utils"; import { MessageType, TMessage } from "utils/MessageUtil"; +import { handleStoreOperations } from "./ActionExecution/StoreActionSaga"; import { ActionDescription } from "@appsmith/entities/DataTree/actionTriggers"; const evalWorker = new GracefulWorkerService( @@ -390,6 +391,9 @@ export function* handleEvalWorkerMessage(message: TMessage) { yield call(evalWorker.respond, message.messageId, result); break; } + case MAIN_THREAD_ACTION.PROCESS_STORE_UPDATES: { + yield call(handleStoreOperations, data); + } } yield call(evalErrorHandler, data?.errors || []); } diff --git a/app/client/src/workers/Evaluation/__tests__/Actions.test.ts b/app/client/src/workers/Evaluation/__tests__/Actions.test.ts index 06c92a6d80..55491be532 100644 --- a/app/client/src/workers/Evaluation/__tests__/Actions.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/Actions.test.ts @@ -316,64 +316,66 @@ describe("Add functions", () => { const key = "some"; const value = "thing"; const persist = false; - const uniqueActionRequestId = "kjebd"; - - // @ts-expect-error: mockReturnValueOnce is not available on uniqueId - uniqueId.mockReturnValueOnce(uniqueActionRequestId); - - expect(evalContext.storeValue(key, value, persist)).resolves.toBe({}); - expect(workerEventMock).lastCalledWith( - messageCreator("STORE_VALUE", { - data: { - trigger: { - type: "STORE_VALUE", - payload: { - key, - value, - persist, - uniqueActionRequestId, - }, - }, - eventType: undefined, - }, - method: "PROCESS_TRIGGER", - }), + jest.useFakeTimers(); + expect(evalContext.storeValue(key, value, persist)).resolves.toStrictEqual( + {}, ); + jest.runAllTimers(); + expect(workerEventMock).lastCalledWith({ + messageType: "DEFAULT", + body: { + data: [ + { + payload: { + key: "some", + persist: false, + value: "thing", + }, + type: "STORE_VALUE", + }, + ], + method: "PROCESS_STORE_UPDATES", + }, + }); }); it("removeValue works", () => { const key = "some"; - expect(evalContext.removeValue(key)).resolves.toBe({}); - expect(workerEventMock).lastCalledWith( - messageCreator("REMOVE_VALUE", { - data: { - trigger: { - type: "REMOVE_VALUE", + jest.useFakeTimers(); + expect(evalContext.removeValue(key)).resolves.toStrictEqual({}); + jest.runAllTimers(); + expect(workerEventMock).lastCalledWith({ + messageType: "DEFAULT", + body: { + data: [ + { payload: { key, }, + type: "REMOVE_VALUE", }, - eventType: undefined, - }, - method: "PROCESS_TRIGGER", - }), - ); + ], + method: "PROCESS_STORE_UPDATES", + }, + }); }); it("clearStore works", () => { - expect(evalContext.clearStore()).resolves.toBe({}); - expect(workerEventMock).lastCalledWith( - messageCreator("CLEAR_STORE", { - data: { - trigger: { - type: "CLEAR_STORE", + jest.useFakeTimers(); + expect(evalContext.clearStore()).resolves.toStrictEqual({}); + jest.runAllTimers(); + expect(workerEventMock).lastCalledWith({ + messageType: "DEFAULT", + body: { + data: [ + { payload: null, + type: "CLEAR_STORE", }, - eventType: undefined, - }, - method: "PROCESS_TRIGGER", - }), - ); + ], + method: "PROCESS_STORE_UPDATES", + }, + }); }); it("download works", () => { diff --git a/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts index ab4b490796..c02b98dd1c 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts @@ -250,10 +250,7 @@ describe("isFunctionAsync", () => { if (typeof testFunc === "string") { testFunc = eval(testFunc); } - functionDeterminer.setupEval({}, {}); - addPlatformFunctionsToEvalContext(self); - const actual = functionDeterminer.isFunctionAsync(testFunc); expect(actual).toBe(testCase.expected); } diff --git a/app/client/src/workers/Evaluation/evaluate.ts b/app/client/src/workers/Evaluation/evaluate.ts index 6ff1f738e3..e818d41fda 100644 --- a/app/client/src/workers/Evaluation/evaluate.ts +++ b/app/client/src/workers/Evaluation/evaluate.ts @@ -19,7 +19,6 @@ import { DOM_APIS } from "./SetupDOM"; import { JSLibraries, libraryReservedIdentifiers } from "../common/JSLibrary"; import { errorModifier, FoundPromiseInSyncEvalError } from "./errorModifier"; import { addDataTreeToContext } from "@appsmith/workers/Evaluation/Actions"; -import { PLATFORM_FUNCTIONS } from "@appsmith/workers/Evaluation/PlatformFunctions"; export type EvalResult = { result: any; @@ -83,7 +82,6 @@ function resetWorkerGlobalScope() { continue; if (JSLibraries.find((lib) => lib.accessor.includes(key))) continue; if (libraryReservedIdentifiers[key]) continue; - if (PLATFORM_FUNCTIONS[key]) continue; try { // @ts-expect-error: Types are not available delete self[key]; diff --git a/app/client/src/workers/Evaluation/fns/fetch.ts b/app/client/src/workers/Evaluation/fns/fetch.ts new file mode 100644 index 0000000000..12a1940dfd --- /dev/null +++ b/app/client/src/workers/Evaluation/fns/fetch.ts @@ -0,0 +1,16 @@ +const _originalFetch = self.fetch; + +export default function interceptAndOverrideHttpRequest() { + Object.defineProperty(self, "fetch", { + writable: false, + configurable: false, + value: function(...args: any) { + if (!self.ALLOW_ASYNC) { + self.IS_ASYNC = true; + return; + } + const request = new Request(args[0], { ...args[1], credentials: "omit" }); + return _originalFetch(request); + }, + }); +} diff --git a/app/client/src/workers/Evaluation/fns/storeFns.ts b/app/client/src/workers/Evaluation/fns/storeFns.ts new file mode 100644 index 0000000000..52465338c0 --- /dev/null +++ b/app/client/src/workers/Evaluation/fns/storeFns.ts @@ -0,0 +1,54 @@ +import { + RemoveValueActionDescription, + StoreValueActionDescription, +} from "ce/entities/DataTree/actionTriggers"; +import set from "lodash/set"; +import { MAIN_THREAD_ACTION } from "@appsmith/workers/Evaluation/evalWorkerActions"; +import { addFn } from "./utils/fnGuard"; +import { TriggerCollector } from "./utils/TriggerCollector"; + +export function initStoreFns(ctx: typeof globalThis) { + const triggerCollector = new TriggerCollector( + MAIN_THREAD_ACTION.PROCESS_STORE_UPDATES, + ); + function storeValue(key: string, value: string, persist = true) { + const requestPayload: StoreValueActionDescription = { + type: "STORE_VALUE", + payload: { + key, + value, + persist, + }, + }; + set(self, ["appsmith", "store", key], value); + triggerCollector.collect(requestPayload); + return Promise.resolve({}); + } + + function removeValue(key: string) { + const requestPayload: RemoveValueActionDescription = { + type: "REMOVE_VALUE", + payload: { + key, + }, + }; + //@ts-expect-error no types for store + delete self.appsmith.store[key]; + triggerCollector.collect(requestPayload); + return Promise.resolve({}); + } + + function clearStore() { + //@ts-expect-error no types for store + self.appsmith.store = {}; + triggerCollector.collect({ + type: "CLEAR_STORE", + payload: null, + }); + return Promise.resolve({}); + } + + addFn(ctx, "storeValue", storeValue); + addFn(ctx, "removeValue", removeValue); + addFn(ctx, "clearStore", clearStore); +} diff --git a/app/client/src/workers/Evaluation/fns/timeout.ts b/app/client/src/workers/Evaluation/fns/timeout.ts new file mode 100644 index 0000000000..b04282b37a --- /dev/null +++ b/app/client/src/workers/Evaluation/fns/timeout.ts @@ -0,0 +1,45 @@ +import { ActionCalledInSyncFieldError } from "../errorModifier"; +import { createEvaluationContext } from "../evaluate"; +import { dataTreeEvaluator } from "../handlers/evalTree"; + +export const _internalSetTimeout = self.setTimeout; +export const _internalClearTimeout = self.clearTimeout; + +export default function overrideTimeout() { + Object.defineProperty(self, "setTimeout", { + writable: true, + configurable: true, + value: function(cb: (...args: any) => any, delay: number, ...args: any) { + if (!self.ALLOW_ASYNC) { + self.IS_ASYNC = true; + throw new ActionCalledInSyncFieldError("setTimeout"); + } + const evalContext = createEvaluationContext({ + dataTree: dataTreeEvaluator?.evalTree || {}, + resolvedFunctions: dataTreeEvaluator?.resolvedFunctions || {}, + isTriggerBased: true, + }); + return _internalSetTimeout( + function(...args: any) { + self.ALLOW_ASYNC = true; + Object.assign(self, evalContext); + cb(...args); + }, + delay, + ...args, + ); + }, + }); + + Object.defineProperty(self, "clearTimeout", { + writable: true, + configurable: true, + value: function(timerId: number) { + if (!self.ALLOW_ASYNC) { + self.IS_ASYNC = true; + throw new ActionCalledInSyncFieldError("clearTimeout"); + } + return _internalClearTimeout(timerId); + }, + }); +} diff --git a/app/client/src/workers/Evaluation/fns/utils/TriggerCollector.ts b/app/client/src/workers/Evaluation/fns/utils/TriggerCollector.ts new file mode 100644 index 0000000000..6e05c8e290 --- /dev/null +++ b/app/client/src/workers/Evaluation/fns/utils/TriggerCollector.ts @@ -0,0 +1,21 @@ +import { MessageType, sendMessage } from "utils/MessageUtil"; +import { MAIN_THREAD_ACTION } from "@appsmith/workers/Evaluation/evalWorkerActions"; +export class TriggerCollector { + private triggers: unknown[] = []; + constructor(private requestType: MAIN_THREAD_ACTION) {} + collect = (trigger: unknown) => { + if (this.triggers.length === 0) { + queueMicrotask(() => { + sendMessage.call(self, { + messageType: MessageType.DEFAULT, + body: { + method: this.requestType, + data: this.triggers, + }, + }); + this.triggers = []; + }); + } + this.triggers.push(trigger); + }; +} diff --git a/app/client/src/workers/Evaluation/fns/utils/fnGuard.ts b/app/client/src/workers/Evaluation/fns/utils/fnGuard.ts new file mode 100644 index 0000000000..096a8f48e7 --- /dev/null +++ b/app/client/src/workers/Evaluation/fns/utils/fnGuard.ts @@ -0,0 +1,26 @@ +import { ActionCalledInSyncFieldError } from "workers/Evaluation/errorModifier"; + +export function addFn( + ctx: typeof globalThis, + fnName: string, + fn: (...args: any[]) => any, + fnGuards = [isAsyncGuard], +) { + Object.defineProperty(ctx, fnName, { + value: function(...args: any[]) { + for (const guard of fnGuards) { + guard(fn, fnName); + } + return fn(...args); + }, + configurable: true, + writable: true, + enumerable: false, + }); +} + +export function isAsyncGuard(_: (...args: any[]) => any, fnName: string) { + if (self.ALLOW_ASYNC) return; + self.IS_ASYNC = true; + throw new ActionCalledInSyncFieldError(fnName); +} diff --git a/app/client/src/workers/Linting/utils.ts b/app/client/src/workers/Linting/utils.ts index 0ed356fdeb..0b588f444f 100644 --- a/app/client/src/workers/Linting/utils.ts +++ b/app/client/src/workers/Linting/utils.ts @@ -53,7 +53,7 @@ import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers"; import { Severity } from "entities/AppsmithConsole"; import { JSLibraries } from "workers/common/JSLibrary"; import { MessageType, sendMessage } from "utils/MessageUtil"; -import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions"; +import { ActionTriggerFunctionNames } from "ce/entities/DataTree/actionTriggers"; export function getlintErrorsFromTree( pathsToLint: string[], @@ -68,7 +68,11 @@ export function getlintErrorsFromTree( skipEntityFunctions: true, }); - addPlatformFunctionsToEvalContext(evalContext); + const platformFnNamesMap = Object.values(ActionTriggerFunctionNames).reduce( + (acc, name) => ({ ...acc, [name]: true }), + {} as { [x: string]: boolean }, + ); + Object.assign(evalContext, platformFnNamesMap); const evalContextWithOutFunctions = createEvaluationContext({ dataTree: unEvalTree,