Skip to content

Commit 0b6131d

Browse files
Merge pull request #5 from sebastianwessel/improve_doc_and_code_cleanup
chore: Improve doc and code cleanup
2 parents f7e73df + 7eaf6d9 commit 0b6131d

23 files changed

+664
-142
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"mix.exs": ".credo.exs, .dialyzer_ignore.exs, .formatter.exs, .iex.exs, .tool-versions, mix.lock",
4747
"next.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, capacitor.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, i18n.config.*, ionic.config.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, next-env.d.ts, next-i18next.config.*, panda.config.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, sst.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, vuetify.config.*, webpack.config.*, windi.config.*",
4848
"nuxt.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .nuxtignore, .nuxtrc, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, capacitor.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, i18n.config.*, ionic.config.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, panda.config.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, sst.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, vuetify.config.*, webpack.config.*, windi.config.*",
49-
"package.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json*, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitmojirc.json, .gitpod*, .huskyrc*, .jslint*, .knip.*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .pylintrc, .release-please*.json, .releaserc*, .ruff.toml, .sentry*, .simple-git-hooks*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, biome.json*, bower.json, build.config.*, bun.lockb, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json*, electron-builder.*, eslint*, firebase.json, grunt*, gulp*, jenkins*, knip.*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-please*.json, release-tasks.sh, release.config.*, renovate*, rollup.config.*, rspack*, ruff.toml, simple-git-hooks*, sonar-project.properties, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, wrangler.toml, xo.config.*, yarn*, tsconfig.json",
49+
"package.json": "jsr.json, .browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json*, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitmojirc.json, .gitpod*, .huskyrc*, .jslint*, .knip.*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .pylintrc, .release-please*.json, .releaserc*, .ruff.toml, .sentry*, .simple-git-hooks*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, biome.json*, bower.json, build.config.*, bun.lockb, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json*, electron-builder.*, eslint*, firebase.json, grunt*, gulp*, jenkins*, knip.*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-please*.json, release-tasks.sh, release.config.*, renovate*, rollup.config.*, rspack*, ruff.toml, simple-git-hooks*, sonar-project.properties, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, wrangler.toml, xo.config.*, yarn*, tsconfig.json",
5050
"Pipfile": ".editorconfig, .flake8, .isort.cfg, .python-version, Pipfile, Pipfile.lock, requirements*.in, requirements*.pip, requirements*.txt, tox.ini",
5151
"pubspec.yaml": ".metadata, .packages, all_lint_rules.yaml, analysis_options.yaml, build.yaml, pubspec.lock, pubspec_overrides.yaml",
5252
"pyproject.toml": ".commitlint*, .dlint.json, .dprint.json*, .editorconfig, .eslint*, .flake8, .flowconfig, .isort.cfg, .jslint*, .lintstagedrc*, .markdownlint*, .pdm-python, .pdm.toml, .prettier*, .pylintrc, .python-version, .ruff.toml, .stylelint*, .textlint*, .xo-config*, .yamllint*, MANIFEST.in, Pipfile, Pipfile.lock, biome.json*, commitlint*, dangerfile*, dlint.json, dprint.json*, eslint*, hatch.toml, lint-staged*, pdm.lock, phpcs.xml, poetry.lock, poetry.toml, prettier*, pyproject.toml, pyrightconfig.json, requirements*.in, requirements*.pip, requirements*.txt, ruff.toml, setup.cfg, setup.py, stylelint*, tox.ini, tslint*, xo.config.*",

docs/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ This TypeScript package allows you to safely execute JavaScript code within a We
55
## Documentation
66

77
- [Installation](./intallation.md)
8+
- Usage and Best Practice:
9+
- [Basic understanding](./basic.md)
10+
- [Fetch in Guest System](./fetch.md)
11+
- [Custom Node Modules](./custom-modules.md)
12+
- [Custom File System](./custom-file-system.md)
813
- Compatibility:
914
- [Core Javascript](./core-js-compatibility.md)
1015
- [NodeJS](./node-compatibility.md)

