@pyscript/core 0.0.1 → 0.0.3

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.
package/README.md CHANGED
@@ -19,7 +19,7 @@ This project requires some automatic artifact creation to:
19
19
 
20
20
  * create a _Worker_ as a _Blob_ based on the same code used by this repo
21
21
  * create automatically the list of runtimes available via the module
22
- * create the `min.js` file used by most integration tests
22
+ * create the `core.js` file used by most integration tests
23
23
  * create a sha256 version of the Blob content for CSP cases
24
24
 
25
25
  Accordingly, to build latest project:
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+ const CLOSEBUTTON = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill="currentColor" width="12px"><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>`;
3
+
4
+ /**
5
+ * These error codes are used to identify the type of error that occurred.
6
+ * @see https://docs.pyscript.net/latest/reference/exceptions.html?highlight=errors
7
+ */
8
+ const ErrorCode = {
9
+ GENERIC: "PY0000", // Use this only for development then change to a more specific error code
10
+ FETCH_ERROR: "PY0001",
11
+ FETCH_NAME_ERROR: "PY0002",
12
+ // Currently these are created depending on error code received from fetching
13
+ FETCH_UNAUTHORIZED_ERROR: "PY0401",
14
+ FETCH_FORBIDDEN_ERROR: "PY0403",
15
+ FETCH_NOT_FOUND_ERROR: "PY0404",
16
+ FETCH_SERVER_ERROR: "PY0500",
17
+ FETCH_UNAVAILABLE_ERROR: "PY0503",
18
+ BAD_CONFIG: "PY1000",
19
+ MICROPIP_INSTALL_ERROR: "PY1001",
20
+ BAD_PLUGIN_FILE_EXTENSION: "PY2000",
21
+ NO_DEFAULT_EXPORT: "PY2001",
22
+ TOP_LEVEL_AWAIT: "PY9000",
23
+ };
24
+ exports.ErrorCode = ErrorCode;
25
+
26
+ class UserError extends Error {
27
+ constructor(errorCode, message = "", messageType = "text") {
28
+ super(`(${errorCode}): ${message}`);
29
+ this.errorCode = errorCode;
30
+ this.messageType = messageType;
31
+ this.name = "UserError";
32
+ }
33
+ }
34
+ exports.UserError = UserError
35
+
36
+ class FetchError extends UserError {
37
+ constructor(errorCode, message) {
38
+ super(errorCode, message);
39
+ this.name = "FetchError";
40
+ }
41
+ }
42
+ exports.FetchError = FetchError
43
+
44
+ class InstallError extends UserError {
45
+ constructor(errorCode, message) {
46
+ super(errorCode, message);
47
+ this.name = "InstallError";
48
+ }
49
+ }
50
+ exports.InstallError = InstallError
51
+
52
+ function _createAlertBanner(
53
+ message,
54
+ level,
55
+ messageType = "text",
56
+ logMessage = true,
57
+ ) {
58
+ switch (`log-${level}-${logMessage}`) {
59
+ case "log-error-true":
60
+ console.error(message);
61
+ break;
62
+ case "log-warning-true":
63
+ console.warn(message);
64
+ break;
65
+ }
66
+
67
+ const content = messageType === "html" ? "innerHTML" : "textContent";
68
+ const banner = Object.assign(document.createElement("div"), {
69
+ className: `alert-banner py-${level}`,
70
+ [content]: message,
71
+ });
72
+
73
+ if (level === "warning") {
74
+ const closeButton = Object.assign(document.createElement("button"), {
75
+ id: "alert-close-button",
76
+ innerHTML: CLOSEBUTTON,
77
+ });
78
+
79
+ banner.appendChild(closeButton).addEventListener("click", () => {
80
+ banner.remove();
81
+ });
82
+ }
83
+
84
+ document.body.prepend(banner);
85
+ }
86
+ exports._createAlertBanner = _createAlertBanner
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+ const { FetchError, ErrorCode } = require("./exceptions");
3
+
4
+ /**
5
+ * This is a fetch wrapper that handles any non 200 responses and throws a
6
+ * FetchError with the right ErrorCode. This is useful because our FetchError
7
+ * will automatically create an alert banner.
8
+ *
9
+ * @param {string} url - URL to fetch
10
+ * @param {Request} [options] - options to pass to fetch
11
+ * @returns {Promise<Response>}
12
+ */
13
+ async function robustFetch(url, options) {
14
+ let response;
15
+
16
+ // Note: We need to wrap fetch into a try/catch block because fetch
17
+ // throws a TypeError if the URL is invalid such as http://blah.blah
18
+ try {
19
+ response = await fetch(url, options);
20
+ } catch (err) {
21
+ const error = err;
22
+ let errMsg;
23
+ if (url.startsWith("http")) {
24
+ errMsg =
25
+ `Fetching from URL ${url} failed with error ` +
26
+ `'${error.message}'. Are your filename and path correct?`;
27
+ } else {
28
+ errMsg = `PyScript: Access to local files
29
+ (using [[fetch]] configurations in &lt;py-config&gt;)
30
+ is not available when directly opening a HTML file;
31
+ you must use a webserver to serve the additional files.
32
+ See <a style="text-decoration: underline;" href="https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062">this reference</a>
33
+ on starting a simple webserver with Python.
34
+ `;
35
+ }
36
+ throw new FetchError(ErrorCode.FETCH_ERROR, errMsg);
37
+ }
38
+
39
+ // Note that response.ok is true for 200-299 responses
40
+ if (!response.ok) {
41
+ const errorMsg = `Fetching from URL ${url} failed with error ${response.status} (${response.statusText}). Are your filename and path correct?`;
42
+ switch (response.status) {
43
+ case 404:
44
+ throw new FetchError(ErrorCode.FETCH_NOT_FOUND_ERROR, errorMsg);
45
+ case 401:
46
+ throw new FetchError(
47
+ ErrorCode.FETCH_UNAUTHORIZED_ERROR,
48
+ errorMsg,
49
+ );
50
+ case 403:
51
+ throw new FetchError(ErrorCode.FETCH_FORBIDDEN_ERROR, errorMsg);
52
+ case 500:
53
+ throw new FetchError(ErrorCode.FETCH_SERVER_ERROR, errorMsg);
54
+ case 503:
55
+ throw new FetchError(
56
+ ErrorCode.FETCH_UNAVAILABLE_ERROR,
57
+ errorMsg,
58
+ );
59
+ default:
60
+ throw new FetchError(ErrorCode.FETCH_ERROR, errorMsg);
61
+ }
62
+ }
63
+ return response;
64
+ }
65
+ exports.robustFetch = robustFetch
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+ require("@ungap/with-resolvers");
3
+ const { $ } = require("basic-devtools");
4
+
5
+ const { define } = require("../index.js");
6
+ const { queryTarget } = require("../script-handler.js");
7
+ const { defineProperty } = require("../utils.js");
8
+ const { getText } = require("../fetch-utils.js");
9
+
10
+ // TODO: should this utility be in core instead?
11
+ const { robustFetch: fetch } = require("./pyscript/fetch.js");
12
+
13
+ // append ASAP CSS to avoid showing content
14
+ document.head.appendChild(document.createElement("style")).textContent = `
15
+ py-script, py-config {
16
+ display: none;
17
+ }
18
+ `;
19
+
20
+ (async () => {
21
+ // create a unique identifier when/if needed
22
+ let id = 0;
23
+ const getID = (prefix = "py") => `${prefix}-${id++}`;
24
+
25
+ // find the shared config for all py-script elements
26
+ let config;
27
+ let pyConfig = $("py-config");
28
+ if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
29
+ else {
30
+ pyConfig = $('script[type="py"]');
31
+ config = pyConfig?.getAttribute("config");
32
+ }
33
+
34
+ if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
35
+
36
+ // generic helper to disambiguate between custom element and script
37
+ const isScript = (element) => element.tagName === "SCRIPT";
38
+
39
+ // helper for all script[type="py"] out there
40
+ const before = (script) => {
41
+ defineProperty(document, "currentScript", {
42
+ configurable: true,
43
+ get: () => script,
44
+ });
45
+ };
46
+
47
+ const after = () => {
48
+ delete document.currentScript;
49
+ };
50
+
51
+ /**
52
+ * Given a generic DOM Element, tries to fetch the 'src' attribute, if present.
53
+ * It either throws an error if the 'src' can't be fetched or it returns a fallback
54
+ * content as source.
55
+ */
56
+ const fetchSource = async (tag) => {
57
+ if (tag.hasAttribute("src")) {
58
+ try {
59
+ const response = await fetch(tag.getAttribute("src"));
60
+ return response.then(getText);
61
+ } catch (error) {
62
+ // TODO _createAlertBanner(err) instead ?
63
+ alert(error.message);
64
+ throw error;
65
+ }
66
+ }
67
+ return tag.textContent;
68
+ };
69
+
70
+ // common life-cycle handlers for any node
71
+ const bootstrapNodeAndPlugins = (pyodide, element, callback, hook) => {
72
+ if (isScript(element)) callback(element);
73
+ for (const fn of hooks[hook]) fn(pyodide, element);
74
+ };
75
+
76
+ const addDisplay = (element) => {
77
+ const id = isScript(element) ? element.target.id : element.id;
78
+ return `
79
+ # this code is just for demo purpose but the basics work
80
+ def _display(what, target="${id}", append=True):
81
+ from js import document
82
+ element = document.getElementById(target)
83
+ element.textContent = what
84
+ display = _display
85
+ `;
86
+ };
87
+
88
+ // define the module as both `<script type="py">` and `<py-script>`
89
+ define("py", {
90
+ config,
91
+ env: "py-script",
92
+ interpreter: "pyodide",
93
+ codeBeforeRunWorker() {
94
+ const { codeBeforeRunWorker: set } = hooks;
95
+ const prefix = 'print("codeBeforeRunWorker")';
96
+ return [prefix].concat(...set).join("\n");
97
+ },
98
+ codeAfterRunWorker() {
99
+ const { codeAfterRunWorker: set } = hooks;
100
+ const prefix = 'print("codeAfterRunWorker")';
101
+ return [prefix].concat(...set).join("\n");
102
+ },
103
+ onBeforeRun(pyodide, element) {
104
+ bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
105
+ pyodide.interpreter.runPython(addDisplay(element));
106
+ },
107
+ onBeforeRunAync(pyodide, element) {
108
+ pyodide.interpreter.runPython(addDisplay(element));
109
+ bootstrapNodeAndPlugins(
110
+ pyodide,
111
+ element,
112
+ before,
113
+ "onBeforeRunAync",
114
+ );
115
+ },
116
+ onAfterRun(pyodide, element) {
117
+ bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
118
+ },
119
+ onAfterRunAsync(pyodide, element) {
120
+ bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
121
+ },
122
+ async onRuntimeReady(pyodide, element) {
123
+ // allows plugins to do whatever they want with the element
124
+ // before regular stuff happens in here
125
+ for (const callback of hooks.onRuntimeReady)
126
+ callback(pyodide, element);
127
+ if (isScript(element)) {
128
+ const {
129
+ attributes: { async: isAsync, target },
130
+ } = element;
131
+ const hasTarget = !!target?.value;
132
+ const show = hasTarget
133
+ ? queryTarget(target.value)
134
+ : document.createElement("script-py");
135
+
136
+ if (!hasTarget) element.after(show);
137
+ if (!show.id) show.id = getID();
138
+
139
+ // allows the code to retrieve the target element via
140
+ // document.currentScript.target if needed
141
+ defineProperty(element, "target", { value: show });
142
+
143
+ pyodide[`run${isAsync ? "Async" : ""}`](
144
+ await fetchSource(element),
145
+ );
146
+ } else {
147
+ // resolve PyScriptElement to allow connectedCallback
148
+ element._pyodide.resolve(pyodide);
149
+ }
150
+ },
151
+ });
152
+
153
+ class PyScriptElement extends HTMLElement {
154
+ constructor() {
155
+ if (!super().id) this.id = getID();
156
+ this._pyodide = Promise.withResolvers();
157
+ this.srcCode = "";
158
+ this.executed = false;
159
+ }
160
+ async connectedCallback() {
161
+ if (!this.executed) {
162
+ this.executed = true;
163
+ const { run } = await this._pyodide.promise;
164
+ this.srcCode = await fetchSource(this);
165
+ this.textContent = "";
166
+ const result = run(this.srcCode);
167
+ if (!this.textContent && result) this.textContent = result;
168
+ this.style.display = "block";
169
+ }
170
+ }
171
+ }
172
+
173
+ customElements.define("py-script", PyScriptElement);
174
+ })();
175
+
176
+ const hooks = {
177
+ /** @type {Set<function>} */
178
+ onBeforeRun: new Set(),
179
+ /** @type {Set<function>} */
180
+ onBeforeRunAync: new Set(),
181
+ /** @type {Set<function>} */
182
+ onAfterRun: new Set(),
183
+ /** @type {Set<function>} */
184
+ onAfterRunAsync: new Set(),
185
+ /** @type {Set<function>} */
186
+ onRuntimeReady: new Set(),
187
+
188
+ /** @type {Set<string>} */
189
+ codeBeforeRunWorker: new Set(),
190
+ /** @type {Set<string>} */
191
+ codeBeforeRunWorkerAsync: new Set(),
192
+ /** @type {Set<string>} */
193
+ codeAfterRunWorker: new Set(),
194
+ /** @type {Set<string>} */
195
+ codeAfterRunWorkerAsync: new Set(),
196
+ };
197
+ exports.hooks = hooks;
package/cjs/custom.js ADDED
@@ -0,0 +1,193 @@
1
+ 'use strict';
2
+ require("@ungap/with-resolvers");
3
+ const { $$ } = require("basic-devtools");
4
+
5
+ const { assign, create } = require("./utils.js");
6
+ const { getDetails } = require("./script-handler.js");
7
+ const {
8
+ registry: defaultRegistry,
9
+ prefixes,
10
+ configs
11
+ } = require("./interpreters.js");
12
+ const { getRuntimeID } = require("./loader.js");
13
+ const { io } = require("./interpreter/_utils.js");
14
+ const { addAllListeners } = require("./listeners.js");
15
+ const { Hook } = require("./worker/hooks.js");
16
+
17
+ const CUSTOM_SELECTORS = [];
18
+ exports.CUSTOM_SELECTORS = CUSTOM_SELECTORS;
19
+
20
+ /**
21
+ * @typedef {Object} Runtime custom configuration
22
+ * @prop {object} interpreter the bootstrapped interpreter
23
+ * @prop {(url:string, options?: object) => Worker} XWorker an XWorker constructor that defaults to same interpreter on the Worker.
24
+ * @prop {object} config a cloned config used to bootstrap the interpreter
25
+ * @prop {(code:string) => any} run an utility to run code within the interpreter
26
+ * @prop {(code:string) => Promise<any>} runAsync an utility to run code asynchronously within the interpreter
27
+ * @prop {(path:string, data:ArrayBuffer) => void} writeFile an utility to write a file in the virtual FS, if available
28
+ */
29
+
30
+ const types = new Map();
31
+ const waitList = new Map();
32
+
33
+ // REQUIRES INTEGRATION TEST
34
+ /* c8 ignore start */
35
+ /**
36
+ * @param {Element} node any DOM element registered via define.
37
+ */
38
+ const handleCustomType = (node) => {
39
+ for (const selector of CUSTOM_SELECTORS) {
40
+ if (node.matches(selector)) {
41
+ const type = types.get(selector);
42
+ const { resolve } = waitList.get(type);
43
+ const { options, known } = registry.get(type);
44
+ if (!known.has(node)) {
45
+ known.add(node);
46
+ const {
47
+ interpreter: runtime,
48
+ version,
49
+ config,
50
+ env,
51
+ onRuntimeReady,
52
+ } = options;
53
+ const name = getRuntimeID(runtime, version);
54
+ const id = env || `${name}${config ? `|${config}` : ""}`;
55
+ const { interpreter: engine, XWorker: Worker } = getDetails(
56
+ runtime,
57
+ id,
58
+ name,
59
+ version,
60
+ config,
61
+ );
62
+ engine.then((interpreter) => {
63
+ const module = create(defaultRegistry.get(runtime));
64
+
65
+ const {
66
+ onBeforeRun,
67
+ onBeforeRunAsync,
68
+ onAfterRun,
69
+ onAfterRunAsync,
70
+ } = options;
71
+
72
+ const hooks = new Hook(options);
73
+
74
+ const XWorker = function XWorker(...args) {
75
+ return Worker.apply(hooks, args);
76
+ };
77
+
78
+ // These two loops mimic a `new Map(arrayContent)` without needing
79
+ // the new Map overhead so that [name, [before, after]] can be easily destructured
80
+ // and new sync or async patches become easy to add (when the logic is the same).
81
+
82
+ // patch sync
83
+ for (const [name, [before, after]] of [
84
+ ["run", [onBeforeRun, onAfterRun]],
85
+ ]) {
86
+ const method = module[name];
87
+ module[name] = function (interpreter, code) {
88
+ if (before) before.call(this, resolved, node);
89
+ const result = method.call(this, interpreter, code);
90
+ if (after) after.call(this, resolved, node);
91
+ return result;
92
+ };
93
+ }
94
+
95
+ // patch async
96
+ for (const [name, [before, after]] of [
97
+ ["runAsync", [onBeforeRunAsync, onAfterRunAsync]],
98
+ ]) {
99
+ const method = module[name];
100
+ module[name] = async function (interpreter, code) {
101
+ if (before) await before.call(this, resolved, node);
102
+ const result = await method.call(
103
+ this,
104
+ interpreter,
105
+ code,
106
+ );
107
+ if (after) await after.call(this, resolved, node);
108
+ return result;
109
+ };
110
+ }
111
+
112
+ module.setGlobal(interpreter, "XWorker", XWorker);
113
+
114
+ const resolved = {
115
+ type,
116
+ interpreter,
117
+ XWorker,
118
+ io: io.get(interpreter),
119
+ config: structuredClone(configs.get(name)),
120
+ run: module.run.bind(module, interpreter),
121
+ runAsync: module.runAsync.bind(module, interpreter),
122
+ };
123
+
124
+ resolve(resolved);
125
+
126
+ onRuntimeReady?.(resolved, node);
127
+ });
128
+ }
129
+ }
130
+ }
131
+ };
132
+ exports.handleCustomType = handleCustomType;
133
+
134
+ /**
135
+ * @type {Map<string, {options:object, known:WeakSet<Element>}>}
136
+ */
137
+ const registry = new Map();
138
+
139
+ /**
140
+ * @typedef {Object} PluginOptions custom configuration
141
+ * @prop {'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi'} interpreter the interpreter to use
142
+ * @prop {string} [version] the optional interpreter version to use
143
+ * @prop {string} [config] the optional config to use within such interpreter
144
+ * @prop {(environment: object, node: Element) => void} [onRuntimeReady] the callback that will be invoked once
145
+ */
146
+
147
+ /**
148
+ * Allows custom types and components on the page to receive interpreters to execute any code
149
+ * @param {string} type the unique `<script type="...">` identifier
150
+ * @param {PluginOptions} options the custom type configuration
151
+ */
152
+ const define = (type, options) => {
153
+ if (defaultRegistry.has(type) || registry.has(type))
154
+ throw new Error(`<script type="${type}"> already registered`);
155
+
156
+ if (!defaultRegistry.has(options?.interpreter))
157
+ throw new Error(`Unspecified interpreter`);
158
+
159
+ // allows reaching out the interpreter helpers on events
160
+ defaultRegistry.set(type, defaultRegistry.get(options?.interpreter));
161
+
162
+ // ensure a Promise can resolve once a custom type has been bootstrapped
163
+ whenDefined(type);
164
+
165
+ // allows selector -> registry by type
166
+ const selectors = [`script[type="${type}"]`, `${type}-script`];
167
+ for (const selector of selectors) types.set(selector, type);
168
+
169
+ CUSTOM_SELECTORS.push(...selectors);
170
+ prefixes.push(`${type}-`);
171
+
172
+ // ensure always same env for this custom type
173
+ registry.set(type, {
174
+ options: assign({ env: type }, options),
175
+ known: new WeakSet(),
176
+ });
177
+
178
+ addAllListeners(document);
179
+ $$(selectors.join(",")).forEach(handleCustomType);
180
+ };
181
+ exports.define = define;
182
+
183
+ /**
184
+ * Resolves whenever a defined custom type is bootstrapped on the page
185
+ * @param {string} type the unique `<script type="...">` identifier
186
+ * @returns {Promise<object>}
187
+ */
188
+ const whenDefined = (type) => {
189
+ if (!waitList.has(type)) waitList.set(type, Promise.withResolvers());
190
+ return waitList.get(type).promise;
191
+ };
192
+ exports.whenDefined = whenDefined;
193
+ /* c8 ignore stop */
package/cjs/index.js CHANGED
@@ -5,13 +5,13 @@ const xworker = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 i
5
5
  const { handle } = require("./script-handler.js");
6
6
  const { assign } = require("./utils.js");
7
7
  const { selectors, prefixes } = require("./interpreters.js");
8
- const { CUSTOM_SELECTORS, handleCustomType } = require("./custom-types.js");
8
+ const { CUSTOM_SELECTORS, handleCustomType } = require("./custom.js");
9
9
  const { listener, addAllListeners } = require("./listeners.js");
10
10
 
11
11
  (m => {
12
12
  exports.define = m.define;
13
13
  exports.whenDefined = m.whenDefined;
14
- })(require("./custom-types.js"));
14
+ })(require("./custom.js"));
15
15
  const XWorker = xworker();
16
16
  exports.XWorker = XWorker;
17
17
 
@@ -10,6 +10,14 @@ const runAsync = (interpreter, code) =>
10
10
  interpreter.runPythonAsync(clean(code));
11
11
  exports.runAsync = runAsync;
12
12
 
13
+ const setGlobal = (interpreter, name, value) =>
14
+ interpreter.globals.set(name, value);
15
+ exports.setGlobal = setGlobal;
16
+
17
+ const deleteGlobal = (interpreter, name) =>
18
+ interpreter.globals.delete(name);
19
+ exports.deleteGlobal = deleteGlobal;
20
+
13
21
  const writeFile = ({ FS }, path, buffer) =>
14
22
  writeFileUtil(FS, path, buffer);
15
23
  exports.writeFile = writeFile;
@@ -1,6 +1,12 @@
1
1
  'use strict';
2
2
  const { fetchPaths, stdio } = require("./_utils.js");
3
- const { run, runAsync, writeFile } = require("./_python.js");
3
+ const {
4
+ run,
5
+ runAsync,
6
+ setGlobal,
7
+ deleteGlobal,
8
+ writeFile
9
+ } = require("./_python.js");
4
10
 
5
11
  const type = "micropython";
6
12
 
@@ -17,16 +23,8 @@ module.exports = {
17
23
  if (config.fetch) await fetchPaths(this, runtime, config.fetch);
18
24
  return runtime;
19
25
  },
20
- setGlobal(interpreter, name, value) {
21
- const id = `__pyscript_${this.type}_${name}`;
22
- globalThis[id] = value;
23
- this.run(interpreter, `from js import ${id};${name}=${id};`);
24
- },
25
- deleteGlobal(interpreter, name) {
26
- const id = `__pyscript_${this.type}_${name}`;
27
- this.run(interpreter, `del ${id};del ${name}`);
28
- delete globalThis[id];
29
- },
26
+ setGlobal,
27
+ deleteGlobal,
30
28
  run,
31
29
  runAsync,
32
30
  writeFile,
@@ -1,6 +1,12 @@
1
1
  'use strict';
2
2
  const { fetchPaths, stdio } = require("./_utils.js");
3
- const { run, runAsync, writeFile } = require("./_python.js");
3
+ const {
4
+ run,
5
+ runAsync,
6
+ setGlobal,
7
+ deleteGlobal,
8
+ writeFile
9
+ } = require("./_python.js");
4
10
 
5
11
  const type = "pyodide";
6
12
 
@@ -25,12 +31,8 @@ module.exports = {
25
31
  }
26
32
  return interpreter;
27
33
  },
28
- setGlobal(interpreter, name, value) {
29
- interpreter.globals.set(name, value);
30
- },
31
- deleteGlobal(interpreter, name) {
32
- interpreter.globals.delete(name);
33
- },
34
+ setGlobal,
35
+ deleteGlobal,
34
36
  run,
35
37
  runAsync,
36
38
  writeFile,
@@ -4,7 +4,7 @@ const coincident = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c
4
4
  const xworker = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require("./xworker.js"));
5
5
  const { assign, defineProperties, absoluteURL } = require("../utils.js");
6
6
  const { getText } = require("../fetch-utils.js");
7
- const workerHooks = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require("./hooks.js"));
7
+ const { Hook } = require("./hooks.js");
8
8
 
9
9
  /**
10
10
  * @typedef {Object} WorkerOptions custom configuration
@@ -21,7 +21,6 @@ module.exports = (...args) =>
21
21
  * @returns {Worker}
22
22
  */
23
23
  function XWorker(url, options) {
24
- const hooks = workerHooks.get(XWorker);
25
24
  const worker = xworker();
26
25
  const { postMessage } = worker;
27
26
  if (args.length) {
@@ -32,7 +31,10 @@ module.exports = (...args) =>
32
31
  if (options?.config) options.config = absoluteURL(options.config);
33
32
  const bootstrap = fetch(url)
34
33
  .then(getText)
35
- .then((code) => postMessage.call(worker, { options, code, hooks }));
34
+ .then((code) => {
35
+ const hooks = this instanceof Hook ? this : void 0;
36
+ postMessage.call(worker, { options, code, hooks });
37
+ });
36
38
  return defineProperties(worker, {
37
39
  postMessage: {
38
40
  value: (data, ...rest) =>