{"version":3,"file":"index.cjs","names":[],"sources":["../src/orchestration/setupInvalidateVizCache.ts","../src/orchestration/setupBuild.ts","../src/orchestration/createRuntime.ts"],"sourcesContent":["import { VizId } from \"@vizhub/viz-types\";\nimport { generateRequestId } from \"./generateRequestId\";\n\nexport const setupInvalidateVizCache =\n  (worker: Worker) =>\n  async (changedVizIds: Array<VizId>): Promise<void> => {\n    const requestId = generateRequestId();\n    return new Promise<void>((resolve) => {\n      const invalidateListener = (e: MessageEvent) => {\n        if (\n          e.data.type === \"invalidateVizCacheResponse\" &&\n          e.data.requestId === requestId\n        ) {\n          worker.removeEventListener(\n            \"message\",\n            invalidateListener,\n          );\n          resolve();\n        }\n      };\n      worker.addEventListener(\n        \"message\",\n        invalidateListener,\n      );\n\n      worker.postMessage({\n        type: \"invalidateVizCacheRequest\",\n        changedVizIds,\n        requestId,\n      });\n    });\n  };\n","import { FileCollection, VizId } from \"@vizhub/viz-types\";\nimport { generateRequestId } from \"./generateRequestId\";\nimport { BuildWorkerMessage } from \"./types\";\nimport { BuildResult } from \"../build/types\";\n\nexport const setupBuild =\n  ({\n    worker,\n    setBuildErrorMessage,\n  }: {\n    worker: Worker;\n    setBuildErrorMessage?: (error: string | null) => void;\n  }) =>\n  ({\n    files,\n    enableSourcemap,\n    vizId,\n  }: {\n    files: FileCollection;\n    enableSourcemap: boolean;\n    vizId?: VizId;\n  }): Promise<BuildResult | undefined> => {\n    const requestId = generateRequestId();\n    return new Promise<BuildResult | undefined>(\n      (resolve) => {\n        const buildListener = (e: MessageEvent) => {\n          const data: BuildWorkerMessage = e.data;\n          if (\n            data.type === \"buildResponse\" &&\n            data.requestId === requestId\n          ) {\n            worker.removeEventListener(\n              \"message\",\n              buildListener,\n            );\n\n            const buildResult: BuildResult | undefined =\n              data.buildResult;\n            const error: string | undefined = data.error;\n\n            if (error) {\n              setBuildErrorMessage?.(error);\n            } else {\n              setBuildErrorMessage?.(null);\n            }\n\n            resolve(buildResult);\n          }\n        };\n\n        worker.addEventListener(\"message\", buildListener);\n\n        const message: BuildWorkerMessage = {\n          type: \"buildRequest\",\n          files,\n          enableSourcemap,\n          requestId,\n          vizId,\n        };\n        worker.postMessage(message);\n      },\n    );\n  };\n","import {\n  FileCollection,\n  VizContent,\n  VizId,\n} from \"@vizhub/viz-types\";\n\nimport { setupInvalidateVizCache } from \"./setupInvalidateVizCache\";\nimport { setupBuild } from \"./setupBuild\";\nimport {\n  BuildWorkerMessage,\n  VizHubRuntime,\n  WindowMessage,\n} from \"./types\";\n\n// Flag for debugging.\nconst DEBUG = false;\n\n// State constants:\n\n// Nothing happening.\nconst IDLE = \"IDLE\";\n\n// An update has been enqueued\n// via requestAnimationFrame.\nconst ENQUEUED = \"ENQUEUED\";\n\n// An update (build and run) is pending,\n// and the files have not changed.\nconst PENDING_CLEAN = \"PENDING_CLEAN\";\n\n// An update (build and run) is pending,\n// and the files have changed\n// while this run is taking place.\nconst PENDING_DIRTY = \"PENDING_DIRTY\";\n\n// Valid State Transitions:\n//\n//  * IDLE --> ENQUEUED\n//    When the system is idle and files are changed.\n//\n//  * ENQUEUED --> PENDING_CLEAN\n//    When the pending changes run.\n//\n//  * PENDING_CLEAN --> IDLE\n//    When the pending update finishes running\n//    and files were not changed in the mean time.\n//\n//  * PENDING_CLEAN --> PENDING_DIRTY\n//    When files are changed while an update is pending.\n//\n//  * PENDING_DIRTY --> ENQUEUED\n//    When the pending update finishes running\n//    and files were changed in the mean time.\n//\n// When a build error happens, the state is set to IDLE.\n// This is to prevent a build error from causing\n// the whole system to stop working.\n\n// Creates an instance of the VizHub Runtime Environment.\n// This is the main entry point for the runtime, for use\n// by front end applications.\n// It sets up the iframe and worker, and handles messages\n// between them.\n// For server-side rendering where only a build is required,\n// just use the build function directly.\nexport const createRuntime = ({\n  iframe,\n  worker,\n  setBuildErrorMessage,\n  getLatestContent,\n  resolveSlugKey,\n  writeFile,\n  handleRuntimeError,\n}: {\n  iframe: HTMLIFrameElement;\n  worker: Worker;\n  setBuildErrorMessage?: (error: string | null) => void;\n  getLatestContent?: (\n    vizId: VizId,\n  ) => Promise<VizContent | null>;\n  resolveSlugKey?: (\n    slugKey: string,\n  ) => Promise<VizId | null>;\n  writeFile?: (fileName: string, content: string) => void;\n  handleRuntimeError?: (\n    formattedErrorMessage: string,\n  ) => void;\n}): VizHubRuntime => {\n  // Track the current state of the runtime\n  let state:\n    | typeof IDLE\n    | typeof ENQUEUED\n    | typeof PENDING_CLEAN\n    | typeof PENDING_DIRTY = IDLE;\n\n  // When a run is requested while the state is PENDING_CLEAN\n  // or PENDING_DIRTY, we need to wait for the pending build to finish,\n  // so until the build finishes, we use `latestFiles` to stash the latest files.\n  let latestFiles: FileCollection | null = null;\n\n  const build = setupBuild({\n    worker,\n    setBuildErrorMessage,\n  });\n\n  // Pending promise resolvers\n  let pendingRunPromise: (() => void) | null = null;\n\n  // This runs when the build worker sends a message.\n  const workerListener: (e: MessageEvent) => void = ({\n    data,\n  }: {\n    data: BuildWorkerMessage;\n  }) => {\n    if (\n      data.type === \"contentRequest\" &&\n      getLatestContent\n    ) {\n      DEBUG &&\n        console.log(\n          \"[worker] contentRequest\",\n          JSON.stringify(data, null, 2),\n        );\n      const { vizId } = data;\n\n      getLatestContent(vizId).then((content) => {\n        worker.postMessage({\n          type: \"contentResponse\",\n          vizId,\n          content,\n        });\n      });\n    } else if (data.type === \"resolveSlugRequest\") {\n      DEBUG &&\n        console.log(\n          \"[worker] resolveSlugRequest\",\n          JSON.stringify(data, null, 2),\n        );\n      const { slugKey, requestId } = data;\n\n      if (resolveSlugKey) {\n        DEBUG &&\n          console.log(\n            \"[worker] resolveSlugRequest: resolving slug key\",\n            slugKey,\n          );\n        resolveSlugKey(slugKey).then(\n          (vizId: VizId | null) => {\n            DEBUG &&\n              console.log(\n                \"[worker] resolveSlugRequest: resolved slug key\",\n                slugKey,\n                \"to vizId\",\n                vizId,\n              );\n            const message: BuildWorkerMessage = {\n              type: \"resolveSlugResponse\",\n              vizId,\n              requestId,\n            };\n            worker.postMessage(message);\n          },\n        );\n      } else {\n        DEBUG &&\n          console.log(\n            \"[worker] resolveSlugRequest: no slug resolver, returning null\",\n            JSON.stringify(data, null, 2),\n          );\n        // If we don't have a slug resolver, we just\n        // send back the slug key as the vizId.\n        const message: BuildWorkerMessage = {\n          type: \"resolveSlugResponse\",\n          vizId: null,\n          requestId,\n        };\n        worker.postMessage(message);\n      }\n    }\n  };\n\n  worker.addEventListener(\"message\", workerListener);\n\n  // Handle messages from the iframe\n  // IMPORTANT: check event.source to only process messages from our own iframe.\n  const windowListener = (event: MessageEvent) => {\n    if (event.source !== iframe.contentWindow) return;\n    const data = event.data;\n\n    if (\n      data.type === \"runDone\" ||\n      data.type === \"runError\"\n    ) {\n      if (pendingRunPromise) {\n        pendingRunPromise();\n        pendingRunPromise = null;\n      }\n\n      if (data.type === \"runError\") {\n        setBuildErrorMessage &&\n          setBuildErrorMessage(data.error.message);\n      }\n    }\n\n    if (\n      data.type === \"runtimeError\" &&\n      handleRuntimeError\n    ) {\n      handleRuntimeError(data.formattedErrorMessage);\n    }\n\n    if (data.type === \"writeFile\" && writeFile) {\n      if (data.fileName && data.content) {\n        writeFile(data.fileName, data.content);\n      }\n    }\n  };\n\n  // This runs when the IFrame sends a message.\n  window.addEventListener(\"message\", windowListener);\n\n  const cleanup = () => {\n    worker.removeEventListener(\"message\", workerListener);\n    window.removeEventListener(\"message\", windowListener);\n  };\n\n  const update = async ({\n    files,\n    enableHotReloading = false,\n    enableSourcemap = false,\n    vizId,\n    clearConsole = true,\n  }: {\n    files: FileCollection;\n    enableHotReloading?: boolean;\n    enableSourcemap?: boolean;\n    vizId?: VizId;\n    clearConsole?: boolean;\n  }) => {\n    state = PENDING_CLEAN;\n\n    DEBUG && console.log(\"[runtime] update: before build\");\n\n    // Build the code\n    const buildResult = await build({\n      files,\n      enableSourcemap,\n      vizId,\n    });\n\n    // In this case, the build failed\n    // and in the meantime the callback `setBuildErrorMessage`\n    // was called.\n    if (!buildResult) {\n      DEBUG &&\n        console.log(\"[runtime] update: build failed\");\n    }\n\n    DEBUG && console.log(\"[runtime] update: after build\");\n\n    if (buildResult) {\n      const { html, runtimeVersion, js, css } = buildResult;\n\n      DEBUG &&\n        console.log(\n          \"[runtime] enableHotReloading\",\n          enableHotReloading,\n        );\n\n      DEBUG &&\n        console.log(\n          \"[runtime] html: \",\n          html?.substring(0, 200),\n        );\n\n      DEBUG &&\n        console.log(\n          \"[runtime] js: \",\n          js?.substring(0, 200),\n        );\n\n      DEBUG &&\n        console.log(\n          \"[runtime] css: \",\n          css?.substring(0, 200),\n        );\n\n      // Clear the console before each run.\n      !DEBUG && clearConsole && console.clear();\n\n      // The `enableHotReloading` only works for v3 and v4.\n      if (\n        enableHotReloading &&\n        (runtimeVersion === \"v3\" || runtimeVersion === \"v4\")\n      ) {\n        if (css) {\n          const runCSSMessage: WindowMessage = {\n            type: \"runCSS\",\n            css,\n          };\n          if (!iframe.contentWindow) {\n            throw new Error(\n              \"iframe.contentWindow is null - this should never happen\",\n            );\n          }\n          iframe.contentWindow.postMessage(\n            runCSSMessage,\n            window.location.origin,\n          );\n        }\n        if (js) {\n          const runJSMessage: WindowMessage = {\n            type: \"runJS\",\n            js,\n          };\n          if (!iframe.contentWindow) {\n            throw new Error(\n              \"iframe.contentWindow is null - this should never happen\",\n            );\n          }\n          iframe.contentWindow.postMessage(\n            runJSMessage,\n            window.location.origin,\n          );\n        }\n      } else {\n        iframe.srcdoc = html || \"\";\n      }\n    }\n\n    // TypeScript can't comprehend that `state`\n    // may change during the await calls above.\n    // @ts-ignore\n    if (state === PENDING_DIRTY) {\n      requestAnimationFrame(() => {\n        if (!latestFiles) {\n          throw new Error(\n            \"latestFiles is not defined and state is PENDING_DIRTY - this should never happen\",\n          );\n        }\n        update({\n          files: latestFiles,\n          enableHotReloading,\n          enableSourcemap,\n          vizId,\n          clearConsole,\n        });\n      });\n      state = ENQUEUED;\n    } else {\n      state = IDLE;\n    }\n  };\n\n  // Handle code changes\n\n  const run = ({\n    files,\n    enableHotReloading = false,\n    enableSourcemap = false,\n    vizId = undefined,\n    clearConsole = true,\n  }: {\n    files: FileCollection;\n    enableHotReloading?: boolean;\n    enableSourcemap?: boolean;\n    vizId?: VizId;\n    clearConsole?: boolean;\n  }) => {\n    DEBUG && console.log(\"[runtime] run\");\n    latestFiles = null;\n    if (state === IDLE) {\n      DEBUG && console.log(\"[runtime] run: IDLE\");\n      state = ENQUEUED;\n      update({\n        files,\n        enableHotReloading,\n        enableSourcemap,\n        vizId,\n        clearConsole,\n      });\n    } else if (state === PENDING_CLEAN) {\n      DEBUG && console.log(\"[runtime] run: PENDING_CLEAN\");\n      latestFiles = files;\n      state = PENDING_DIRTY;\n    } else if (state === PENDING_DIRTY) {\n      DEBUG && console.log(\"[runtime] run: PENDING_DIRTY\");\n      latestFiles = files;\n    } else if (state === ENQUEUED) {\n      DEBUG && console.log(\"[runtime] run: ENQUEUED\");\n      latestFiles = files;\n    } else {\n      throw new Error(`Unexpected state: ${state}`);\n    }\n  };\n\n  return {\n    invalidateVizCache: setupInvalidateVizCache(worker),\n    run,\n    cleanup,\n  };\n};\n"],"mappings":"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"}