docs/about.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/basic.md

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
---
2+
title: Basic Understanding
3+
description: Get a basic understanding on how to the QuickJS module works
4+
---
5+
6+
This documentation provides essential information to help you avoid common pitfalls when working with QuickJS WebAssembly runtime. The terms "host" and "guest" are used to describe your main application and the QuickJS runtime, respectively.
7+
8+
## Synchronous Execution
9+
10+
### Blocking the JavaScript Event Loop
11+
12+
When the `eval` method is called on the host, the event loop of the host system is blocked until the method returns.
13+
14+
Here is an example of how the host system can be blocked 🔥:
15+
16+
```typescript
17+
import { quickJS } from '@sebastianwessel/quickjs'
18+
19+
const { createRuntime } = await quickJS()
20+
const { evalCode } = await createRuntime()
21+
22+
setInterval(() => {
23+
console.log('y')
24+
}, 2000)
25+
26+
console.log('start')
27+
28+
const result = await evalCode(`
29+
const fn = () => new Promise(() => {
30+
while(true) {
31+
}
32+
})
33+
export default await fn()
34+
`)
35+
```
36+
37+
You might expect that this code does not block the host system, but it does, even with `await evalCode`. The host system must wait for the guest system to return a value. In this example, the value is never returned because of the endless while-loop.
38+
39+
### Setting Execution Timeouts
40+
41+
**❗ Set Execution Timeouts if Possible**
42+
It is highly recommended to set a default timeout value to avoid blocking the host system indefinitely. The execution timeout can be set in the options of `createRuntime` and `evalCode`. The smaller value between the two functions will be chosen. Setting the `executionTimeout` to `0` or `undefined` disables the execution timeout.
43+
44+
Timeout values are in seconds for better readability.
45+
46+
### Workers and Threads
47+
48+
It is **highly recommended** to run the guest system in separate workers or threads rather than the main thread. This approach has several critical benefits:
49+
50+
1. It ensures that the main event loop is not blocked.
51+
2. Multiple workers can boost performance.
52+
3. The host application can terminate a single worker anytime. If the guest system exceeds the maximum runtime, restarting the worker ensures a clean state.
53+
54+
## Asynchronous Behavior
55+
56+
The provided QuickJS WebAssembly runtime does not have an internal event loop like a regular runtime. Instead, the host system must trigger the loop for any provided promises. This library starts an interval on the host that triggers `executePendingJobs` in QuickJS. The interval is automatically stopped and removed when no longer needed.
57+
58+
When a promise is provided by the host and used by the client, the client executes until it reaches the promise. If the promise is not settled, the QuickJS runtime pauses execution. Once the promise is settled, the host needs to call `executePendingJobs` to instruct QuickJS to resume execution.
59+
60+
## Data Exchange Between Host and Guest
61+
62+
### Host to Guest
63+
64+
The host system can provide various data types to the guest system, including primitives, objects, functions, and promises. This library uses an `env` pattern for data and functions provided by the host, which is mirrored to `process.env` inside the guest system.
65+
66+
Example:
67+
68+
```typescript
69+
import { quickJS } from '@sebastianwessel/quickjs'
70+
71+
const { createRuntime } = await quickJS()
72+
73+
const keyValueStoreOnHost = new Map<string, string>()
74+
75+
const { evalCode } = await createRuntime({
76+
env: {
77+
MY_PROCESS_ENV: 'some environment variable provided by the host',
78+
KV: {
79+
set: (key: string, value: string) => keyValueStoreOnHost.set(key, value),
80+
get: (key: string) => keyValueStoreOnHost.get(key),
81+
},
82+
},
83+
})
84+
85+
const result = await evalCode(`
86+
console.log(env.MY_PROCESS_ENV)
87+
env.KV.set('guest-key', 'value set by guest system')
88+
const value = env.KV.get('guest-key')
89+
export default value
90+
`)
91+
92+
console.log('result from guest:', result.data) // result from guest: value set by guest system
93+
console.log('result from host:', keyValueStoreOnHost.get('guest-key')) // result from host: value set by guest system
94+
```
95+
96+
#### Wrapping Functions
97+
98+
If a function is provided from host to guest, it should be wrapped in a dummy function.
99+
100+
👎 **Incorrect**:
101+
102+
```typescript
103+
const { evalCode } = await createRuntime({
104+
env: {
105+
KV: {
106+
set: keyValueStoreOnHost.set,
107+
get: keyValueStoreOnHost.get,
108+
},
109+
},
110+
})
111+
```
112+
113+
👍 **Correct**:
114+
115+
```typescript
116+
const { evalCode } = await createRuntime({
117+
env: {
118+
KV: {
119+
set: (key: string, value: string) => keyValueStoreOnHost.set(key, value),
120+
get: (key: string) => keyValueStoreOnHost.get(key),
121+
},
122+
},
123+
})
124+
```
125+
126+
**🚨 Security Information ‼️**
127+
The host system only provides the given values but never reads them back. Even if the guest system modifies `env.KV.set`, it will not impact the host side.
128+
129+
Example:
130+
131+
```typescript
132+
import { quickJS } from '@sebastianwessel/quickjs'
133+
134+
const { createRuntime } = await quickJS()
135+
const keyValueStoreOnHost = new Map<string, string>()
136+
137+
const { evalCode } = await createRuntime({
138+
env: {
139+
KV: {
140+
set: (key: string, value: string) => keyValueStoreOnHost.set(key, value),
141+
get: (key: string) => keyValueStoreOnHost.get(key),
142+
},
143+
},
144+
})
145+
146+
const result = await evalCode(`
147+
env.KV.set('guest-key', 'value set by guest system')
148+
const value = env.KV.get('guest-key')
149+
env.KV.get = () => { throw new Error('Security!!!') }
150+
export default value
151+
`)
152+
153+
console.log('result from guest:', result)
154+
console.log('result from host:', keyValueStoreOnHost.get('guest-key'))
155+
```
156+
157+
### Guest to Host
158+
159+
#### Usage of Return Value
160+
161+
The guest system can return a final value using `export default`. The library sets the execution mode to `module`, expecting the executed script to provide its return value via `export default`.
162+
163+
Example:
164+
165+
```typescript
166+
import { quickJS } from '@sebastianwessel/quickjs'
167+
168+
const { createRuntime } = await quickJS()
169+
const { evalCode } = await createRuntime()
170+
171+
const result = await evalCode(`
172+
export default 'my value'
173+
`)
174+
175+
console.log('result from guest:', result.data) // result from guest: my value
176+
```
177+
178+
**❗ Promises Must Be Awaited**
179+
If the executed script returns a promise, the promise must be awaited.
180+
181+
👎 **Incorrect**:
182+
183+
```typescript
184+
import { quickJS } from '@sebastianwessel/quickjs'
185+
186+
const { createRuntime } = await quickJS()
187+
const { evalCode } = await createRuntime()
188+
189+
const result = await evalCode(`
190+
const prom = async () => {
191+
return 'my value'
192+
}
193+
export default prom() // promise is not awaited!!
194+
`)
195+
196+
console.log('result from guest:', result.data) // result from guest: my value
197+
```
198+
199+
👍 **Correct**:
200+
201+
```typescript
202+
import { quickJS } from '@sebastianwessel/quickjs'
203+
204+
const { createRuntime } = await quickJS()
205+
const { evalCode } = await createRuntime()
206+
207+
const result = await evalCode(`
208+
const prom = async () => {
209+
return 'my value'
210+
}
211+
export default await prom() // promise is awaited
212+
`)
213+
214+
console.log('result from guest:', result.data) // result from guest: my value
215+
```
216+
217+
The library wraps the result of the `eval` method into a result object, similar to the result of the `fetch` method. This makes handling success and error paths easier for developers.
218+
219+
A success response:
220+
221+
```typescript
222+
{
223+
ok: true,
224+
data: 'the return value'
225+
}
226+
```
227+
228+
An error response:
229+
230+
```typescript
231+
{
232+
ok: false,
233+
isSyntaxError: true,
234+
error: {
235+
name: "SyntaxError",
236+
message: "unexpected end of string",
237+
stack: " at /src/index.js:9:1\n",
238+
}
239+
}
240+
```
241+
242+
#### Using Provided Env-Functions
243+
244+
It is also possible to exchange values between client and host, while the guest system is running. Therefor, the recommended approach is to call functions, provided by the host, from the client system.
245+
Here, async functions are supported as well.
246+
247+
#### Setting Values in Host by Guest System (Not Recommended!)
248+
249+
The guest system can change the values in an object provided by the host. Although possible, this pattern is not recommended. Instead, provide functions to mutate the object or array on the host side, allowing for validation and additional functionality like emitting events. This approach keeps the control on the host side.
250+
251+
Example of exchanging data by changing an object's key-value:
252+
253+
```typescript
254+
import { quickJS } from '@sebastianwessel/quickjs'
255+
256+
const { createRuntime } = await quickJS()
257+
const dangerousSync = {
258+
mutable: 'init value',
259+
}
260+
261+
const { evalCode } = await createRuntime({
262+
dangerousSync,
263+
})
264+
265+
await evalCode(`
266+
__dangerousSync.mutable = 'changed by guest'
267+
`)
268+
269+
console.log(dangerousSync)
270+
```
271+
272+
**🚨 Security Advice ‼️**
273+
274+
As the guest system can access and potentially overwrite the values provided by the host, ensure that these values do not affect security. Do not provide functions and make sure the provided values are secure.

docs/custom-file-system.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Custom File System
3+
description: Learn, how you can mount your own virtual file system into the QuickJS runtime
4+
---
5+
6+
documentation soon.

0 commit comments

Comments
 (0)