Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2633369
chore: rename WrakerApp to WrakerAppBase
josselinonduty Sep 27, 2024
11d145d
feat: add type utilities
josselinonduty Sep 27, 2024
3f375d5
chore: rename handler (non-class) and fix previous commit
josselinonduty Sep 27, 2024
4a73aab
feat: add factory functions with typings ; modify fixtures
josselinonduty Sep 27, 2024
d5163fc
fix: rollup doesn't handle default export correctly (for now)
josselinonduty Sep 27, 2024
979eef1
fix: move plugin option to WrakerAppBase
josselinonduty Sep 27, 2024
09c7e2c
tests: add basic tests for plugin; lint
josselinonduty Sep 27, 2024
574a7f1
feat: add type utilities to extract keys and types in interface
josselinonduty Sep 27, 2024
6c6ee30
feat: add lifecycle emits support
josselinonduty Sep 27, 2024
fcfd292
tests: add tests for lifecycle hooks; remove dead code
josselinonduty Sep 27, 2024
9ffc7c5
fix: rename WrakerAppBase back to WrakerApp
josselinonduty Sep 28, 2024
8bd3a78
tests: add lifecycle tests ; fix isolation issue with event listeners
josselinonduty Sep 28, 2024
f800a97
chore: update rollup, @rollup/plugin-node-resolve, @types/node
josselinonduty Sep 28, 2024
ac49304
chore: update @rollup/plugin-commonjs
josselinonduty Sep 28, 2024
a2e6438
chore: add @vitest/ui for better DX
josselinonduty Sep 28, 2024
541a19e
fix: hints missing from WrakerApp when using plugin with type extension
josselinonduty Sep 28, 2024
7c2db2d
tests: enhance defineWrakerAppPlugin tests to check for extended prop…
josselinonduty Sep 28, 2024
9a23839
Merge branch 'main' of github.com:wrakerjs/core into feat/plugins
josselinonduty Nov 9, 2024
2d9ac56
Merge pull request #94 from wrakerjs/dev
josselinonduty May 15, 2025
c6e3f69
1.1.3
josselinonduty May 15, 2025
e4bc1dd
Merge branch 'main' into feat/plugins
josselinonduty Feb 22, 2026
50fb804
chore: remove useless exports
josselinonduty Feb 22, 2026
3785129
feat: add remaining essential lifecycle hooks and their tests
josselinonduty Feb 22, 2026
f6bae8e
chore: update readme with basic plugin usage
josselinonduty Feb 22, 2026
65206d6
chore: fix package lock
josselinonduty Feb 22, 2026
7081701
feat: implement client-side plugin system
josselinonduty Feb 22, 2026
1301e8b
chore: update dependencies
josselinonduty Feb 22, 2026
faa26b4
chore: fix node version (20+ required), added 24.x
josselinonduty Feb 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ jobs:
build:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.52.0-jammy
image: mcr.microsoft.com/playwright:v1.58.2-jammy

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
node-version: [20.x, 22.x, 24.x]

steps:
- uses: actions/checkout@v4
Expand Down
120 changes: 116 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ It makes it easier to manage the communication between the main thread and the w
Create a new file `worker.js`:

```js
import { WrakerApp } from "https://cdn.jsdelivr.net/npm/@wraker/core/+esm";
import { defineWrakerApp } from "https://cdn.jsdelivr.net/npm/@wraker/core/+esm";

const app = new WrakerApp();
const app = defineWrakerApp();

app.get("/ping", (req, res) => {
res.send("pong");
});

app.listen();
await app.listen();
```

Then, create a new file `main.js`:
Expand Down Expand Up @@ -91,7 +91,7 @@ You can take advantage of this feature to create a worker with Wraker:

```js
// worker.js
import { WrakerApp } from "wraker";
import { defineWrakerApp } from "wraker";
```

```js
Expand Down Expand Up @@ -141,6 +141,118 @@ const worker = new Wraker(myWorkerUrl, {

> ℹ️ You may be able to use the `?worker` or `?url` shorthands, but it is using workarounds and may not work as expected. Refer to [this discussion](https://github.com/vitejs/vite/issues/13680) for details.

## Plugins

Wraker supports a plugin system on **both sides** of the worker boundary:

- **Server-side** (`WrakerApp`, inside the worker) - extend the app with new methods, intercept incoming messages, hook into the request lifecycle.
- **Client-side** (`Wraker`, in the main thread) - extend the client with new methods, intercept outgoing/incoming messages.

### Using plugins

Use the `defineWrakerApp` / `defineWraker` factory functions with a `plugins` array. The factories ensure TypeScript infers the type extensions contributed by each plugin.

```ts
// worker.ts (server-side, runs inside the Web Worker)
import { defineWrakerApp } from "@wraker/core";
import { myPluginServer } from "my-wraker-plugin";

const app = defineWrakerApp({
plugins: [myPluginServer({ greeting: "Hello" })],
});

app.get("/demo", (req, res) => {
res.send(app.greet("world"));
});

await app.listen();
```

```ts
// main.ts (client-side, runs in the main thread)
import { defineWraker } from "@wraker/core";
import { myPluginClient } from "my-wraker-plugin";

const wraker = defineWraker(new URL("worker.ts", import.meta.url), {
type: "module",
plugins: [myPluginClient()],
});

// `wraker.greet` is fully typed
console.log(wraker.greet("world"));

const response = await wraker.fetch("/demo");
console.log(response.body);
```

### Creating a plugin

Use `defineWrakerAppPlugin` (server) and `defineWrakerPlugin` (client) to create reusable plugin factories. Plugins can:

- **Extend** the instance with new properties/methods via the `Extension` type parameter.
- **Accept options** via the `Options` type parameter.
- **Hook into lifecycle events** by implementing one or more hook callbacks.
- **Stop propagation** by returning `false` from any hook.

```ts
// my-wraker-plugin/server.ts
import { defineWrakerAppPlugin } from "@wraker/core";

type Extension = { greet: (name: string) => string };
type Options = { greeting: string };

export const myPluginServer = defineWrakerAppPlugin<Extension, Options>({
name: "my-plugin",
init(app, options) {
app.greet = (name) => `${options?.greeting ?? "Hi"}, ${name}!`;
},
});
```

```ts
// my-wraker-plugin/client.ts
import { defineWrakerPlugin } from "@wraker/core";

type Extension = { greet: (name: string) => string };

export const myPluginClient = defineWrakerPlugin<Extension>({
name: "my-plugin",
init(wraker) {
wraker.greet = (name) => `Hello from client, ${name}!`;
},
});
```

### Available lifecycle hooks

#### Server-side (`WrakerAppPlugin`)

| Hook | When it runs |
| ------------------------ | ------------------------------------------------------- |
| `init` | Immediately when the app is constructed |
| `onListen` | When `app.listen()` is called |
| `onBeforeMessageHandled` | Before each incoming message is processed |
| `onAfterMessageHandled` | After each incoming message has been processed |
| `onError` | When a route handler throws an error |
| `onMount` | When a sub-router or sub-app is mounted via `app.use()` |
| `destroy` | When `app.destroy()` is called |

Returning `false` from any hook stops the remaining plugins in the chain from running for that event. For `onBeforeMessageHandled`, returning `false` also prevents the request from being processed.

#### Client-side (`WrakerPlugin`)

| Hook | When it runs |
| ------------------------- | --------------------------------------------------- |
| `init` | Immediately when the Wraker instance is constructed |
| `onBeforeMessageSent` | Before a `fetch` call posts a message to the worker |
| `onAfterMessageSent` | After the message has been posted to the worker |
| `onBeforeMessageReceived` | Before an incoming worker message is handled |
| `onAfterMessageReceived` | After an incoming worker message has been handled |
| `onError` | When a plugin hook throws an error |
| `destroy` | When `wraker.kill()` is called |

Returning `false` from any hook stops the remaining plugins in the chain. For `onBeforeMessageSent`, returning `false` prevents the message from being sent. For `onBeforeMessageReceived`, returning `false` prevents the default response handling (useful for intercepting custom message types).

## Contributing

Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information.
Expand Down
Loading