mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 23:48:52 +00:00
Compare commits
1 Commits
plugin/res
...
1-runner
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d33aca0a5 |
23
.gitattributes
vendored
23
.gitattributes
vendored
@@ -8,26 +8,3 @@ src/bun.js/bindings/sqlite/sqlite3_local.h linguist-vendored
|
||||
*.zig text eol=lf
|
||||
src/bun.js/bindings/simdutf.cpp linguist-vendored
|
||||
src/bun.js/bindings/simdutf.h linguist-vendored
|
||||
|
||||
src/js/out/WebCoreJSBuiltins.cpp linguist-generated
|
||||
src/js/out/WebCoreJSBuiltins.h linguist-generated
|
||||
src/js/out/WebCoreJSBuiltins.d.ts linguist-generated
|
||||
|
||||
src/bun.js/bindings/ZigGeneratedClasses.h linguist-generated
|
||||
src/bun.js/bindings/ZigGeneratedClasses.cpp linguist-generated
|
||||
|
||||
src/bun.js/bindings/ZigGeneratedCode.h linguist-generated
|
||||
src/bun.js/bindings/ZigGeneratedCode.cpp linguist-generated
|
||||
|
||||
src/bun.js/bindings/headers.h linguist-generated
|
||||
src/bun.js/bindings/headers.zig linguist-generated
|
||||
|
||||
src/bun.js/bindings/JSSink.h linguist-generated
|
||||
src/bun.js/bindings/JSSink.zig linguist-generated
|
||||
|
||||
src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h linguist-generated
|
||||
src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h linguist-generated
|
||||
src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h linguist-generated
|
||||
src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h linguist-generated
|
||||
|
||||
docs/**/* linguist-documentation
|
||||
|
||||
3
.github/workflows/bun-deploy-site.yml
vendored
3
.github/workflows/bun-deploy-site.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
deploy:
|
||||
name: Deploy site
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
steps:
|
||||
- name: Trigger Vercel build
|
||||
run: curl ${{ secrets.VERCEL_DEPLOY_HOOK }}
|
||||
run: curl ${{ secrets.VERCEL_DEPLOY_HOOK }}
|
||||
1
.github/workflows/bun-ecosystem-test.yml
vendored
1
.github/workflows/bun-ecosystem-test.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
||||
test:
|
||||
name: ${{ matrix.tag }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
6
.github/workflows/bun-linux-aarch64.yml
vendored
6
.github/workflows/bun-linux-aarch64.yml
vendored
@@ -1,9 +1,4 @@
|
||||
name: bun-linux
|
||||
|
||||
concurrency:
|
||||
group: bun-linux-aarch64-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
@@ -26,7 +21,6 @@ jobs:
|
||||
linux:
|
||||
name: ${{matrix.tag}}
|
||||
runs-on: ${{matrix.runner}}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
11
.github/workflows/bun-linux-build.yml
vendored
11
.github/workflows/bun-linux-build.yml
vendored
@@ -1,9 +1,4 @@
|
||||
name: bun-linux
|
||||
|
||||
concurrency:
|
||||
group: bun-linux-build-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
@@ -35,7 +30,6 @@ jobs:
|
||||
linux:
|
||||
name: ${{matrix.tag}}
|
||||
runs-on: ${{matrix.runner}}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -190,13 +184,12 @@ jobs:
|
||||
sudo mv bun /usr/local/bin/bun
|
||||
bun --version
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
# if: ${{github.event.inputs.use_bun == 'false'}}
|
||||
name: Test
|
||||
run: |
|
||||
bun install
|
||||
bun install --cwd test
|
||||
bun install --cwd packages/bun-internal-test
|
||||
node packages/bun-internal-test/src/runner.node.mjs || true
|
||||
bun run test || true
|
||||
- name: Comment on PR
|
||||
if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
|
||||
15
.github/workflows/bun-mac-aarch64.yml
vendored
15
.github/workflows/bun-mac-aarch64.yml
vendored
@@ -1,9 +1,4 @@
|
||||
name: bun-macOS-aarch64
|
||||
|
||||
concurrency:
|
||||
group: bun-macOS-aarch64-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
@@ -33,7 +28,6 @@ jobs:
|
||||
macos-object-files:
|
||||
name: macOS Object
|
||||
runs-on: med-ubuntu
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -106,7 +100,6 @@ jobs:
|
||||
macOS-cpp:
|
||||
name: macOS C++
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -244,7 +237,6 @@ jobs:
|
||||
macOS:
|
||||
name: macOS Link
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
needs: [macOS-cpp, macos-object-files]
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
@@ -396,7 +388,7 @@ jobs:
|
||||
name: Tests ${{matrix.tag}}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: [macOS]
|
||||
if: github.event_name == 'pull_request' && github.repository_owner == 'oven-sh'
|
||||
if: github.event_name == 'pull_request'
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
failing_tests: ${{ steps.test.outputs.failing_tests }}
|
||||
@@ -429,13 +421,12 @@ jobs:
|
||||
sudo mv bun /usr/local/bin/bun
|
||||
bun --version
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
# if: ${{github.event.inputs.use_bun == 'false'}}
|
||||
name: Test
|
||||
run: |
|
||||
bun install
|
||||
bun install --cwd test
|
||||
bun install --cwd packages/bun-internal-test
|
||||
node packages/bun-internal-test/src/runner.node.mjs || true
|
||||
bun run test || true
|
||||
- name: Comment on PR
|
||||
if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
|
||||
15
.github/workflows/bun-mac-x64-baseline.yml
vendored
15
.github/workflows/bun-mac-x64-baseline.yml
vendored
@@ -1,9 +1,4 @@
|
||||
name: bun-macOS-x64-baseline
|
||||
|
||||
concurrency:
|
||||
group: bun-macOS-x64-baseline-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
@@ -33,7 +28,6 @@ jobs:
|
||||
macos-object-files:
|
||||
name: macOS Object
|
||||
runs-on: med-ubuntu
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -106,7 +100,6 @@ jobs:
|
||||
macOS-cpp:
|
||||
name: macOS C++
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -245,7 +238,6 @@ jobs:
|
||||
macOS:
|
||||
name: macOS Link
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
needs: [macOS-cpp, macos-object-files]
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
@@ -400,7 +392,7 @@ jobs:
|
||||
name: Tests ${{matrix.tag}}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: [macOS]
|
||||
if: github.event_name == 'pull_request' && github.repository_owner == 'oven-sh'
|
||||
if: github.event_name == 'pull_request'
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
failing_tests: ${{ steps.test.outputs.failing_tests }}
|
||||
@@ -433,13 +425,12 @@ jobs:
|
||||
sudo mv bun /usr/local/bin/bun
|
||||
bun --version
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
# if: ${{github.event.inputs.use_bun == 'false'}}
|
||||
name: Test
|
||||
run: |
|
||||
bun install
|
||||
bun install --cwd test
|
||||
bun install --cwd packages/bun-internal-test
|
||||
node packages/bun-internal-test/src/runner.node.mjs || true
|
||||
bun run test || true
|
||||
- name: Comment on PR
|
||||
if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
|
||||
15
.github/workflows/bun-mac-x64.yml
vendored
15
.github/workflows/bun-mac-x64.yml
vendored
@@ -1,9 +1,4 @@
|
||||
name: bun-macOS-x64
|
||||
|
||||
concurrency:
|
||||
group: bun-macOS-x64-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
@@ -33,7 +28,6 @@ jobs:
|
||||
macos-object-files:
|
||||
name: macOS Object
|
||||
runs-on: med-ubuntu
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -106,7 +100,6 @@ jobs:
|
||||
macOS-cpp:
|
||||
name: macOS C++
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -247,7 +240,6 @@ jobs:
|
||||
macOS:
|
||||
name: macOS Link
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
needs: [macOS-cpp, macos-object-files]
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
@@ -402,7 +394,7 @@ jobs:
|
||||
name: Tests ${{matrix.tag}}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: [macOS]
|
||||
if: github.event_name == 'pull_request' && github.repository_owner == 'oven-sh'
|
||||
if: github.event_name == 'pull_request'
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
failing_tests: ${{ steps.test.outputs.failing_tests }}
|
||||
@@ -435,13 +427,12 @@ jobs:
|
||||
sudo mv bun /usr/local/bin/bun
|
||||
bun --version
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
# if: ${{github.event.inputs.use_bun == 'false'}}
|
||||
name: Test
|
||||
run: |
|
||||
bun install
|
||||
bun install --cwd test
|
||||
bun install --cwd packages/bun-internal-test
|
||||
node packages/bun-internal-test/src/runner.node.mjs || true
|
||||
bun run test || true
|
||||
- name: Comment on PR
|
||||
if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
|
||||
4
.github/workflows/bun-release-canary.yml
vendored
4
.github/workflows/bun-release-canary.yml
vendored
@@ -8,7 +8,6 @@ jobs:
|
||||
sign:
|
||||
name: Sign Release
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-release
|
||||
@@ -41,7 +40,6 @@ jobs:
|
||||
name: Release to NPM
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-release
|
||||
@@ -110,7 +108,6 @@ jobs:
|
||||
name: Release to Dockerhub
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
steps:
|
||||
- id: checkout
|
||||
name: Checkout
|
||||
@@ -152,7 +149,6 @@ jobs:
|
||||
name: Upload to S3
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-release
|
||||
|
||||
@@ -11,7 +11,6 @@ jobs:
|
||||
npm-types:
|
||||
name: Release types to NPM
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-types
|
||||
|
||||
6
.github/workflows/bun-release.yml
vendored
6
.github/workflows/bun-release.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
||||
sign:
|
||||
name: Sign Release
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-release
|
||||
@@ -54,7 +53,6 @@ jobs:
|
||||
name: Release to NPM
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-release
|
||||
@@ -87,7 +85,6 @@ jobs:
|
||||
name: Release types to NPM
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-types
|
||||
@@ -130,7 +127,6 @@ jobs:
|
||||
name: Release to Dockerhub
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
steps:
|
||||
- id: checkout
|
||||
name: Checkout
|
||||
@@ -181,7 +177,6 @@ jobs:
|
||||
name: Release to Homebrew
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
steps:
|
||||
- id: checkout
|
||||
name: Checkout
|
||||
@@ -223,7 +218,6 @@ jobs:
|
||||
name: Upload to S3
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
if: github.repository_owner == 'oven-sh'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/bun-release
|
||||
|
||||
59
.github/workflows/bun-test.yml
vendored
Normal file
59
.github/workflows/bun-test.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "The version of Bun to run"
|
||||
required: true
|
||||
default: "canary"
|
||||
type: string
|
||||
path:
|
||||
description: "The path to the test files"
|
||||
required: true
|
||||
default: "test/"
|
||||
type: string
|
||||
timeout:
|
||||
description: "The timeout per file in milliseconds"
|
||||
required: true
|
||||
default: "15000"
|
||||
type: number
|
||||
jobs:
|
||||
test:
|
||||
name: ${{ matrix.tag }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
tag: linux-x64
|
||||
url: linux/x64?avx2=true
|
||||
- os: ubuntu-latest
|
||||
tag: linux-x64-baseline
|
||||
url: linux/x64?baseline=true
|
||||
# FIXME: runner fails with "No tests found"?
|
||||
#- os: macos-latest
|
||||
# tag: darwin-x64
|
||||
# url: darwin/x64?avx2=true
|
||||
- os: macos-latest
|
||||
tag: darwin-x64-baseline
|
||||
url: darwin/x64?baseline=true
|
||||
steps:
|
||||
- id: checkout
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: false
|
||||
- id: setup
|
||||
name: Setup
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-download-url: https://bun.sh/download/${{ github.event.inputs.version }}/${{ matrix.url }}
|
||||
- id: test
|
||||
name: Test
|
||||
run: |
|
||||
bun install
|
||||
bun install --cwd test
|
||||
bun x ts-node --esm .github/scripts/test-runner.ts ${{ github.event.inputs.path }} --isolated --timeout=${{ github.event.inputs.timeout }}
|
||||
34
.github/workflows/bun-typecheck.yml
vendored
Normal file
34
.github/workflows/bun-typecheck.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: typecheck
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/bun-types/**"
|
||||
- "test/**"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "packages/bun-types/**"
|
||||
- "test/**"
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: check-tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: canary
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
working-directory: test
|
||||
- name: Typecheck tests
|
||||
run: bun run typecheck
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -119,5 +119,3 @@ myscript.sh
|
||||
|
||||
cold-jsc-start
|
||||
cold-jsc-start.d
|
||||
|
||||
/test.ts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
src/fallback.html
|
||||
src/bun.js/WebKit
|
||||
src/js/out
|
||||
src/bun.js/builtins/js/*.js
|
||||
src/*.out.js
|
||||
src/*out.*.js
|
||||
src/deps
|
||||
@@ -11,3 +11,4 @@ test/snapshots-no-hmr
|
||||
test/js/deno/*.test.ts
|
||||
test/js/deno/**/*.test.ts
|
||||
bench/react-hello-world/react-hello-world.node.js
|
||||
src/bun.js/builtins/WebCoreJSBuiltins.d.ts
|
||||
|
||||
8
.vscode/c_cpp_properties.json
vendored
8
.vscode/c_cpp_properties.json
vendored
@@ -15,8 +15,8 @@
|
||||
"${workspaceFolder}/src/bun.js/bindings/webcore/",
|
||||
"${workspaceFolder}/src/bun.js/bindings/sqlite/",
|
||||
"${workspaceFolder}/src/bun.js/bindings/webcrypto/",
|
||||
"${workspaceFolder}/src/js/builtins/",
|
||||
"${workspaceFolder}/src/js/out",
|
||||
"${workspaceFolder}/src/bun.js/builtins/",
|
||||
"${workspaceFolder}/src/bun.js/builtins/cpp",
|
||||
"${workspaceFolder}/src/deps/boringssl/include/",
|
||||
"${workspaceFolder}/src/deps",
|
||||
"${workspaceFolder}/src/deps/uws/uSockets/src"
|
||||
@@ -34,8 +34,8 @@
|
||||
"${workspaceFolder}/src/bun.js/bindings/sqlite/",
|
||||
"${workspaceFolder}/src/bun.js/bindings/webcrypto/",
|
||||
"${workspaceFolder}/src/bun.js/bindings/webcore/",
|
||||
"${workspaceFolder}/src/js/builtins/*",
|
||||
"${workspaceFolder}/src/js/out/*",
|
||||
"${workspaceFolder}/src/bun.js/builtins/*",
|
||||
"${workspaceFolder}/src/bun.js/builtins/cpp/*",
|
||||
"${workspaceFolder}/src/bun.js/modules/*",
|
||||
"${workspaceFolder}/src/deps",
|
||||
"${workspaceFolder}/src/deps/boringssl/include/",
|
||||
|
||||
225
.vscode/launch.json
generated
vendored
225
.vscode/launch.json
generated
vendored
@@ -1,195 +1,130 @@
|
||||
{
|
||||
// The usage of BUN_GARBAGE_COLLECTOR_LEVEL=2 is important for debugging
|
||||
// It will force the garbage collector to run after every test and every call to expect()
|
||||
// it makes our tests very slow
|
||||
// But it helps catch memory bugs
|
||||
|
||||
// SIGHUP must be ignored or the debugger will pause when a spawned subprocess exits:
|
||||
// { "initCommands": ["process handle -p false -s false -n false SIGHUP"] }
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [file]",
|
||||
"name": "bun test",
|
||||
"program": "bun-debug",
|
||||
"args": ["test", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1",
|
||||
"BUN_GARBAGE_COLLECTOR_LEVEL": "2"
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [file] (fast)",
|
||||
"name": "bun test --watch",
|
||||
"program": "bun-debug",
|
||||
"args": ["test", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"args": ["--watch", "test", "${file}"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test (all)",
|
||||
"program": "bun-debug",
|
||||
"args": ["test"],
|
||||
"cwd": "${workspaceFolder}/test",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run current file",
|
||||
"program": "bun-debug",
|
||||
"args": ["${file}"],
|
||||
"cwd": "${file}/../../",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run (watch)",
|
||||
"program": "bun-debug",
|
||||
"args": ["--watch", "${file}"],
|
||||
"cwd": "${file}/../../",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run (hot)",
|
||||
"program": "bun-debug",
|
||||
"args": ["--hot", "${file}"],
|
||||
"cwd": "${file}/../../",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run",
|
||||
"program": "bun-debug",
|
||||
"args": ["check.tsx", "-c"],
|
||||
"cwd": "${env:HOME}/Build/react-ssr",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [file] (verbose)",
|
||||
"name": "bun http example",
|
||||
"program": "bun-debug",
|
||||
"args": ["test", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"args": ["run", "examples/http.ts"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [file] --watch",
|
||||
"name": "bun http file example",
|
||||
"program": "bun-debug",
|
||||
"args": ["test", "--watch", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [file] --only",
|
||||
"program": "bun-debug",
|
||||
"args": ["test", "--only", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [*]",
|
||||
"program": "bun-debug",
|
||||
"args": ["test"],
|
||||
"cwd": "${workspaceFolder}/test",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1",
|
||||
"BUN_GARBAGE_COLLECTOR_LEVEL": "2"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [*] (fast)",
|
||||
"program": "bun-debug",
|
||||
"args": ["test"],
|
||||
"cwd": "${workspaceFolder}/test",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun test [*] --only",
|
||||
"program": "bun-debug",
|
||||
"args": ["test", "--only"],
|
||||
"cwd": "${workspaceFolder}/test",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run [file]",
|
||||
"program": "bun-debug",
|
||||
"args": ["run", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run [file] (gc)",
|
||||
"program": "bun-debug",
|
||||
"args": ["run", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1",
|
||||
"BUN_GARBAGE_COLLECTOR_LEVEL": "2"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run [file] (verbose)",
|
||||
"program": "bun-debug",
|
||||
"args": ["run", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"args": ["run", "examples/bun/http-file.ts"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run [file] --watch",
|
||||
"name": "bun html-rewriter example",
|
||||
"program": "bun-debug",
|
||||
"args": ["run", "--watch", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"args": ["run", "examples/bun/html-rewriter.ts"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run [file] --hot",
|
||||
"program": "bun-debug",
|
||||
"args": ["run", "--hot", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
@@ -204,14 +139,12 @@
|
||||
"request": "launch",
|
||||
"name": "bun build debug",
|
||||
"program": "bun-debug",
|
||||
"args": ["bun", "${file}"],
|
||||
"args": ["bun", "./test/fixtures/bundle/trivial/index.js"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "internalConsole",
|
||||
"env": {
|
||||
"BUN_CONFIG_MINIFY_WHITESPACE": "1"
|
||||
},
|
||||
// SIGHUP must be ignored or the debugger will pause when a spawned subprocess exits.
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
|
||||
21
.vscode/settings.json
vendored
21
.vscode/settings.json
vendored
@@ -2,7 +2,7 @@
|
||||
"git.autoRepositoryDetection": "openEditors",
|
||||
"search.quickOpen.includeSymbols": false,
|
||||
"search.seedWithNearestWord": true,
|
||||
"search.smartCase": false,
|
||||
"search.smartCase": true,
|
||||
"search.exclude": {},
|
||||
"search.followSymlinks": false,
|
||||
"search.useIgnoreFiles": true,
|
||||
@@ -37,6 +37,8 @@
|
||||
"[yaml]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"zig.beforeDebugCmd": "make build-unit ${file} ${filter} ${bin}",
|
||||
"zig.testCmd": "make test ${file} ${filter} ${bin}",
|
||||
"[markdown]": {
|
||||
"editor.unicodeHighlight.ambiguousCharacters": false,
|
||||
"editor.unicodeHighlight.invisibleCharacters": false,
|
||||
@@ -76,8 +78,7 @@
|
||||
"src/deps/c-ares": true,
|
||||
"src/deps/tinycc": true,
|
||||
"src/deps/zstd": true,
|
||||
"test/snippets/package-json-exports/_node_modules_copy": true,
|
||||
"src/js/out": true
|
||||
"test/snippets/package-json-exports/_node_modules_copy": true
|
||||
},
|
||||
"C_Cpp.files.exclude": {
|
||||
"**/.vscode": true,
|
||||
@@ -107,6 +108,11 @@
|
||||
"editor.defaultFormatter": "xaver.clang-format"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.{setting,comp,fuse,fu}": "lua",
|
||||
"*.ctk": "json",
|
||||
"*.json5": "json5",
|
||||
"*.json": "jsonc",
|
||||
"*.scriptlib": "lua",
|
||||
"*.lock": "yarnlock",
|
||||
"*.idl": "cpp",
|
||||
"memory": "cpp",
|
||||
@@ -215,7 +221,14 @@
|
||||
"regex": "cpp",
|
||||
"span": "cpp",
|
||||
"valarray": "cpp",
|
||||
"codecvt": "cpp"
|
||||
"codecvt": "cpp",
|
||||
"hash_map": "cpp",
|
||||
"source_location": "cpp",
|
||||
"numbers": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"stdfloat": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"cfenv": "cpp"
|
||||
},
|
||||
"cmake.configureOnOpen": false,
|
||||
"C_Cpp.errorSquiggles": "enabled",
|
||||
|
||||
@@ -59,9 +59,7 @@ The module loader is in [`src/bun.js/module_loader.zig`](src/bun.js/module_loade
|
||||
|
||||
### JavaScript Builtins
|
||||
|
||||
TODO: update this with the new build process that uses TypeScript and `$` instead of `@`.
|
||||
|
||||
JavaScript builtins are located in [`src/js/builtins/*.ts`](src/js/builtins).
|
||||
JavaScript builtins are located in [`src/bun.js/builtins/*.js`](src/bun.js/builtins).
|
||||
|
||||
These files support a JavaScriptCore-only syntax for internal slots. `@` is used to access an internal slot. For example: `new @Array(123)` will create a new `Array` similar to `new Array(123)`, except if a library modifies the `Array` global, it will not affect the internal slot (`@Array`). These names must be allow-listed in `BunBuiltinNames.h` (though JavaScriptCore allowlists some names by default).
|
||||
|
||||
|
||||
80
Makefile
80
Makefile
@@ -20,7 +20,6 @@ CPU_TARGET ?= native
|
||||
MARCH_NATIVE = -mtune=$(CPU_TARGET)
|
||||
NATIVE_OR_OLD_MARCH =
|
||||
|
||||
MMD_IF_LOCAL =
|
||||
DEFAULT_MIN_MACOS_VERSION=
|
||||
ARCH_NAME :=
|
||||
DOCKER_BUILDARCH =
|
||||
@@ -42,14 +41,8 @@ endif
|
||||
MIN_MACOS_VERSION ?= $(DEFAULT_MIN_MACOS_VERSION)
|
||||
BUN_BASE_VERSION = 0.6
|
||||
|
||||
CI ?= false
|
||||
|
||||
AR=
|
||||
|
||||
ifeq ($(CI), false)
|
||||
MMD_IF_LOCAL = -MMD
|
||||
endif
|
||||
|
||||
BUN_OR_NODE = $(shell which bun 2>/dev/null || which node 2>/dev/null)
|
||||
|
||||
CXX_VERSION=c++2a
|
||||
@@ -293,14 +286,14 @@ SRC_WEBCORE_FILES := $(wildcard $(SRC_DIR)/webcore/*.cpp)
|
||||
SRC_SQLITE_FILES := $(wildcard $(SRC_DIR)/sqlite/*.cpp)
|
||||
SRC_NODE_OS_FILES := $(wildcard $(SRC_DIR)/node_os/*.cpp)
|
||||
SRC_IO_FILES := $(wildcard src/io/*.cpp)
|
||||
SRC_BUILTINS_FILES := $(wildcard src/js/out/*.cpp)
|
||||
SRC_BUILTINS_FILES := $(wildcard src/bun.js/builtins/*.cpp)
|
||||
SRC_WEBCRYPTO_FILES := $(wildcard $(SRC_DIR)/webcrypto/*.cpp)
|
||||
|
||||
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRC_FILES))
|
||||
WEBCORE_OBJ_FILES := $(patsubst $(SRC_DIR)/webcore/%.cpp,$(OBJ_DIR)/%.o,$(SRC_WEBCORE_FILES))
|
||||
SQLITE_OBJ_FILES := $(patsubst $(SRC_DIR)/sqlite/%.cpp,$(OBJ_DIR)/%.o,$(SRC_SQLITE_FILES))
|
||||
NODE_OS_OBJ_FILES := $(patsubst $(SRC_DIR)/node_os/%.cpp,$(OBJ_DIR)/%.o,$(SRC_NODE_OS_FILES))
|
||||
BUILTINS_OBJ_FILES := $(patsubst src/js/out/%.cpp,$(OBJ_DIR)/%.o,$(SRC_BUILTINS_FILES))
|
||||
BUILTINS_OBJ_FILES := $(patsubst src/bun.js/builtins/%.cpp,$(OBJ_DIR)/%.o,$(SRC_BUILTINS_FILES))
|
||||
IO_FILES := $(patsubst src/io/%.cpp,$(OBJ_DIR)/%.o,$(SRC_IO_FILES))
|
||||
MODULES_OBJ_FILES := $(patsubst $(MODULES_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(MODULES_FILES))
|
||||
WEBCRYPTO_OBJ_FILES := $(patsubst $(SRC_DIR)/webcrypto/%.cpp,$(OBJ_DIR)/%.o,$(SRC_WEBCRYPTO_FILES))
|
||||
@@ -309,7 +302,7 @@ DEBUG_OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_FILES)
|
||||
DEBUG_WEBCORE_OBJ_FILES := $(patsubst $(SRC_DIR)/webcore/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_WEBCORE_FILES))
|
||||
DEBUG_SQLITE_OBJ_FILES := $(patsubst $(SRC_DIR)/sqlite/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_SQLITE_FILES))
|
||||
DEBUG_NODE_OS_OBJ_FILES := $(patsubst $(SRC_DIR)/node_os/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_NODE_OS_FILES))
|
||||
DEBUG_BUILTINS_OBJ_FILES := $(patsubst src/js/out/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_BUILTINS_FILES))
|
||||
DEBUG_BUILTINS_OBJ_FILES := $(patsubst src/bun.js/builtins/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_BUILTINS_FILES))
|
||||
DEBUG_IO_FILES := $(patsubst src/io/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(SRC_IO_FILES))
|
||||
DEBUG_MODULES_OBJ_FILES := $(patsubst $(MODULES_DIR)/%.cpp,$(DEBUG_OBJ_DIR)/%.o,$(MODULES_FILES))
|
||||
DEBUG_WEBCRYPTO_OBJ_FILES := $(patsubst $(SRC_DIR)/webcrypto/%.cpp, $(DEBUG_OBJ_DIR)/%.o, $(SRC_WEBCRYPTO_FILES))
|
||||
@@ -330,12 +323,12 @@ ALL_JSC_INCLUDE_DIRS := -I$(WEBKIT_RELEASE_DIR)/WTF/Headers \
|
||||
-I$(WEBKIT_RELEASE_DIR)/WTF/PrivateHeaders
|
||||
|
||||
SHARED_INCLUDE_DIR = -I$(realpath src/bun.js/bindings)/ \
|
||||
-I$(realpath src/js/builtins/) \
|
||||
-I$(realpath src/js/out/) \
|
||||
-I$(realpath src/bun.js/builtins/) \
|
||||
-I$(realpath src/bun.js/bindings) \
|
||||
-I$(realpath src/bun.js/bindings/webcore) \
|
||||
-I$(realpath src/bun.js/bindings/webcrypto) \
|
||||
-I$(realpath src/bun.js/bindings/sqlite) \
|
||||
-I$(realpath src/bun.js/builtins/cpp) \
|
||||
-I$(realpath src/bun.js/bindings/node_os) \
|
||||
-I$(realpath src/bun.js/modules) \
|
||||
-I$(JSC_INCLUDE_DIR)
|
||||
@@ -555,11 +548,7 @@ PYTHON=$(shell which python 2>/dev/null || which python3 2>/dev/null || which py
|
||||
|
||||
.PHONY: builtins
|
||||
builtins:
|
||||
NODE_ENV=production bun src/js/builtins/codegen/index.ts --minify
|
||||
|
||||
.PHONY: esm
|
||||
esm:
|
||||
NODE_ENV=production bun src/js/build-esm.ts
|
||||
bun src/bun.js/builtins/codegen/index.ts --minify
|
||||
|
||||
.PHONY: generate-builtins
|
||||
generate-builtins: builtins
|
||||
@@ -677,7 +666,6 @@ require:
|
||||
@which aclocal > /dev/null || (echo -e "ERROR: automake is required. Install with:\n\n $(POSIX_PKG_MANAGER) install automake"; exit 1)
|
||||
@which $(LIBTOOL) > /dev/null || (echo -e "ERROR: libtool is required. Install with:\n\n $(POSIX_PKG_MANAGER) install libtool"; exit 1)
|
||||
@which ninja > /dev/null || (echo -e "ERROR: Ninja is required. Install with:\n\n $(POSIX_PKG_MANAGER) install $(PKGNAME_NINJA)"; exit 1)
|
||||
@which pkg-config > /dev/null || (echo -e "ERROR: pkg-config is required. Install with:\n\n $(POSIX_PKG_MANAGER) install pkg-config"; exit 1)
|
||||
@echo "You have the dependencies installed! Woo"
|
||||
|
||||
init-submodules:
|
||||
@@ -1085,7 +1073,7 @@ dev-obj-linux:
|
||||
$(ZIG) build obj -Dtarget=x86_64-linux-gnu -Dcpu="$(CPU_TARGET)"
|
||||
|
||||
.PHONY: dev
|
||||
dev: mkdir-dev esm dev-obj link
|
||||
dev: mkdir-dev dev-obj bun-link-lld-debug
|
||||
|
||||
mkdir-dev:
|
||||
mkdir -p $(DEBUG_PACKAGE_DIR)
|
||||
@@ -1356,21 +1344,15 @@ mimalloc-wasm:
|
||||
cd $(BUN_DEPS_DIR)/mimalloc; emcmake cmake -DMI_BUILD_SHARED=OFF -DMI_BUILD_STATIC=ON -DMI_BUILD_TESTS=OFF -DMI_BUILD_OBJECT=ON ${MIMALLOC_OVERRIDE_FLAG} -DMI_USE_CXX=ON .; emmake make;
|
||||
cp $(BUN_DEPS_DIR)/mimalloc/$(MIMALLOC_INPUT_PATH) $(BUN_DEPS_OUT_DIR)/$(MIMALLOC_FILE).wasm
|
||||
|
||||
# alias for link, incase anyone still types that
|
||||
.PHONY: bun-link-lld-debug
|
||||
bun-link-lld-debug: link
|
||||
|
||||
# link a debug build of bun
|
||||
.PHONY: link
|
||||
link:
|
||||
bun-link-lld-debug:
|
||||
$(CXX) $(BUN_LLD_FLAGS_DEBUG) $(DEBUG_FLAGS) $(SYMBOLS) \
|
||||
-g \
|
||||
$(DEBUG_BIN)/bun-debug.o \
|
||||
-W \
|
||||
-o $(DEBUG_BIN)/bun-debug
|
||||
@rm -f $(DEBUG_BIN)/bun-debug.o.o 2> /dev/null # workaround for https://github.com/ziglang/zig/issues/14080
|
||||
@rm -f $(DEBUG_BIN)/bun-debug.o.o 2> /dev/null # workaround for https://github.com/ziglang/zig/issues/14080
|
||||
|
||||
link-no-jsc:
|
||||
bun-link-lld-debug-no-jsc:
|
||||
$(CXX) $(BUN_LLD_FLAGS_WITHOUT_JSC) $(SYMBOLS) \
|
||||
-g \
|
||||
$(DEBUG_BIN)/bun-debug.o \
|
||||
@@ -1480,7 +1462,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) $(UWS_INCLUDE) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1491,7 +1473,7 @@ $(OBJ_DIR)/%.o: src/bun.js/modules/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) $(UWS_INCLUDE) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1502,7 +1484,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/webcore/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1513,7 +1495,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/sqlite/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1524,7 +1506,7 @@ $(OBJ_DIR)/%.o: src/io/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1535,18 +1517,18 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/node_os/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
$(EMIT_LLVM) \
|
||||
-c -o $@ $<
|
||||
|
||||
$(OBJ_DIR)/%.o: src/js/out/%.cpp
|
||||
$(OBJ_DIR)/%.o: src/bun.js/builtins/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1558,7 +1540,7 @@ $(OBJ_DIR)/%.o: src/bun.js/bindings/webcrypto/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1572,7 +1554,7 @@ $(DEBUG_OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) $(UWS_INCLUDE) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1587,7 +1569,7 @@ $(DEBUG_OBJ_DIR)/%.o: $(SRC_DIR)/webcore/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1600,7 +1582,7 @@ $(DEBUG_OBJ_DIR)/%.o: src/io/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1616,7 +1598,7 @@ $(DEBUG_OBJ_DIR)/%.o: $(SRC_DIR)/sqlite/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1631,7 +1613,7 @@ $(DEBUG_OBJ_DIR)/%.o: $(SRC_DIR)/node_os/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1641,12 +1623,12 @@ $(DEBUG_OBJ_DIR)/%.o: $(SRC_DIR)/node_os/%.cpp
|
||||
|
||||
# $(DEBUG_OBJ_DIR) is not included here because it breaks
|
||||
# detecting if a file needs to be rebuilt
|
||||
.PHONY: src/js/out/builtins/%.cpp
|
||||
$(DEBUG_OBJ_DIR)/%.o: src/js/out/%.cpp
|
||||
.PHONY: src/bun.js/builtins/%.cpp
|
||||
$(DEBUG_OBJ_DIR)/%.o: src/bun.js/builtins/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1659,7 +1641,7 @@ $(DEBUG_OBJ_DIR)/%.o: src/bun.js/modules/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1673,7 +1655,7 @@ $(DEBUG_OBJ_DIR)/%.o: src/bun.js/bindings/webcrypto/%.cpp
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(DEBUG_OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-I$(SRC_DIR) \
|
||||
-fno-rtti \
|
||||
@@ -1853,7 +1835,7 @@ cold-jsc-start:
|
||||
$(CXX_WITH_CCACHE) $(CLANG_FLAGS) \
|
||||
$(MACOS_MIN_FLAG) \
|
||||
$(OPTIMIZATION_LEVEL) \
|
||||
${MMD_IF_LOCAL} \
|
||||
-MMD \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-ferror-limit=1000 \
|
||||
@@ -1885,7 +1867,7 @@ regenerate-bindings:
|
||||
@make bindings -j$(CPU_COUNT)
|
||||
|
||||
.PHONY: setup
|
||||
setup: vendor-dev identifier-cache clean-bindings
|
||||
setup: vendor-dev builtins identifier-cache clean-bindings
|
||||
make jsc-check
|
||||
make bindings -j$(CPU_COUNT)
|
||||
@echo ""
|
||||
|
||||
BIN
bench/bun.lockb
BIN
bench/bun.lockb
Binary file not shown.
@@ -1,31 +0,0 @@
|
||||
import EventEmitter3 from "eventemitter3";
|
||||
import { group } from "mitata";
|
||||
import EventEmitterNative from "node:events";
|
||||
|
||||
export const implementations = [
|
||||
{
|
||||
EventEmitter: EventEmitterNative,
|
||||
name: process.isBun ? (EventEmitterNative.init ? "bun" : "C++") : "node:events",
|
||||
monkey: true,
|
||||
},
|
||||
// { EventEmitter: EventEmitter3, name: "EventEmitter3" },
|
||||
].filter(Boolean);
|
||||
|
||||
for (const impl of implementations) {
|
||||
impl.EventEmitter?.setMaxListeners?.(Infinity);
|
||||
}
|
||||
|
||||
export function groupForEmitter(name, cb) {
|
||||
if (implementations.length === 1) {
|
||||
return cb({
|
||||
...implementations[0],
|
||||
name: `${name}: ${implementations[0].name}`,
|
||||
});
|
||||
} else {
|
||||
return group(name, () => {
|
||||
for (let impl of implementations) {
|
||||
cb(impl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import { bench, run } from "mitata";
|
||||
import { groupForEmitter } from "./implementations.mjs";
|
||||
|
||||
var id = 0;
|
||||
|
||||
groupForEmitter("single emit", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(name, () => {
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
groupForEmitter("on x 10_000 (handler)", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
bench(name, () => {
|
||||
var cb = event => {
|
||||
event.preventDefault();
|
||||
};
|
||||
emitter.on("hey", cb);
|
||||
var called = false;
|
||||
for (let i = 0; i < 10_000; i++)
|
||||
emitter.emit("hey", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
called = true;
|
||||
},
|
||||
});
|
||||
|
||||
if (!called) throw new Error("not called");
|
||||
});
|
||||
});
|
||||
|
||||
// for (let { impl: EventEmitter, name, monkey } of []) {
|
||||
// if (monkey) {
|
||||
// var monkeyEmitter = Object.assign({}, EventEmitter.prototype);
|
||||
// monkeyEmitter.on("hello", event => {
|
||||
// event.preventDefault();
|
||||
// });
|
||||
|
||||
// bench(`[monkey] ${className}.emit`, () => {
|
||||
// var called = false;
|
||||
// monkeyEmitter.emit("hello", {
|
||||
// preventDefault() {
|
||||
// id++;
|
||||
// called = true;
|
||||
// },
|
||||
// });
|
||||
|
||||
// if (!called) {
|
||||
// throw new Error("monkey failed");
|
||||
// }
|
||||
// });
|
||||
|
||||
// bench(`[monkey] ${className}.on x 10_000 (handler)`, () => {
|
||||
// var cb = () => {
|
||||
// event.preventDefault();
|
||||
// };
|
||||
// monkeyEmitter.on("hey", cb);
|
||||
// for (let i = 0; i < 10_000; i++)
|
||||
// monkey.emit("hey", {
|
||||
// preventDefault() {
|
||||
// id++;
|
||||
// },
|
||||
// });
|
||||
// monkeyEmitter.off("hey", cb);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// var target = new EventTarget();
|
||||
// target.addEventListener("hello", event => {});
|
||||
// bench("EventTarget.dispatch", () => {
|
||||
// target.dispatchEvent(event);
|
||||
// });
|
||||
|
||||
// var hey = new Event("hey");
|
||||
|
||||
// bench("EventTarget.on x 10_000 (handler)", () => {
|
||||
// var handler = event => {};
|
||||
// target.addEventListener("hey", handler);
|
||||
|
||||
// for (let i = 0; i < 10_000; i++) target.dispatchEvent(hey);
|
||||
// target.removeEventListener("hey", handler);
|
||||
// });
|
||||
|
||||
await run();
|
||||
@@ -1,40 +0,0 @@
|
||||
import { bench, run } from "mitata";
|
||||
import { groupForEmitter } from "./implementations.mjs";
|
||||
|
||||
var id = 0;
|
||||
|
||||
groupForEmitter("test 1", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(name, () => {
|
||||
emitter.once("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
groupForEmitter("test 2", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
bench(name, () => {
|
||||
emitter.once("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -1,63 +0,0 @@
|
||||
import { bench, run } from "mitata";
|
||||
import { groupForEmitter } from "./implementations.mjs";
|
||||
|
||||
// Psuedo RNG is derived from https://stackoverflow.com/a/424445
|
||||
let rngState = 123456789;
|
||||
function nextInt() {
|
||||
const m = 0x80000000; // 2**31;
|
||||
const a = 1103515245;
|
||||
const c = 12345;
|
||||
rngState = (a * rngState + c) % m;
|
||||
return rngState;
|
||||
}
|
||||
function nextRange(start, end) {
|
||||
// returns in range [start, end): including start, excluding end
|
||||
// can't modulu nextInt because of weak randomness in lower bits
|
||||
const rangeSize = end - start;
|
||||
const randomUnder1 = nextInt() / 0x7fffffff; // 2**31 - 1
|
||||
return start + Math.floor(randomUnder1 * rangeSize);
|
||||
}
|
||||
|
||||
const chunks = new Array(1024).fill(null).map((_, j) => {
|
||||
const arr = new Uint8Array(1024);
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] = nextRange(0, 256);
|
||||
}
|
||||
return arr;
|
||||
});
|
||||
|
||||
groupForEmitter("stream simulation", ({ EventEmitter, name }) => {
|
||||
bench(name, () => {
|
||||
let id = 0;
|
||||
const stream = new EventEmitter();
|
||||
|
||||
stream.on("start", res => {
|
||||
if (res.status !== 200) throw new Error("not 200");
|
||||
});
|
||||
|
||||
const recived = [];
|
||||
stream.on("data", req => {
|
||||
recived.push(req);
|
||||
});
|
||||
|
||||
stream.on("end", ev => {
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
// simulate a stream
|
||||
stream.emit("start", { status: 200 });
|
||||
for (let chunk of chunks) {
|
||||
stream.emit("data", chunk);
|
||||
}
|
||||
stream.emit("end", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
|
||||
if (id !== 1) throw new Error("not implemented right");
|
||||
if (recived.length !== 1024) throw new Error("not implemented right");
|
||||
});
|
||||
});
|
||||
|
||||
await run();
|
||||
1986
bench/package-lock.json
generated
Normal file
1986
bench/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,11 @@
|
||||
{
|
||||
"name": "bench",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.10",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@swc/core": "^1.2.133",
|
||||
"benchmark": "^2.1.4",
|
||||
"mitata": "^0.1.6",
|
||||
"esbuild": "^0.14.12",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"mitata": "^0.1.6"
|
||||
"@swc/core": "^1.2.133",
|
||||
"@babel/core": "^7.16.10",
|
||||
"@babel/preset-react": "^7.16.7"
|
||||
},
|
||||
"scripts": {
|
||||
"ffi": "cd ffi && bun run deps && bun run build && bun run bench",
|
||||
|
||||
BIN
bench/snippets/bun.lockb
Executable file
BIN
bench/snippets/bun.lockb
Executable file
Binary file not shown.
@@ -1,29 +0,0 @@
|
||||
// https://github.com/oven-sh/bun/issues/2190
|
||||
import { bench, run } from "mitata";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const data =
|
||||
"Delightful remarkably mr on announcing themselves entreaties favourable. About to in so terms voice at. Equal an would is found seems of. The particular friendship one sufficient terminated frequently themselves. It more shed went up is roof if loud case. Delay music in lived noise an. Beyond genius really enough passed is up.";
|
||||
|
||||
const scenarios = [
|
||||
{ alg: "md5", digest: "hex" },
|
||||
{ alg: "md5", digest: "base64" },
|
||||
{ alg: "sha1", digest: "hex" },
|
||||
{ alg: "sha1", digest: "base64" },
|
||||
{ alg: "sha256", digest: "hex" },
|
||||
{ alg: "sha256", digest: "base64" },
|
||||
];
|
||||
|
||||
for (const { alg, digest } of scenarios) {
|
||||
bench(`${alg}-${digest}`, () => {
|
||||
createHash(alg).update(data).digest(digest);
|
||||
});
|
||||
|
||||
if ("Bun" in globalThis) {
|
||||
bench(`${alg}-${digest} (Bun.CryptoHasher)`, () => {
|
||||
new Bun.CryptoHasher(alg).update(data).digest(digest);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -3,13 +3,13 @@ import { bench, run } from "./runner.mjs";
|
||||
|
||||
import crypto from "node:crypto";
|
||||
|
||||
var foo = Buffer.allocUnsafe(512);
|
||||
var foo = Buffer.allocUnsafe(16384);
|
||||
foo.fill(123);
|
||||
|
||||
// if ("Bun" in globalThis) {
|
||||
// const { CryptoHasher } = Bun;
|
||||
// bench("Bun.CryptoHasher(sha512)", () => {
|
||||
// var hasher = new CryptoHasher("sha512");
|
||||
// bench("CryptoHasher Blake2b256", () => {
|
||||
// var hasher = new CryptoHasher("blake2b256");
|
||||
// hasher.update(foo);
|
||||
// hasher.digest();
|
||||
// });
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// https://github.com/oven-sh/bun/issues/2190
|
||||
import { bench, run } from "mitata";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const data =
|
||||
"Delightful remarkably mr on announcing themselves entreaties favourable. About to in so terms voice at. Equal an would is found seems of. The particular friendship one sufficient terminated frequently themselves. It more shed went up is roof if loud case. Delay music in lived noise an. Beyond genius really enough passed is up.";
|
||||
|
||||
const scenarios = [
|
||||
{ alg: "md5", digest: "hex" },
|
||||
{ alg: "md5", digest: "base64" },
|
||||
{ alg: "sha1", digest: "hex" },
|
||||
{ alg: "sha1", digest: "base64" },
|
||||
{ alg: "sha256", digest: "hex" },
|
||||
{ alg: "sha256", digest: "base64" },
|
||||
];
|
||||
|
||||
for (const { alg, digest } of scenarios) {
|
||||
bench(`${alg}-${digest}`, () => {
|
||||
const hasher = createHash(alg);
|
||||
hasher.write(data);
|
||||
hasher.end();
|
||||
hasher.read();
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
101
bench/snippets/emitter.mjs
Normal file
101
bench/snippets/emitter.mjs
Normal file
@@ -0,0 +1,101 @@
|
||||
// **so this file can run in node**
|
||||
import { createRequire } from "node:module";
|
||||
const require = createRequire(import.meta.url);
|
||||
// --
|
||||
|
||||
const EventEmitterNative = require("node:events").EventEmitter;
|
||||
const TypedEmitter = require("tiny-typed-emitter").TypedEmitter;
|
||||
const EventEmitter3 = require("eventemitter3").EventEmitter;
|
||||
import { bench, run } from "../../node_modules/mitata/src/cli.mjs";
|
||||
const event = new Event("hello");
|
||||
var id = 0;
|
||||
for (let [EventEmitter, className] of [
|
||||
[EventEmitterNative, "EventEmitter"],
|
||||
[TypedEmitter, "TypedEmitter"],
|
||||
[EventEmitter3, "EventEmitter3"],
|
||||
]) {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(`${className}.emit`, () => {
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
bench(`${className}.on x 10_000 (handler)`, () => {
|
||||
var cb = event => {
|
||||
event.preventDefault();
|
||||
};
|
||||
emitter.on("hey", cb);
|
||||
var called = false;
|
||||
for (let i = 0; i < 10_000; i++)
|
||||
emitter.emit("hey", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
called = true;
|
||||
},
|
||||
});
|
||||
emitter.off("hey", cb);
|
||||
|
||||
if (!called) throw new Error("not called");
|
||||
});
|
||||
|
||||
if (EventEmitter !== EventEmitter3) {
|
||||
var monkey = Object.assign({}, EventEmitter.prototype);
|
||||
monkey.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(`[monkey] ${className}.emit`, () => {
|
||||
var called = false;
|
||||
monkey.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
called = true;
|
||||
},
|
||||
});
|
||||
|
||||
if (!called) {
|
||||
throw new Error("monkey failed");
|
||||
}
|
||||
});
|
||||
|
||||
bench(`[monkey] ${className}.on x 10_000 (handler)`, () => {
|
||||
var cb = () => {
|
||||
event.preventDefault();
|
||||
};
|
||||
monkey.on("hey", cb);
|
||||
for (let i = 0; i < 10_000; i++)
|
||||
monkey.emit("hey", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
monkey.off("hey", cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var target = new EventTarget();
|
||||
target.addEventListener("hello", event => {});
|
||||
bench("EventTarget.dispatch", () => {
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
|
||||
var hey = new Event("hey");
|
||||
|
||||
bench("EventTarget.on x 10_000 (handler)", () => {
|
||||
var handler = event => {};
|
||||
target.addEventListener("hey", handler);
|
||||
|
||||
for (let i = 0; i < 10_000; i++) target.dispatchEvent(hey);
|
||||
target.removeEventListener("hey", handler);
|
||||
});
|
||||
|
||||
await run();
|
||||
7
bench/snippets/package.json
Normal file
7
bench/snippets/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.0",
|
||||
"tiny-typed-emitter": "latest"
|
||||
},
|
||||
"prettier": "../../.prettierrc.cjs"
|
||||
}
|
||||
Binary file not shown.
728
bench/sqlite/package-lock.json
generated
Normal file
728
bench/sqlite/package-lock.json
generated
Normal file
@@ -0,0 +1,728 @@
|
||||
{
|
||||
"name": "bench",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bench",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^8.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.0.1.tgz",
|
||||
"integrity": "sha512-JhTZjpyapA1icCEjIZB4TSSgkGdFgpWZA2Wszg7Cf4JwJwKQmbvuNnJBeR+EYG/Z29OXvR4G//Rbg31BW/Z7Yg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
|
||||
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz",
|
||||
"integrity": "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"better-sqlite3": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.0.1.tgz",
|
||||
"integrity": "sha512-JhTZjpyapA1icCEjIZB4TSSgkGdFgpWZA2Wszg7Cf4JwJwKQmbvuNnJBeR+EYG/Z29OXvR4G//Rbg31BW/Z7Yg==",
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"requires": {
|
||||
"mimic-response": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
|
||||
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w=="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||
},
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz",
|
||||
"integrity": "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==",
|
||||
"requires": {
|
||||
"semver": "^7.3.5"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"requires": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"requires": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
|
||||
},
|
||||
"tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"requires": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
1
bench/stream-file-upload-client/.gitignore
vendored
1
bench/stream-file-upload-client/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
hello.txt
|
||||
@@ -1,35 +0,0 @@
|
||||
# HTTP request file upload benchmark
|
||||
|
||||
This is a simple benchmark of uploading a file to a web server in different runtimes.
|
||||
|
||||
## Usage
|
||||
|
||||
Generate a file to upload (default is `hello.txt`):
|
||||
|
||||
```bash
|
||||
bun generate-file.js
|
||||
```
|
||||
|
||||
Run the server:
|
||||
|
||||
```bash
|
||||
node server-node.mjs
|
||||
```
|
||||
|
||||
Run the benchmark in bun:
|
||||
|
||||
```bash
|
||||
bun stream-file-bun.js
|
||||
```
|
||||
|
||||
Run the benchmark in node:
|
||||
|
||||
```bash
|
||||
node stream-file-node.mjs
|
||||
```
|
||||
|
||||
Run the benchmark in deno:
|
||||
|
||||
```bash
|
||||
deno run -A stream-file-deno.js
|
||||
```
|
||||
File diff suppressed because one or more lines are too long
@@ -1,15 +0,0 @@
|
||||
import { createServer } from "node:http";
|
||||
const server = createServer((req, res) => {
|
||||
var chunkSize = 0;
|
||||
req.on("data", chunk => {
|
||||
chunkSize += chunk.byteLength;
|
||||
});
|
||||
|
||||
req.on("end", () => {
|
||||
console.log("Received", chunkSize, "bytes");
|
||||
res.end(`${chunkSize}`);
|
||||
});
|
||||
});
|
||||
server.listen(parseInt(process.env.PORT ?? "3000"), (err, port) => {
|
||||
console.log(`http://localhost:${server.address().port}`);
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import { file } from "bun";
|
||||
console.time("stream-file-bun");
|
||||
const response = await fetch(process.env.URL ?? "http://localhost:3000", {
|
||||
method: "POST",
|
||||
body: file(process.env.FILE ?? "hello.txt"),
|
||||
});
|
||||
console.timeEnd("stream-file-bun");
|
||||
|
||||
console.log("Sent", await response.text(), "bytes");
|
||||
@@ -1,12 +0,0 @@
|
||||
const file = await Deno.open(Deno.env.get("FILE") ?? "hello.txt", {
|
||||
read: true,
|
||||
});
|
||||
|
||||
console.time("stream-file-deno");
|
||||
const response = await fetch(Deno.env.get("URL") ?? "http://localhost:3000", {
|
||||
method: "POST",
|
||||
body: file.readable,
|
||||
});
|
||||
console.timeEnd("stream-file-deno");
|
||||
|
||||
console.log("Sent", await response.text(), "bytes");
|
||||
@@ -1,19 +0,0 @@
|
||||
import { createReadStream } from "node:fs";
|
||||
import http from "node:http";
|
||||
|
||||
console.time("stream-file-node");
|
||||
createReadStream(process.env.FILE ?? "hello.txt")
|
||||
.pipe(
|
||||
http
|
||||
.request(process.env.URL ?? "http://localhost:3000", {
|
||||
method: "POST",
|
||||
})
|
||||
.on("response", response => {
|
||||
response.on("data", data => {
|
||||
console.log("Sent", parseInt(data.toString(), 10), "bytes");
|
||||
});
|
||||
}),
|
||||
)
|
||||
.on("close", () => {
|
||||
console.timeEnd("stream-file-node");
|
||||
});
|
||||
@@ -1,988 +0,0 @@
|
||||
This page is intended as an introduction to working with binary data in JavaScript. Bun implements a number of data types and utilities for working with binary data, most of which are Web-standard. Any Bun-specific APIs will be noted as such.
|
||||
|
||||
Below is a quick "cheat sheet" that doubles as a table of contents. Click an item in the left column to jump to that section.
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- [`TypedArray`](#typedarray)
|
||||
- A family of classes that provide an `Array`-like interface for interacting with binary data. Includes `Uint8Array`, `Uint16Array`, `Int8Array`, and more.
|
||||
|
||||
---
|
||||
|
||||
- [`Buffer`](#buffer)
|
||||
- A subclass of `Uint8Array` that implements a wide range of convenience methods. Unlike the other elements in this table, this is a Node.js API (which Bun implements). It can't be used in the browser.
|
||||
|
||||
---
|
||||
|
||||
- [`DataView`](#dataview)
|
||||
- A class that provides a `get/set` API for writing some number of bytes to an `ArrayBuffer` at a particular byte offset. Often used reading or writing binary protocols.
|
||||
|
||||
---
|
||||
|
||||
- [`Blob`](#blob)
|
||||
- A readonly blob of binary data usually representing a file. Has a MIME `type`, a `size`, and methods for converting to `ArrayBuffer`, `ReadableStream`, and string.
|
||||
|
||||
---
|
||||
|
||||
<!-- - [`File`](#file)
|
||||
- _Browser only_. A subclass of `Blob` that represents a file. Has a `name` and `lastModified` timestamp. There is experimental support in Node.js v20; Bun does not support `File` yet; most of its functionality is provided by `BunFile`.
|
||||
|
||||
--- -->
|
||||
|
||||
- [`BunFile`](#bunfile)
|
||||
- _Bun only_. A subclass of `Blob` that represents a lazily-loaded file on disk. Created with `Bun.file(path)`.
|
||||
|
||||
{% /table %}
|
||||
|
||||
## `ArrayBuffer` and views
|
||||
|
||||
Until 2009, there was no language-native way to store and manipulate binary data in JavaScript. ECMAScript v5 introduced a range of new mechanisms for this. The most fundamental building block is `ArrayBuffer`, a simple data structure that represents a sequence of bytes in memory.
|
||||
|
||||
```ts
|
||||
// this buffer can store 8 bytes
|
||||
const buf = new ArrayBuffer(8);
|
||||
```
|
||||
|
||||
Despite the name, it isn't an array and supports none of the array methods and operators one might expect. In fact, there is no way to directly read or write values from an `ArrayBuffer`. There's very little you can do with one except check its size and create "slices" from it.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(8);
|
||||
|
||||
buf.byteLength; // => 8
|
||||
|
||||
const slice = buf.slice(0, 4); // returns new ArrayBuffer
|
||||
slice.byteLength; // => 4
|
||||
```
|
||||
|
||||
To do anything interesting we need a construct known as a "view". A view is a class that _wraps_ an `ArrayBuffer` instance and lets you read and manipulate the underlying data. There are two types of views: _typed arrays_ and `DataView`.
|
||||
|
||||
### `DataView`
|
||||
|
||||
The `DataView` class is a lower-level interface for reading and manipulating the data in an `ArrayBuffer`.
|
||||
|
||||
Below we create a new `DataView` and set the first byte to 5.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(4);
|
||||
// [0x0, 0x0, 0x0, 0x0]
|
||||
|
||||
const dv = new DataView(buf);
|
||||
dv.setUint8(0, 3); // write value 3 at byte offset 0
|
||||
dv.getUint8(0); // => 3
|
||||
// [0x11, 0x0, 0x0, 0x0]
|
||||
```
|
||||
|
||||
Now lets write a `Uint16` at byte offset `1`. This requires two bytes. We're using the value `513`, which is `2 * 256 + 1`; in bytes, that's `00000010 00000001`.
|
||||
|
||||
```ts
|
||||
dv.setUint16(1, 513);
|
||||
// [0x11, 0x10, 0x1, 0x0]
|
||||
|
||||
console.log(dv.getUint16(1)); // => 513
|
||||
```
|
||||
|
||||
We've now assigned a value to the first three bytes in our underlying `ArrayBuffer`. Even though the second and third bytes were created using `setUint16()`, we can still read each of its component bytes using `getUint8()`.
|
||||
|
||||
```ts
|
||||
console.log(dv.getUint8(1)); // => 2
|
||||
console.log(dv.getUint8(2)); // => 1
|
||||
```
|
||||
|
||||
Attempting to write a value that requires more space than is available in the underlying `ArrayBuffer` will cuase an error. Below we attempt to write a `Float64` (which requires 8 bytes) at byte offset `0`, but there are only four total bytes in the buffer.
|
||||
|
||||
```ts
|
||||
dv.setFloat64(0, 3.1415);
|
||||
// ^ RangeError: Out of bounds access
|
||||
```
|
||||
|
||||
The following methods are available on `DataView`:
|
||||
|
||||
{% table %}
|
||||
|
||||
- Getters
|
||||
- Setters
|
||||
|
||||
---
|
||||
|
||||
- [`getBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64)
|
||||
- [`setBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64)
|
||||
|
||||
---
|
||||
|
||||
- [`getBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64)
|
||||
- [`setBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64)
|
||||
|
||||
---
|
||||
|
||||
- [`getFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32)
|
||||
- [`setFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32)
|
||||
|
||||
---
|
||||
|
||||
- [`getFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64)
|
||||
- [`setFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64)
|
||||
|
||||
---
|
||||
|
||||
- [`getInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16)
|
||||
- [`setInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16)
|
||||
|
||||
---
|
||||
|
||||
- [`getInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32)
|
||||
- [`setInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32)
|
||||
|
||||
---
|
||||
|
||||
- [`getInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8)
|
||||
- [`setInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8)
|
||||
|
||||
---
|
||||
|
||||
- [`getUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16)
|
||||
- [`setUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16)
|
||||
|
||||
---
|
||||
|
||||
- [`getUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32)
|
||||
- [`setUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32)
|
||||
|
||||
---
|
||||
|
||||
- [`getUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8)
|
||||
- [`setUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8)
|
||||
|
||||
{% /table %}
|
||||
|
||||
### `TypedArray`
|
||||
|
||||
Typed arrays are a family of classes that provide an `Array`-like interface for interacting with data in an `ArrayBuffer`. Whereas a `DataView` lets you write numbers of varying size at a particular offset, a `TypedArray` interprets the underlying bytes as an array of numbers, each of a fixed size.
|
||||
|
||||
{% callout %}
|
||||
**Note** — It's common to refer to this family of classes collectively by their shared superclass `TypedArray`. This class as _internal_ to JavaScript; you can't directly create instances of it, and `TypedArray` is not defined in the global scope. Think of it as an `interface` or an abstract class.
|
||||
{% /callout %}
|
||||
|
||||
```ts
|
||||
const buffer = new ArrayBuffer(3);
|
||||
const arr = new Uint8Array(buffer);
|
||||
|
||||
// contents are initialized to zero
|
||||
console.log(arr); // Uint8Array(3) [0, 0, 0]
|
||||
|
||||
// assign values like an array
|
||||
arr[0] = 0;
|
||||
arr[1] = 10;
|
||||
arr[2] = 255;
|
||||
arr[3] = 255; // no-op, out of bounds
|
||||
```
|
||||
|
||||
While an `ArrayBuffer` is a generic sequence of bytes, these typed array classes interpret the bytes as an array of numbers of a given byte size.
|
||||
The top row contains the raw bytes, and the later rows contain how these bytes will be interpreted when _viewed_ using different typed array classes.
|
||||
|
||||
The following classes are typed arrays, along with a description of how they interpret the bytes in an `ArrayBuffer`:
|
||||
|
||||
{% table %}
|
||||
|
||||
- Class
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)
|
||||
- Every one (1) byte is interpreted as an unsigned 8-bit integer. Range 0 to 255.
|
||||
|
||||
---
|
||||
|
||||
- [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array)
|
||||
- Every two (2) bytes are interpreted as an unsigned 16-bit integer. Range 0 to 65535.
|
||||
|
||||
---
|
||||
|
||||
- [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array)
|
||||
- Every four (4) bytes are interpreted as an unsigned 32-bit integer. Range 0 to 4294967295.
|
||||
|
||||
---
|
||||
|
||||
- [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array)
|
||||
- Every one (1) byte is interpreted as a signed 8-bit integer. Range -128 to 127.
|
||||
|
||||
---
|
||||
|
||||
- [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array)
|
||||
- Every two (2) bytes are interpreted as a signed 16-bit integer. Range -32768 to 32767.
|
||||
|
||||
---
|
||||
|
||||
- [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array)
|
||||
- Every four (4) bytes are interpreted as a signed 32-bit integer. Range -2147483648 to 2147483647.
|
||||
|
||||
---
|
||||
|
||||
- [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array)
|
||||
- Every four (4) bytes are interpreted as a 32-bit floating point number. Range -3.4e38 to 3.4e38.
|
||||
|
||||
---
|
||||
|
||||
- [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array)
|
||||
- Every eight (8) bytes are interpreted as a 64-bit floating point number. Range -1.7e308 to 1.7e308.
|
||||
|
||||
---
|
||||
|
||||
- [`BigInt64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array)
|
||||
- Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range -9223372036854775808 to 9223372036854775807 (though `BigInt` is capable of representing larger numbers).
|
||||
|
||||
---
|
||||
|
||||
- [`BigUint64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array)
|
||||
- Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range 0 to 18446744073709551615 (though `BigInt` is capable of representing larger numbers).
|
||||
|
||||
---
|
||||
|
||||
- [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray)
|
||||
- Same as `Uint8Array`, but automatically "clamps" to the range 0-255 when assigning a value to an element.
|
||||
|
||||
{% /table %}
|
||||
|
||||
The table below demonstrates how the bytes in an `ArrayBuffer` are interpreted when viewed using different typed array classes.
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- `ArrayBuffer`
|
||||
- `00000000`
|
||||
- `00000001`
|
||||
- `00000010`
|
||||
- `00000011`
|
||||
- `00000100`
|
||||
- `00000101`
|
||||
- `00000110`
|
||||
- `00000111`
|
||||
|
||||
---
|
||||
|
||||
- `Uint8Array`
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
|
||||
---
|
||||
|
||||
- `Uint16Array`
|
||||
- 256 (`1 * 256 + 0`) {% colspan=2 %}
|
||||
- 770 (`3 * 256 + 2`) {% colspan=2 %}
|
||||
- 1284 (`5 * 256 + 4`) {% colspan=2 %}
|
||||
- 1798 (`7 * 256 + 6`) {% colspan=2 %}
|
||||
|
||||
---
|
||||
|
||||
- `Uint32Array`
|
||||
- 50462976 {% colspan=4 %}
|
||||
- 117835012 {% colspan=4 %}
|
||||
|
||||
---
|
||||
|
||||
- `BigUint64Array`
|
||||
- 506097522914230528n {% colspan=8 %}
|
||||
|
||||
{% /table %}
|
||||
|
||||
To create a typed array from a pre-defined `ArrayBuffer`:
|
||||
|
||||
```ts
|
||||
// create typed array from ArrayBuffer
|
||||
const buf = new ArrayBuffer(10);
|
||||
const arr = new Uint8Array(buf);
|
||||
|
||||
arr[0] = 30;
|
||||
arr[1] = 60;
|
||||
|
||||
// all elements are initialized to zero
|
||||
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];
|
||||
```
|
||||
|
||||
If we tried to instantiate a `Uint32Array` from this same `ArrayBuffer`, we'd get an error.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(10);
|
||||
const arr = new Uint32Array(buf);
|
||||
// ^ RangeError: ArrayBuffer length minus the byteOffset
|
||||
// is not a multiple of the element size
|
||||
```
|
||||
|
||||
A `Uint32` value requires four bytes (16 bits). Because the `ArrayBuffer` is 10 bytes long, there's no way to cleanly divide its contents into 4-byte chunks.
|
||||
|
||||
To fix this, we can create a typed array over a particular "slice" of an `ArrayBuffer`. The `Uint16Array` below only "views" the _first_ 8 bytes of the underlying `ArrayBuffer`. To achieve these, we specify a `byteOffset` of `0` and a `length` of `2`, which indicates the number of `Uint32` numbers we want our array to hold.
|
||||
|
||||
```ts
|
||||
// create typed array from ArrayBuffer slice
|
||||
const buf = new ArrayBuffer(10);
|
||||
const arr = new Uint32Array(buf, 0, 2);
|
||||
|
||||
/*
|
||||
buf _ _ _ _ _ _ _ _ _ _ 10 bytes
|
||||
arr [_______,_______] 2 4-byte elements
|
||||
*/
|
||||
|
||||
arr.byteOffset; // 0
|
||||
arr.length; // 2
|
||||
```
|
||||
|
||||
You don't need to explicitly create an `ArrayBuffer` instance; you can instead directly specify a length in the typed array constructor:
|
||||
|
||||
```ts
|
||||
const arr2 = new Uint8Array(5);
|
||||
|
||||
// all elements are initialized to zero
|
||||
// => Uint8Array(5) [0, 0, 0, 0, 0]
|
||||
```
|
||||
|
||||
Typed arrays can also be instantiated directly from an array of numbers, or another typed array:
|
||||
|
||||
```ts
|
||||
// from an array of numbers
|
||||
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
arr1[0]; // => 0;
|
||||
arr1[7]; // => 7;
|
||||
|
||||
// from another typed array
|
||||
const arr2 = new Uint8Array(arr);
|
||||
```
|
||||
|
||||
Broadly speaking, typed arrays provide the same methods as regular arrays, with a few exceptions. For example, `push` and `pop` are not available on typed arrays, because they would require resizing the underlying `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
|
||||
// supports common array methods
|
||||
arr.filter(n => n > 128); // Uint8Array(1) [255]
|
||||
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
|
||||
arr.reduce((acc, n) => acc + n, 0); // 28
|
||||
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
|
||||
arr.every(n => n < 10); // true
|
||||
arr.find(n => n > 5); // 6
|
||||
arr.includes(5); // true
|
||||
arr.indexOf(5); // 5
|
||||
```
|
||||
|
||||
Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) for more information on the properties and methods of typed arrays.
|
||||
|
||||
### `Uint8Array`
|
||||
|
||||
It's worth specifically highlighting `Uint8Array`, as it represents a classic "byte array"—a sequence of 8-bit unsigned integers between 0 and 255. This is the most common typed array you'll encounter in JavaScript.
|
||||
|
||||
It is the return value of [`TextEncoder#encode`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder), and the input type of [`TextDecoder#decode`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder), two utility classes designed to translate strings and various binary encodings, most notably `"utf-8"`.
|
||||
|
||||
```ts
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode("hello world");
|
||||
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const text = decoder.decode(bytes);
|
||||
// => hello world
|
||||
```
|
||||
|
||||
### `Buffer`
|
||||
|
||||
Bun implements `Buffer`, a Node.js API for working with binary data that pre-dates the introduction of typed arrays in the JavaScript spec. It has since been re-implemented as a subclass of `Uint8Array`. It provides a wide range of methods, including several Array-like and `DataView`-like methods.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello world");
|
||||
// => Buffer(16) [ 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 115, 116, 114, 105, 110, 103 ]
|
||||
|
||||
buf.length; // => 11
|
||||
buf[0]; // => 104, ascii for 'h'
|
||||
buf.writeUInt8(72, 0); // => ascii for 'H'
|
||||
|
||||
console.log(buf.toString());
|
||||
// => Hello world
|
||||
```
|
||||
|
||||
For complete documentation, refer to the [Node.js documentation](https://nodejs.org/api/buffer.html).
|
||||
|
||||
## `Blob`
|
||||
|
||||
`Blob` is a Web API commonly used for representing files. `Blob` was initially implemented in browsers (unlike `ArrayBuffer` which is part of JavaScript itself), but it is now supported in Node and Bun.
|
||||
|
||||
It isn't common to directly create `Blob` instances. More often, you'll recieve instances of `Blob` from an external source (like an `<input type="file">` element in the browser) or library. That said, it is possible to create a `Blob` from one or more string or binary "blob parts".
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["<html>Hello</html>"], {
|
||||
type: "text/html",
|
||||
});
|
||||
|
||||
blob.type; // => text/html
|
||||
blob.size; // => 19
|
||||
```
|
||||
|
||||
These parts can be `string`, `ArrayBuffer`, `TypedArray`, `DataView`, or other `Blob` instances. The blob parts are concatenated together in the order they are provided.
|
||||
|
||||
```ts
|
||||
const blob = new Blob([
|
||||
"<html>",
|
||||
new Blob(["<body>"]),
|
||||
new Uint8Array([104, 101, 108, 108, 111]), // "hello" in binary
|
||||
"</body></html>",
|
||||
]);
|
||||
```
|
||||
|
||||
The contents of a `Blob` can be asynchronously read in various formats.
|
||||
|
||||
```ts
|
||||
await blob.text(); // => <html><body>hello</body></html>
|
||||
await blob.arrayBuffer(); // => ArrayBuffer (copies contents)
|
||||
await blob.stream(); // => ReadableStream
|
||||
```
|
||||
|
||||
### `BunFile`
|
||||
|
||||
`BunFile` is a subclass of `Blob` used to represent a lazily-loaded file on disk. Like `File`, it adds a `name` and `lastModified` property. Unlike `File`, it does not require the file to be loaded into memory.
|
||||
|
||||
```ts
|
||||
const file = Bun.file("index.txt");
|
||||
// => BunFile
|
||||
```
|
||||
|
||||
### `File`
|
||||
|
||||
{% callout %}
|
||||
Browser only. Experimental support in Node.js 20.
|
||||
{% /callout %}
|
||||
|
||||
[`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is a subclass of `Blob` that adds a `name` and `lastModified` property. It's commonly used in the browser to represent files uploaded via a `<input type="file">` element. Node.js and Bun implement `File`.
|
||||
|
||||
```ts
|
||||
// on browser!
|
||||
// <input type="file" id="file" />
|
||||
|
||||
const files = document.getElementById("file").files;
|
||||
// => File[]
|
||||
```
|
||||
|
||||
```ts
|
||||
const file = new File(["<html>Hello</html>"], "index.html", {
|
||||
type: "text/html",
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) for complete docs information.
|
||||
|
||||
## Streams
|
||||
|
||||
Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.
|
||||
|
||||
Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
|
||||
|
||||
{% callout %}
|
||||
Bun also implements the `node:stream` module, including [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer to the Node.js docs.
|
||||
{% /callout %}
|
||||
|
||||
To create a simple readable stream:
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The contents of this stream can be read chunk-by-chunk with `for await` syntax.
|
||||
|
||||
```ts
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
// => "hello"
|
||||
// => "world"
|
||||
}
|
||||
```
|
||||
|
||||
For a more complete discusson of streams in Bun, see [API > Streams](/docs/api/streams).
|
||||
|
||||
## Conversion
|
||||
|
||||
Converting from one binary format to another is a common task. This section is intended as a reference.
|
||||
|
||||
### From `ArrayBuffer`
|
||||
|
||||
Since `ArrayBuffer` stores the data that underlies other binary structures like `TypedArray`, the snippets below are not _converting_ from `ArrayBuffer` to another format. Instead, they are _creating_ a new instance using the data stored underlying data.
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
new Uint8Array(buf);
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
new DataView(buf);
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
// create Buffer over entire ArrayBuffer
|
||||
Buffer.from(buf);
|
||||
|
||||
// create Buffer over a slice of the ArrayBuffer
|
||||
Buffer.from(buf, 0, 10);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
```ts
|
||||
new TextDecoder().decode(buf);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(new Uint8Array(buf));
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([buf], { type: "text/plain" });
|
||||
```
|
||||
|
||||
<!-- #### To `File`
|
||||
|
||||
```ts
|
||||
new File([buf], "filename.txt", { type: "text/plain", lastModified: Date.now() });
|
||||
``` -->
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
The following snippet creates a `ReadableStream` and enqueues the entire `ArrayBuffer` as a single chunk.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(buf);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% details summary="With chunking" %}
|
||||
To stream the `ArrayBuffer` in chunks, use a `Uint8Array` view and enqueue each chunk.
|
||||
|
||||
```ts
|
||||
const view = new Uint8Array(buf);
|
||||
const chunkSize = 1024;
|
||||
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < view.length; i += chunkSize) {
|
||||
controller.enqueue(view.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
### From `TypedArray`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
This retrieves the underlying `ArrayBuffer`. Note that a `TypedArray` can be a view of a _slice_ of the underlying buffer, so the sizes may differ.
|
||||
|
||||
```ts
|
||||
arr.buffer;
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
To creates a `DataView` over the same byte range as the TypedArray.
|
||||
|
||||
```ts
|
||||
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
Buffer.from(arr);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
```ts
|
||||
new TextDecoder().decode(arr);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(arr);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([arr.buffer], { type: "text/plain" });
|
||||
```
|
||||
|
||||
<!-- #### To `File`
|
||||
|
||||
```ts
|
||||
new File([arr.buffer], "filename.txt", { type: "text/plain", lastModified: Date.now() });
|
||||
``` -->
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(arr);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% details summary="With chunking" %}
|
||||
To stream the `ArrayBuffer` in chunks, split the `TypedArray` into chunks and enqueue each one individually.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
controller.enqueue(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
### From `DataView`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
```ts
|
||||
view.buffer;
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
Only works if the `byteLength` of the `DataView` is a multiple of the `BYTES_PER_ELEMENT` of the `TypedArray` subclass.
|
||||
|
||||
```ts
|
||||
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
||||
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
|
||||
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
|
||||
// etc...
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
Buffer.from(view.buffer, view.byteOffset, view.byteLength);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
```ts
|
||||
new TextDecoder().decode(view);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(view);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([view.buffer], { type: "text/plain" });
|
||||
```
|
||||
|
||||
<!-- #### To `File`
|
||||
|
||||
```ts
|
||||
new File([view.buffer], "filename.txt", { type: "text/plain", lastModified: Date.now() });
|
||||
``` -->
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(view.buffer);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% details summary="With chunking" %}
|
||||
To stream the `ArrayBuffer` in chunks, split the `DataView` into chunks and enqueue each one individually.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < view.byteLength; i += chunkSize) {
|
||||
controller.enqueue(view.buffer.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
### From `Buffer`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
```ts
|
||||
buf.buffer;
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
new Uint8Array(buf);
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
```ts
|
||||
buf.toString();
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(buf);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([buf], { type: "text/plain" });
|
||||
```
|
||||
|
||||
<!-- #### To `File`
|
||||
|
||||
```ts
|
||||
new File([buf], "filename.txt", { type: "text/plain", lastModified: Date.now() });
|
||||
``` -->
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(buf);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% details summary="With chunking" %}
|
||||
To stream the `ArrayBuffer` in chunks, split the `Buffer` into chunks and enqueue each one individually.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < buf.length; i += chunkSize) {
|
||||
controller.enqueue(buf.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
### From `Blob`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
The `Blob` class provides a convenience method for this purpose.
|
||||
|
||||
```ts
|
||||
await blob.arrayBuffer();
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
new Uint8Array(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
new DataView(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
Buffer.from(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
```ts
|
||||
await blob.text();
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(new Uint8Array(await blob.arrayBuffer()));
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
blob.stream();
|
||||
```
|
||||
|
||||
<!-- ### From `File` -->
|
||||
|
||||
### From `ReadableStream`
|
||||
|
||||
It's common to use [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) as a convenient intermediate representation to make it easier to convert `ReadableStream` to other formats.
|
||||
|
||||
```ts
|
||||
stream; // ReadableStream
|
||||
|
||||
const buffer = new Response(stream).arrayBuffer();
|
||||
```
|
||||
|
||||
However this approach is verbose and adds overhead that slows down overall performance unnecessarily. Bun implements a set of optimized convenience functions for converting `ReadableStream` various binary formats.
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
new Response(stream).arrayBuffer();
|
||||
|
||||
// with Bun function
|
||||
Bun.readableStreamToArrayBuffer(stream);
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
new Uint8Array(buf);
|
||||
|
||||
// with Bun function
|
||||
new Uint8Array(Bun.readableStreamToArrayBuffer(stream));
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
new DataView(buf);
|
||||
|
||||
// with Bun function
|
||||
new DataView(Bun.readableStreamToArrayBuffer(stream));
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
Buffer.from(buf);
|
||||
|
||||
// with Bun function
|
||||
Buffer.from(Bun.readableStreamToArrayBuffer(stream));
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
new Response(stream).text();
|
||||
|
||||
// with Bun function
|
||||
await Bun.readableStreamToString(stream);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
Array.from(new Uint8Array(buf));
|
||||
|
||||
// with Bun function
|
||||
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));
|
||||
```
|
||||
|
||||
Bun provides a utility for resolving a `ReadableStream` to an array of its chunks. Each chunk may be a string, typed array, or `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
// with Bun function
|
||||
Bun.readableStreamToArray(stream);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Response(stream).blob();
|
||||
```
|
||||
|
||||
<!-- #### To `File`
|
||||
|
||||
```ts
|
||||
new Response(stream)
|
||||
.blob()
|
||||
.then(blob => new File([blob], "filename.txt", { type: "text/plain", lastModified: Date.now() }));
|
||||
``` -->
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
To split a `ReadableStream` into two streams that can be consumed independently:
|
||||
|
||||
```ts
|
||||
const [a, b] = stream.tee();
|
||||
```
|
||||
|
||||
<!-- - Use Buffer
|
||||
- TextEncoder
|
||||
- `Bun.ArrayBufferSink`
|
||||
- ReadableStream
|
||||
- AsyncIterator
|
||||
- TypedArray vs ArrayBuffer vs DataView
|
||||
- Bun.indexOfLine
|
||||
- “direct” readablestream
|
||||
- readable stream has assumptions about
|
||||
- its very generic
|
||||
- all data is copies and queued
|
||||
- direct : no queueing
|
||||
- just a write function
|
||||
- you can write strings
|
||||
- more synchronous
|
||||
- corking works better -->
|
||||
@@ -1,139 +0,0 @@
|
||||
{% callout %}
|
||||
|
||||
Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](https://nodejs.org/api/crypto.html) in addition to the Bun-native APIs documented below.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## `Bun.hash`
|
||||
|
||||
`Bun.hash` is a collection of utilities for _non-cryptographic_ hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security.
|
||||
|
||||
The standard `Bun.hash` functions uses [Wyhash](https://github.com/wangyi-fudan/wyhash) to generate a 64-bit hash from an input of arbitrary size.
|
||||
|
||||
```ts
|
||||
Bun.hash("some data here");
|
||||
// 976213160445840
|
||||
```
|
||||
|
||||
The input can be a string, `TypedArray`, `DataView`, `ArrayBuffer`, or `SharedArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array([1, 2, 3, 4]);
|
||||
|
||||
Bun.hash("some data here");
|
||||
Bun.hash(arr);
|
||||
Bun.hash(arr.buffer);
|
||||
Bun.hash(new DataView(arr.buffer));
|
||||
```
|
||||
|
||||
Optionally, an integer seed can be specified as the second parameter.
|
||||
|
||||
```ts
|
||||
Bun.hash("some data here", 1234);
|
||||
// 1173484059023252
|
||||
```
|
||||
|
||||
Additional hashing algorithms are available as properties on `Bun.hash`. The API is the same for each.
|
||||
|
||||
```ts
|
||||
Bun.hash.wyhash("data", 1234); // equivalent to Bun.hash()
|
||||
Bun.hash.crc32("data", 1234);
|
||||
Bun.hash.adler32("data", 1234);
|
||||
Bun.hash.cityHash32("data", 1234);
|
||||
Bun.hash.cityHash64("data", 1234);
|
||||
Bun.hash.murmur32v3("data", 1234);
|
||||
Bun.hash.murmur64v2("data", 1234);
|
||||
```
|
||||
|
||||
## `Bun.CryptoHasher`
|
||||
|
||||
`Bun.CryptoHasher` is a general-purpose utility class that lets you incrementally compute a hash of string or binary data using a range of cryptographic hash algorithms. The following algorithms are supported:
|
||||
|
||||
- `"blake2b256"`
|
||||
- `"md4"`
|
||||
- `"md5"`
|
||||
- `"ripemd160"`
|
||||
- `"sha1"`
|
||||
- `"sha224"`
|
||||
- `"sha256"`
|
||||
- `"sha384"`
|
||||
- `"sha512"`
|
||||
- `"sha512-256"`
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
hasher.update("hello world");
|
||||
hasher.digest();
|
||||
// Uint8Array(32) [ <byte>, <byte>, ... ]
|
||||
```
|
||||
|
||||
Once initialized, data can be incrementally fed to to the hasher using `.update()`. This method accepts `string`, `TypedArray`, and `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher();
|
||||
|
||||
hasher.update("hello world");
|
||||
hasher.update(new Uint8Array([1, 2, 3]));
|
||||
hasher.update(new ArrayBuffer(10));
|
||||
```
|
||||
|
||||
If a `string` is passed, an optional second parameter can be used to specify the encoding (default `'utf-8'`). The following encodings are supported:
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- Binary encodings
|
||||
- `"base64"` `"base64url"` `"hex"` `"binary"`
|
||||
|
||||
---
|
||||
|
||||
- Character encodings
|
||||
- `"utf8"` `"utf-8"` `"utf16le"` `"latin1"`
|
||||
|
||||
---
|
||||
|
||||
- Legacy character encodings
|
||||
- `"ascii"` `"binary"` `"ucs2"` `"ucs-2"`
|
||||
|
||||
{% /table %}
|
||||
|
||||
```ts
|
||||
hasher.update("hello world"); // defaults to utf8
|
||||
hasher.update("hello world", "hex");
|
||||
hasher.update("hello world", "base64");
|
||||
hasher.update("hello world", "latin1");
|
||||
```
|
||||
|
||||
After the data has been feed into the hasher, a final hash can be computed using `.digest()`. By default, this method returns a `Uint8Array` containing the hash.
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher();
|
||||
hasher.update("hello world");
|
||||
|
||||
hasher.digest();
|
||||
// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ]
|
||||
```
|
||||
|
||||
The `.digest()` method can optionally return the hash as a string. To do so, specify an encoding:
|
||||
|
||||
```ts
|
||||
hasher.digest("base64");
|
||||
// => "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
|
||||
|
||||
hasher.digest("hex");
|
||||
// => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
||||
```
|
||||
|
||||
Alternatively, the method can write the hash into a pre-existing `TypedArray` instance. This may be desirable in some performance-sensitive applications.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(32);
|
||||
|
||||
hasher.digest(arr);
|
||||
|
||||
console.log(arr);
|
||||
// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ]
|
||||
```
|
||||
|
||||
<!-- Bun.sha; -->
|
||||
@@ -1,12 +1,19 @@
|
||||
The page primarily documents the Bun-native `Bun.serve` API. Bun also implements [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules.
|
||||
|
||||
{% callout %}
|
||||
These modules have been re-implemented to use Bun's fast internal HTTP infrastructure. Feel free to use these modules directly; frameworks like [Express](https://expressjs.com/) that depend on these modules should work out of the box. For granular compatibility information, see [Runtime > Node.js APIs](/docs/runtime/nodejs-apis).
|
||||
**Note** — This page documents the `Bun.serve` API. This API is heavily optimized and represents the recommended way to build HTTP servers in Bun. Existing Node.js projects may use Bun's [nearly complete](/docs/runtime/nodejs-apis#node_http) implementation of the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules.
|
||||
{% /callout %}
|
||||
|
||||
To start a high-performance HTTP server with a clean API, the recommended approach is [`Bun.serve`](#start-a-server-bun-serve).
|
||||
## Send a request (`fetch()`)
|
||||
|
||||
## `Bun.serve()`
|
||||
Bun implements the Web `fetch` API for making HTTP requests. The `fetch` function is available in the global scope.
|
||||
|
||||
```ts
|
||||
const response = await fetch("https://bun.sh/manifest.json");
|
||||
const result = (await response.json()) as any;
|
||||
console.log(result.icons[0].src);
|
||||
// => "/logo-square.jpg"
|
||||
```
|
||||
|
||||
## Start a server (`Bun.serve()`)
|
||||
|
||||
Start an HTTP server in Bun with `Bun.serve`.
|
||||
|
||||
@@ -43,7 +50,7 @@ Bun.serve({
|
||||
});
|
||||
```
|
||||
|
||||
## Error handling
|
||||
### Error handling
|
||||
|
||||
To activate development mode, set `development: true`. By default, development mode is _enabled_ unless `NODE_ENV` is `production`.
|
||||
|
||||
@@ -93,72 +100,36 @@ const server = Bun.serve({
|
||||
server.stop();
|
||||
```
|
||||
|
||||
## TLS
|
||||
### TLS
|
||||
|
||||
Bun supports TLS out of the box, powered by [OpenSSL](https://www.openssl.org/). Enable TLS by passing in a value for `key` and `cert`; both are required to enable TLS. If needed, supply a `passphrase` to decrypt the `keyFile`.
|
||||
Bun supports TLS out of the box, powered by [OpenSSL](https://www.openssl.org/). Enable TLS by passing in a value for `keyFile` and `certFile`; both are required to enable TLS. If needed, supply a `passphrase` to decrypt the `keyFile`.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
return new Response("Hello!!!");
|
||||
},
|
||||
|
||||
// can be string, BunFile, TypedArray, Buffer, or array thereof
|
||||
key: Bun.file("./key.pem"),
|
||||
cert: Bun.file("./cert.pem"),
|
||||
|
||||
// passphrase, only required if key is encrypted
|
||||
passphrase: "super-secret",
|
||||
keyFile: "./key.pem", // path to TLS key
|
||||
certFile: "./cert.pem", // path to TLS cert
|
||||
passphrase: "super-secret", // optional passphrase
|
||||
});
|
||||
```
|
||||
|
||||
The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch() {},
|
||||
|
||||
// BunFile
|
||||
key: Bun.file("./key.pem"),
|
||||
// Buffer
|
||||
key: fs.readFileSync("./key.pem"),
|
||||
// string
|
||||
key: fs.readFileSync("./key.pem", "utf8"),
|
||||
// array of above
|
||||
key: [Bun.file('./key1.pem'), Bun.file('./key2.pem']
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
**Note** — Earlier versions of Bun supported passing a file path as `keyFile` and `certFile`; this has been deprecated as of `v0.6.3`.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Optionally, you can override the trusted CA certificates by passing a value for `ca`. By default, the server will trust the list of well-known CAs curated by Mozilla. When `ca` is specified, the Mozilla list is overwritten.
|
||||
The root CA and Diffie-Helman parameters can be configured as well.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
return new Response("Hello!!!");
|
||||
},
|
||||
key: Bun.file("./key.pem"), // path to TLS key
|
||||
cert: Bun.file("./cert.pem"), // path to TLS cert
|
||||
ca: Bun.file("./ca.pem"), // path to root CA certificate
|
||||
keyFile: "./key.pem", // path to TLS key
|
||||
certFile: "./cert.pem", // path to TLS cert
|
||||
caFile: "./ca.pem", // path to root CA certificate
|
||||
dhParamsFile: "./dhparams.pem", // Diffie Helman parameters
|
||||
});
|
||||
```
|
||||
|
||||
To override Diffie-Helman parameters:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
// ...
|
||||
dhParamsFile: "./dhparams.pem", // path to Diffie Helman parameters
|
||||
});
|
||||
```
|
||||
|
||||
## Hot reloading
|
||||
### Hot reloading
|
||||
|
||||
Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax.
|
||||
|
||||
@@ -182,7 +153,7 @@ $ bun --hot server.ts
|
||||
|
||||
It's possible to configure hot reloading while using the explicit `Bun.serve` API; for details refer to [Runtime > Hot reloading](/docs/runtime/hot).
|
||||
|
||||
## Streaming files
|
||||
### Streaming files
|
||||
|
||||
To stream a file, return a `Response` object with a `BunFile` object as the body.
|
||||
|
||||
@@ -220,7 +191,7 @@ Bun.serve({
|
||||
});
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
### Benchmarks
|
||||
|
||||
Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`.
|
||||
|
||||
@@ -263,7 +234,7 @@ The `Bun.serve` server can handle roughly 2.5x more requests per second than Nod
|
||||
|
||||
{% image width="499" alt="image" src="https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png" /%}
|
||||
|
||||
## Reference
|
||||
### Reference
|
||||
|
||||
{% details summary="See TypeScript definitions" %}
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
The `import.meta` object is a way for a module to access information about itself. It's part of the JavaScript language, but its contents are not standardized. Each "host" (browser, runtime, etc) is free to implement any properties it wishes on the `import.meta` object.
|
||||
|
||||
Bun implements the following properties.
|
||||
|
||||
```ts#/path/to/project/file.ts
|
||||
import.meta.dir; // => "/path/to/project"
|
||||
import.meta.file; // => "file.ts"
|
||||
import.meta.path; // => "/path/to/project/file.ts"
|
||||
|
||||
import.meta.main; // `true` if this file is directly executed by `bun run`
|
||||
// `false` otherwise
|
||||
|
||||
import.meta.resolveSync("zod")
|
||||
// resolve an import specifier relative to the directory
|
||||
```
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- `import.meta.dir`
|
||||
- Absolute path to the directory containing the current fil, e.g. `/path/to/project`. Equivalent to `__dirname` in Node.js.
|
||||
|
||||
---
|
||||
|
||||
- `import.meta.file`
|
||||
- The name of the current file, e.g. `index.tsx`. Equivalent to `__filename` in Node.js.
|
||||
|
||||
---
|
||||
|
||||
- `import.meta.path`
|
||||
- Absolute path to the current file, e.g. `/path/to/project/index.tx`.
|
||||
|
||||
---
|
||||
|
||||
- `import.meta.main`
|
||||
- `boolean` Indicates whether the current file is the entrypoint to the current `bun` process. Is the file being directly executed by `bun run` or is it being imported?
|
||||
|
||||
---
|
||||
|
||||
- `import.meta.resolve{Sync}`
|
||||
- Resolve a module specifier (e.g. `"zod"` or `"./file.tsx`) to an absolute path. While file would be imported if the specifier were imported from this file?
|
||||
|
||||
```ts
|
||||
import.meta.resolveSync("zod");
|
||||
// => "/path/to/project/node_modules/zod/index.ts"
|
||||
|
||||
import.meta.resolveSync("./file.tsx");
|
||||
// => "/path/to/project/file.tsx"
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
@@ -74,7 +74,7 @@ Note: `close()` is called automatically when the database is garbage collected.
|
||||
|
||||
```ts
|
||||
const olddb = new Database("mydb.sqlite");
|
||||
const contents = olddb.serialize(); // => Uint8Array
|
||||
const contents = db.serialize(); // => Uint8Array
|
||||
const newdb = new Database(contents);
|
||||
```
|
||||
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.
|
||||
|
||||
Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
|
||||
|
||||
{% callout %}
|
||||
Bun also implements the `node:stream` module, including [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer to the [Node.js docs](https://nodejs.org/api/stream.html).
|
||||
{% /callout %}
|
||||
|
||||
To create a simple `ReadableStream`:
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The contents of a `ReadableStream` can be read chunk-by-chunk with `for await` syntax.
|
||||
|
||||
```ts
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
// => "hello"
|
||||
// => "world"
|
||||
}
|
||||
```
|
||||
|
||||
For a more complete discusson of streams in Bun, see [API > Streams](/docs/api/streams).
|
||||
|
||||
## Direct `ReadableStream`
|
||||
|
||||
Bun implements an optimized version of `ReadableStream` that avoid unnecessary data copying & queue management logic. With a traditional `ReadableStream`, chunks of data are _enqueued_. Each chunk is copied into a queue, where it sits until the stream is ready to send more data.
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
With a direct `ReadableStream`, chunks of data are written directly to the stream. No queueing happens, and there's no need to clone the chunk data into memory. The `controller` API is updated to reflect this; instead of `.enqueue()` you call `.write`.
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
type: "direct",
|
||||
pull(controller) {
|
||||
controller.write("hello");
|
||||
controller.write("world");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
When using a direct `ReadableStream`, all chunk queueing is handled by the destination. The consumer of the stream receives exactly what is passed to `controller.write()`, without any encoding or modification.
|
||||
|
||||
## `Bun.ArrayBufferSink`
|
||||
|
||||
The `Bun.ArrayBufferSink` class is a fast incremental writer for constructing an `ArrayBuffer` of unknown size.
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink();
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
|
||||
sink.end();
|
||||
// ArrayBuffer(5) [ 104, 101, 108, 108, 111 ]
|
||||
```
|
||||
|
||||
To instead retrieve the data as a `Uint8Array`, pass the `asUint8Array` option to the constructor.
|
||||
|
||||
```ts-diff
|
||||
const sink = new Bun.ArrayBufferSink({
|
||||
+ asUint8Array: true
|
||||
});
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
|
||||
sink.end();
|
||||
// Uint8Array(5) [ 104, 101, 108, 108, 111 ]
|
||||
```
|
||||
|
||||
The `.write()` method supports strings, typed arrays, `ArrayBuffer`, and `SharedArrayBuffer`.
|
||||
|
||||
```ts
|
||||
sink.write("h");
|
||||
sink.write(new Uint8Array([101, 108]));
|
||||
sink.write(Buffer.from("lo").buffer);
|
||||
|
||||
sink.end();
|
||||
```
|
||||
|
||||
Once `.end()` is called, no more data can be written to the `ArrayBufferSink`. However, in the context of buffering a stream, it's useful to continuously write data and periodically `.flush()` the contents (say, into a `WriteableStream`). To support this, pass `stream: true` to the constructor.
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink({
|
||||
stream: true,
|
||||
});
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.flush();
|
||||
// ArrayBuffer(5) [ 104, 101, 108 ]
|
||||
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
sink.flush();
|
||||
// ArrayBuffer(5) [ 108, 111 ]
|
||||
```
|
||||
|
||||
The `.flush()` method returns the buffered data as an `ArrayBuffer` (or `Uint8Array` if `asUint8Array: true`) and clears internal buffer.
|
||||
|
||||
To manually set the size of the internal buffer in bytes, pass a value for `highWaterMark`:
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink({
|
||||
highWaterMark: 1024 * 1024, // 1 MB
|
||||
});
|
||||
```
|
||||
|
||||
{% details summary="Reference" %}
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Fast incremental writer that becomes an `ArrayBuffer` on end().
|
||||
*/
|
||||
export class ArrayBufferSink {
|
||||
constructor();
|
||||
|
||||
start(options?: {
|
||||
asUint8Array?: boolean;
|
||||
/**
|
||||
* Preallocate an internal buffer of this size
|
||||
* This can significantly improve performance when the chunk size is small
|
||||
*/
|
||||
highWaterMark?: number;
|
||||
/**
|
||||
* On {@link ArrayBufferSink.flush}, return the written data as a `Uint8Array`.
|
||||
* Writes will restart from the beginning of the buffer.
|
||||
*/
|
||||
stream?: boolean;
|
||||
}): void;
|
||||
|
||||
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
||||
/**
|
||||
* Flush the internal buffer
|
||||
*
|
||||
* If {@link ArrayBufferSink.start} was passed a `stream` option, this will return a `ArrayBuffer`
|
||||
* If {@link ArrayBufferSink.start} was passed a `stream` option and `asUint8Array`, this will return a `Uint8Array`
|
||||
* Otherwise, this will return the number of bytes written since the last flush
|
||||
*
|
||||
* This API might change later to separate Uint8ArraySink and ArrayBufferSink
|
||||
*/
|
||||
flush(): number | Uint8Array | ArrayBuffer;
|
||||
end(): ArrayBuffer | Uint8Array;
|
||||
}
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
@@ -59,7 +59,7 @@ Bun.listen<SocketData>({
|
||||
});
|
||||
```
|
||||
|
||||
To enable TLS, pass a `tls` object containing `key` and `cert` fields.
|
||||
To enable TLS, pass a `tls` object containing `keyFile` and `certFile` properties.
|
||||
|
||||
```ts
|
||||
Bun.listen({
|
||||
@@ -69,38 +69,13 @@ Bun.listen({
|
||||
data(socket, data) {},
|
||||
},
|
||||
tls: {
|
||||
// can be string, BunFile, TypedArray, Buffer, or array thereof
|
||||
key: Bun.file("./key.pem"),
|
||||
cert: Bun.file("./cert.pem"),
|
||||
certFile: "cert.pem",
|
||||
keyFile: "key.pem",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
**Note** — Earlier versions of Bun supported passing a file path as `keyFile` and `certFile`; this has been deprecated as of `v0.6.3`.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`.
|
||||
|
||||
```ts
|
||||
Bun.listen({
|
||||
// ...
|
||||
tls: {
|
||||
// BunFile
|
||||
key: Bun.file("./key.pem"),
|
||||
// Buffer
|
||||
key: fs.readFileSync("./key.pem"),
|
||||
// string
|
||||
key: fs.readFileSync("./key.pem", "utf8"),
|
||||
// array of above
|
||||
key: [Bun.file('./key1.pem'), Bun.file('./key2.pem']
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The result of `Bun.listen` is a server that conforms to the `TCPSocket` interface.
|
||||
The result of `Bun.listen` is a server that conforms to the `TCPSocket` instance.
|
||||
|
||||
```ts
|
||||
const server = Bun.listen({
|
||||
|
||||
@@ -1,47 +1,4 @@
|
||||
## `Bun.version`
|
||||
|
||||
A `string` containing the version of the `bun` CLI that is currently running.
|
||||
|
||||
```ts
|
||||
Bun.version;
|
||||
// => "0.6.4"
|
||||
```
|
||||
|
||||
## `Bun.revision`
|
||||
|
||||
The git commit of [Bun](https://github.com/oven-sh/bun) that was compiled to create the current `bun` CLI.
|
||||
|
||||
```ts
|
||||
Bun.revision;
|
||||
// => "f02561530fda1ee9396f51c8bc99b38716e38296"
|
||||
```
|
||||
|
||||
## `Bun.env`
|
||||
|
||||
An alias for `process.env`.
|
||||
|
||||
## `Bun.main`
|
||||
|
||||
An absolute path to the entrypoint of the current program (the file that was executed with `bun run`).
|
||||
|
||||
```ts#script.ts
|
||||
Bun.main;
|
||||
// /path/to/script.ts
|
||||
```
|
||||
|
||||
This is particular useful for determining whether a script is being directly executed, as opposed to being imported by another script.
|
||||
|
||||
```ts
|
||||
if (import.meta.path === Bun.main) {
|
||||
// this script is being directly executed
|
||||
} else {
|
||||
// this file is being imported from another script
|
||||
}
|
||||
```
|
||||
|
||||
This is analogous to the [`require.main = module` trick](https://stackoverflow.com/questions/6398196/detect-if-called-through-require-or-directly-by-command-line) in Node.js.
|
||||
|
||||
## `Bun.sleep()`
|
||||
## `Bun.sleep`
|
||||
|
||||
`Bun.sleep(ms: number)` (added in Bun v0.5.6)
|
||||
|
||||
@@ -63,33 +20,11 @@ await Bun.sleep(oneSecondInFuture);
|
||||
console.log("hello one second later!");
|
||||
```
|
||||
|
||||
## `Bun.sleepSync()`
|
||||
|
||||
`Bun.sleepSync(ms: number)` (added in Bun v0.5.6)
|
||||
|
||||
A blocking synchronous version of `Bun.sleep`.
|
||||
|
||||
```ts
|
||||
console.log("hello");
|
||||
Bun.sleepSync(1000); // blocks thread for one second
|
||||
console.log("hello one second later!");
|
||||
```
|
||||
|
||||
Alternatively, pass a `Date` object to receive a `Promise` that resolves at that point in time.
|
||||
|
||||
```ts
|
||||
const oneSecondInFuture = new Date(Date.now() + 1000);
|
||||
|
||||
console.log("hello");
|
||||
await Bun.sleep(oneSecondInFuture);
|
||||
console.log("hello one second later!");
|
||||
```
|
||||
|
||||
## `Bun.which()`
|
||||
## `Bun.which`
|
||||
|
||||
`Bun.which(bin: string)`
|
||||
|
||||
Returns the path to an executable, similar to typing `which` in your terminal.
|
||||
Find the path to an executable, similar to typing `which` in your terminal.
|
||||
|
||||
```ts
|
||||
const ls = Bun.which("ls");
|
||||
@@ -116,11 +51,11 @@ const ls = Bun.which("ls", {
|
||||
console.log(ls); // null
|
||||
```
|
||||
|
||||
## `Bun.peek()`
|
||||
## `Bun.peek`
|
||||
|
||||
`Bun.peek(prom: Promise)` (added in Bun v0.2.2)
|
||||
|
||||
Reads a promise's result without `await` or `.then`, but only if the promise has already fulfilled or rejected.
|
||||
`Bun.peek` is a utility function that lets you read a promise's result without `await` or `.then`, but only if the promise has already fulfilled or rejected.
|
||||
|
||||
```ts
|
||||
import { peek } from "bun";
|
||||
@@ -182,9 +117,9 @@ test("peek.status", () => {
|
||||
});
|
||||
```
|
||||
|
||||
## `Bun.openInEditor()`
|
||||
## `Bun.openInEditor`
|
||||
|
||||
Opens a file in your default editor. Bun auto-detects your editor via the `$VISUAL` or `$EDITOR` environment variables.
|
||||
Open a file in your default editor. Bun auto-detects your editor via the `$VISUAL` or `$EDITOR` environment variables.
|
||||
|
||||
```ts
|
||||
const currentFile = import.meta.url;
|
||||
@@ -207,290 +142,3 @@ Bun.openInEditor(import.meta.url, {
|
||||
column: 5,
|
||||
});
|
||||
```
|
||||
|
||||
Bun.ArrayBufferSink;
|
||||
|
||||
## `Bun.deepEquals()`
|
||||
|
||||
Nestedly checks if two objects are equivalent. This is used internally by `expect().toEqual()` in `bun:test`.
|
||||
|
||||
```ts
|
||||
const foo = { a: 1, b: 2, c: { d: 3 } };
|
||||
|
||||
// true
|
||||
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 3 } });
|
||||
|
||||
// false
|
||||
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 4 } });
|
||||
```
|
||||
|
||||
A third boolean parameter can be used to enable "strict" mode. This is used by `expect().toStrictEqual()` in the test runner.
|
||||
|
||||
```ts
|
||||
const a = { entries: [1, 2] };
|
||||
const b = { entries: [1, 2], extra: undefined };
|
||||
|
||||
Bun.deepEquals(a, b); // => true
|
||||
Bun.deepEquals(a, b, true); // => false
|
||||
```
|
||||
|
||||
In strict mode, the following are considered unequal:
|
||||
|
||||
```ts
|
||||
// undefined values
|
||||
Bun.deepEquals({}, { a: undefined }, true); // false
|
||||
|
||||
// undefined in arrays
|
||||
Bun.deepEquals(["asdf"], ["asdf", undefined], true); // false
|
||||
|
||||
// sparse arrays
|
||||
Bun.deepEquals([, 1], [undefined, 1], true); // false
|
||||
|
||||
// object literals vs instances w/ same properties
|
||||
class Foo {
|
||||
a = 1;
|
||||
}
|
||||
Bun.deepEquals(new Foo(), { a: 1 }, true); // false
|
||||
```
|
||||
|
||||
## `Bun.escapeHTML()`
|
||||
|
||||
`Bun.escapeHTML(value: string | object | number | boolean): boolean`
|
||||
|
||||
Escapes the following characters from an input string:
|
||||
|
||||
- `"` becomes `"""`
|
||||
- `&` becomes `"&"`
|
||||
- `'` becomes `"'"`
|
||||
- `<` becomes `"<"`
|
||||
- `>` becomes `">"`
|
||||
|
||||
This function is optimized for large input. On an M1X, it processes 480 MB/s -
|
||||
20 GB/s, depending on how much data is being escaped and whether there is non-ascii
|
||||
text. Non-string types will be converted to a string before escaping.
|
||||
|
||||
<!-- ## `Bun.enableANSIColors()` -->
|
||||
|
||||
## `Bun.fileURLToPath()`
|
||||
|
||||
Converts a `file://` URL to an absolute path.
|
||||
|
||||
```ts
|
||||
const path = Bun.fileURLToPath(new URL("file:///foo/bar.txt"));
|
||||
console.log(path); // "/foo/bar.txt"
|
||||
```
|
||||
|
||||
## `Bun.pathToFileURL()`
|
||||
|
||||
Converts an absolute path to a `file://` URL.
|
||||
|
||||
```ts
|
||||
const url = Bun.pathToFileURL("/foo/bar.txt");
|
||||
console.log(url); // "file:///foo/bar.txt"
|
||||
```
|
||||
|
||||
<!-- Bun.hash; -->
|
||||
|
||||
## `Bun.gzipSync()`
|
||||
|
||||
Compresses a `Uint8Array` using zlib's DEFLATE algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array
|
||||
const compressed = Bun.gzipSync(buf);
|
||||
|
||||
buf; // => Uint8Array(500)
|
||||
compressed; // => Uint8Array(30)
|
||||
```
|
||||
|
||||
Optionally, pass a parameters object as the second argument:
|
||||
|
||||
{% details summary="zlib compression options"%}
|
||||
|
||||
```ts
|
||||
export type ZlibCompressionOptions = {
|
||||
/**
|
||||
* The compression level to use. Must be between `-1` and `9`.
|
||||
* - A value of `-1` uses the default compression level (Currently `6`)
|
||||
* - A value of `0` gives no compression
|
||||
* - A value of `1` gives least compression, fastest speed
|
||||
* - A value of `9` gives best compression, slowest speed
|
||||
*/
|
||||
level?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
/**
|
||||
* How much memory should be allocated for the internal compression state.
|
||||
*
|
||||
* A value of `1` uses minimum memory but is slow and reduces compression ratio.
|
||||
*
|
||||
* A value of `9` uses maximum memory for optimal speed. The default is `8`.
|
||||
*/
|
||||
memLevel?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
/**
|
||||
* The base 2 logarithm of the window size (the size of the history buffer).
|
||||
*
|
||||
* Larger values of this parameter result in better compression at the expense of memory usage.
|
||||
*
|
||||
* The following value ranges are supported:
|
||||
* - `9..15`: The output will have a zlib header and footer (Deflate)
|
||||
* - `-9..-15`: The output will **not** have a zlib header or footer (Raw Deflate)
|
||||
* - `25..31` (16+`9..15`): The output will have a gzip header and footer (gzip)
|
||||
*
|
||||
* The gzip header will have no file name, no extra data, no comment, no modification time (set to zero) and no header CRC.
|
||||
*/
|
||||
windowBits?:
|
||||
| -9
|
||||
| -10
|
||||
| -11
|
||||
| -12
|
||||
| -13
|
||||
| -14
|
||||
| -15
|
||||
| 9
|
||||
| 10
|
||||
| 11
|
||||
| 12
|
||||
| 13
|
||||
| 14
|
||||
| 15
|
||||
| 25
|
||||
| 26
|
||||
| 27
|
||||
| 28
|
||||
| 29
|
||||
| 30
|
||||
| 31;
|
||||
/**
|
||||
* Tunes the compression algorithm.
|
||||
*
|
||||
* - `Z_DEFAULT_STRATEGY`: For normal data **(Default)**
|
||||
* - `Z_FILTERED`: For data produced by a filter or predictor
|
||||
* - `Z_HUFFMAN_ONLY`: Force Huffman encoding only (no string match)
|
||||
* - `Z_RLE`: Limit match distances to one (run-length encoding)
|
||||
* - `Z_FIXED` prevents the use of dynamic Huffman codes
|
||||
*
|
||||
* `Z_RLE` is designed to be almost as fast as `Z_HUFFMAN_ONLY`, but give better compression for PNG image data.
|
||||
*
|
||||
* `Z_FILTERED` forces more Huffman coding and less string matching, it is
|
||||
* somewhat intermediate between `Z_DEFAULT_STRATEGY` and `Z_HUFFMAN_ONLY`.
|
||||
* Filtered data consists mostly of small values with a somewhat random distribution.
|
||||
*/
|
||||
strategy?: number;
|
||||
};
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
## `Bun.gunzipSync()`
|
||||
|
||||
Uncompresses a `Uint8Array` using zlib's INFLATE algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array
|
||||
const compressed = Bun.gunzipSync(buf);
|
||||
|
||||
const dec = new TextDecoder();
|
||||
const uncompressed = Bun.inflateSync(compressed);
|
||||
dec.decode(uncompressed);
|
||||
// => "hellohellohello..."
|
||||
```
|
||||
|
||||
## `Bun.deflateSync()`
|
||||
|
||||
Compresses a `Uint8Array` using zlib's DEFLATE algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100));
|
||||
const compressed = Bun.deflateSync(buf);
|
||||
|
||||
buf; // => Uint8Array(25)
|
||||
compressed; // => Uint8Array(10)
|
||||
```
|
||||
|
||||
The second argument supports the same set of configuration options as [`Bun.gzipSync`](#bun.gzipSync).
|
||||
|
||||
## `Bun.inflateSync()`
|
||||
|
||||
Uncompresses a `Uint8Array` using zlib's INFLATE algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100));
|
||||
const compressed = Bun.deflateSync(buf);
|
||||
|
||||
const dec = new TextDecoder();
|
||||
const uncompressed = Bun.inflateSync(compressed);
|
||||
dec.decode(uncompressed);
|
||||
// => "hellohellohello..."
|
||||
```
|
||||
|
||||
## `Bun.inspect()`
|
||||
|
||||
Serializes an object to a `string` exactly as it would be printed by `console.log`.
|
||||
|
||||
```ts
|
||||
const obj = { foo: "bar" };
|
||||
const str = Bun.inspect(obj);
|
||||
// => '{\nfoo: "bar" \n}'
|
||||
|
||||
const arr = new Uint8Array([1, 2, 3]);
|
||||
const str = Bun.inspect(arr);
|
||||
// => "Uint8Array(3) [ 1, 2, 3 ]"
|
||||
```
|
||||
|
||||
## `Bun.nanoseconds()`
|
||||
|
||||
Returns the number of nanoseconds since the current `bun` process started, as a `number`. Useful for high-precision timing and benchmarking.
|
||||
|
||||
```ts
|
||||
Bun.nanoseconds();
|
||||
// => 7288958
|
||||
```
|
||||
|
||||
## `Bun.readableStreamTo*()`
|
||||
|
||||
Bun implements a set of convenience functions for asynchronously consuming the body of a `ReadableStream` and converting it to various binary formats.
|
||||
|
||||
```ts
|
||||
const stream = (await fetch("https://bun.sh")).body;
|
||||
stream; // => ReadableStream
|
||||
|
||||
await Bun.readableStreamToArrayBuffer(stream);
|
||||
// => ArrayBuffer
|
||||
|
||||
await Bun.readableStreamToBlob(stream);
|
||||
// => Blob
|
||||
|
||||
await Bun.readableStreamToJSON(stream);
|
||||
// => object
|
||||
|
||||
await Bun.readableStreamToText(stream);
|
||||
// => string
|
||||
|
||||
// returns all chunks as an array
|
||||
await Bun.readableStreamToArray(stream);
|
||||
// => unknown[]
|
||||
```
|
||||
|
||||
## `Bun.resolveSync()`
|
||||
|
||||
Resolves a file path or module specifier using Bun's internal module resolution algorithm. The first argument is the path to resolve, and the second argument is the "root". If no match is found, an `Error` is thrown.
|
||||
|
||||
```ts
|
||||
Bun.resolveSync("./foo.ts", "/path/to/project");
|
||||
// => "/path/to/project/foo.ts"
|
||||
|
||||
Bun.resolveSync("zod", "/path/to/project");
|
||||
// => "/path/to/project/node_modules/zod/index.ts"
|
||||
```
|
||||
|
||||
To resolve relative to the current working directory, pass `process.cwd` or `"."` as the root.
|
||||
|
||||
```ts
|
||||
Bun.resolveSync("./foo.ts", process.cwd());
|
||||
Bun.resolveSync("./foo.ts", "/path/to/project");
|
||||
```
|
||||
|
||||
To resolve relative to the directory containing the current file, pass `import.meta.dir`.
|
||||
|
||||
```ts
|
||||
Bun.resolveSync("./foo.ts", import.meta.dir);
|
||||
```
|
||||
|
||||
@@ -216,12 +216,12 @@ If a value is specified for `publicPath`, the import will use value as a prefix
|
||||
---
|
||||
|
||||
- `"https://cdn.example.com/"`
|
||||
- `https://cdn.example.com/logo.svg`
|
||||
- `https://cdn.example.com/`
|
||||
|
||||
{% /table %}
|
||||
|
||||
{% callout %}
|
||||
The location and file name of the copied file is determined by the value of [`naming.asset`](/docs/bundler#naming).
|
||||
The location and file name of the copied file is determined by the value of [`naming.asset`](/docs/cli/build#naming).
|
||||
{% /callout %}
|
||||
This loader is copied into the `outdir` as-is. The name of the copied file is determined using the value of `naming.asset`.
|
||||
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
Macros are a mechanism for running JavaScript functions _at bundle-time_. The value returned from these functions are directly inlined into your bundle.
|
||||
|
||||
<!-- embed the result in your (browser) bundle. This is useful for things like embedding the current Git commit hash in your code, making fetch requests to your API at build-time, dead code elimination, and more. -->
|
||||
|
||||
As a toy example, consider this simple function that returns a random number.
|
||||
|
||||
```ts
|
||||
export function random() {
|
||||
return Math.random();
|
||||
}
|
||||
```
|
||||
|
||||
This is just a regular function in a regular file, but we can use it as a macro like so:
|
||||
|
||||
```ts#cli.tsx
|
||||
import { random } from './random.ts' with { type: 'macro' };
|
||||
|
||||
console.log(`Your random number is ${random()}`);
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Note** — Macros are indicated using [_import attribute_](https://github.com/tc39/proposal-import-attributes) syntax. If you haven't seen this syntax before, it's a Stage 3 TC39 proposal that lets you attach additional metadata to `import` statements.
|
||||
{% /callout %}
|
||||
|
||||
Now we'll bundle this file with `bun build`. The bundled file will be printed to stdout.
|
||||
|
||||
```bash
|
||||
$ bun build ./cli.tsx
|
||||
console.log(`Your random number is ${0.6805550949689833}`);
|
||||
```
|
||||
|
||||
As you can see, the source code of the `random` function occurs nowhere in the bundle. Instead, it is executed _during bundling_ and function call (`random()`) is replaced with the result of the function. Since the source code will never be included in the bundle, macros can safely perform privileged operations like reading from a database.
|
||||
|
||||
## When to use macros
|
||||
|
||||
If you have several build scripts for small things where you would otherwise have a one-off build script, bundle-time code execution can be easier to maintain. It lives with the rest of your code, it runs with the rest of the build, it is automatically paralellized, and if it fails, the build fails too.
|
||||
|
||||
If you find yourself running a lot of code at bundle-time though, consider running a server instead.
|
||||
|
||||
## Import attributes
|
||||
|
||||
Bun Macros are import statements annotated using either:
|
||||
|
||||
- `with { type: 'macro' }` — an [import attribute](https://github.com/tc39/proposal-import-attributes), a Stage 3 ECMA Scrd
|
||||
- `assert { type: 'macro' }` — an import assertion, an earlier incarnation of import attributes that has now been abandoned (but is [already supported](https://caniuse.com/mdn-javascript_statements_import_import_assertions) by a number of browsers and runtimes)
|
||||
|
||||
## Security considerations
|
||||
|
||||
Macros must explicitly be imported with `{ type: "macro" }` in order to be executed at bundle-time. These imports have no effect if they are not called, unlike regular JavaScript imports which may have side effects.
|
||||
|
||||
You can disable macros entirely by passing the `--no-macros` flag to Bun. It produces a build error like this:
|
||||
|
||||
```js
|
||||
error: Macros are disabled
|
||||
|
||||
foo();
|
||||
^
|
||||
./hello.js:3:1 53
|
||||
```
|
||||
|
||||
To reduce the potential attack surface for malicious packages, macros cannot be _invoked_ from inside `node_modules/**/*`. If a package attempts to invoke a macro, you'll see an error like this:
|
||||
|
||||
```js
|
||||
error: For security reasons, macros cannot be run from node_modules.
|
||||
|
||||
beEvil();
|
||||
^
|
||||
node_modules/evil/index.js:3:1 50
|
||||
```
|
||||
|
||||
Your application code can still import macros from `node_modules` and invoke them.
|
||||
|
||||
```ts
|
||||
import {macro} from "some-package" with { type: "macro" };
|
||||
|
||||
macro();
|
||||
```
|
||||
|
||||
## Export condition `"macro"`
|
||||
|
||||
When shipping a library containing a macro to `npm` or another package registry, use the `"macro"` [export condition](https://nodejs.org/api/packages.html#conditional-exports) to provide a special version of your package exclusively for the macro environment.
|
||||
|
||||
```jsonc#package.json
|
||||
{
|
||||
"name": "my-package",
|
||||
"exports": {
|
||||
"import": "./index.js",
|
||||
"require": "./index.js",
|
||||
"default": "./index.js",
|
||||
"macro": "./index.macro.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With this configuration, users can consume your package at runtime or at bundle-time using the same import specifier:
|
||||
|
||||
```ts
|
||||
import pkg from "my-package"; // runtime import
|
||||
import {macro} from "my-package" with { type: "macro" }; // macro import
|
||||
```
|
||||
|
||||
The first import will resolve to `./node_modules/my-package/index.js`, while the second will be resolved by Bun's bundler to `./node_modules/my-package/index.macro.js`.
|
||||
|
||||
## Execution
|
||||
|
||||
When Bun's transpiler sees a macro import, it calls the function inside the transpiler using Bun's JavaScript runtime and converts the return value from JavaScript into an AST node. These JavaScript functions are called at bundle-time, not runtime.
|
||||
|
||||
Macros are executed synchronously in the transpiler during the visiting phase—before plugins and before the transpiler generates the AST. They are executed in the order they are imported. The transpiler will wait for the macro to finish executing before continuing. The transpiler will also `await` any `Promise` returned by a macro.
|
||||
|
||||
Bun's bundler is multi-threaded. As such, macros execute in parallel inside of multiple spawned JavaScript "workers".
|
||||
|
||||
## Dead code elimination
|
||||
|
||||
The bundler performs dead code elimination _after_ running and inlining macros. So given the following macro:
|
||||
|
||||
```ts#returnFalse.ts
|
||||
export function returnFalse() {
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
...then bundling the following file will produce an empty bundle.
|
||||
|
||||
```ts
|
||||
import {returnFalse} from './returnFalse.ts' with { type: 'macro' };
|
||||
|
||||
if (returnFalse()) {
|
||||
console.log("This code is eliminated");
|
||||
}
|
||||
```
|
||||
|
||||
## Serializablility
|
||||
|
||||
Bun's transpiler needs to be able to serialize the result of the macro so it can be inlined into the AST. All JSON-compatible data structures are supported:
|
||||
|
||||
```ts#macro.ts
|
||||
export function getObject() {
|
||||
return {
|
||||
foo: "bar",
|
||||
baz: 123,
|
||||
array: [ 1, 2, { nested: "value" }],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Macros can be async, or return `Promise` instances. Bun's transpiler will automatically `await` the `Promise` and inline the result.
|
||||
|
||||
```ts#macro.ts
|
||||
export async function getText() {
|
||||
return "async value";
|
||||
}
|
||||
```
|
||||
|
||||
The transpiler implements special logic for serializing common data formats like `Response`, `Blob`, `TypedArray`.
|
||||
|
||||
- `TypedArray`: Resolves to a base64-encoded string.
|
||||
- `Response`: Bun will read the `Content-Type` and serialize accordingly; for instance, a `Response` with type `application/json` will be automatically parsed into an object and `text/plain` will be inlined as a string. Responses with an unrecognized or `undefined` `type` will be base-64 encoded.
|
||||
- `Blob`: As with `Response`, the serialization depends on the `type` property.
|
||||
|
||||
The result of `fetch` is `Promise<Response>`, so it can be directly returned.
|
||||
|
||||
```ts#macro.ts
|
||||
export function getObject() {
|
||||
return fetch("https://bun.sh")
|
||||
}
|
||||
```
|
||||
|
||||
Functions and instances of most classes (except those mentioned above) are not serializable.
|
||||
|
||||
```ts
|
||||
export function getText(url: string) {
|
||||
// this doesn't work!
|
||||
return () => {};
|
||||
}
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed:
|
||||
|
||||
```ts
|
||||
import {getText} from './getText.ts' with { type: 'macro' };
|
||||
|
||||
export function howLong() {
|
||||
// the value of `foo` cannot be statically known
|
||||
const foo = Math.random() ? "foo" : "bar";
|
||||
|
||||
const text = getText(`https://example.com/${foo}`);
|
||||
console.log("The page is ", text.length, " characters long");
|
||||
}
|
||||
```
|
||||
|
||||
However, if the value of `foo` is known at bundle-time (say, if it's a constant or the result of another macro) then it's allowed:
|
||||
|
||||
```ts
|
||||
import {getText} from './getText.ts' with { type: 'macro' };
|
||||
import {getFoo} from './getFoo.ts' with { type: 'macro' };
|
||||
|
||||
export function howLong() {
|
||||
// this works because getFoo() is statically known
|
||||
const foo = getFoo();
|
||||
const text = getText(`https://example.com/${foo}`);
|
||||
console.log("The page is", text.length, "characters long");
|
||||
}
|
||||
```
|
||||
|
||||
This outputs:
|
||||
|
||||
```ts
|
||||
function howLong() {
|
||||
console.log("The page is", 1322, "characters long");
|
||||
}
|
||||
export { howLong };
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Embed latest git commit hash
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#getGitCommitHash.ts
|
||||
export function getGitCommitHash() {
|
||||
const {stdout} = Bun.spawnSync({
|
||||
cmd: ["git", "rev-parse", "HEAD"],
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
return stdout.toString();
|
||||
}
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
<!-- --target=browser so they can clearly see it's for browsers -->
|
||||
|
||||
When we build it, the `getGitCommitHash` is replaced with the result of calling the function:
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#input
|
||||
import { getGitCommitHash } from './getGitCommitHash.ts' with { type: 'macro' };
|
||||
|
||||
console.log(`The current Git commit hash is ${getGitCommitHash()}`);
|
||||
```
|
||||
|
||||
```bash#output
|
||||
console.log(`The current Git commit hash is 3ee3259104f`);
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
You're probably thinking "Why not just use `process.env.GIT_COMMIT_HASH`?" Well, you can do that too. But can you do this with an environment variable?
|
||||
|
||||
### Make `fetch()` requests at bundle-time
|
||||
|
||||
In this example, we make an outgoing HTTP request using `fetch()`, parse the HTML response using `HTMLRewriter`, and return an object containing the title and meta tags–all at bundle-time.
|
||||
|
||||
```ts
|
||||
export async function extractMetaTags(url: string) {
|
||||
const response = await fetch(url);
|
||||
const meta = {
|
||||
title: "",
|
||||
};
|
||||
new HTMLRewriter()
|
||||
.on("title", {
|
||||
text(element) {
|
||||
meta.title += element.text;
|
||||
},
|
||||
})
|
||||
.on("meta", {
|
||||
element(element) {
|
||||
const name =
|
||||
element.getAttribute("name") || element.getAttribute("property") || element.getAttribute("itemprop");
|
||||
|
||||
if (name) meta[name] = element.getAttribute("content");
|
||||
},
|
||||
})
|
||||
.transform(response);
|
||||
|
||||
return meta;
|
||||
}
|
||||
```
|
||||
|
||||
<!-- --target=browser so they can clearly see it's for browsers -->
|
||||
|
||||
The `extractMetaTags` function is erased at bundle-time and replaced with the result of the function call. This means that the `fetch` request happens at bundle-time, and the result is embedded in the bundle. Also, the branch throwing the error is eliminated since it's unreachable.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#input
|
||||
import { extractMetaTags } from './meta.ts' with { type: 'macro' };
|
||||
|
||||
export const Head = () => {
|
||||
const headTags = extractMetaTags("https://example.com");
|
||||
|
||||
if (headTags.title !== "Example Domain") {
|
||||
throw new Error("Expected title to be 'Example Domain'");
|
||||
}
|
||||
|
||||
return <head>
|
||||
<title>{headTags.title}</title>
|
||||
<meta name="viewport" content={headTags.viewport} />
|
||||
</head>;
|
||||
};
|
||||
```
|
||||
|
||||
```ts#output
|
||||
import { jsx, jsxs } from "react/jsx-runtime";
|
||||
export const Head = () => {
|
||||
jsxs("head", {
|
||||
children: [
|
||||
jsx("title", {
|
||||
children: "Example Domain",
|
||||
}),
|
||||
jsx("meta", {
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1",
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export { Head };
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
@@ -1,5 +1,5 @@
|
||||
{% callout %}
|
||||
**Note** — Available in Bun v0.6.0 and later.
|
||||
**Note** — Available in the Bun v0.6.0 nightly. Run `bun upgrade --canary` to try it out.
|
||||
{% /callout %}
|
||||
|
||||
Bun's bundler API is inspired heavily by [esbuild](https://esbuild.github.io/). Migrating to Bun's bundler from esbuild should be relatively painless. This guide will briefly explain why you might consider migrating to Bun's bundler and provide a side-by-side API comparison reference for those who are already familiar with esbuild's API.
|
||||
@@ -11,9 +11,9 @@ There are a few behavioral differences to note.
|
||||
|
||||
## Performance
|
||||
|
||||
With an performance-minded API coupled with the extensively optimized Zig-based JS/TS parser, Bun's bundler is 1.75x faster than esbuild on esbuild's [three.js benchmark](https://github.com/oven-sh/bun/tree/main/bench/bundle).
|
||||
This is the simplest reason to migrate to Bun's bundler. With an performance-minded API inspired by esbuild coupled with the extensively optimized Zig-based JS/TS parser, Bun's bundler is roughly 50% faster than esbuild on most benchmarks.
|
||||
|
||||
{% image src="/images/bundler-speed.png" caption="Bundling 10 copies of three.js from scratch, with sourcemaps and minification" /%}
|
||||
IMAGE HERE
|
||||
|
||||
## CLI API
|
||||
|
||||
@@ -31,15 +31,11 @@ Bun.build({
|
||||
});
|
||||
```
|
||||
|
||||
<!-- It can also be "registered" with the Bun runtime using the `Bun.plugin()` function. Once registered, the currently executing `bun` process will incorporate the plugin into its module resolution algorithm.
|
||||
It can also be "registered" with the Bun runtime using the `Bun.plugin()` function. Once registered, the currently executing `bun` process will incorporate the plugin into its module resolution algorithm.
|
||||
|
||||
```ts
|
||||
import {plugin} from "bun";
|
||||
|
||||
plugin(myPlugin);
|
||||
``` -->
|
||||
|
||||
## `--preload`
|
||||
Bun.plugin(myPlugin);
|
||||
```
|
||||
|
||||
To consume this plugin, add this file to the `preload` option in your [`bunfig.toml`](/docs/runtime/configuration). Bun automatically loads the files/modules specified in `preload` before running a file.
|
||||
|
||||
@@ -84,7 +80,7 @@ plugin(
|
||||
// application code
|
||||
```
|
||||
|
||||
Bun's plugin API is based on [esbuild](https://esbuild.github.io/plugins). Only [a subset](/docs/bundler/vs-esbuild#plugin-api) of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official [MDX loader](https://mdxjs.com/packages/esbuild/):
|
||||
Bun's plugin API is based on [esbuild](https://esbuild.github.io/plugins). Only [a subset](/docs/bundler/migration#plugin-api) of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official [MDX loader](https://mdxjs.com/packages/esbuild/):
|
||||
|
||||
```jsx
|
||||
import { plugin } from "bun";
|
||||
@@ -278,25 +274,25 @@ console.log(mySvelteComponent.render());
|
||||
|
||||
## Reading `Bun.build`'s config
|
||||
|
||||
Plugins can read and write to the [build config](/docs/bundler#api) with `build.config`.
|
||||
Plugins can read and write to the [build config](/docs/cli/build#api) with `build.config`.
|
||||
|
||||
```ts
|
||||
Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
sourcemap: "external",
|
||||
sourcemap: 'external',
|
||||
plugins: [
|
||||
{
|
||||
name: "demo",
|
||||
name: 'demo',
|
||||
setup(build) {
|
||||
console.log(build.config.sourcemap); // "external"
|
||||
|
||||
build.config.minify = true; // enable minification
|
||||
|
||||
// `plugins` is readonly
|
||||
console.log(`Number of plugins: ${build.config.plugins.length}`);
|
||||
},
|
||||
},
|
||||
console.log(`Number of plugins: ${build.config.plugins.length}`);
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
Bun ships with a fast built-in test runner. Tests are executed with the Bun runtime, and support the following features.
|
||||
|
||||
- TypeScript and JSX
|
||||
- Lifecycle hooks
|
||||
- Snapshot testing
|
||||
- UI & DOM testing
|
||||
- Watch mode with `--watch`
|
||||
- Script pre-loading with `--preload`
|
||||
Bun ships with a built-in test runner.
|
||||
|
||||
## Run tests
|
||||
|
||||
@@ -36,7 +29,25 @@ You can filter the set of tests to run by passing additional positional argument
|
||||
$ bun test <filter> <filter> ...
|
||||
```
|
||||
|
||||
The test runner runs all tests in a single process. It loads all `--preload` scripts (see [Lifecycle](/docs/test/lifecycle) for details), then runs all tests. If a test fails, the test runner will exit with a non-zero exit code.
|
||||
## Snapshot testing
|
||||
|
||||
Snapshots are supported by `bun test`. First, write a test using the `.toMatchSnapshot()` matcher:
|
||||
|
||||
```ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("snap", () => {
|
||||
expect("foo").toMatchSnapshot();
|
||||
});
|
||||
```
|
||||
|
||||
Then generate snapshots with the following command:
|
||||
|
||||
```bash
|
||||
bun test --update-snapshots
|
||||
```
|
||||
|
||||
Snapshots will be stored in a `__snapshots__` directory next to the test file.
|
||||
|
||||
## Watch mode
|
||||
|
||||
@@ -46,37 +57,6 @@ Similar to `bun run`, you can pass the `--watch` flag to `bun test` to watch for
|
||||
$ bun test --watch
|
||||
```
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
Bun supports the following lifecycle hooks:
|
||||
|
||||
| Hook | Description |
|
||||
| ------------ | --------------------------- |
|
||||
| `beforeAll` | Runs once before all tests. |
|
||||
| `beforeEach` | Runs before each test. |
|
||||
| `afterEach` | Runs after each test. |
|
||||
| `afterAll` | Runs once after all tests. |
|
||||
|
||||
These hooks can be define inside test files, or in a separate file that is preloaded with the `--preload` flag.
|
||||
|
||||
```ts
|
||||
$ bun test --preload ./setup.ts
|
||||
```
|
||||
|
||||
See [Test > Lifecycle](/docs/test/lifecycle) for complete documentation.
|
||||
|
||||
## Snapshot testing
|
||||
|
||||
Snapshots are supported by `bun test`. See [Test > Snapshots](/docs/test/snapshots) for complete documentation.
|
||||
|
||||
## UI & DOM testing
|
||||
|
||||
Bun is compatible with popular UI testing libraries:
|
||||
|
||||
- [HappyDOM](https://github.com/capricorn86/happy-dom)
|
||||
- [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro/)
|
||||
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
|
||||
|
||||
## Performance
|
||||
|
||||
Bun's test runner is fast.
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
[Elysia](https://elysiajs.com) is a Bun-first performance focused web framework that takes full advantage of Bun's HTTP, file system, and hot reloading APIs.
|
||||
Designed with TypeScript in mind, you don't need to understand TypeScript to gain the benefit of TypeScript with Elysia. The library understands what you want and automatically infers the type from your code.
|
||||
|
||||
:zap: Elysia is [one of the fastest Bun web frameworks](https://github.com/SaltyAom/bun-http-framework-benchmark)
|
||||
[Elysia](https://elysiajs.com) is a Bun-first web framework that takes full advantage of Bun's HTTP, file system, and hot reloading APIs.
|
||||
|
||||
```ts#server.ts
|
||||
import { Elysia } from 'elysia'
|
||||
@@ -21,4 +18,4 @@ $ cd myapp
|
||||
$ bun run dev
|
||||
```
|
||||
|
||||
Refer to the Elysia [documentation](https://elysiajs.com/quick-start.html) for more information.
|
||||
Refer to the Elysia [documentation](https://elysiajs.com/quick-start.html) for more information.
|
||||
@@ -61,5 +61,3 @@ Bun.serve({
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
React `18.3` and later includes an [SSR optimization](https://github.com/facebook/react/pull/25597) that takes advantage of Bun's "direct" `ReadableStream` implementation.
|
||||
|
||||
@@ -9,10 +9,9 @@ $ bun run index.tsx # TS and JSX supported out of the box
|
||||
The `bun` command-line tool also implements a test runner, script runner, and Node.js-compatible package manager, all significantly faster than existing tools and usable in existing Node.js projects with little to no changes necessary.
|
||||
|
||||
```bash
|
||||
$ bun test # run tests
|
||||
$ bun run start # run the `start` script
|
||||
$ bun install <pkg> # install a package
|
||||
$ bun build ./index.tsx # bundle a project for browsers
|
||||
$ bun test # run tests
|
||||
$ bunx cowsay "Hello, world!" # execute a package
|
||||
```
|
||||
|
||||
@@ -27,7 +26,7 @@ Get started with one of the quick links below, or read on to learn more about Bu
|
||||
{% arrowbutton href="/docs/quickstart" text="Do the quickstart" /%}
|
||||
{% arrowbutton href="/docs/cli/install" text="Install a package" /%}
|
||||
{% arrowbutton href="/docs/templates" text="Use a project template" /%}
|
||||
{% arrowbutton href="/docs/bundler" text="Bundle code for production" /%}
|
||||
{% arrowbutton href="/docs/cli/build" text="Bundle code for production" /%}
|
||||
{% arrowbutton href="/docs/api/http" text="Build an HTTP server" /%}
|
||||
{% arrowbutton href="/docs/api/websockets" text="Build a Websocket server" /%}
|
||||
{% arrowbutton href="/docs/api/file-io" text="Read and write files" /%}
|
||||
@@ -60,11 +59,11 @@ Bun is designed as a faster, leaner, more modern replacement for Node.js. Node.j
|
||||
|
||||
## Design goals
|
||||
|
||||
Bun is designed from the ground-up with today's JavaScript ecosystem in mind.
|
||||
Bun is designed from the ground-up with the today's JavaScript ecosystem in mind.
|
||||
|
||||
- **Speed**. Bun processes start [4x faster than Node.js](https://twitter.com/jarredsumner/status/1499225725492076544) currently (try it yourself!)
|
||||
- **TypeScript & JSX support**. You can directly execute `.jsx`, `.ts`, and `.tsx` files; Bun's transpiler converts these to vanilla JavaScript before execution.
|
||||
- **ESM & CommonJS compatibility**. The world is moving towards ES modules (ESM), but millions of packages on npm still require CommonJS. Bun recommends ES modules, but supports CommonJS.
|
||||
- **ESM & CommonJS compatibility**. Internally, Bun uses ESM exclusively, but CommonJS modules can be imported as-is.
|
||||
- **Web-standard APIs**. Bun implements standard Web APIs like `fetch`, `WebSocket`, and `ReadableStream`. Bun is powered by the JavaScriptCore engine, which is developed by Apple for Safari, so some APIs like [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) and [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) directly use [Safari's implementation](https://github.com/oven-sh/bun/blob/HEAD/src/bun.js/bindings/webcore/JSFetchHeaders.cpp).
|
||||
- **Node.js compatibility**. In addition to supporting Node-style module resolution, Bun aims for full compatibility with built-in Node.js globals (`process`, `Buffer`) and modules (`path`, `fs`, `http`, etc.) _This is an ongoing effort that is not complete._ Refer to the compatibility page for the current status.
|
||||
|
||||
|
||||
42
docs/nav.ts
42
docs/nav.ts
@@ -90,9 +90,9 @@ export default {
|
||||
page("runtime/typescript", "TypeScript", {
|
||||
description: `Bun can directly execute TypeScript files without additional configuration.`,
|
||||
}),
|
||||
page("runtime/jsx", "JSX", {
|
||||
description: `Bun can directly execute TypeScript files without additional configuration.`,
|
||||
}),
|
||||
// page("runtime/jsx", "JSX", {
|
||||
// description: `Bun can directly execute TypeScript files without additional configuration.`,
|
||||
// }),
|
||||
// page("runtime/apis", "APIs", {
|
||||
// description: `Bun is a new JavaScript runtime designed to be a faster, leaner, more modern replacement for Node.js.`,
|
||||
// }),
|
||||
@@ -154,7 +154,7 @@ export default {
|
||||
}),
|
||||
|
||||
divider("Bundler"),
|
||||
page("bundler", "`Bun.build`", {
|
||||
page("cli/build", "`Bun.build`", {
|
||||
description: "Bundle code for comsumption in the browser with Bun's native bundler.",
|
||||
}),
|
||||
// page("bundler/intro", "How bundlers work", {
|
||||
@@ -169,10 +169,7 @@ export default {
|
||||
page("bundler/executables", "Executables", {
|
||||
description: "Compile a TypeScript or JavaScript file to a standalone cross-platform executable",
|
||||
}),
|
||||
page("bundler/macros", "Macros", {
|
||||
description: `Run JavaScript functions at bundle-time and inline the results into your bundle`,
|
||||
}),
|
||||
page("bundler/vs-esbuild", "vs esbuild", {
|
||||
page("bundler/migration", "Migration", {
|
||||
description: `Guides for migrating from other bundlers to Bun.`,
|
||||
}),
|
||||
|
||||
@@ -184,11 +181,8 @@ export default {
|
||||
description:
|
||||
"Write your tests using Jest-like expect matchers, plus setup/teardown hooks, snapshot testing, and more",
|
||||
}),
|
||||
page("test/lifecycle", "Lifecycle hooks", {
|
||||
description: "Add lifecycle hooks to your tests that run before/after each test or test run",
|
||||
}),
|
||||
page("test/snapshots", "Snapshots", {
|
||||
description: "Add lifecycle hooks to your tests that run before/after each test or test run",
|
||||
page("test/extending", "Extending the test runner", {
|
||||
description: "Add lifecycle hooks to your tests that run before/after each test, or before/after all tests.",
|
||||
}),
|
||||
page("test/hot", "Watch mode", {
|
||||
description: "Reload your tests automatically on change.",
|
||||
@@ -227,45 +221,33 @@ export default {
|
||||
}),
|
||||
|
||||
divider("API"),
|
||||
page("api/http", "HTTP server", {
|
||||
page("api/http", "HTTP", {
|
||||
description: `Bun implements Web-standard fetch, plus a Bun-native API for building fast HTTP servers.`,
|
||||
}), // "`Bun.serve`"),
|
||||
page("api/websockets", "WebSockets", {
|
||||
description: `Bun supports server-side WebSockets with on-the-fly compression, TLS support, and a Bun-native pubsub API.`,
|
||||
}), // "`Bun.serve`"),
|
||||
page("api/binary-data", "Binary data", {
|
||||
description: `How to represent and manipulate binary data in Bun.`,
|
||||
}), // "`Bun.serve`"),
|
||||
page("api/streams", "Streams", {
|
||||
description: `Reading, writing, and manipulating streams of data in Bun.`,
|
||||
}), // "`Bun.serve`"),
|
||||
page("api/tcp", "TCP Sockets", {
|
||||
description: `Bun's native API implements Web-standard TCP Sockets, plus a Bun-native API for building fast TCP servers.`,
|
||||
}), // "`Bun.{listen|connect}`"),
|
||||
page("api/file-io", "File I/O", {
|
||||
description: `Read and write files fast with Bun's heavily optimized file system API.`,
|
||||
}), // "`Bun.write`"),
|
||||
page("api/import-meta", "import.meta", {
|
||||
description: `Module-scoped metadata and utilities`,
|
||||
}), // "`bun:sqlite`"),
|
||||
page("api/sqlite", "SQLite", {
|
||||
description: `The fastest SQLite driver for JavaScript is baked directly into Bun.`,
|
||||
}), // "`bun:sqlite`"),
|
||||
page("api/file-system-router", "FileSystemRouter", {
|
||||
description: `Resolve incoming HTTP requests against a local file system directory with Bun's fast, Next.js-compatible router.`,
|
||||
}), // "`Bun.FileSystemRouter`"),
|
||||
page("api/tcp", "TCP sockets", {
|
||||
description: `Bun's native API implements Web-standard TCP Sockets, plus a Bun-native API for building fast TCP servers.`,
|
||||
}), // "`Bun.{listen|connect}`")
|
||||
page("api/globals", "Globals", {
|
||||
description: `Bun implements a range of Web APIs, Node.js APIs, and Bun-native APIs that are available in the global scope.`,
|
||||
}), // "`Bun.write`"),
|
||||
page("api/spawn", "Child processes", {
|
||||
page("api/spawn", "Spawn", {
|
||||
description: `Spawn sync and async child processes with easily configurable input and output streams.`,
|
||||
}), // "`Bun.spawn`"),
|
||||
page("api/transpiler", "Transpiler", {
|
||||
description: `Bun exposes its internal transpiler as a pluggable API.`,
|
||||
}), // "`Bun.Transpiler`"),
|
||||
page("api/hashing", "Hashing", {
|
||||
description: `Native support for a range of fast hashing algorithms.`,
|
||||
}), // "`Bun.serve`"),
|
||||
page("api/console", "Console", {
|
||||
description: `Bun implements a Node.js-compatible \`console\` object with colorized output and deep pretty-printing.`,
|
||||
}), // "`Node-API`"),
|
||||
|
||||
@@ -2,34 +2,6 @@ Configuring a development environment for Bun can take 10-30 minutes depending o
|
||||
|
||||
If you are using Windows, you must use a WSL environment as Bun does not yet compile on Windows natively.
|
||||
|
||||
Before starting, you will need to already have a release build of Bun installed, as we use our bundler to transpile and minify our code.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```bash#Native
|
||||
$ curl -fsSL https://bun.sh/install | bash # for macOS, Linux, and WSL
|
||||
```
|
||||
|
||||
```bash#npm
|
||||
$ npm install -g bun # the last `npm` command you'll ever need
|
||||
```
|
||||
|
||||
```bash#Homebrew
|
||||
$ brew tap oven-sh/bun # for macOS and Linux
|
||||
$ brew install bun
|
||||
```
|
||||
|
||||
```bash#Docker
|
||||
$ docker pull oven/bun
|
||||
$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun
|
||||
```
|
||||
|
||||
```bash#proto
|
||||
$ proto install bun
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
## Install LLVM
|
||||
|
||||
Bun requires LLVM 15 and Clang 15 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
@@ -132,13 +104,13 @@ VSCode is the recommended IDE for working on Bun, as it has been configured. Onc
|
||||
|
||||
## JavaScript builtins
|
||||
|
||||
When you change anything in `src/js/builtins/*` or switch branches, run this:
|
||||
When you change anything in `src/bun.js/builtins/js/*` or switch branches, run this:
|
||||
|
||||
```bash
|
||||
$ make regenerate-bindings
|
||||
```
|
||||
|
||||
That inlines the TypeScript code into C++ headers.
|
||||
That inlines the JavaScript code into C++ headers using the same builtins generator script that Safari uses.
|
||||
|
||||
{% callout %}
|
||||
Make sure you have `ccache` installed, otherwise regeneration will take much longer than it should.
|
||||
@@ -176,17 +148,11 @@ $ make generate-sink
|
||||
|
||||
You probably won't need to run that one much.
|
||||
|
||||
## Modifying ESM modules
|
||||
## Modifying ESM core modules
|
||||
|
||||
Certain modules like `node:fs`, `node:stream`, `bun:sqlite`, and `ws` are implemented in JavaScript. These live in `src/js/{node,bun,thirdparty}` files and are pre-bundled using Bun. The bundled code is committed so CI builds can run without needing a copy of Bun.
|
||||
Certain modules like `node:fs`, `node:path`, `node:stream`, and `bun:sqlite` are implemented in JavaScript. These live in `src/bun.js/*.exports.js` files.
|
||||
|
||||
When these are changed, run:
|
||||
|
||||
```
|
||||
$ make esm
|
||||
```
|
||||
|
||||
In debug builds, Bun automatically loads these from the filesystem, wherever it was compiled, so no need to re-run `make dev`. In release builds, this same behavior can be done via the environment variable `BUN_OVERRIDE_MODULE_PATH`. When set to the repository root, Bun will read from the bundled modules in the repository instead of the ones baked into the binary.
|
||||
While Bun is in beta, you can modify them at runtime in release builds via the environment variable `BUN_OVERRIDE_MODULE_PATH`. When set, Bun will look in the override directory for `<name>.exports.js` before checking the files from `src/bun.js` (which are now baked in to the binary). This lets you test changes to the ESM modules without needing to re-compile Bun.
|
||||
|
||||
## Release build
|
||||
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
Bun implements a set of native APIs on the `Bun` global object and through a number of built-in modules. These APIs are heavily optimized and represent the canonical "Bun-native" way to implement some common functionality.
|
||||
|
||||
Bun strives to implement standard Web APIs wherever possible. Bun introduces new APIs primarily for server-side tasks where no standard exists, such as file I/O and starting an HTTP server. In these cases, Bun's approach still builds atop standard APIs like `Blob`, `URL`, and `Request`.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req: Request) {
|
||||
return new Response("Success!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Click the link in the right column to jump to the associated documentation.
|
||||
Bun implements a set of native APIs on the `Bun` global object and through a number of built-in modules. These APIs represent the canonical "Bun-native" way to perform some common development tasks. They are all heavily optimized for performance. Click the link in the left column to view the associated documentation.
|
||||
|
||||
{% table %}
|
||||
|
||||
@@ -19,53 +7,38 @@ Click the link in the right column to jump to the associated documentation.
|
||||
|
||||
---
|
||||
|
||||
- HTTP server
|
||||
- [`Bun.serve`](/docs/api/http#bun-serve)
|
||||
- [HTTP](/docs/api/http)
|
||||
- `Bun.serve`
|
||||
|
||||
---
|
||||
|
||||
- Bundler
|
||||
- [`Bun.build`](/docs/bundler)
|
||||
- [File I/O](/docs/api/file-io)
|
||||
- `Bun.file` `Bun.write`
|
||||
|
||||
---
|
||||
|
||||
- File I/O
|
||||
- [`Bun.file`](/docs/api/file-io#reading-files-bun-file) [`Bun.write`](/docs/api/file-io#writing-files-bun-write)
|
||||
- [Processes](/docs/api/spawn)
|
||||
- `Bun.spawn` `Bun.spawnSync`
|
||||
|
||||
---
|
||||
|
||||
- Child processes
|
||||
- [`Bun.spawn`](/docs/api/spawn#spawn-a-process-bun-spawn) [`Bun.spawnSync`](/docs/api/spawn#blocking-api-bun-spawnsync)
|
||||
- [TCP](/docs/api/tcp)
|
||||
- `Bun.listen` `Bun.connect`
|
||||
|
||||
---
|
||||
|
||||
- TCP
|
||||
- [`Bun.listen`](/docs/api/tcp#start-a-server-bun-listen) [`Bun.connect`](/docs/api/tcp#start-a-server-bun-listen)
|
||||
- [Transpiler](/docs/api/transpiler)
|
||||
- `Bun.Transpiler`
|
||||
|
||||
---
|
||||
|
||||
- Transpiler
|
||||
- [`Bun.Transpiler`](/docs/api/transpiler)
|
||||
- [Routing](/docs/api/file-system-router)
|
||||
- `Bun.FileSystemRouter`
|
||||
|
||||
---
|
||||
|
||||
- Routing
|
||||
- [`Bun.FileSystemRouter`](/docs/api/file-system-router)
|
||||
|
||||
---
|
||||
|
||||
- HTML Rewriting
|
||||
- [`HTMLRewriter`](/docs/api/html-rewriter)
|
||||
|
||||
---
|
||||
|
||||
- Hashing
|
||||
- [`Bun.hash`](/docs/api/hashing#bun-hash) [`Bun.CryptoHasher`](/docs/api/hashing#bun-cryptohasher)
|
||||
|
||||
---
|
||||
|
||||
- import.meta
|
||||
- [`import.meta`](/docs/api/import-meta)
|
||||
- [HTMLRewriter](/docs/api/html-rewriter)
|
||||
- `HTMLRewriter`
|
||||
|
||||
---
|
||||
|
||||
@@ -74,27 +47,29 @@ Click the link in the right column to jump to the associated documentation.
|
||||
|
||||
--- -->
|
||||
|
||||
- SQLite
|
||||
- [`bun:sqlite`](/docs/api/sqlite)
|
||||
- [Utils](/docs/api/utils)
|
||||
- `Bun.peek` `Bun.which`
|
||||
|
||||
---
|
||||
|
||||
- FFI
|
||||
- [`bun:ffi`](/docs/api/ffi)
|
||||
- [SQLite](/docs/api/sqlite)
|
||||
- `bun:sqlite`
|
||||
|
||||
---
|
||||
|
||||
- Testing
|
||||
- [`bun:test`](/docs/cli/test)
|
||||
- [FFI](/docs/api/ffi)
|
||||
- `bun:ffi`
|
||||
|
||||
---
|
||||
|
||||
- Node-API
|
||||
- [`Node-API`](/docs/api/node-api)
|
||||
- [Testing](/docs/api/test)
|
||||
- `bun:test`
|
||||
|
||||
---
|
||||
|
||||
- Utilities
|
||||
- [`Bun.version`](/docs/api/utils#bun-version) [`Bun.revision`](/docs/api/utils#bun-revision) [`Bun.env`](/docs/api/utils#bun-env) [`Bun.main`](/docs/api/utils#bun-main) [`Bun.sleep()`](/docs/api/utils#bun-sleep) [`Bun.sleepSync()`](/docs/api/utils#bun-sleepsync) [`Bun.which()`](/docs/api/utils#bun-which) [`Bun.peek()`](/docs/api/utils#bun-peek) [`Bun.openInEditor()`](/docs/api/utils#bun-openineditor) [`Bun.deepEquals()`](/docs/api/utils#bun-deepequals) [`Bun.escapeHTML()`](/docs/api/utils#bun-escapehtlm) [`Bun.enableANSIColors()`](/docs/api/utils#bun-enableansicolors) [`Bun.fileURLToPath()`](/docs/api/utils#bun-fileurltopath) [`Bun.pathToFileURL()`](/docs/api/utils#bun-pathtofileurl) [`Bun.gzipSync()`](/docs/api/utils#bun-gzipsync) [`Bun.gunzipSync()`](/docs/api/utils#bun-gunzipsync) [`Bun.deflateSync()`](/docs/api/utils#bun-deflatesync) [`Bun.inflateSync()`](/docs/api/utils#bun-inflatesync) [`Bun.inspect()`](/docs/api/utils#bun-inspect) [`Bun.nanoseconds()`](/docs/api/utils#bun-nanoseconds) [`Bun.readableStreamTo*()`](/docs/api/utils#bun-readablestreamto) [`Bun.resolveSync()`](/docs/api/utils#bun-resolvesync)
|
||||
- [Node-API](/docs/api/node-api)
|
||||
- `Node-API`
|
||||
|
||||
---
|
||||
|
||||
{% /table %}
|
||||
|
||||
@@ -14,99 +14,12 @@ If both a global and local `bunfig` are detected, the results are shallow-merged
|
||||
|
||||
## Environment variables
|
||||
|
||||
These environment variables are checked by Bun to detect functionality and toggle features.
|
||||
<!-- - `GOMAXPROCS`: For `bun bun`, this sets the maximum number of threads to use. If you’re experiencing an issue with `bun bun`, try setting `GOMAXPROCS=1` to force Bun to run single-threaded -->
|
||||
|
||||
{% table %}
|
||||
- `DISABLE_BUN_ANALYTICS=1` this disables Bun's analytics. Bun records bundle timings (so we can answer with data, "is Bun getting faster?") and feature usage (e.g., "are people actually using macros?"). The request body size is about 60 bytes, so it’s not a lot of data
|
||||
- `TMPDIR`: Bun occasionally requires a directory to store intermediate assets during bundling or other operations. If unset, `TMPDIR` defaults to the platform-specific temporary directory (on Linux, `/tmp` and on macOS `/private/tmp`).
|
||||
|
||||
- Name
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- `TMPDIR`
|
||||
- Bun occasionally requires a directory to store intermediate assets during bundling or other operations. If unset, defaults to the platform-specific temporary directory: `/tmp` on Linux, `/private/tmp` on macOS.
|
||||
|
||||
---
|
||||
|
||||
- `NO_COLOR`
|
||||
- If `NO_COLOR=1`, then ANSI color output is [disabled](https://no-color.org/).
|
||||
|
||||
---
|
||||
|
||||
- `FORCE_COLOR`
|
||||
- If `FORCE_COLOR=1`, then ANSI color output is force enabled, even if `NO_COLOR` is set.
|
||||
|
||||
---
|
||||
|
||||
- `DO_NOT_TRACK`
|
||||
- If `DO_NOT_TRACK=1`, then analytics are [disabled](https://do-not-track.dev/). Bun records bundle timings (so we can answer with data, "is Bun getting faster?") and feature usage (e.g., "are people actually using macros?"). The request body size is about 60 bytes, so it's not a lot of data.
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Runtime
|
||||
|
||||
```toml
|
||||
# scripts to run before `bun run`ning a file or script
|
||||
# useful for registering plugins
|
||||
preload = ["./preload.ts"]
|
||||
|
||||
# equivalent to corresponding tsconfig compilerOptions
|
||||
jsx = "react"
|
||||
jsxFactory = "h"
|
||||
jsxFragment = "Fragment"
|
||||
jsxImportSource = "react"
|
||||
|
||||
# Set a default framework to use
|
||||
# By default, Bun will look for an npm package like `bun-framework-${framework}`, followed by `${framework}`
|
||||
logLevel = "debug"
|
||||
|
||||
# publicDir = "public"
|
||||
# external = ["jquery"]
|
||||
|
||||
[define]
|
||||
# Replace any usage of "process.env.bagel" with the string `lox`.
|
||||
# The values are parsed as JSON, except single-quoted strings are supported and `'undefined'` becomes `undefined` in JS.
|
||||
# This will probably change in a future release to be just regular TOML instead. It is a holdover from the CLI argument parsing.
|
||||
"process.env.bagel" = "'lox'"
|
||||
|
||||
[loaders]
|
||||
# When loading a .bagel file, run the JS parser
|
||||
".bagel" = "js"
|
||||
# - "atom"
|
||||
# If you pass it a file path, it will open with the file path instead
|
||||
# It will recognize non-GUI editors, but I don't think it will work yet
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```toml
|
||||
[debug]
|
||||
# When navigating to a blob: or src: link, open the file in your editor
|
||||
# If not, it tries $EDITOR or $VISUAL
|
||||
# If that still fails, it will try Visual Studio Code, then Sublime Text, then a few others
|
||||
# This is used by Bun.openInEditor()
|
||||
editor = "code"
|
||||
|
||||
# List of editors:
|
||||
# - "subl", "sublime"
|
||||
# - "vscode", "code"
|
||||
# - "textmate", "mate"
|
||||
# - "idea"
|
||||
# - "webstorm"
|
||||
# - "nvim", "neovim"
|
||||
# - "vim","vi"
|
||||
# - "emacs"
|
||||
```
|
||||
|
||||
## Test runner
|
||||
|
||||
```toml
|
||||
[test]
|
||||
# setup scripts to run before all test files
|
||||
preload = ["./setup.ts"]
|
||||
```
|
||||
|
||||
## Package manager
|
||||
## Configure `bun install`
|
||||
|
||||
Package management is a complex issue; to support a range of use cases, the behavior of `bun install` can be configured in [`bunfig.toml`](/docs/runtime/configuration).
|
||||
|
||||
@@ -207,9 +120,7 @@ save = true
|
||||
print = "yarn"
|
||||
```
|
||||
|
||||
## Dev server (`bun dev`)
|
||||
|
||||
{% The `bun dev` command is likely to change soon and will likely be deprecated in an upcoming release. We recommend %}
|
||||
## Configure `bun dev`
|
||||
|
||||
Here is an example:
|
||||
|
||||
@@ -217,6 +128,17 @@ Here is an example:
|
||||
# Set a default framework to use
|
||||
# By default, Bun will look for an npm package like `bun-framework-${framework}`, followed by `${framework}`
|
||||
framework = "next"
|
||||
logLevel = "debug"
|
||||
|
||||
# publicDir = "public"
|
||||
# external = ["jquery"]
|
||||
|
||||
[macros]
|
||||
# Remap any import like this:
|
||||
# import {graphql} from 'react-relay';
|
||||
# To:
|
||||
# import {graphql} from 'macro:bun-macro-relay';
|
||||
react-relay = { "graphql" = "bun-macro-relay" }
|
||||
|
||||
[bundle]
|
||||
saveTo = "node_modules.bun"
|
||||
@@ -232,4 +154,33 @@ entryPoints = ["./app/index.ts"]
|
||||
# Also inherited by Bun.serve
|
||||
port = 5000
|
||||
|
||||
[define]
|
||||
# Replace any usage of "process.env.bagel" with the string `lox`.
|
||||
# The values are parsed as JSON, except single-quoted strings are supported and `'undefined'` becomes `undefined` in JS.
|
||||
# This will probably change in a future release to be just regular TOML instead. It is a holdover from the CLI argument parsing.
|
||||
"process.env.bagel" = "'lox'"
|
||||
|
||||
[loaders]
|
||||
# When loading a .bagel file, run the JS parser
|
||||
".bagel" = "js"
|
||||
|
||||
[debug]
|
||||
# When navigating to a blob: or src: link, open the file in your editor
|
||||
# If not, it tries $EDITOR or $VISUAL
|
||||
# If that still fails, it will try Visual Studio Code, then Sublime Text, then a few others
|
||||
# This is used by Bun.openInEditor()
|
||||
editor = "code"
|
||||
|
||||
# List of editors:
|
||||
# - "subl", "sublime"
|
||||
# - "vscode", "code"
|
||||
# - "textmate", "mate"
|
||||
# - "idea"
|
||||
# - "webstorm"
|
||||
# - "nvim", "neovim"
|
||||
# - "vim","vi"
|
||||
# - "emacs"
|
||||
# - "atom"
|
||||
# If you pass it a file path, it will open with the file path instead
|
||||
# It will recognize non-GUI editors, but I don't think it will work yet
|
||||
```
|
||||
|
||||
@@ -12,291 +12,14 @@ function Component(props: {message: string}) {
|
||||
console.log(<Component message="Hello world!" />);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Bun implements special logging for JSX to make debugging easier.
|
||||
|
||||
Bun reads your `tsconfig.json` or `jsconfig.json` configuration files to determines how to perform the JSX transform internally. To avoid using either of these, the following options can also be defined in [`bunfig.json`](/docs/runtime/configuration).
|
||||
|
||||
The following compiler options are respected.
|
||||
|
||||
### [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
|
||||
|
||||
How JSX constructs are transformed into vanilla JavaScript internally. The table below lists the possible values of `jsx`, along with their transpilation of the following simple JSX component:
|
||||
|
||||
```tsx
|
||||
<Box width={5}>Hello</Box>
|
||||
```bash
|
||||
$ bun run react.tsx
|
||||
<Component message="Hello world!" />
|
||||
```
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { createElement } from "react";
|
||||
createElement("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsx } from "react/jsx-runtime";
|
||||
jsx("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react-jsxdev"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsxDEV } from "react/jsx-dev-runtime";
|
||||
jsxDEV("Box", { width: 5, children: "Hello" }, undefined, false, undefined, this);
|
||||
```
|
||||
|
||||
The `jsxDEV` variable name is a convention used by React. The `DEV` suffix is a visible way to indicate that the code is intended for use in development. The development version of React is slowers and includes additional validity checks & debugging tools.
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "preserve"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// JSX is not transpiled
|
||||
// "preserve" is not supported by Bun currently
|
||||
<Box width={5}>Hello</Box>
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
<!-- {% table %}
|
||||
|
||||
- `react`
|
||||
- `React.createElement("Box", {width: 5}, "Hello")`
|
||||
|
||||
---
|
||||
|
||||
- `react-jsx`
|
||||
- `jsx("Box", {width: 5}, "Hello")`
|
||||
|
||||
---
|
||||
|
||||
- `react-jsxdev`
|
||||
- `jsxDEV("Box", {width: 5}, "Hello", void 0, false)`
|
||||
|
||||
---
|
||||
|
||||
- `preserve`
|
||||
- `<Box width={5}>Hello</Box>` Left as-is; not yet supported by Bun.
|
||||
|
||||
{% /table %} -->
|
||||
|
||||
### [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
|
||||
|
||||
{% callout %}
|
||||
**Note** — Only applicable when `jsx` is `react`.
|
||||
{% /callout %}
|
||||
|
||||
The function name used to represent JSX constructs. Default value is `"createElement"`. This is useful for libraries like [Preact](https://preactjs.com/) that use a different function name (`"h"`).
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { createhElement } from "react";
|
||||
h("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
### [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)
|
||||
|
||||
{% callout %}
|
||||
**Note** — Only applicable when `jsx` is `react`.
|
||||
{% /callout %}
|
||||
|
||||
The function name used to represent [JSX fragments](https://react.dev/reference/react/Fragment) such as `<>Hello</>`; only applicable when `jsx` is `react`. Default value is `"Fragment"`.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react",
|
||||
"jsxFactory": "myjsx",
|
||||
"jsxFragmentFactory": "MyFragment"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// input
|
||||
<>Hello</>;
|
||||
|
||||
// output
|
||||
import { myjsx, MyFragment } from "react";
|
||||
createElement("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
### [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)
|
||||
|
||||
{% callout %}
|
||||
**Note** — Only applicable when `jsx` is `react-jsx` or `react-jsxdev`.
|
||||
{% /callout %}
|
||||
|
||||
The module from which the component factory function (`createElement`, `jsx`, `jsxDEV`, etc) will be imported. Default value is `"react"`. This will typically be necessary when using a component library like Preact.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react"
|
||||
// jsxImportSource is not defined
|
||||
// default to "react"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsx } from "react/jsx-runtime";
|
||||
jsx("Box", { width: 5, children: "Hello" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsx } from "preact/jsx-runtime";
|
||||
jsx("Box", { width: 5, children: "Hello" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react-jsxdev",
|
||||
"jsxImportSource": "preact"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// /jsx-runtime is automatically appended
|
||||
import { jsxDEV } from "preact/jsx-dev-runtime";
|
||||
jsxDEV("Box", { width: 5, children: "Hello" }, undefined, false, undefined, this);
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
### JSX pragma
|
||||
|
||||
All of these values can be set on a per-file basis using _pragmas_. A pragma is a special comment that sets a compiler option in a particular file.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Pragma
|
||||
- Equivalent config
|
||||
|
||||
---
|
||||
|
||||
- ```ts
|
||||
// @jsx h
|
||||
```
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsxFactory": "h"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```ts
|
||||
// @jsxFrag MyFragment
|
||||
```
|
||||
- ```jsonc
|
||||
{
|
||||
"jsxFragmentFactory": "MyFragment"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```ts
|
||||
// @jsxImportSource preact
|
||||
```
|
||||
- ```jsonc
|
||||
{
|
||||
"jsxImportSource": "preact"
|
||||
}
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Logging
|
||||
|
||||
Bun implements special logging for JSX to make debugging easier. Given the following file:
|
||||
|
||||
```tsx#index.tsx
|
||||
import { Stack, UserCard } from "./components";
|
||||
|
||||
console.log(
|
||||
<Stack>
|
||||
<UserCard name="Dom" bio="Street racer and Corona lover" />
|
||||
<UserCard name="Jakob" bio="Super spy and Dom's secret brother" />
|
||||
</Stack>
|
||||
);
|
||||
```
|
||||
|
||||
Bun will pretty-print the component tree when logged:
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/d29db51d-6837-44e2-b8be-84fc1b9e9d97" / %}
|
||||
|
||||
## Prop punning
|
||||
<!-- ### Prop punning
|
||||
|
||||
The Bun runtime also supports "prop punning" for JSX. This is a shorthand syntax useful for assigning a variable to a prop with the same name.
|
||||
|
||||
@@ -309,4 +32,4 @@ function Div(props: {className: string;}) {
|
||||
// with punning
|
||||
return <div {className} />;
|
||||
}
|
||||
```
|
||||
``` -->
|
||||
|
||||
@@ -121,7 +121,7 @@ Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-
|
||||
"name": "foo",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./package.json": "./package.json" // subpath
|
||||
"./package.json": "./package.json" # subpath
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -156,90 +156,3 @@ In the spirit of treating TypeScript as a first-class citizen, the Bun runtime w
|
||||
```
|
||||
|
||||
If you aren't a TypeScript user, you can create a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root to achieve the same behavior.
|
||||
|
||||
## CommonJS
|
||||
|
||||
Bun has native support for CommonJS modules (added in Bun v0.6.5). ES Modules are the recommended module format, but CommonJS modules are still widely used in the Node.js ecosystem. Bun supports both module formats, so that existing CommonJS packages can be used.
|
||||
|
||||
In Bun's JavaScript runtime, `require` can be used by both ES Modules and CommonJS modules.
|
||||
|
||||
In Bun, you can `require()` ESM modules from CommonJS modules.
|
||||
|
||||
| Module Type | `require()` | `import * as` |
|
||||
| ----------- | ---------------- | ----------------------------------------------------------------------- |
|
||||
| ES Module | Module Namespace | Module Namespace |
|
||||
| CommonJS | module.exports | `default` is `module.exports`, keys of module.exports are named exports |
|
||||
|
||||
If the target module is an ES Module, `require` returns the module namespace object (equivalent to `import * as`).
|
||||
If the target module is a CommonJS module, `require` returns the `module.exports` object.
|
||||
|
||||
### What is a CommonJS module?
|
||||
|
||||
In 2016, ECMAScript added support for [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). ES Modules are the standard for JavaScript modules. However, millions of npm packages still use CommonJS modules.
|
||||
|
||||
CommonJS modules are modules that use `module.exports` to export values. Typically, `require` is used to import CommonJS modules.
|
||||
|
||||
```ts
|
||||
// my-commonjs.cjs
|
||||
const stuff = require("./stuff");
|
||||
module.exports = { stuff };
|
||||
```
|
||||
|
||||
The biggest difference between CommonJS and ES Modules is that CommonJS modules are synchronous, while ES Modules are asynchronous. There are other differences too, like ES Modules support top-level `await` and CommonJS modules don't. ES Modules are always in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode), while CommonJS modules are not. Browsers do not have native support for CommonJS modules, but they do have native support for ES Modules (`<script type="module">`). CommonJS modules are not statically analyzable, while ES Modules only allow static imports and exports.
|
||||
|
||||
### Importing CommonJS from ESM
|
||||
|
||||
You can `import` or `require` CommonJS modules from ESM modules.
|
||||
|
||||
```ts
|
||||
import { stuff } from "./my-commonjs.cjs";
|
||||
import Stuff from "./my-commonjs.cjs";
|
||||
const myStuff = require("./my-commonjs.cjs");
|
||||
```
|
||||
|
||||
### Importing ESM from CommonJS
|
||||
|
||||
```ts
|
||||
// this works in Bun v0.6.5+
|
||||
// It does not work in Node.js
|
||||
const { stuff } = require("./my-esm.mjs");
|
||||
```
|
||||
|
||||
### Importing CommonJS from CommonJS
|
||||
|
||||
You can `require()` CommonJS modules from CommonJS modules.
|
||||
|
||||
```ts
|
||||
const { stuff } = require("./my-commonjs.cjs");
|
||||
```
|
||||
|
||||
#### Top-level await
|
||||
|
||||
If you are using top-level await, you must use `import()` to import ESM modules from CommonJS modules.
|
||||
|
||||
```ts
|
||||
import("./my-esm.js").then(({ stuff }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// this will throw an error if "my-esm.js" uses top-level await
|
||||
const { stuff } = require("./my-esm.js");
|
||||
```
|
||||
|
||||
#### Low-level details of CommonJS interop in Bun
|
||||
|
||||
Bun's JavaScript runtime has native support for CommonJS as of Bun v0.6.5.
|
||||
|
||||
When Bun's JavaScript transpiler detects usages of `module.exports`, it treats the file as CommonJS. The module loader will then wrap the transpiled module in a function shaped like this:
|
||||
|
||||
```js
|
||||
(function (module, exports, require) {
|
||||
// transpiled module
|
||||
})(module, exports, require);
|
||||
```
|
||||
|
||||
`module`, `exports`, and `require` are very much like the `module`, `exports`, and `require` in Node.js. These are assigned via a [`with scope`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) in C++. An internal `Map` stores the `exports` object to handle cyclical `require` calls before the module is fully loaded.
|
||||
|
||||
Once the CommonJS module is successfully evaluated, a Synthetic Module Record is created with the `default` ES Module [export set to `module.exports`](https://github.com/oven-sh/bun/blob/9b6913e1a674ceb7f670f917fc355bb8758c6c72/src/bun.js/bindings/CommonJSModuleRecord.cpp#L212-L213) and keys of the `module.exports` object are re-exported as named exports (if the `module.exports` object is an object).
|
||||
|
||||
When using Bun's bundler, this works differently. The bundler will wrap the CommonJS module in a `require_${moduleName}` function which returns the `module.exports` object.
|
||||
|
||||
@@ -81,7 +81,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
- {% anchor id="node_events" %} [`node:events`](https://nodejs.org/api/events.html) {% /anchor %}
|
||||
- 🟡
|
||||
- Missing `EventEmitterAsyncResource` `events.on`.
|
||||
- Missing `EventEmitterAsyncResource`. `EventEmitter` is missing `{get}set}MaxListeners` `usingDomains` `init`.
|
||||
|
||||
---
|
||||
|
||||
@@ -104,8 +104,8 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
---
|
||||
|
||||
- {% anchor id="node_https" %} [`node:https`](https://nodejs.org/api/https.html) {% /anchor %}
|
||||
- 🟢
|
||||
- Fully implemented.
|
||||
- 🟡
|
||||
- See `node:http`.
|
||||
|
||||
---
|
||||
|
||||
@@ -236,8 +236,8 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
---
|
||||
|
||||
- {% anchor id="node_vm" %} [`node:vm`](https://nodejs.org/api/vm.html) {% /anchor %}
|
||||
- 🟡
|
||||
- Partially implemented.
|
||||
- 🔴
|
||||
- Not implemented.
|
||||
|
||||
---
|
||||
|
||||
|
||||
162
docs/test/extending.md
Normal file
162
docs/test/extending.md
Normal file
@@ -0,0 +1,162 @@
|
||||
Like the runtime, `bun:test` also supports `--preload` scripts. These scripts are loaded before any tests are run. This is useful for setting up test fixtures, mocking, and configuring the test environment.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#preloaded.ts
|
||||
import { beforeAll, beforeEach, afterEach, afterAll } from "bun:test";
|
||||
|
||||
beforeAll(() => {
|
||||
console.log("beforeAll");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
console.log("beforeEach");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.log("afterEach");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.log("afterAll");
|
||||
});
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
Test file:
|
||||
|
||||
```ts
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("1 + 1", () => {
|
||||
expect(1 + 1).toEqual(2);
|
||||
console.log("1 + 1");
|
||||
});
|
||||
```
|
||||
|
||||
Run the test with `--preload`:
|
||||
|
||||
```sh
|
||||
$ bun test --preload=preloaded.ts
|
||||
```
|
||||
|
||||
It outputs:
|
||||
|
||||
```sh
|
||||
beforeAll
|
||||
beforeEach
|
||||
1 + 1
|
||||
afterEach
|
||||
afterAll
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
To save yourself from having to type `--preload` every time you run tests, you can add it to your `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[test]
|
||||
preload = ["./preloaded.ts"]
|
||||
```
|
||||
|
||||
## List of lifecycle hooks
|
||||
|
||||
The following lifecycle hooks are available in `--preload`:
|
||||
|
||||
| Hook | Description |
|
||||
| ------------ | --------------------------- |
|
||||
| `beforeAll` | Runs once before all tests. |
|
||||
| `beforeEach` | Runs before each test. |
|
||||
| `afterEach` | Runs after each test. |
|
||||
| `afterAll` | Runs once after all tests. |
|
||||
|
||||
Calling `expect`, `test`, or any other test function inside a lifecycle hook will throw an error. Calling `test` inside `beforeAll`, `afterAll`, `beforeEach` or `afterEach` will also throw an error.
|
||||
|
||||
You can use `console.log` or any other function otherwise inside a lifecycle hook.
|
||||
|
||||
We haven't implemented timer simulation, test isolation, or `Math.random` mocking yet. If you need these features, please [open an issue](https://bun.sh/issues).
|
||||
|
||||
### The lifecycle of bun:test
|
||||
|
||||
The test runner is a single process that runs all tests. It loads all `--preload` scripts, then runs all tests. If a test fails, the test runner will exit with a non-zero exit code.
|
||||
|
||||
Before running each test, it transpiles the source code and all dependencies into vanilla JavaScript using Bun's transpiler and module resolver. This means you can use TypeScript, JSX, ESM, and CommonJS in your tests.
|
||||
|
||||
#### Globals
|
||||
|
||||
Like Jest, you can use `describe`, `test`, `expect`, and other functions without importing them.
|
||||
|
||||
But unlike Jest, they are not globals. They are imported from `bun:test` and are exclusively available in test files or when preloading scripts.
|
||||
|
||||
```ts
|
||||
typeof globalThis.describe; // "undefined"
|
||||
typeof describe; // "function"
|
||||
```
|
||||
|
||||
This works via a transpiler integration in Bun. When `describe`, `expect`, `it`, etc are used in a test file, the transpiler auto-imports from `bun:test`. This transpiler plugin is only enabled inside test files and when preloading scripts. If you try to use Jest globals in other files, you will get an error.
|
||||
|
||||
Every `describe`, `test`, and `expect` is scoped to the current test file. Importing from `"bun:test"` creates a new scope. This means you can't use `describe` from one test file in another test file because belong to different scopes.
|
||||
|
||||
## Loaders & Resolvers
|
||||
|
||||
{% note %}
|
||||
Plugin support is not implemented yet. **There is a bug and this feature is not working**.
|
||||
{% /note %}
|
||||
|
||||
`bun:test` supports the same plugin API as bun's runtime and bun's bundler. See [Plugins](/docs/bundler/plugins#usage) for more information.
|
||||
|
||||
## Example loader
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#loader.ts
|
||||
import { plugin } from 'bun';
|
||||
|
||||
plugin({
|
||||
name: 'My loader',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /\.txt$/ }, (args) => {
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: 'my-loader',
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad({ filter: /my-loader:.txt$/ }, (args) => {
|
||||
return {
|
||||
contents: 'Hello world!',
|
||||
loader: 'text',
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
Now in your test file, you can import `.txt` files:
|
||||
|
||||
```ts#my-test.test.ts
|
||||
import { expect, test } from "bun:test";
|
||||
import text from "./hello.txt";
|
||||
|
||||
test("text is 'Hello world!'", () => {
|
||||
expect(text).toEqual("Hello world!");
|
||||
});
|
||||
```
|
||||
|
||||
To run the test, you need to add `loader.ts` to `preload`:
|
||||
|
||||
```toml
|
||||
[test]
|
||||
preload = ["loader.ts"]
|
||||
```
|
||||
|
||||
Or you can pass --preload to the command line:
|
||||
|
||||
```sh
|
||||
$ bun test --preload=loader.ts
|
||||
```
|
||||
|
||||
TODO: `expect.extend`
|
||||
@@ -1,81 +0,0 @@
|
||||
The test runner supports the following lifecycle hooks. This is useful for loading test fixtures, mocking data, and configuring the test environment.
|
||||
|
||||
| Hook | Description |
|
||||
| ------------ | --------------------------- |
|
||||
| `beforeAll` | Runs once before all tests. |
|
||||
| `beforeEach` | Runs before each test. |
|
||||
| `afterEach` | Runs after each test. |
|
||||
| `afterAll` | Runs once after all tests. |
|
||||
|
||||
Perform per-test setup and teardown logic with `beforeEach` and `afterEach`.
|
||||
|
||||
```ts
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
beforeEach(() => {
|
||||
console.log("running test.");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.log("done with test.");
|
||||
});
|
||||
|
||||
// tests...
|
||||
```
|
||||
|
||||
Perform per-scope setup and teardown logic with `beforeAll` and `afterAll`. The _scope_ is determined by where the hook is defined.
|
||||
|
||||
To scope the hooks to a particular `describe` block:
|
||||
|
||||
```ts
|
||||
import { describe, beforeAll } from "bun:test";
|
||||
|
||||
describe("test group", () => {
|
||||
beforeAll(() => {
|
||||
// setup
|
||||
});
|
||||
|
||||
// tests...
|
||||
});
|
||||
```
|
||||
|
||||
To scope the hooks to a test file:
|
||||
|
||||
```ts
|
||||
import { describe, beforeAll } from "bun:test";
|
||||
|
||||
describe("test group", () => {
|
||||
beforeAll(() => {
|
||||
// setup
|
||||
});
|
||||
|
||||
// tests...
|
||||
});
|
||||
```
|
||||
|
||||
To scope the hooks to an entire multi-file test run, define the hooks in a separate file.
|
||||
|
||||
```ts#setup.ts
|
||||
import { beforeAll, afterAll } from "bun:test";
|
||||
|
||||
beforeAll(() => {
|
||||
// global setup
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// global teardown
|
||||
});
|
||||
```
|
||||
|
||||
Then use `--preload` to run the setup script before any test files.
|
||||
|
||||
```ts
|
||||
bun test --preload ./setup.ts
|
||||
```
|
||||
|
||||
To avoid typing `--preload` every time you run tests, it can be added to your `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[test]
|
||||
preload = ["./setup.ts"]
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
Snapshot tests are written using the `.toMatchSnapshot()` matcher:
|
||||
|
||||
```ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("snap", () => {
|
||||
expect("foo").toMatchSnapshot();
|
||||
});
|
||||
```
|
||||
|
||||
The first time this test is run, the argument to `expect` will be serialized and written to a special snapshot file in a `__snapshots__` directory alongside the test file. On future runs, the argument is compared against the snapshot on disk. Snapshots can be re-generated with the following command:
|
||||
|
||||
```bash
|
||||
$ bun test --update-snapshots
|
||||
```
|
||||
@@ -12,17 +12,6 @@ test("2 + 2", () => {
|
||||
});
|
||||
```
|
||||
|
||||
{% details summary="Jest-style globals" %}
|
||||
As in Jest, you can use `describe`, `test`, `expect`, and other functions without importing them. Unlike Jest, they are not injected into the global scope. Instead, the Bun transpiler will automatically inject an import from `bun:test` internally.
|
||||
|
||||
```ts
|
||||
typeof globalThis.describe; // "undefined"
|
||||
typeof describe; // "function"
|
||||
```
|
||||
|
||||
This transpiler integration only occurs during `bun test`, and only for test files & preloaded scripts. In practice there's no significant difference to the end user.
|
||||
{% /details %}
|
||||
|
||||
Tests can be grouped into suites with `describe`.
|
||||
|
||||
```ts#math.test.ts
|
||||
@@ -63,7 +52,7 @@ test("2 * 2", done => {
|
||||
});
|
||||
```
|
||||
|
||||
Skip individual tests with `test.skip`. These tests will not be run.
|
||||
Skip individual tests with `test.skip`.
|
||||
|
||||
```ts
|
||||
import { expect, test } from "bun:test";
|
||||
@@ -74,14 +63,41 @@ test.skip("wat", () => {
|
||||
});
|
||||
```
|
||||
|
||||
Mark a test as a todo with `test.todo`. These tests _will_ be run, and the test runner will expect them to fail. If they pass, you will be prompted to mark it as a regular test.
|
||||
## Setup and teardown
|
||||
|
||||
Perform per-test setup and teardown logic with `beforeEach` and `afterEach`.
|
||||
|
||||
```ts
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test.todo("fix this", () => {
|
||||
myTestFunction();
|
||||
beforeEach(() => {
|
||||
console.log("running test.");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.log("done with test.");
|
||||
});
|
||||
|
||||
// tests...
|
||||
```
|
||||
|
||||
Perform per-scope setup and teardown logic with `beforeAll` and `afterAll`. At the top-level, the _scope_ is the current file; in a `describe` block, the scope is the block itself.
|
||||
|
||||
```ts
|
||||
import { expect, test, beforeAll, afterAll } from "bun:test";
|
||||
|
||||
let db: Database;
|
||||
beforeAll(() => {
|
||||
// connect to database
|
||||
db = initializeDatabase();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// close connection
|
||||
db.close();
|
||||
});
|
||||
|
||||
// tests...
|
||||
```
|
||||
|
||||
## Matchers
|
||||
@@ -90,8 +106,6 @@ Bun implements the following matchers. Full Jest compatibility is on the roadmap
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- 🟢
|
||||
- [`.not`](https://jestjs.io/docs/expect#not)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"prettier": "^2.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node src/runner.node.mjs",
|
||||
"test": "bun scripts/run-bun-tests.ts",
|
||||
"test:ecosystem": "bun scripts/run-ecosystem-tests.ts"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ export function parseTest(stderr: string, options: ParseTestOptions = {}): Parse
|
||||
};
|
||||
};
|
||||
const parseTestLine = (line: string): Test | undefined => {
|
||||
const match = /^(✓|‚úì|✗|‚úó|»|-|✎) (.*)$/.exec(line);
|
||||
const match = /^(✓|‚úì|✗|‚úó|-|✎) (.*)$/.exec(line);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -333,7 +333,6 @@ export function parseTest(stderr: string, options: ParseTestOptions = {}): Parse
|
||||
case "‚úó":
|
||||
status = "fail";
|
||||
break;
|
||||
case "»":
|
||||
case "-":
|
||||
status = "skip";
|
||||
break;
|
||||
|
||||
@@ -23,6 +23,30 @@ function* findTests(dir, query) {
|
||||
}
|
||||
}
|
||||
|
||||
function dump(buf) {
|
||||
var offset = 0,
|
||||
length = buf.byteLength;
|
||||
while (offset < length) {
|
||||
try {
|
||||
const wrote = writeSync(1, buf);
|
||||
offset += wrote;
|
||||
if (offset < length) {
|
||||
try {
|
||||
fsyncSync(1);
|
||||
} catch (e) {}
|
||||
|
||||
buf = buf.slice(wrote);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code === "EAGAIN") {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var failingTests = [];
|
||||
|
||||
async function runTest(path) {
|
||||
@@ -34,7 +58,7 @@ async function runTest(path) {
|
||||
status: exitCode,
|
||||
error: timedOut,
|
||||
} = spawnSync("bun", ["test", path], {
|
||||
stdio: "inherit",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
timeout: 1000 * 60 * 3,
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -51,8 +75,64 @@ async function runTest(path) {
|
||||
failingTests.push(name);
|
||||
if (timedOut) console.error(timedOut);
|
||||
}
|
||||
|
||||
if (isAction && !passed) {
|
||||
findErrors(stdout);
|
||||
findErrors(stderr);
|
||||
}
|
||||
|
||||
if (isAction) {
|
||||
const prefix = passed ? "PASS" : `FAIL`;
|
||||
action.startGroup(`${prefix} - ${name}`);
|
||||
}
|
||||
|
||||
stdout && stdout?.byteLength && dump(stdout);
|
||||
stderr && stderr?.byteLength && dump(stderr);
|
||||
|
||||
if (isAction) {
|
||||
action.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
function findErrors(data) {
|
||||
const text = new StringDecoder().write(new Buffer(data.buffer)).replaceAll(/\u001b\[.*?m/g, "");
|
||||
let index = 0;
|
||||
do {
|
||||
index = text.indexOf("error: ", index);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
const messageEnd = text.indexOf("\n", index);
|
||||
if (messageEnd === -1) {
|
||||
break;
|
||||
}
|
||||
const message = text.slice(index + 7, messageEnd);
|
||||
index = text.indexOf("at ", index);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
const startAt = index;
|
||||
index = text.indexOf("\n", index);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
const at = text.slice(startAt + 3, index);
|
||||
let file = at.slice(0, at.indexOf(":"));
|
||||
if (file.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startLine = at.slice(at.indexOf(":") + 1, at.indexOf(":") + 1 + at.slice(at.indexOf(":") + 1).indexOf(":"));
|
||||
const startColumn = at.slice(at.indexOf(":") + 1 + at.slice(at.indexOf(":") + 1).indexOf(":") + 1);
|
||||
|
||||
if (file.startsWith("/")) {
|
||||
file = relative(cwd, file);
|
||||
}
|
||||
|
||||
action.error(message, { file, startLine, startColumn });
|
||||
} while (index !== -1);
|
||||
}
|
||||
var tests = [];
|
||||
var testFileNames = [];
|
||||
for (const path of findTests(resolve(cwd, "test"))) {
|
||||
|
||||
123
packages/bun-macro-relay/README.md
Normal file
123
packages/bun-macro-relay/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# bun-macro-relay
|
||||
|
||||
This lets you use Facebook's [Relay](https://github.com/facebook/relay) framework (GraphQL) with bun.
|
||||
|
||||
Specifically, this implements the bun equivalent of [`babel-plugin-relay`](https://github.com/facebook/relay/tree/main/packages/babel-plugin-relay). It parses `graphql` queries, but does not compile/save them to your artifacts directory, you still need [`relay-compiler`](https://github.com/facebook/relay/tree/main/packages/relay-compiler) for that.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install -D bun-macro-relay
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
With three lines in your project's `bunfig.toml`, `react-relay` works automatically with bun.
|
||||
|
||||
Add this to your `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[macros]
|
||||
react-relay = {graphql = "bun-macro-relay"}
|
||||
relay-runtime = {graphql = "bun-macro-relay"}
|
||||
```
|
||||
|
||||
This tells bun to automatically pretend every import statement to `react-relay` with a `graphql` import came from `macro:bun-macro-relay/bun-macro-relay.tsx`.
|
||||
|
||||
Effectively, it applies this diff in-memory so you can use `bun-macro-relay` without making other changes to your code:
|
||||
|
||||
```js
|
||||
// bun will remap this import:
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
// To this:
|
||||
import { graphql } from "macro:bun-macro-relay/bun-macro-relay.tsx";
|
||||
```
|
||||
|
||||
You can still use the other imports from `react-relay`. It only affects the `graphql` export from `react-relay`.
|
||||
|
||||
```js
|
||||
// bun will remap this import:
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
// To this:
|
||||
import { graphql } from "macro:bun-macro-relay/bun-macro-relay.tsx";
|
||||
import { useFragment } from "react-relay";
|
||||
```
|
||||
|
||||
Ultimately, the `graphql` import should no longer appear in transpiled output:
|
||||
|
||||
```js
|
||||
import { useFragment } from "react-relay";
|
||||
```
|
||||
|
||||
If you'd rather not modify your project's `package.json`, you can do this instead:
|
||||
|
||||
```js
|
||||
import { graphql } from "macro:bun-macro-relay";
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
For performance reasons, `bun-macro-relay` does not read `relay-config`. That means your Relay configuration will _not_ be honored.
|
||||
|
||||
Fortunately, the only configuration option relevant to `bun-macro-relay` is modifying the artifacts directory (the directory where `relay-compiler` saves compiled `.graphql` files).
|
||||
|
||||
You can still change that with `bun-macro-relay`.
|
||||
|
||||
### Changing the artifacts directory
|
||||
|
||||
Pass the `BUN_MACRO_RELAY_ARTIFACT_DIRECTORY` environment variable to bun:
|
||||
|
||||
```bash
|
||||
BUN_MACRO_RELAY_ARTIFACT_DIRECTORY="__generated__" bun
|
||||
```
|
||||
|
||||
You can also save it in `.env`, `.env.local`, or `.env.dev`. The path should be relative to the directory containing the project's package.json without a leading `.` or `./`. You can also pass it an absolute path.
|
||||
|
||||
## What does `bun-macro-relay` actually do?
|
||||
|
||||
1. Parses GraphQL (using the same `graphql` npm package as babel-plugin-relay)
|
||||
2. Injects an import to the correct compiled GraphQL file in the Relay artifacts directory
|
||||
3. Replaces the use of the `graphql` template literal with the `default` import from the compiled GraphQL file.
|
||||
|
||||
Here's an example.
|
||||
|
||||
Input:
|
||||
|
||||
```tsx
|
||||
import { graphql, useLazyLoadQuery } from "react-relay";
|
||||
|
||||
const Tweet = () => {
|
||||
const data = useLazyLoadQuery(
|
||||
graphql`
|
||||
query TweetQuery {
|
||||
...Tweet_tweet
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
if (!data.tweet) return null;
|
||||
return <TweetComponent tweet={data.tweet} />;
|
||||
};
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```jsx
|
||||
import TweetQuery from "../__generated__/TweetQuery.graphql.ts";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
|
||||
const Tweet = () => {
|
||||
const data = useLazyLoadQuery(TweetQuery, {});
|
||||
if (!data.tweet) return null;
|
||||
return <TweetComponent tweet={data.tweet} />;
|
||||
};
|
||||
```
|
||||
|
||||
bun automatically transpiles JSX & TypeScript, but that's not relevant to this example.
|
||||
|
||||
### What does `bun-macro-relay` not do?
|
||||
|
||||
1. This first version doesn't hash the contents of the `graphql` query, so it won't detect when the GraphQL query is out of sync with the compiled `.graphql` file in development. However, if you're running Relay's CLI, bun's hot module reloading will automatically update. As long as you run Relay's CLI, it shouldn't matter. This will be fixed eventually (have to expose a native MD5 hashing function)
|
||||
2. Compile GraphQL. You still need to use `relay-compiler` for that.
|
||||
3
packages/bun-macro-relay/__generated__/FooOperation.graphql.ts
generated
Normal file
3
packages/bun-macro-relay/__generated__/FooOperation.graphql.ts
generated
Normal file
@@ -0,0 +1,3 @@
|
||||
export class FooOperation {}
|
||||
|
||||
export default FooOperation;
|
||||
79
packages/bun-macro-relay/bun-macro-relay.tsx
Normal file
79
packages/bun-macro-relay/bun-macro-relay.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { parse, print } from "graphql/index.js";
|
||||
import { resolve } from "path";
|
||||
|
||||
//
|
||||
// 1. Parse the GraphQL tag.
|
||||
// 2. From the parsed GraphQL query, get the AST definition.
|
||||
// 3. From the AST definition, inject an import to that file inside the artifact directory
|
||||
// 4. (TODO) MD5 the printed source text
|
||||
// 5. (TODO) At runtime, if md5 !== import.md5, then warn the user that the query has changed
|
||||
// but the file hasn't been updated so it must be reloaded.
|
||||
// 6. Replace the TemplateLiteral with the default identifier from the injected import
|
||||
let artifactDirectory: string = `__generated__`;
|
||||
|
||||
const { RELAY_ARTIFACT_DIRECTORY, BUN_MACRO_RELAY_ARTIFACT_DIRECTORY } =
|
||||
Bun.env;
|
||||
|
||||
if (RELAY_ARTIFACT_DIRECTORY) {
|
||||
artifactDirectory = RELAY_ARTIFACT_DIRECTORY;
|
||||
}
|
||||
|
||||
if (BUN_MACRO_RELAY_ARTIFACT_DIRECTORY) {
|
||||
artifactDirectory = BUN_MACRO_RELAY_ARTIFACT_DIRECTORY;
|
||||
}
|
||||
|
||||
artifactDirectory = resolve(artifactDirectory);
|
||||
|
||||
export function graphql(node) {
|
||||
let query;
|
||||
|
||||
if (node instanceof <call />) {
|
||||
query = node.arguments[0].toString();
|
||||
} else if (node instanceof <template />) {
|
||||
query = node.toString();
|
||||
}
|
||||
|
||||
if (typeof query !== "string" || query.length === 0) {
|
||||
throw new Error("BunMacroRelay: Unexpected empty graphql string.");
|
||||
}
|
||||
|
||||
const ast = parse(query);
|
||||
|
||||
if (ast.definitions.length === 0) {
|
||||
throw new Error("BunMacroRelay: Unexpected empty graphql tag.");
|
||||
}
|
||||
|
||||
const definition = ast.definitions[0];
|
||||
|
||||
if (
|
||||
definition.kind !== "FragmentDefinition" &&
|
||||
definition.kind !== "OperationDefinition"
|
||||
) {
|
||||
throw new Error(
|
||||
`BunMacroRelay: Expected a fragment, mutation, query, or subscription, got "${definition.kind}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const graphqlDefinition = definition;
|
||||
|
||||
const definitionName = graphqlDefinition.name && graphqlDefinition.name.value;
|
||||
if (!definitionName) {
|
||||
throw new Error("GraphQL operations and fragments must contain names");
|
||||
}
|
||||
|
||||
const identifiername = `${definitionName}_$gql`;
|
||||
|
||||
const importStmt = (
|
||||
<import
|
||||
default={identifiername}
|
||||
path={`${artifactDirectory}/${definitionName}.graphql`}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<inject>{importStmt}</inject>
|
||||
<id to={importStmt.namespace[identifiername]} pure />
|
||||
</>
|
||||
);
|
||||
}
|
||||
BIN
packages/bun-macro-relay/bun.lockb
Executable file
BIN
packages/bun-macro-relay/bun.lockb
Executable file
Binary file not shown.
15
packages/bun-macro-relay/package.json
Normal file
15
packages/bun-macro-relay/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "bun-macro-relay",
|
||||
"version": "0.1.2",
|
||||
"module": "bun-macro-relay.tsx",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"graphql": "^15.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"graphql": "^15.6.0"
|
||||
},
|
||||
"files": [
|
||||
"bun-macro-relay.tsx"
|
||||
]
|
||||
}
|
||||
11
packages/bun-macro-relay/test/foo.tsx
Normal file
11
packages/bun-macro-relay/test/foo.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
export const Foo = () => {
|
||||
const definition = graphql`
|
||||
query FooOperation {
|
||||
foo
|
||||
}
|
||||
`;
|
||||
|
||||
return <div>{definition.operation.name}</div>;
|
||||
};
|
||||
6
packages/bun-macro-relay/tsconfig.json
Normal file
6
packages/bun-macro-relay/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
1
packages/bun-types/buffer.d.ts
vendored
1
packages/bun-types/buffer.d.ts
vendored
@@ -547,7 +547,6 @@ declare module "buffer" {
|
||||
type: "Buffer";
|
||||
data: number[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns `true` if both `buf` and `otherBuffer` have exactly the same bytes,`false` otherwise. Equivalent to `buf.compare(otherBuffer) === 0`.
|
||||
*
|
||||
|
||||
285
packages/bun-types/bun-test.d.ts
vendored
285
packages/bun-types/bun-test.d.ts
vendored
@@ -33,41 +33,7 @@ declare module "bun:test" {
|
||||
*/
|
||||
export type Describe = {
|
||||
(label: string, fn: () => void): void;
|
||||
/**
|
||||
* Skips all other tests, except this group of tests.
|
||||
*
|
||||
* @param label the label for the tests
|
||||
* @param fn the function that defines the tests
|
||||
*/
|
||||
only(label: string, fn: () => void): void;
|
||||
/**
|
||||
* Skips this group of tests.
|
||||
*
|
||||
* @param label the label for the tests
|
||||
* @param fn the function that defines the tests
|
||||
*/
|
||||
skip(label: string, fn: () => void): void;
|
||||
/**
|
||||
* Marks this group of tests as to be written or to be fixed.
|
||||
*
|
||||
* @param label the label for the tests
|
||||
* @param fn the function that defines the tests
|
||||
*/
|
||||
todo(label: string, fn?: () => void): void;
|
||||
/**
|
||||
* Runs this group of tests, only if `condition` is true.
|
||||
*
|
||||
* This is the opposite of `describe.skipIf()`.
|
||||
*
|
||||
* @param condition if these tests should run
|
||||
*/
|
||||
if(condition: boolean): (label: string, fn: () => void) => void;
|
||||
/**
|
||||
* Skips this group of tests, if `condition` is true.
|
||||
*
|
||||
* @param condition if these tests should be skipped
|
||||
*/
|
||||
skipIf(condition: boolean): (label: string, fn: () => void) => void;
|
||||
skip: (label: string, fn: () => void) => void;
|
||||
};
|
||||
/**
|
||||
* Describes a group of related tests.
|
||||
@@ -156,31 +122,6 @@ declare module "bun:test" {
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
): void;
|
||||
export type TestOptions = {
|
||||
/**
|
||||
* Sets the timeout for the test in milliseconds.
|
||||
*
|
||||
* If the test does not complete within this time, the test will fail with:
|
||||
* ```ts
|
||||
* 'Timeout: test {name} timed out after 5000ms'
|
||||
* ```
|
||||
*
|
||||
* @default 5000 // 5 seconds
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* Sets the number of times to retry the test if it fails.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
retry?: number;
|
||||
/**
|
||||
* Sets the number of times to repeat the test, regardless of whether it passed or failed.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
repeats?: number;
|
||||
};
|
||||
/**
|
||||
* Runs a test.
|
||||
*
|
||||
@@ -194,13 +135,8 @@ declare module "bun:test" {
|
||||
* expect(response.ok).toBe(true);
|
||||
* });
|
||||
*
|
||||
* test("can set a timeout", async () => {
|
||||
* await Bun.sleep(100);
|
||||
* }, 50); // or { timeout: 50 }
|
||||
*
|
||||
* @param label the label for the test
|
||||
* @param fn the test function
|
||||
* @param options the test timeout or options
|
||||
*/
|
||||
export type Test = {
|
||||
(
|
||||
@@ -209,44 +145,45 @@ declare module "bun:test" {
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
/**
|
||||
* - If a `number`, sets the timeout for the test in milliseconds.
|
||||
* - If an `object`, sets the options for the test.
|
||||
* - `timeout` sets the timeout for the test in milliseconds.
|
||||
* - `retry` sets the number of times to retry the test if it fails.
|
||||
* - `repeats` sets the number of times to repeat the test, regardless of whether it passed or failed.
|
||||
* @default 300_000 milliseconds (5 minutes)
|
||||
*
|
||||
* After this many milliseconds, the test will fail with an error message like:
|
||||
* ```ts
|
||||
* 'Timeout: test "name" timed out after 300_000ms'
|
||||
* ```
|
||||
*/
|
||||
options?: number | TestOptions,
|
||||
timeoutMs?: number,
|
||||
): void;
|
||||
/**
|
||||
* Skips all other tests, except this test.
|
||||
* @deprecated Not yet implemented.
|
||||
*
|
||||
* @param label the label for the test
|
||||
* @param fn the test function
|
||||
* @param options the test timeout or options
|
||||
* @param timeoutMs the timeout for the test
|
||||
*/
|
||||
only(
|
||||
label: string,
|
||||
fn:
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
options?: number | TestOptions,
|
||||
timeoutMs?: number,
|
||||
): void;
|
||||
/**
|
||||
* Skips this test.
|
||||
*
|
||||
* @param label the label for the test
|
||||
* @param fn the test function
|
||||
* @param options the test timeout or options
|
||||
*/
|
||||
skip(
|
||||
label: string,
|
||||
fn:
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
options?: number | TestOptions,
|
||||
timeoutMs?: number,
|
||||
): void;
|
||||
/**
|
||||
* Marks this test as to be written or to be fixed.
|
||||
* Indicate a test is yet to be written or implemented correctly.
|
||||
*
|
||||
* When a test function is passed, it will be marked as `todo` in the test results
|
||||
* as long the test does not pass. When the test passes, the test will be marked as
|
||||
@@ -255,45 +192,13 @@ declare module "bun:test" {
|
||||
*
|
||||
* @param label the label for the test
|
||||
* @param fn the test function
|
||||
* @param options the test timeout or options
|
||||
*/
|
||||
todo(
|
||||
label: string,
|
||||
fn?:
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
options?: number | TestOptions,
|
||||
): void;
|
||||
/**
|
||||
* Runs this test, if `condition` is true.
|
||||
*
|
||||
* This is the opposite of `test.skipIf()`.
|
||||
*
|
||||
* @param condition if the test should run
|
||||
*/
|
||||
if(
|
||||
condition: boolean,
|
||||
): (
|
||||
label: string,
|
||||
fn:
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
options?: number | TestOptions,
|
||||
) => void;
|
||||
/**
|
||||
* Skips this test, if `condition` is true.
|
||||
*
|
||||
* @param condition if the test should be skipped
|
||||
*/
|
||||
skipIf(
|
||||
condition: boolean,
|
||||
): (
|
||||
label: string,
|
||||
fn:
|
||||
| (() => void | Promise<unknown>)
|
||||
| ((done: (err?: unknown) => void) => void),
|
||||
options?: number | TestOptions,
|
||||
) => void;
|
||||
};
|
||||
/**
|
||||
* Runs a test.
|
||||
@@ -618,170 +523,6 @@ declare module "bun:test" {
|
||||
* @param hint Hint used to identify the snapshot in the snapshot file.
|
||||
*/
|
||||
toMatchSnapshot(propertyMatchers?: Object, hint?: string): void;
|
||||
/**
|
||||
* Asserts that a value is empty.
|
||||
*
|
||||
* @example
|
||||
* expect("").toBeEmpty();
|
||||
* expect([]).toBeEmpty();
|
||||
* expect({}).toBeEmpty();
|
||||
* expect(new Set()).toBeEmpty();
|
||||
*/
|
||||
toBeEmpty(): void;
|
||||
/**
|
||||
* Asserts that a value is `null` or `undefined`.
|
||||
*
|
||||
* @example
|
||||
* expect(null).toBeNil();
|
||||
* expect(undefined).toBeNil();
|
||||
*/
|
||||
toBeNil(): void;
|
||||
/**
|
||||
* Asserts that a value is a `boolean`.
|
||||
*
|
||||
* @example
|
||||
* expect(true).toBeBoolean();
|
||||
* expect(false).toBeBoolean();
|
||||
* expect(null).not.toBeBoolean();
|
||||
* expect(0).not.toBeBoolean();
|
||||
*/
|
||||
toBeBoolean(): void;
|
||||
/**
|
||||
* Asserts that a value is `true`.
|
||||
*
|
||||
* @example
|
||||
* expect(true).toBeTrue();
|
||||
* expect(false).not.toBeTrue();
|
||||
* expect(1).not.toBeTrue();
|
||||
*/
|
||||
toBeTrue(): void;
|
||||
/**
|
||||
* Asserts that a value is `false`.
|
||||
*
|
||||
* @example
|
||||
* expect(false).toBeFalse();
|
||||
* expect(true).not.toBeFalse();
|
||||
* expect(0).not.toBeFalse();
|
||||
*/
|
||||
toBeFalse(): void;
|
||||
/**
|
||||
* Asserts that a value is a `number`.
|
||||
*
|
||||
* @example
|
||||
* expect(1).toBeNumber();
|
||||
* expect(3.14).toBeNumber();
|
||||
* expect(NaN).toBeNumber();
|
||||
* expect(BigInt(1)).not.toBeNumber();
|
||||
*/
|
||||
toBeNumber(): void;
|
||||
/**
|
||||
* Asserts that a value is a `number`, and is an integer.
|
||||
*
|
||||
* @example
|
||||
* expect(1).toBeInteger();
|
||||
* expect(3.14).not.toBeInteger();
|
||||
* expect(NaN).not.toBeInteger();
|
||||
*/
|
||||
toBeInteger(): void;
|
||||
/**
|
||||
* Asserts that a value is a `number`, and is not `NaN` or `Infinity`.
|
||||
*
|
||||
* @example
|
||||
* expect(1).toBeFinite();
|
||||
* expect(3.14).toBeFinite();
|
||||
* expect(NaN).not.toBeFinite();
|
||||
* expect(Infinity).not.toBeFinite();
|
||||
*/
|
||||
toBeFinite(): void;
|
||||
/**
|
||||
* Asserts that a value is a positive `number`.
|
||||
*
|
||||
* @example
|
||||
* expect(1).toBePositive();
|
||||
* expect(-3.14).not.toBePositive();
|
||||
* expect(NaN).not.toBePositive();
|
||||
*/
|
||||
toBePositive(): void;
|
||||
/**
|
||||
* Asserts that a value is a negative `number`.
|
||||
*
|
||||
* @example
|
||||
* expect(-3.14).toBeNegative();
|
||||
* expect(1).not.toBeNegative();
|
||||
* expect(NaN).not.toBeNegative();
|
||||
*/
|
||||
toBeNegative(): void;
|
||||
/**
|
||||
* Asserts that a value is a number between a start and end value.
|
||||
*
|
||||
* @param start the start number (inclusive)
|
||||
* @param end the end number (exclusive)
|
||||
*/
|
||||
toBeWithin(start: number, end: number): void;
|
||||
/**
|
||||
* Asserts that a value is a `symbol`.
|
||||
*
|
||||
* @example
|
||||
* expect(Symbol("foo")).toBeSymbol();
|
||||
* expect("foo").not.toBeSymbol();
|
||||
*/
|
||||
toBeSymbol(): void;
|
||||
/**
|
||||
* Asserts that a value is a `function`.
|
||||
*
|
||||
* @example
|
||||
* expect(() => {}).toBeFunction();
|
||||
*/
|
||||
toBeFunction(): void;
|
||||
/**
|
||||
* Asserts that a value is a `Date` object.
|
||||
*
|
||||
* To check if a date is valid, use `toBeValidDate()` instead.
|
||||
*
|
||||
* @example
|
||||
* expect(new Date()).toBeDate();
|
||||
* expect(new Date(null)).toBeDate();
|
||||
* expect("2020-03-01").not.toBeDate();
|
||||
*/
|
||||
toBeDate(): void;
|
||||
/**
|
||||
* Asserts that a value is a valid `Date` object.
|
||||
*
|
||||
* @example
|
||||
* expect(new Date()).toBeValidDate();
|
||||
* expect(new Date(null)).not.toBeValidDate();
|
||||
* expect("2020-03-01").not.toBeValidDate();
|
||||
*/
|
||||
toBeValidDate(): void;
|
||||
/**
|
||||
* Asserts that a value is a `string`.
|
||||
*
|
||||
* @example
|
||||
* expect("foo").toBeString();
|
||||
* expect(new String("bar")).toBeString();
|
||||
* expect(123).not.toBeString();
|
||||
*/
|
||||
toBeString(): void;
|
||||
/**
|
||||
* Asserts that a value includes a `string`.
|
||||
*
|
||||
* For non-string values, use `toContain()` instead.
|
||||
*
|
||||
* @param expected the expected substring
|
||||
*/
|
||||
toInclude(expected: string): void;
|
||||
/**
|
||||
* Asserts that a value starts with a `string`.
|
||||
*
|
||||
* @param expected the string to start with
|
||||
*/
|
||||
toStartWith(expected: string): void;
|
||||
/**
|
||||
* Asserts that a value ends with a `string`.
|
||||
*
|
||||
* @param expected the string to end with
|
||||
*/
|
||||
toEndWith(expected: string): void;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
255
packages/bun-types/bun.d.ts
vendored
255
packages/bun-types/bun.d.ts
vendored
@@ -2,6 +2,27 @@ interface VoidFunction {
|
||||
(): void;
|
||||
}
|
||||
|
||||
declare namespace Bun {
|
||||
interface Env extends Dict<string> {
|
||||
NODE_ENV: string;
|
||||
|
||||
/**
|
||||
* The timezone used by Intl, Date, etc.
|
||||
*
|
||||
* To change the timezone, set `Bun.env.TZ` or `process.env.TZ` to the time zone you want to use.
|
||||
*
|
||||
* You can view the current timezone with `Intl.DateTimeFormat().resolvedOptions().timeZone`
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* Bun.env.TZ = "America/Los_Angeles";
|
||||
* console.log(Intl.DateTimeFormat().resolvedOptions().timeZone); // "America/Los_Angeles"
|
||||
* ```
|
||||
*/
|
||||
TZ?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Bun.js runtime APIs
|
||||
@@ -22,26 +43,6 @@ interface VoidFunction {
|
||||
declare module "bun" {
|
||||
type ArrayBufferView = TypedArray | DataView;
|
||||
import { Encoding as CryptoEncoding } from "crypto";
|
||||
|
||||
export interface Env extends Dict<string>, NodeJS.ProcessEnv {
|
||||
NODE_ENV: string;
|
||||
|
||||
/**
|
||||
* The timezone used by Intl, Date, etc.
|
||||
*
|
||||
* To change the timezone, set `Bun.env.TZ` or `process.env.TZ` to the time zone you want to use.
|
||||
*
|
||||
* You can view the current timezone with `Intl.DateTimeFormat().resolvedOptions().timeZone`
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* Bun.env.TZ = "America/Los_Angeles";
|
||||
* console.log(Intl.DateTimeFormat().resolvedOptions().timeZone); // "America/Los_Angeles"
|
||||
* ```
|
||||
*/
|
||||
TZ?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The environment variables of the process
|
||||
*
|
||||
@@ -50,7 +51,7 @@ declare module "bun" {
|
||||
* Changes to `process.env` at runtime won't automatically be reflected in the default value. For that, you can pass `process.env` explicitly.
|
||||
*
|
||||
*/
|
||||
export const env: Env;
|
||||
export const env: Bun.Env;
|
||||
export const origin: string;
|
||||
|
||||
/**
|
||||
@@ -281,7 +282,7 @@ declare module "bun" {
|
||||
* @returns A promise that resolves with the concatenated chunks or the concatenated chunks as an `ArrayBuffer`.
|
||||
*/
|
||||
export function readableStreamToArrayBuffer(
|
||||
stream: ReadableStream<ArrayBufferView | ArrayBufferLike>,
|
||||
stream: ReadableStream,
|
||||
): Promise<ArrayBuffer> | ArrayBuffer;
|
||||
|
||||
/**
|
||||
@@ -322,7 +323,7 @@ declare module "bun" {
|
||||
*
|
||||
*/
|
||||
export function readableStreamToArray<T>(
|
||||
stream: ReadableStream<T>,
|
||||
stream: ReadableStream,
|
||||
): Promise<T[]> | T[];
|
||||
|
||||
/**
|
||||
@@ -666,10 +667,6 @@ declare module "bun" {
|
||||
* A UNIX timestamp indicating when the file was last modified.
|
||||
*/
|
||||
lastModified: number;
|
||||
/**
|
||||
* The name or path of the file, as specified in the constructor.
|
||||
*/
|
||||
name?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1012,197 +1009,6 @@ declare module "bun" {
|
||||
// importSource?: string; // default: "react"
|
||||
// };
|
||||
}
|
||||
namespace Password {
|
||||
export type AlgorithmLabel = "bcrypt" | "argon2id" | "argon2d" | "argon2i";
|
||||
|
||||
export interface Argon2Algorithm {
|
||||
algorithm: "argon2id" | "argon2d" | "argon2i";
|
||||
/**
|
||||
* Memory cost, which defines the memory usage, given in kibibytes.
|
||||
*/
|
||||
memoryCost?: number;
|
||||
/**
|
||||
* Defines the amount of computation realized and therefore the execution
|
||||
* time, given in number of iterations.
|
||||
*/
|
||||
timeCost?: number;
|
||||
}
|
||||
|
||||
export interface BCryptAlgorithm {
|
||||
algorithm: "bcrypt";
|
||||
/**
|
||||
* A number between 4 and 31. The default is 10.
|
||||
*/
|
||||
cost?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash and verify passwords using argon2 or bcrypt. The default is argon2.
|
||||
* Password hashing functions are necessarily slow, and this object will
|
||||
* automatically run in a worker thread.
|
||||
*
|
||||
* The underlying implementation of these functions are provided by the Zig
|
||||
* Standard Library. Thanks to @jedisct1 and other Zig constributors for their
|
||||
* work on this.
|
||||
*
|
||||
* ### Example with argon2
|
||||
*
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world");
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*
|
||||
* ### Example with bcrypt
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world", "bcrypt");
|
||||
* // algorithm is optional, will be inferred from the hash if not specified
|
||||
* const verify = await password.verify("hello world", hash, "bcrypt");
|
||||
*
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*/
|
||||
export const password: {
|
||||
/**
|
||||
* Verify a password against a previously hashed password.
|
||||
*
|
||||
* @returns true if the password matches, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
* await password.verify("hey", "$argon2id$v=19$m=65536,t=2,p=1$ddbcyBcbAcagei7wSkZFiouX6TqnUQHmTyS5mxGCzeM$+3OIaFatZ3n6LtMhUlfWbgJyNp7h8/oIsLK+LzZO+WI");
|
||||
* // true
|
||||
* ```
|
||||
*
|
||||
* @throws If the algorithm is specified and does not match the hash
|
||||
* @throws If the algorithm is invalid
|
||||
* @throws if the hash is invalid
|
||||
*
|
||||
*/
|
||||
verify(
|
||||
/**
|
||||
* The password to verify.
|
||||
*
|
||||
* If empty, always returns false
|
||||
*/
|
||||
password: StringOrBuffer,
|
||||
/**
|
||||
* Previously hashed password.
|
||||
* If empty, always returns false
|
||||
*/
|
||||
hash: StringOrBuffer,
|
||||
/**
|
||||
* If not specified, the algorithm will be inferred from the hash.
|
||||
*
|
||||
* If specified and the algorithm does not match the hash, this function
|
||||
* throws an error.
|
||||
*/
|
||||
algorithm?: Password.AlgorithmLabel,
|
||||
): Promise<boolean>;
|
||||
/**
|
||||
* Asynchronously hash a password using argon2 or bcrypt. The default is argon2.
|
||||
*
|
||||
* @returns A promise that resolves to the hashed password
|
||||
*
|
||||
* ## Example with argon2
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
* const hash = await password.hash("hello world");
|
||||
* console.log(hash); // $argon2id$v=1...
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* ```
|
||||
* ## Example with bcrypt
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
* const hash = await password.hash("hello world", "bcrypt");
|
||||
* console.log(hash); // $2b$10$...
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* ```
|
||||
*/
|
||||
hash(
|
||||
/**
|
||||
* The password to hash
|
||||
*
|
||||
* If empty, this function throws an error. It is usually a programming
|
||||
* mistake to hash an empty password.
|
||||
*/
|
||||
password: StringOrBuffer,
|
||||
/**
|
||||
* @default "argon2id"
|
||||
*
|
||||
* When using bcrypt, passwords exceeding 72 characters will be SHA512'd before
|
||||
*/
|
||||
algorithm?:
|
||||
| Password.AlgorithmLabel
|
||||
| Password.Argon2Algorithm
|
||||
| Password.BCryptAlgorithm,
|
||||
): Promise<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously hash and verify passwords using argon2 or bcrypt. The default is argon2.
|
||||
* Warning: password hashing is slow, consider using {@link Bun.password}
|
||||
* instead which runs in a worker thread.
|
||||
*
|
||||
* The underlying implementation of these functions are provided by the Zig
|
||||
* Standard Library. Thanks to @jedisct1 and other Zig constributors for their
|
||||
* work on this.
|
||||
*
|
||||
* ### Example with argon2
|
||||
*
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world");
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*
|
||||
* ### Example with bcrypt
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world", "bcrypt");
|
||||
* // algorithm is optional, will be inferred from the hash if not specified
|
||||
* const verify = await password.verify("hello world", hash, "bcrypt");
|
||||
*
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*/
|
||||
export const passwordSync: {
|
||||
verify(
|
||||
password: StringOrBuffer,
|
||||
hash: StringOrBuffer,
|
||||
/**
|
||||
* If not specified, the algorithm will be inferred from the hash.
|
||||
*/
|
||||
algorithm?: Password.AlgorithmLabel,
|
||||
): boolean;
|
||||
hash(
|
||||
/**
|
||||
* The password to hash
|
||||
*
|
||||
* If empty, this function throws an error. It is usually a programming
|
||||
* mistake to hash an empty password.
|
||||
*/
|
||||
password: StringOrBuffer,
|
||||
/**
|
||||
* @default "argon2id"
|
||||
*
|
||||
* When using bcrypt, passwords exceeding 72 characters will be SHA256'd before
|
||||
*/
|
||||
algorithm?:
|
||||
| Password.AlgorithmLabel
|
||||
| Password.Argon2Algorithm
|
||||
| Password.BCryptAlgorithm,
|
||||
): string;
|
||||
};
|
||||
|
||||
interface BuildArtifact extends Blob {
|
||||
path: string;
|
||||
@@ -1862,7 +1668,7 @@ declare module "bun" {
|
||||
*
|
||||
* @deprecated since v0.6.3 - Use `key: Bun.file(path)` instead.
|
||||
*/
|
||||
keyFile?: string;
|
||||
keyFile: string;
|
||||
/**
|
||||
* File path to a TLS certificate
|
||||
*
|
||||
@@ -1870,7 +1676,7 @@ declare module "bun" {
|
||||
*
|
||||
* @deprecated since v0.6.3 - Use `cert: Bun.file(path)` instead.
|
||||
*/
|
||||
certFile?: string;
|
||||
certFile: string;
|
||||
|
||||
/**
|
||||
* Passphrase for the TLS key
|
||||
@@ -2018,7 +1824,7 @@ declare module "bun" {
|
||||
* consistently in all cases and it doesn't yet call the `error` handler
|
||||
* consistently. This needs to be fixed
|
||||
*/
|
||||
fetch(request: Request | string): Response | Promise<Response>;
|
||||
fetch(request: Request): Response | Promise<Response>;
|
||||
|
||||
/**
|
||||
* Upgrade a {@link Request} to a {@link ServerWebSocket}
|
||||
@@ -2937,10 +2743,7 @@ declare module "bun" {
|
||||
* The kind of import this resolve is for.
|
||||
*/
|
||||
kind: ImportKind;
|
||||
/**
|
||||
* The= directory of the importing module. Commonly used to resolve `args.path` to an absolute path.
|
||||
*/
|
||||
resolveDir: string;
|
||||
// resolveDir: string;
|
||||
// pluginData: any;
|
||||
}
|
||||
|
||||
@@ -2958,7 +2761,7 @@ declare module "bun" {
|
||||
* ```
|
||||
*/
|
||||
namespace?: string;
|
||||
external?: boolean;
|
||||
external: boolean;
|
||||
}
|
||||
|
||||
type OnResolveCallback = (
|
||||
|
||||
2
packages/bun-types/child_process.d.ts
vendored
2
packages/bun-types/child_process.d.ts
vendored
@@ -704,7 +704,7 @@ declare module "child_process" {
|
||||
uid?: number | undefined;
|
||||
gid?: number | undefined;
|
||||
cwd?: string | URL | undefined;
|
||||
env?: Partial<import("bun").Env> | undefined;
|
||||
env?: Partial<Bun.Env> | undefined;
|
||||
}
|
||||
interface CommonOptions extends ProcessEnvOptions {
|
||||
/**
|
||||
|
||||
6
packages/bun-types/globals.d.ts
vendored
6
packages/bun-types/globals.d.ts
vendored
@@ -187,7 +187,6 @@ declare namespace NodeJS {
|
||||
(id: string): any;
|
||||
resolve: RequireResolve;
|
||||
}
|
||||
interface ProcessEnv {}
|
||||
type Signals =
|
||||
| "SIGABRT"
|
||||
| "SIGALRM"
|
||||
@@ -368,7 +367,7 @@ interface Process {
|
||||
platform: Platform;
|
||||
argv: string[];
|
||||
execArgv: string[];
|
||||
env: import("bun").Env;
|
||||
env: Bun.Env;
|
||||
|
||||
/** Whether you are using Bun */
|
||||
isBun: 1; // FIXME: this should actually return a boolean
|
||||
@@ -2389,9 +2388,6 @@ interface UnderlyingSource<R = any> {
|
||||
cancel?: UnderlyingSourceCancelCallback;
|
||||
pull?: UnderlyingSourcePullCallback<R>;
|
||||
start?: UnderlyingSourceStartCallback<R>;
|
||||
/**
|
||||
* Mode "bytes" is not currently supported.
|
||||
*/
|
||||
type?: undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { expectType } from "tsd";
|
||||
|
||||
declare module "bun" {
|
||||
export interface Env {
|
||||
FOO: "FOO";
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
BAR: "BAR";
|
||||
namespace Bun {
|
||||
interface Env {
|
||||
WHATEVER: "WHATEVER";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expectType<"FOO">(process.env.FOO);
|
||||
expectType<"BAR">(process.env.BAR);
|
||||
expectType<"WHATEVER">(process.env.WHATEVER);
|
||||
|
||||
process.env.FOO;
|
||||
process.env.BAR;
|
||||
process.env.OTHER;
|
||||
Bun.env.FOO;
|
||||
Bun.env.BAR;
|
||||
export {};
|
||||
new Bun.Transpiler({
|
||||
macro: {
|
||||
"react-relay": {
|
||||
graphql: "bun-macro-relay/bun-macro-relay.tsx",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Event;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Bun.hash.wyhash("asdf", 1234);
|
||||
@@ -31,5 +31,3 @@ const sp = new URLSearchParams("q=foo&bar=baz");
|
||||
for (const q of sp) {
|
||||
console.log(q);
|
||||
}
|
||||
|
||||
fetch;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
new ReadableStream({
|
||||
type: "direct",
|
||||
pull(controller) {
|
||||
controller.write("hello");
|
||||
controller.write("world");
|
||||
controller.close();
|
||||
},
|
||||
cancel() {
|
||||
// called if stream.cancel() is called
|
||||
},
|
||||
});
|
||||
@@ -86,25 +86,6 @@ Bun.listen({
|
||||
},
|
||||
});
|
||||
|
||||
Bun.listen({
|
||||
data: { arg: "asdf" },
|
||||
socket: {
|
||||
data(socket) {
|
||||
socket.data.arg.toLowerCase();
|
||||
},
|
||||
open() {
|
||||
console.log("asdf");
|
||||
},
|
||||
},
|
||||
hostname: "adsf",
|
||||
port: 324,
|
||||
tls: {
|
||||
cert: "asdf",
|
||||
key: Bun.file("adsf"),
|
||||
ca: Buffer.from("asdf"),
|
||||
},
|
||||
});
|
||||
|
||||
Bun.listen({
|
||||
data: { arg: "asdf" },
|
||||
socket: {
|
||||
|
||||
@@ -1,32 +1,6 @@
|
||||
import tls from "node:tls";
|
||||
|
||||
tls.getCiphers()[0];
|
||||
|
||||
tls.connect({
|
||||
host: "localhost",
|
||||
port: 80,
|
||||
ca: "asdf",
|
||||
cert: "path to cert",
|
||||
});
|
||||
|
||||
tls.connect({
|
||||
host: "localhost",
|
||||
port: 80,
|
||||
ca: Bun.file("asdf"),
|
||||
cert: Bun.file("path to cert"),
|
||||
ciphers: "adsf",
|
||||
});
|
||||
|
||||
tls.connect({
|
||||
host: "localhost",
|
||||
port: 80,
|
||||
ca: Buffer.from("asdf"),
|
||||
cert: Buffer.from("asdf"),
|
||||
});
|
||||
|
||||
tls.connect({
|
||||
host: "localhost",
|
||||
port: 80,
|
||||
ca: new Uint8Array([1, 2, 3]),
|
||||
cert: new Uint8Array([1, 2, 3]),
|
||||
});
|
||||
|
||||
24
packages/bun-types/tls.d.ts
vendored
24
packages/bun-types/tls.d.ts
vendored
@@ -861,13 +861,7 @@ declare module "tls" {
|
||||
* the well-known CAs curated by Mozilla. Mozilla's CAs are completely
|
||||
* replaced when CAs are explicitly specified using this option.
|
||||
*/
|
||||
ca?:
|
||||
| string
|
||||
| Buffer
|
||||
| TypedArray
|
||||
| BunFile
|
||||
| Array<string | Buffer | BunFile>
|
||||
| undefined;
|
||||
ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile> | undefined;
|
||||
/**
|
||||
* Cert chains in PEM format. One cert chain should be provided per
|
||||
* private key. Each cert chain should consist of the PEM formatted
|
||||
@@ -879,13 +873,7 @@ declare module "tls" {
|
||||
* intermediate certificates are not provided, the peer will not be
|
||||
* able to validate the certificate, and the handshake will fail.
|
||||
*/
|
||||
cert?:
|
||||
| string
|
||||
| Buffer
|
||||
| TypedArray
|
||||
| BunFile
|
||||
| Array<string | Buffer | TypedArray | BunFile>
|
||||
| undefined;
|
||||
cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile> | undefined;
|
||||
/**
|
||||
* Colon-separated list of supported signature algorithms. The list
|
||||
* can contain digest algorithms (SHA256, MD5 etc.), public key
|
||||
@@ -943,13 +931,7 @@ declare module "tls" {
|
||||
* object.passphrase is optional. Encrypted keys will be decrypted with
|
||||
* object.passphrase if provided, or options.passphrase if it is not.
|
||||
*/
|
||||
key?:
|
||||
| string
|
||||
| Buffer
|
||||
| BunFile
|
||||
| TypedArray
|
||||
| Array<string | Buffer | BunFile | TypedArray | KeyObject>
|
||||
| undefined;
|
||||
key?: string | Buffer | BunFile | Array<string | Buffer | BunFile | KeyObject> | undefined;
|
||||
/**
|
||||
* Name of an OpenSSL engine to get private key from. Should be used
|
||||
* together with privateKeyIdentifier.
|
||||
|
||||
@@ -49,7 +49,7 @@ pub inline fn getStartTime() i128 {
|
||||
|
||||
pub const version: @import("./install/semver.zig").Version = .{
|
||||
.major = 0,
|
||||
.minor = 6,
|
||||
.minor = 5,
|
||||
.patch = build_id,
|
||||
};
|
||||
|
||||
|
||||
7853
src/api/demo/package-lock.json
generated
Normal file
7853
src/api/demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
8
|
||||
3
|
||||
|
||||
@@ -61,7 +61,6 @@ pub const JSBundler = struct {
|
||||
code_splitting: bool = false,
|
||||
minify: Minify = .{},
|
||||
server_components: ServerComponents = ServerComponents{},
|
||||
no_macros: bool = false,
|
||||
|
||||
names: Names = .{},
|
||||
external: bun.StringSet = bun.StringSet.init(bun.default_allocator),
|
||||
@@ -189,12 +188,6 @@ pub const JSBundler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getTruthy(globalThis, "macros")) |macros_flag| {
|
||||
if (!macros_flag.coerce(bool, globalThis)) {
|
||||
this.no_macros = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
|
||||
this.target = target;
|
||||
}
|
||||
@@ -893,7 +886,6 @@ pub const JSBundler = struct {
|
||||
importer: *const ZigString,
|
||||
context: *anyopaque,
|
||||
u8,
|
||||
resolve_dir: *const ZigString,
|
||||
) void;
|
||||
|
||||
pub fn hasAnyMatches(
|
||||
@@ -950,10 +942,7 @@ pub const JSBundler = struct {
|
||||
ZigString.fromUTF8(namespace);
|
||||
const path_string = ZigString.fromUTF8(path);
|
||||
const importer_string = ZigString.fromUTF8(importer);
|
||||
// TODO: improve this for virtual modules
|
||||
const resolve_dir = std.fs.path.dirname(importer) orelse "/";
|
||||
const resolve_dir_string = ZigString.fromUTF8(resolve_dir);
|
||||
JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind), &resolve_dir_string);
|
||||
JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind));
|
||||
}
|
||||
|
||||
pub fn addPlugin(
|
||||
|
||||
@@ -75,7 +75,6 @@ const TranspilerOptions = struct {
|
||||
minify_whitespace: bool = false,
|
||||
minify_identifiers: bool = false,
|
||||
minify_syntax: bool = false,
|
||||
no_macros: bool = false,
|
||||
};
|
||||
|
||||
// Mimalloc gets unstable if we try to move this to a different thread
|
||||
@@ -386,7 +385,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
single_external[0] = std.fmt.allocPrint(allocator, "{}", .{external}) catch unreachable;
|
||||
transpiler.transform.external = single_external;
|
||||
} else if (toplevel_type.isArray()) {
|
||||
const count = external.getLength(globalThis);
|
||||
const count = external.getLengthOfArray(globalThis);
|
||||
if (count == 0) break :external;
|
||||
|
||||
var externals = allocator.alloc(string, count) catch unreachable;
|
||||
@@ -464,6 +463,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
&transpiler.log,
|
||||
logger.Source.initPathString("tsconfig.json", transpiler.tsconfig_buf),
|
||||
&VirtualMachine.get().bundler.resolver.caches.json,
|
||||
true,
|
||||
) catch null) |parsed_tsconfig| {
|
||||
transpiler.tsconfig = parsed_tsconfig;
|
||||
}
|
||||
@@ -479,10 +479,6 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
if (object.getIfPropertyExists(globalThis, "macro")) |macros| {
|
||||
macros: {
|
||||
if (macros.isUndefinedOrNull()) break :macros;
|
||||
if (macros.isBoolean()) {
|
||||
transpiler.no_macros = !macros.asBoolean();
|
||||
break :macros;
|
||||
}
|
||||
const kind = macros.jsType();
|
||||
const is_object = kind.isObject();
|
||||
if (!(kind.isStringLike() or is_object)) {
|
||||
@@ -610,7 +606,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
var length_iter = iter;
|
||||
while (length_iter.next()) |value| {
|
||||
if (value.isString()) {
|
||||
const length = @truncate(u32, value.getLength(globalThis));
|
||||
const length = @truncate(u32, value.getLengthOfArray(globalThis));
|
||||
string_count += @as(u32, @boolToInt(length > 0));
|
||||
total_name_buf_len += length;
|
||||
}
|
||||
@@ -683,7 +679,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value.isObject() and value.getLength(globalObject) == 2) {
|
||||
if (value.isObject() and value.getLengthOfArray(globalObject) == 2) {
|
||||
const replacementValue = JSC.JSObject.getIndex(value, globalThis, 1);
|
||||
if (exportReplacementValue(replacementValue, globalThis)) |to_replace| {
|
||||
const replacementKey = JSC.JSObject.getIndex(value, globalThis, 0);
|
||||
@@ -779,7 +775,7 @@ pub fn constructor(
|
||||
globalThis.throwError(err, "Error creating transpiler");
|
||||
return null;
|
||||
};
|
||||
bundler.options.no_macros = transpiler_options.no_macros;
|
||||
|
||||
bundler.configureLinkerWithAutoJSX(false);
|
||||
bundler.options.env.behavior = .disable;
|
||||
bundler.configureDefines() catch |err| {
|
||||
|
||||
@@ -486,17 +486,18 @@ pub fn getFilePath(ctx: js.JSContextRef, arguments: []const js.JSValueRef, buf:
|
||||
}
|
||||
|
||||
const value = arguments[0];
|
||||
if (JSC.JSValue.c(value).isString()) {
|
||||
const out = JSC.JSValue.c(value).toSlice(ctx, bun.default_allocator);
|
||||
defer out.deinit();
|
||||
if (js.JSValueIsString(ctx, value)) {
|
||||
var out = ZigString.Empty;
|
||||
JSValue.toZigString(JSValue.fromRef(value), &out, ctx.ptr());
|
||||
var out_slice = out.slice();
|
||||
|
||||
// The dots are kind of unnecessary. They'll be normalized.
|
||||
if (out.len == 0 or std.mem.eql(u8, out.slice(), "..") or std.mem.eql(u8, out.slice(), "../")) {
|
||||
if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) {
|
||||
JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
|
||||
return null;
|
||||
}
|
||||
|
||||
var parts = [_]string{out.slice()};
|
||||
var parts = [_]string{out_slice};
|
||||
// This does the equivalent of Node's path.normalize(path.join(cwd, out_slice))
|
||||
var res = VirtualMachine.get().bundler.fs.absBuf(&parts, buf);
|
||||
|
||||
@@ -521,15 +522,8 @@ pub fn getFilePath(ctx: js.JSContextRef, arguments: []const js.JSValueRef, buf:
|
||||
return null;
|
||||
}
|
||||
|
||||
const out = JSC.JSValue.c(value).toSlice(ctx, bun.default_allocator);
|
||||
defer out.deinit();
|
||||
|
||||
// The dots are kind of unnecessary. They'll be normalized.
|
||||
if (out.len == 0 or std.mem.eql(u8, out.slice(), "..") or std.mem.eql(u8, out.slice(), "../")) {
|
||||
JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception);
|
||||
return null;
|
||||
}
|
||||
|
||||
var out = ZigString.Empty;
|
||||
JSValue.toZigString(item, &out, ctx.ptr());
|
||||
const out_slice = out.slice();
|
||||
|
||||
temp_strings_list[temp_strings_list_len] = out_slice;
|
||||
@@ -1632,803 +1626,6 @@ pub const Crypto = struct {
|
||||
|
||||
return ZigString.fromUTF8(error_message).toErrorInstance(globalThis);
|
||||
}
|
||||
const unknwon_password_algorithm_message = "unknown algorithm, expected one of: \"bcrypt\", \"argon2id\", \"argon2d\", \"argon2i\" (default is \"argon2id\")";
|
||||
|
||||
pub const PasswordObject = struct {
|
||||
pub const pwhash = std.crypto.pwhash;
|
||||
pub const Algorithm = enum {
|
||||
argon2i,
|
||||
argon2d,
|
||||
argon2id,
|
||||
bcrypt,
|
||||
|
||||
pub const Value = union(Algorithm) {
|
||||
argon2i: Argon2Params,
|
||||
argon2d: Argon2Params,
|
||||
argon2id: Argon2Params,
|
||||
// bcrypt only accepts "cost"
|
||||
bcrypt: u6,
|
||||
|
||||
pub const bcrpyt_default = 10;
|
||||
|
||||
pub const default = Algorithm.Value{
|
||||
.argon2id = .{},
|
||||
};
|
||||
|
||||
pub fn fromJS(globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) ?Value {
|
||||
if (value.isObject()) {
|
||||
if (value.getTruthy(globalObject, "algorithm")) |algorithm_value| {
|
||||
if (!algorithm_value.isString()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
return null;
|
||||
}
|
||||
|
||||
const algorithm_string = algorithm_value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", unknwon_password_algorithm_message);
|
||||
return null;
|
||||
}) {
|
||||
.bcrypt => {
|
||||
var algorithm = PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
|
||||
if (value.getTruthy(globalObject, "cost")) |rounds_value| {
|
||||
if (!rounds_value.isNumber()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "cost", "number");
|
||||
return null;
|
||||
}
|
||||
|
||||
const rounds = rounds_value.coerce(i32, globalObject);
|
||||
|
||||
if (rounds < 4 or rounds > 31) {
|
||||
globalObject.throwInvalidArguments("Rounds must be between 4 and 31", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
algorithm.bcrypt = @intCast(u6, rounds);
|
||||
}
|
||||
|
||||
return algorithm;
|
||||
},
|
||||
inline .argon2id, .argon2d, .argon2i => |tag| {
|
||||
var argon = Algorithm.Argon2Params{};
|
||||
|
||||
if (value.getTruthy(globalObject, "timeCost")) |time_value| {
|
||||
if (!time_value.isNumber()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "timeCost", "number");
|
||||
return null;
|
||||
}
|
||||
|
||||
const time_cost = time_value.coerce(i32, globalObject);
|
||||
|
||||
if (time_cost < 1) {
|
||||
globalObject.throwInvalidArguments("Time cost must be greater than 0", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
argon.time_cost = @intCast(u32, time_cost);
|
||||
}
|
||||
|
||||
if (value.getTruthy(globalObject, "memoryCost")) |memory_value| {
|
||||
if (!memory_value.isNumber()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "memoryCost", "number");
|
||||
return null;
|
||||
}
|
||||
|
||||
const memory_cost = memory_value.coerce(i32, globalObject);
|
||||
|
||||
if (memory_cost < 1) {
|
||||
globalObject.throwInvalidArguments("Memory cost must be greater than 0", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
argon.memory_cost = @intCast(u32, memory_cost);
|
||||
}
|
||||
|
||||
return @unionInit(Algorithm.Value, @tagName(tag), argon);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
} else {
|
||||
globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string");
|
||||
return null;
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
const algorithm_string = value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", unknwon_password_algorithm_message);
|
||||
return null;
|
||||
}) {
|
||||
.bcrypt => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
},
|
||||
.argon2id => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2id = .{},
|
||||
};
|
||||
},
|
||||
.argon2d => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2d = .{},
|
||||
};
|
||||
},
|
||||
.argon2i => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2i = .{},
|
||||
};
|
||||
},
|
||||
}
|
||||
} else {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
return null;
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Argon2Params = struct {
|
||||
// we don't support the other options right now, but can add them later if someone asks
|
||||
memory_cost: u32 = pwhash.argon2.Params.interactive_2id.m,
|
||||
time_cost: u32 = pwhash.argon2.Params.interactive_2id.t,
|
||||
|
||||
pub fn toParams(this: Argon2Params) pwhash.argon2.Params {
|
||||
return pwhash.argon2.Params{
|
||||
.t = this.time_cost,
|
||||
.m = this.memory_cost,
|
||||
.p = 1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const argon2 = Algorithm.argon2id;
|
||||
|
||||
pub const label = bun.ComptimeStringMap(
|
||||
Algorithm,
|
||||
.{
|
||||
.{ "argon2i", .argon2i },
|
||||
.{ "argon2d", .argon2d },
|
||||
.{ "argon2id", .argon2id },
|
||||
.{ "bcrypt", .bcrypt },
|
||||
},
|
||||
);
|
||||
|
||||
pub const default = Algorithm.argon2;
|
||||
|
||||
pub fn get(pw: []const u8) ?Algorithm {
|
||||
if (pw[0] != '$') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// PHC format looks like $<algorithm>$<params>$<salt>$<hash><optional stuff>
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2d$")) {
|
||||
return .argon2d;
|
||||
}
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2i$")) {
|
||||
return .argon2i;
|
||||
}
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2id$")) {
|
||||
return .argon2id;
|
||||
}
|
||||
|
||||
if (strings.hasPrefixComptime(pw[1..], "bcrypt")) {
|
||||
return .bcrypt;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Crypt_(C)
|
||||
if (strings.hasPrefixComptime(pw[1..], "2")) {
|
||||
return .bcrypt;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const HashError = pwhash.Error || error{UnsupportedAlgorithm};
|
||||
|
||||
// This is purposely simple because nobody asked to make it more complicated
|
||||
pub fn hash(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
algorithm: Algorithm.Value,
|
||||
) HashError![]const u8 {
|
||||
switch (algorithm) {
|
||||
inline .argon2i, .argon2d, .argon2id => |argon| {
|
||||
var outbuf: [4096]u8 = undefined;
|
||||
const hash_options = pwhash.argon2.HashOptions{
|
||||
.params = argon.toParams(),
|
||||
.allocator = allocator,
|
||||
.mode = switch (algorithm) {
|
||||
.argon2i => .argon2i,
|
||||
.argon2d => .argon2d,
|
||||
.argon2id => .argon2id,
|
||||
else => unreachable,
|
||||
},
|
||||
.encoding = .phc,
|
||||
};
|
||||
// warning: argon2's code may spin up threads if paralellism is set to > 0
|
||||
// we don't expose this option
|
||||
// but since it parses from phc format, it's possible that it will be set
|
||||
// eventually we should do something that about that.
|
||||
const out_bytes = try pwhash.argon2.strHash(password, hash_options, &outbuf);
|
||||
return try allocator.dupe(u8, out_bytes);
|
||||
},
|
||||
.bcrypt => |cost| {
|
||||
var outbuf: [4096]u8 = undefined;
|
||||
var outbuf_slice: []u8 = outbuf[0..];
|
||||
var password_to_use = password;
|
||||
// bcrypt silently truncates passwords longer than 72 bytes
|
||||
// we use SHA512 to hash the password if it's longer than 72 bytes
|
||||
if (password.len > 72) {
|
||||
var sha_256 = bun.sha.SHA512.init();
|
||||
sha_256.update(password);
|
||||
sha_256.final(outbuf[0..bun.sha.SHA512.digest]);
|
||||
password_to_use = outbuf[0..bun.sha.SHA512.digest];
|
||||
outbuf_slice = outbuf[bun.sha.SHA512.digest..];
|
||||
}
|
||||
|
||||
const hash_options = pwhash.bcrypt.HashOptions{
|
||||
.params = pwhash.bcrypt.Params{ .rounds_log = cost },
|
||||
.allocator = allocator,
|
||||
.encoding = .crypt,
|
||||
};
|
||||
const out_bytes = try pwhash.bcrypt.strHash(password_to_use, hash_options, outbuf_slice);
|
||||
return try allocator.dupe(u8, out_bytes);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
previous_hash: []const u8,
|
||||
algorithm: ?Algorithm,
|
||||
) HashError!bool {
|
||||
if (previous_hash.len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return verifyWithAlgorithm(
|
||||
allocator,
|
||||
password,
|
||||
previous_hash,
|
||||
algorithm orelse Algorithm.get(previous_hash) orelse return error.UnsupportedAlgorithm,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verifyWithAlgorithm(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
previous_hash: []const u8,
|
||||
algorithm: Algorithm,
|
||||
) HashError!bool {
|
||||
switch (algorithm) {
|
||||
.argon2id, .argon2d, .argon2i => {
|
||||
pwhash.argon2.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| {
|
||||
if (err == error.PasswordVerificationFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
.bcrypt => {
|
||||
pwhash.bcrypt.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| {
|
||||
if (err == error.PasswordVerificationFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const JSPasswordObject = struct {
|
||||
const PascalToUpperUnderscoreCaseFormatter = struct {
|
||||
input: []const u8,
|
||||
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
for (self.input) |c| {
|
||||
if (std.ascii.isUpper(c)) {
|
||||
try writer.writeByte('_');
|
||||
try writer.writeByte(c);
|
||||
} else if (std.ascii.isLower(c)) {
|
||||
try writer.writeByte(std.ascii.toUpper(c));
|
||||
} else {
|
||||
try writer.writeByte(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub export fn JSPasswordObject__create(globalObject: *JSC.JSGlobalObject, sync: bool) JSC.JSValue {
|
||||
var object = JSValue.createEmptyObject(globalObject, 2);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("hash"),
|
||||
if (!sync)
|
||||
JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash, false)
|
||||
else
|
||||
JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hashSync, false),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("verify"),
|
||||
if (!sync)
|
||||
JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify, false)
|
||||
else
|
||||
JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verifySync, false),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
const HashJob = struct {
|
||||
algorithm: PasswordObject.Algorithm.Value,
|
||||
password: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: JSC.PollRef = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: JSC.PollRef = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
hash: []const u8,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD_{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory");
|
||||
defer bun.default_allocator.free(error_code);
|
||||
const instance = globalObject.createErrorInstance("Password hashing failed with error \"{s}\"", .{@errorName(this.err)});
|
||||
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toValueGC(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
var global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.hash => |value| {
|
||||
const js_string = JSC.ZigString.init(value).toValueGC(global);
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.resolve(global, js_string);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *HashJob) void {
|
||||
this.ref = .{};
|
||||
this.promise.strong.deinit();
|
||||
bun.default_allocator.free(this.password);
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn getValue(password: []const u8, algorithm: PasswordObject.Algorithm.Value) Result.Value {
|
||||
const value = PasswordObject.hash(bun.default_allocator, password, algorithm) catch |err| {
|
||||
return Result.Value{ .err = err };
|
||||
};
|
||||
return Result.Value{ .hash = value };
|
||||
}
|
||||
|
||||
pub fn run(task: *bun.ThreadPool.Task) void {
|
||||
var this = @fieldParentPtr(HashJob, "task", task);
|
||||
|
||||
var result = bun.default_allocator.create(Result) catch @panic("out of memory");
|
||||
result.* = Result{
|
||||
.value = getValue(this.password, this.algorithm),
|
||||
.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result),
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
};
|
||||
this.ref = .{};
|
||||
this.promise.strong = .{};
|
||||
|
||||
var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory");
|
||||
concurrent_task.* = JSC.ConcurrentTask{
|
||||
.task = JSC.Task.init(&result.task),
|
||||
.auto_delete = true,
|
||||
};
|
||||
this.event_loop.enqueueTaskConcurrent(concurrent_task);
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
pub fn hash(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
password: []const u8,
|
||||
algorithm: PasswordObject.Algorithm.Value,
|
||||
comptime sync: bool,
|
||||
) JSC.JSValue {
|
||||
std.debug.assert(password.len > 0); // caller must check
|
||||
|
||||
if (comptime sync) {
|
||||
const value = HashJob.getValue(password, algorithm);
|
||||
switch (value) {
|
||||
.err => {
|
||||
const error_instance = value.toErrorInstance(globalObject);
|
||||
globalObject.throwValue(error_instance);
|
||||
},
|
||||
.hash => |h| {
|
||||
return JSC.ZigString.init(h).toValueGC(globalObject);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
var job = bun.default_allocator.create(HashJob) catch @panic("out of memory");
|
||||
var promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
job.* = HashJob{
|
||||
.algorithm = algorithm,
|
||||
.password = password,
|
||||
.promise = promise,
|
||||
.event_loop = globalObject.bunVM().eventLoop(),
|
||||
.global = globalObject,
|
||||
};
|
||||
|
||||
job.ref.ref(globalObject.bunVM());
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return promise.value();
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
password: []const u8,
|
||||
prev_hash: []const u8,
|
||||
algorithm: ?PasswordObject.Algorithm,
|
||||
comptime sync: bool,
|
||||
) JSC.JSValue {
|
||||
std.debug.assert(password.len > 0); // caller must check
|
||||
|
||||
if (comptime sync) {
|
||||
const value = VerifyJob.getValue(password, prev_hash, algorithm);
|
||||
switch (value) {
|
||||
.err => {
|
||||
const error_instance = value.toErrorInstance(globalObject);
|
||||
globalObject.throwValue(error_instance);
|
||||
return JSC.JSValue.undefined;
|
||||
},
|
||||
.pass => |pass| {
|
||||
return JSC.JSValue.jsBoolean(pass);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
var job = bun.default_allocator.create(VerifyJob) catch @panic("out of memory");
|
||||
var promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
job.* = VerifyJob{
|
||||
.algorithm = algorithm,
|
||||
.password = password,
|
||||
.prev_hash = prev_hash,
|
||||
.promise = promise,
|
||||
.event_loop = globalObject.bunVM().eventLoop(),
|
||||
.global = globalObject,
|
||||
};
|
||||
|
||||
job.ref.ref(globalObject.bunVM());
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return promise.value();
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__hash(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
if (string_or_buffer.slice().len == 0) {
|
||||
globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
string_or_buffer.deinit();
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
string_or_buffer.ensureCloned(bun.default_allocator) catch {
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
return hash(globalObject, string_or_buffer.slice(), algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__hashSync(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
if (string_or_buffer.slice().len == 0) {
|
||||
globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
string_or_buffer.deinit();
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
string_or_buffer.ensureCloned(bun.default_allocator) catch {
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
defer string_or_buffer.deinit();
|
||||
|
||||
return hash(globalObject, string_or_buffer.slice(), algorithm, true);
|
||||
}
|
||||
|
||||
const VerifyJob = struct {
|
||||
algorithm: ?PasswordObject.Algorithm = null,
|
||||
password: []const u8,
|
||||
prev_hash: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: JSC.PollRef = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: JSC.PollRef = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
pass: bool,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory");
|
||||
defer bun.default_allocator.free(error_code);
|
||||
const instance = globalObject.createErrorInstance("Password verification failed with error \"{s}\"", .{@errorName(this.err)});
|
||||
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toValueGC(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
var global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.pass => |pass| {
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.resolve(global, JSC.JSValue.jsBoolean(pass));
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *VerifyJob) void {
|
||||
this.ref = .{};
|
||||
this.promise.strong.deinit();
|
||||
bun.default_allocator.free(this.password);
|
||||
bun.default_allocator.free(this.prev_hash);
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn getValue(password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm) Result.Value {
|
||||
const pass = PasswordObject.verify(bun.default_allocator, password, prev_hash, algorithm) catch |err| {
|
||||
return Result.Value{ .err = err };
|
||||
};
|
||||
return Result.Value{ .pass = pass };
|
||||
}
|
||||
|
||||
pub fn run(task: *bun.ThreadPool.Task) void {
|
||||
var this = @fieldParentPtr(VerifyJob, "task", task);
|
||||
|
||||
var result = bun.default_allocator.create(Result) catch @panic("out of memory");
|
||||
result.* = Result{
|
||||
.value = getValue(this.password, this.prev_hash, this.algorithm),
|
||||
.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result),
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
};
|
||||
this.ref = .{};
|
||||
this.promise.strong = .{};
|
||||
|
||||
var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory");
|
||||
concurrent_task.* = JSC.ConcurrentTask{
|
||||
.task = JSC.Task.init(&result.task),
|
||||
.auto_delete = true,
|
||||
};
|
||||
this.event_loop.enqueueTaskConcurrent(concurrent_task);
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__verify(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
const algorithm_string = arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", unknwon_password_algorithm_message);
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
}
|
||||
|
||||
var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
|
||||
password.deinit();
|
||||
globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
if (hash_.slice().len == 0) {
|
||||
password.deinit();
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
if (password.slice().len == 0) {
|
||||
hash_.deinit();
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
password.ensureCloned(bun.default_allocator) catch {
|
||||
hash_.deinit();
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
hash_.ensureCloned(bun.default_allocator) catch {
|
||||
password.deinit();
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
return verify(globalObject, password.slice(), hash_.slice(), algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__verifySync(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
const algorithm_string = arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", unknwon_password_algorithm_message);
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
}
|
||||
|
||||
var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
|
||||
password.deinit();
|
||||
globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
defer password.deinit();
|
||||
defer hash_.deinit();
|
||||
|
||||
if (hash_.slice().len == 0) {
|
||||
return JSC.JSValue.jsBoolean(false);
|
||||
}
|
||||
|
||||
if (password.slice().len == 0) {
|
||||
return JSC.JSValue.jsBoolean(false);
|
||||
}
|
||||
|
||||
return verify(globalObject, password.slice(), hash_.slice(), algorithm, true);
|
||||
}
|
||||
};
|
||||
|
||||
pub const CryptoHasher = struct {
|
||||
evp: EVP = undefined,
|
||||
|
||||
@@ -3435,7 +2632,7 @@ pub const Unsafe = struct {
|
||||
switch (array_buffer.typed_array_type) {
|
||||
.Uint16Array, .Int16Array => {
|
||||
var zig_str = ZigString.init("");
|
||||
zig_str._unsafe_ptr_do_not_use = @ptrCast([*]const u8, @alignCast(@alignOf([*]align(1) const u16), array_buffer.ptr));
|
||||
zig_str.ptr = @ptrCast([*]const u8, @alignCast(@alignOf([*]align(1) const u16), array_buffer.ptr));
|
||||
zig_str.len = array_buffer.len;
|
||||
zig_str.markUTF16();
|
||||
// the deinitializer for string causes segfaults
|
||||
@@ -3736,7 +2933,7 @@ pub const Timer = struct {
|
||||
args_buf[0] = arguments;
|
||||
args = args_buf[0..1];
|
||||
} else {
|
||||
const count = arguments.getLength(globalThis);
|
||||
const count = arguments.getLengthOfArray(globalThis);
|
||||
if (count > 0) {
|
||||
if (count > args_buf.len) {
|
||||
args = bun.default_allocator.alloc(JSC.JSValue, count) catch unreachable;
|
||||
@@ -5103,9 +4300,3 @@ pub const JSZlib = struct {
|
||||
};
|
||||
|
||||
pub usingnamespace @import("./bun/subprocess.zig");
|
||||
|
||||
comptime {
|
||||
if (!JSC.is_bindgen) {
|
||||
_ = Crypto.JSPasswordObject.JSPasswordObject__create;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,23 +93,6 @@ const Handlers = struct {
|
||||
this.active_connections += 1;
|
||||
}
|
||||
|
||||
pub const Scope = struct {
|
||||
handlers: *Handlers,
|
||||
socket_context: *uws.SocketContext,
|
||||
|
||||
pub fn exit(this: *Scope, ssl: bool) void {
|
||||
this.handlers.markInactive(ssl, this.socket_context);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn enter(this: *Handlers, context: *uws.SocketContext) Scope {
|
||||
this.markActive();
|
||||
return .{
|
||||
.handlers = this,
|
||||
.socket_context = context,
|
||||
};
|
||||
}
|
||||
|
||||
// corker: Corker = .{},
|
||||
|
||||
pub fn resolvePromise(this: *Handlers, value: JSValue) void {
|
||||
@@ -1160,24 +1143,18 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
return this.this_value;
|
||||
}
|
||||
|
||||
pub fn onEnd(this: *This, socket: Socket) void {
|
||||
pub fn onEnd(this: *This, _: Socket) void {
|
||||
JSC.markBinding(@src());
|
||||
log("onEnd", .{});
|
||||
this.detached = true;
|
||||
defer this.markInactive();
|
||||
|
||||
const handlers = this.handlers;
|
||||
|
||||
this.poll_ref.unref(handlers.vm);
|
||||
|
||||
const callback = handlers.onEnd;
|
||||
if (callback == .zero) return;
|
||||
|
||||
// the handlers must be kept alive for the duration of the function call
|
||||
// that way if we need to call the error handler, we can
|
||||
var scope = handlers.enter(socket.context());
|
||||
defer scope.exit(ssl);
|
||||
|
||||
const globalObject = handlers.globalObject;
|
||||
const this_value = this.getThisValue(globalObject);
|
||||
const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{
|
||||
@@ -1189,7 +1166,7 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onHandshake(this: *This, socket: Socket, success: i32, ssl_error: uws.us_bun_verify_error_t) void {
|
||||
pub fn onHandshake(this: *This, _: Socket, success: i32, ssl_error: uws.us_bun_verify_error_t) void {
|
||||
log("onHandshake({d})", .{success});
|
||||
JSC.markBinding(@src());
|
||||
|
||||
@@ -1210,11 +1187,6 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
is_open = true;
|
||||
}
|
||||
|
||||
// the handlers must be kept alive for the duration of the function call
|
||||
// that way if we need to call the error handler, we can
|
||||
var scope = handlers.enter(socket.context());
|
||||
defer scope.exit(ssl);
|
||||
|
||||
const globalObject = handlers.globalObject;
|
||||
const this_value = this.getThisValue(globalObject);
|
||||
|
||||
@@ -1252,7 +1224,7 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onClose(this: *This, socket: Socket, err: c_int, _: ?*anyopaque) void {
|
||||
pub fn onClose(this: *This, _: Socket, err: c_int, _: ?*anyopaque) void {
|
||||
JSC.markBinding(@src());
|
||||
log("onClose", .{});
|
||||
this.detached = true;
|
||||
@@ -1264,11 +1236,6 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
const callback = handlers.onClose;
|
||||
if (callback == .zero) return;
|
||||
|
||||
// the handlers must be kept alive for the duration of the function call
|
||||
// that way if we need to call the error handler, we can
|
||||
var scope = handlers.enter(socket.context());
|
||||
defer scope.exit(ssl);
|
||||
|
||||
var globalObject = handlers.globalObject;
|
||||
const this_value = this.getThisValue(globalObject);
|
||||
const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{
|
||||
@@ -1281,7 +1248,7 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onData(this: *This, socket: Socket, data: []const u8) void {
|
||||
pub fn onData(this: *This, _: Socket, data: []const u8) void {
|
||||
JSC.markBinding(@src());
|
||||
log("onData({d})", .{data.len});
|
||||
if (this.detached) return;
|
||||
@@ -1293,12 +1260,6 @@ fn NewSocket(comptime ssl: bool) type {
|
||||
const globalObject = handlers.globalObject;
|
||||
const this_value = this.getThisValue(globalObject);
|
||||
const output_value = handlers.binary_type.toJS(data, globalObject);
|
||||
|
||||
// the handlers must be kept alive for the duration of the function call
|
||||
// that way if we need to call the error handler, we can
|
||||
var scope = handlers.enter(socket.context());
|
||||
defer scope.exit(ssl);
|
||||
|
||||
// const encoding = handlers.encoding;
|
||||
const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{
|
||||
this_value,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user