diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..123abef --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,48 @@ +name: Benchmark + +on: + push: + branches: [main] + pull_request: + branches: [main] + # `workflow_dispatch` allows CodSpeed to trigger backtest + # performance analysis in order to generate initial data. + workflow_dispatch: + +permissions: + contents: read + id-token: write + +jobs: + benchmarks: + name: Run benchmarks + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Build WASM + run: make + + - name: Install dependencies + run: deno install + + - name: Run benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings + run: node bench/mod.ts diff --git a/README.md b/README.md index e6b853b..a7309d5 100644 --- a/README.md +++ b/README.md @@ -149,25 +149,28 @@ Pass pointer state to `render()` to have clayterm do hit detection and return pointer events in addition to the byte sequence. ```typescript -let { output, events } = term.render([ - open("root", { - layout: { width: grow(), height: grow(), direction: "ltr" }, - }), - open("sidebar", { - layout: { width: fixed(20), height: grow() }, - bg: rgba(30, 30, 40), - }), - text("Sidebar"), - close(), - open("main", { - layout: { width: grow(), height: grow() }, - }), - text("Main content"), - close(), - close(), -], { - pointer: { x: mouseX, y: mouseY, down: mouseDown }, -}); +let { output, events } = term.render( + [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ltr" }, + }), + open("sidebar", { + layout: { width: fixed(20), height: grow() }, + bg: rgba(30, 30, 40), + }), + text("Sidebar"), + close(), + open("main", { + layout: { width: grow(), height: grow() }, + }), + text("Main content"), + close(), + close(), + ], + { + pointer: { x: mouseX, y: mouseY, down: mouseDown }, + }, +); for (let event of events) { // { type: "pointerenter", id: "sidebar" } diff --git a/bench/input.bench.ts b/bench/input.bench.ts new file mode 100644 index 0000000..8e2c96b --- /dev/null +++ b/bench/input.bench.ts @@ -0,0 +1,55 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { createInput } from "../input.ts"; + +function bytes(...values: number[]): Uint8Array { + return new Uint8Array(values); +} + +function str(s: string): Uint8Array { + return new TextEncoder().encode(s); +} + +let input = await createInput({ escLatency: 25 }); + +let longBurst = new Uint8Array(200); +for (let i = 0; i < 200; i++) { + longBurst[i] = 0x61 + (i % 26); +} + +let bench = withCodSpeed(new Bench()); + +bench + .add("printable ASCII (single char)", () => { + input.scan(bytes(0x61)); + }) + .add("printable ASCII (short string)", () => { + input.scan(str("hello world")); + }) + .add("arrow key (CSI sequence)", () => { + input.scan(bytes(0x1b, 0x5b, 0x41)); + }) + .add("modifier combo (Ctrl+Shift+Arrow)", () => { + input.scan(bytes(0x1b, 0x5b, 0x31, 0x3b, 0x38, 0x41)); + }) + .add("SGR mouse press", () => { + input.scan(str("\x1b[<0;35;12M")); + }) + .add("multi-event burst (arrows + text)", () => { + input.scan(bytes(0x1b, 0x5b, 0x41, 0x1b, 0x5b, 0x42, 0x68, 0x69)); + }) + .add("UTF-8 3-byte character", () => { + input.scan(bytes(0xe4, 0xb8, 0xad)); + }) + .add("UTF-8 4-byte emoji", () => { + input.scan(bytes(0xf0, 0x9f, 0x8e, 0x89)); + }) + .add("Kitty protocol (CSI u with modifiers)", () => { + input.scan(str("\x1b[97;3u")); + }) + .add("long input burst (200 bytes)", () => { + input.scan(longBurst); + }); + +await bench.run(); +console.table(bench.table()); diff --git a/bench/mod.ts b/bench/mod.ts new file mode 100644 index 0000000..ca9de34 --- /dev/null +++ b/bench/mod.ts @@ -0,0 +1,3 @@ +import "./input.bench.ts"; +import "./render.bench.ts"; +import "./ops.bench.ts"; diff --git a/bench/ops.bench.ts b/bench/ops.bench.ts new file mode 100644 index 0000000..3019792 --- /dev/null +++ b/bench/ops.bench.ts @@ -0,0 +1,124 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { close, fixed, grow, open, pack, rgba, text } from "../ops.ts"; +import type { Op } from "../ops.ts"; + +function makeBuf(size: number): ArrayBuffer { + return new ArrayBuffer(size); +} + +let simpleOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + text("Hello, World!"), + close(), +]; + +let complexOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + open("header", { + layout: { + width: grow(), + height: fixed(3), + padding: { left: 1, right: 1 }, + direction: "ltr", + }, + bg: rgba(30, 30, 40), + border: { + color: rgba(100, 100, 120), + bottom: 1, + }, + }), + text("Title", { color: rgba(255, 255, 255), fontSize: 1 }), + close(), + open("body", { + layout: { + width: grow(), + height: grow(), + direction: "ltr", + gap: 1, + }, + }), + open("sidebar", { + layout: { + width: fixed(20), + height: grow(), + direction: "ttb", + padding: { left: 1, right: 1, top: 1 }, + }, + bg: rgba(25, 25, 35), + border: { + color: rgba(60, 60, 80), + right: 1, + }, + }), + text("Menu Item 1"), + text("Menu Item 2"), + text("Menu Item 3"), + close(), + open("main", { + layout: { + width: grow(), + height: grow(), + direction: "ttb", + padding: { left: 2, top: 1 }, + }, + }), + text("Main content area with longer text to exercise the encoder"), + close(), + close(), + open("footer", { + layout: { + width: grow(), + height: fixed(1), + padding: { left: 1 }, + direction: "ltr", + }, + bg: rgba(30, 30, 40), + }), + text("Status: OK"), + close(), + close(), +]; + +let listOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + ...Array.from({ length: 50 }, (_, i) => [ + open(`item-${i}`, { + layout: { + width: grow(), + height: fixed(1), + padding: { left: 2 }, + direction: "ltr", + }, + bg: i % 2 === 0 ? rgba(30, 30, 40) : rgba(35, 35, 45), + }), + text(`List item ${i}: some description text`), + close(), + ]).flat(), + close(), +]; + +let bench = withCodSpeed(new Bench()); + +bench + .add("simple tree (root + text)", () => { + let buf = makeBuf(4096); + pack(simpleOps, buf, 0); + }) + .add("complex layout (header + sidebar + main + footer)", () => { + let buf = makeBuf(8192); + pack(complexOps, buf, 0); + }) + .add("large list (50 items)", () => { + let buf = makeBuf(32768); + pack(listOps, buf, 0); + }); + +await bench.run(); +console.table(bench.table()); diff --git a/bench/render.bench.ts b/bench/render.bench.ts new file mode 100644 index 0000000..1c3b8c3 --- /dev/null +++ b/bench/render.bench.ts @@ -0,0 +1,158 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { createTerm } from "../term.ts"; +import { close, fixed, grow, open, rgba, text } from "../ops.ts"; +import type { Op } from "../ops.ts"; + +let term = await createTerm({ width: 80, height: 24 }); +let termPtr = await createTerm({ width: 80, height: 24 }); + +let helloOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + text("Hello, World!"), + close(), +]; + +let borderedOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + open("box", { + layout: { + width: grow(), + height: grow(), + padding: { left: 1, right: 1, top: 1, bottom: 1 }, + direction: "ttb", + }, + border: { + color: rgba(0, 255, 0), + left: 1, + right: 1, + top: 1, + bottom: 1, + }, + cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 }, + }), + text("Bordered content"), + close(), + close(), +]; + +let dashboardOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + open("header", { + layout: { + width: grow(), + height: fixed(3), + padding: { left: 1 }, + direction: "ltr", + }, + bg: rgba(30, 30, 40), + border: { color: rgba(80, 80, 100), bottom: 1 }, + }), + text("Dashboard", { color: rgba(255, 255, 255) }), + close(), + open("body", { + layout: { width: grow(), height: grow(), direction: "ltr" }, + }), + open("sidebar", { + layout: { + width: fixed(20), + height: grow(), + direction: "ttb", + padding: { left: 1, top: 1 }, + }, + bg: rgba(25, 25, 35), + border: { color: rgba(60, 60, 80), right: 1 }, + }), + text("Nav 1"), + text("Nav 2"), + text("Nav 3"), + text("Nav 4"), + close(), + open("main", { + layout: { + width: grow(), + height: grow(), + direction: "ttb", + padding: { left: 2, top: 1 }, + }, + }), + ...Array.from({ length: 10 }, (_, i) => [ + open(`row-${i}`, { + layout: { + width: grow(), + height: fixed(1), + direction: "ltr", + }, + bg: i % 2 === 0 ? rgba(35, 35, 45) : undefined, + }), + text(`Row ${i}: data value ${i * 42}`), + close(), + ]).flat(), + close(), + close(), + open("footer", { + layout: { + width: grow(), + height: fixed(1), + padding: { left: 1 }, + }, + bg: rgba(30, 30, 40), + }), + text("Ready"), + close(), + close(), +]; + +let uiOps: Op[] = [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + open("button", { + layout: { + width: fixed(20), + height: fixed(3), + padding: { left: 1, right: 1 }, + }, + bg: rgba(50, 50, 200), + border: { + color: rgba(100, 100, 255), + left: 1, + right: 1, + top: 1, + bottom: 1, + }, + cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 }, + }), + text("Click me"), + close(), + close(), +]; + +let bench = withCodSpeed(new Bench()); + +bench + .add("simple text", () => { + term.render(helloOps); + }) + .add("bordered box with corner radius", () => { + term.render(borderedOps); + }) + .add("dashboard layout", () => { + term.render(dashboardOps); + }) + .add("diff render (second frame)", () => { + term.render(dashboardOps); + term.render(dashboardOps); + }) + .add("render with pointer hit testing", () => { + termPtr.render(uiOps, { pointer: { x: 10, y: 1, down: false } }); + }); + +await bench.run(); +console.table(bench.table()); diff --git a/deno.json b/deno.json index fab9ce2..808d018 100644 --- a/deno.json +++ b/deno.json @@ -7,7 +7,8 @@ "fmt:check": "deno fmt --check && clang-format --dry-run --Werror src/*.c src/*.h", "build:npm": "deno run -A tasks/build-npm.ts", "build:jsr": "deno run -A tasks/build-jsr.ts", - "demo": "deno run demo/keyboard.ts" + "demo": "deno run demo/keyboard.ts", + "bench": "deno run -A bench/mod.ts" }, "imports": { "@std/testing": "jsr:@std/testing@1", @@ -15,7 +16,9 @@ "@sinclair/typebox": "npm:@sinclair/typebox@^0.34", "dnt": "jsr:@deno/dnt@0.42.3", "effection": "npm:effection@^4.0.2", - "@std/encoding": "jsr:@std/encoding@1" + "@std/encoding": "jsr:@std/encoding@1", + "@codspeed/tinybench-plugin": "npm:@codspeed/tinybench-plugin@^5.4.0", + "tinybench": "npm:tinybench@^5.0.0" }, "exports": { ".": "./mod.ts", @@ -25,6 +28,7 @@ "include": ["*.ts"], "exclude": ["!wasm.ts"] }, + "nodeModulesDir": "auto", "fmt": { "exclude": ["clay", "build"] }, diff --git a/deno.lock b/deno.lock index 7947a80..d0f8ef2 100644 --- a/deno.lock +++ b/deno.lock @@ -20,9 +20,11 @@ "jsr:@std/testing@1": "1.0.17", "jsr:@ts-morph/bootstrap@0.27": "0.27.0", "jsr:@ts-morph/common@0.27": "0.27.0", + "npm:@codspeed/tinybench-plugin@^5.4.0": "5.4.0_tinybench@5.1.0", "npm:@sinclair/typebox@*": "0.34.48", "npm:@sinclair/typebox@0.34": "0.34.48", "npm:effection@^4.0.2": "4.0.2", + "npm:tinybench@5": "5.1.0", "npm:valrs@*": "0.1.0" }, "jsr": { @@ -109,14 +111,222 @@ } }, "npm": { + "@codspeed/core@5.4.0": { + "integrity": "sha512-SwGjXDixN/zX1awBR95LzS0KxIs931qwf7Hbk7BRWv1jAdlMYf9o9GlSnWER4zGBHz941BvzFQJ1O2RIofW3cg==", + "dependencies": [ + "axios", + "find-up", + "form-data", + "node-gyp-build" + ] + }, + "@codspeed/tinybench-plugin@5.4.0_tinybench@5.1.0": { + "integrity": "sha512-jzuFoyyoGxc3Lc+TTl54PnRsgqO3CYbbbnwYSVp/m/4rqvCwSUZChY9EuQJ6uZFbamT3UhWF2N6tDEGShkvsrw==", + "dependencies": [ + "@codspeed/core", + "stack-trace", + "tinybench" + ] + }, "@sinclair/typebox@0.34.48": { "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==" }, + "agent-base@6.0.2": { + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": [ + "debug" + ] + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios@1.16.1": { + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "dependencies": [ + "follow-redirects", + "form-data", + "https-proxy-agent", + "proxy-from-env" + ] + }, + "call-bind-apply-helpers@1.0.2": { + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": [ + "es-errors", + "function-bind" + ] + }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": [ + "delayed-stream" + ] + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "dunder-proto@1.0.1": { + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": [ + "call-bind-apply-helpers", + "es-errors", + "gopd" + ] + }, "effection@4.0.2": { "integrity": "sha512-O8WMGP10nPuJDwbNGILcaCNWS+CvDYjcdsUSD79nWZ+WtUQ8h1MEV7JJwCSZCSeKx8+TdEaZ/8r6qPTR2o/o8w==" }, + "es-define-property@1.0.1": { + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms@1.1.2": { + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dependencies": [ + "es-errors" + ] + }, + "es-set-tostringtag@2.1.0": { + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": [ + "es-errors", + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, + "find-up@6.3.0": { + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dependencies": [ + "locate-path", + "path-exists" + ] + }, + "follow-redirects@1.16.0": { + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==" + }, + "form-data@4.0.5": { + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "hasown", + "mime-types" + ] + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic@1.3.0": { + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "es-errors", + "es-object-atoms", + "function-bind", + "get-proto", + "gopd", + "has-symbols", + "hasown", + "math-intrinsics" + ] + }, + "get-proto@1.0.1": { + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": [ + "dunder-proto", + "es-object-atoms" + ] + }, + "gopd@1.2.0": { + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-symbols@1.1.0": { + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, + "hasown@2.0.3": { + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dependencies": [ + "function-bind" + ] + }, + "https-proxy-agent@5.0.1": { + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": [ + "agent-base", + "debug" + ] + }, + "locate-path@7.2.0": { + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dependencies": [ + "p-locate" + ] + }, + "math-intrinsics@1.1.0": { + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db" + ] + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node-gyp-build@4.8.4": { + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": true + }, + "p-limit@4.0.0": { + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dependencies": [ + "yocto-queue" + ] + }, + "p-locate@6.0.0": { + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dependencies": [ + "p-limit" + ] + }, + "path-exists@5.0.0": { + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==" + }, + "proxy-from-env@2.1.0": { + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==" + }, + "stack-trace@1.0.0-pre2": { + "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==" + }, + "tinybench@5.1.0": { + "integrity": "sha512-LXKNtFualiKOm6gADe1UXPtf8+Nfn1CtPMEHAT33Fd2YjQatrujkDcK0+4wRC1X6t7fxUDXUs6BsvuIgfkDgDg==" + }, "valrs@0.1.0": { "integrity": "sha512-BqVkjx3qhsRLHerblLDoqEx0OEx7ms0DB6LPv40oWkMfFKUVKrqVuklaGdrPrHyubC5hSHYfEtUiQXrCkC6xHQ==" + }, + "yocto-queue@1.2.2": { + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==" } }, "workspace": { @@ -125,8 +335,10 @@ "jsr:@std/encoding@1", "jsr:@std/expect@1", "jsr:@std/testing@1", + "npm:@codspeed/tinybench-plugin@^5.4.0", "npm:@sinclair/typebox@0.34", - "npm:effection@^4.0.2" + "npm:effection@^4.0.2", + "npm:tinybench@5" ] } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +}