Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to combine REPL with multi tab worker (Uncaught (in promise) TypeError: Cannot read from private field) #519

Closed
JasonYao opened this issue Feb 4, 2025 · 2 comments
Assignees

Comments

@JasonYao
Copy link

JasonYao commented Feb 4, 2025

Previous issues

This sounds very similar to #346, but unlike them I'm still running into this issue whenever I want to allow REPL access to the web worker instance

Minimal example

// Inside of worker.ts
import { PGlite, type PGliteOptions } from "@electric-sql/pglite";
import { worker } from "@electric-sql/pglite/worker";

worker({
  async init(options: PGliteOptions): Promise<PGlite> {
    return new PGlite({
      dataDir: options.dataDir,
    });
  },
});
// Inside of App.vue
<script setup lang="ts">
import {
  electricSync,
  type SyncShapeToTableResult,
} from "@electric-sql/pglite-sync";
import { live } from "@electric-sql/pglite/live";
import { vector } from "@electric-sql/pglite/vector";
import { PGliteWorker } from "@electric-sql/pglite/worker";
import { onMounted, onUnmounted, provide, ref } from "vue";

const pg = ref<PGliteWorker | null>(null);
const shouldRender = ref(false);

// On application's first load- note that this setup function is async
// due to PGliteWorker::create returning a promise, so we do a
// conditional render here, where we "don't render the REPL
// component until we've successfully loaded the PGliteWorker"

onMounted(async () => {
  console.log("Initializing local postgres DB");
  const pgWorker = await PGliteWorker.create(
    new Worker(new URL("./worker.ts", import.meta.url), {
      type: "module",
    }),
    {
      dataDir: "memory://",
      extensions: {
        live,
        vector,
        electric: electricSync(),
      },
    },
  );

  pg.value = pgWorker;  
  shouldRender.value = true;
});
</script>

<template>
  <template v-if="shouldRender">
    <p>Loaded local postgres instance for REPL</p>
    <pglite-repl :pg="pg" />
  </template>
  <template v-else>
    <p>Loading local postgres instance for REPL</p>
  </template>
</template>

I can confirm that the web worker is working correctly by issuing a live query, and getting a successful response:

const response = await pg.value.live.query("SELECT * FROM NOW()");
console.log("pg result: ", response.initialResults.rows[0]);
// Logs out: "2025-02-04T04:45:56.128Z"

However, while the webworker is working as expected, the REPL is failing with the error message:

Uncaught (in promise) TypeError: Cannot read from private field

and the REPL is just constantly showing the "Loading..." message

Image

And to make sure that this is just about a bug between PGliteWorker and the REPL, I've confirmed that the component works as expected when I instead create a PGlite instance and pass it directly to the REPL

Image
@JasonYao
Copy link
Author

JasonYao commented Feb 4, 2025

Some workarounds found: For anybody coming across this in the future, deep refs for the webworker are busted, but direct access and shallowRef works- I was previously using:

const pg = await PGliteWorker.create(<blah blah blah>);

since this was the recommendation (but forced me to do that async workaround), but it turns out that I can just create the web worker synchronously, and pass the value directly:

Direct constructor workaround

<script setup lang="ts">
// Note that we're not using the PGliteWorker::create async promise, just the direct constructor now
const pgWorker = new PGliteWorker(
  new Worker(new URL("./worker.ts", import.meta.url), {
    type: "module",
  }),
  {
    dataDir: "memory://",
    extensions: {
      live,
      vector,
      electric: electricSync(),
    },
  },
);
</script>

<template>
    <p>
      Successful REPL when I pass in a PGLiteWorker directly, constructed
      through new PGLiteWorker and not assigning to a reference
    </p>
    <pglite-repl :pg="pgWorker" />
</template>

This results in a working REPL combined with the webworker

Image

Shallowref workaround

Likewise, if you create a shallowRef instead of a ref, you can work around the issue

<script setup lang="ts">
const pg = shallowRef<PGliteWorker | null>(null);

onMounted(async () => {
    // blah blah blah the onMounted code from above
});
</script>
<template>
<p>
      Successful REPL when I pass in a PGLiteWorker wrapped in a shallowRef, constructed
      through the async PGLiteWorker::create call
    </p>
    <pglite-repl :pg="pg" />
</template>

@copiltembel
Copy link
Collaborator

Thank you, @JasonYao !

The shallowRef route is the way to go here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants