workerflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.oxfmtrc.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "trailingComma": "none",
4
+ "printWidth": 120,
5
+ "experimentalSortPackageJson": false,
6
+ "jsdoc": {
7
+ "commentLineStrategy": "multiline",
8
+ "capitalizeDescriptions": true,
9
+ "descriptionWithDot": true
10
+ }
11
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["typescript"],
4
+ "categories": {
5
+ "correctness": "error"
6
+ },
7
+ "rules": {
8
+ "no-explicit-any": "error",
9
+ "no-unused-vars": [
10
+ "error",
11
+ {
12
+ "argsIgnorePattern": "^_",
13
+ "varsIgnorePattern": "^_",
14
+ "caughtErrorsIgnorePattern": "^_"
15
+ }
16
+ ]
17
+ },
18
+ "ignorePatterns": ["**/env.d.ts"]
19
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nimesh Nayaju
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ ## workerflow
2
+
3
+ This is a user-land implementation of a workflow engine using Cloudflare primitives. The runtime is implemented as a Durable Object called `WorkflowRuntime`. It owns durable state in SQLite, including step state, step and workflow events, and inbound events, and it drives execution by repeatedly invoking your workflow definition, which is a normal Cloudflare WorkerEntrypoint.
4
+
5
+ One advantage of owning your own runtime as a Durable Object is that you can hook into the original state changes and extend the runtime’s capabilities however you want. For example, because the runtime is just a Durable Object, you can let clients connect over WebSocket or expose a method that streams step-state updates as they happen. The runtime does expect a few things, such as alarms to wake it up at the right time, but most of the implementation is open to extension.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install workerflow
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Import **`WorkflowRuntime`** and **`WorkflowDefinition`**, then define two classes: a **Durable Object** subclass that resolves definition versions, and a **`WorkerEntrypoint`** subclass that implements **`execute()`** using **`run`**, **`sleep`**, and **`wait`**.
16
+
17
+ Pin SQLite-backed storage on the runtime class in **`wrangler.toml`** (or the equivalent config) so the DO can use **`SqlStorage`**. Set the **`nodejs_compat`** compatibility flag so **`node:async_hooks`** (**`AsyncLocalStorage`**, used by **`WorkflowDefinition`**) resolves in the Workers runtime.
18
+
19
+ ```toml
20
+ # wrangler.toml (illustrative)
21
+ name = "example-worker"
22
+ main = "src/worker.ts"
23
+ compatibility_date = "2026-01-28"
24
+ compatibility_flags = [ "nodejs_compat" ]
25
+
26
+ [durable_objects]
27
+ bindings = [
28
+ { name = "ORDER_WORKFLOW", class_name = "OrderWorkflowRuntime" }
29
+ ]
30
+
31
+ [[migrations]]
32
+ tag = "v1"
33
+ new_sqlite_classes = ["OrderWorkflowRuntime"]
34
+ ```
35
+
36
+ In your Worker module, export the runtime, the definition, and a **`fetch`** handler (or queue consumer, cron trigger, and so on) that obtains a namespace stub and calls **`create`** to pin a definition version and optional input:
37
+
38
+ ```ts
39
+ // src/worker.ts
40
+ import { WorkflowDefinition, WorkflowRuntime } from "workerflow";
41
+
42
+ export class OrderWorkflowRuntime extends WorkflowRuntime<{ orderId: string }> {
43
+ /** Resolves which `WorkerEntrypoint` implementation runs for a pinned `definitionVersion`. */
44
+ protected getDefinition(version: string) {
45
+ switch (version) {
46
+ case "2026-04-01":
47
+ return this.ctx.exports.OrderWorkflowDefinition;
48
+ default:
49
+ throw new Error(`Unsupported workflow definition version: ${version}`);
50
+ }
51
+ }
52
+ }
53
+
54
+ export class OrderWorkflowDefinition extends WorkflowDefinition<{ orderId: string }> {
55
+ async execute(): Promise<void> {
56
+ const { orderId } = this.ctx.props.input;
57
+
58
+ await this.run("reserve-inventory", async () => {
59
+ // Durable: replay returns the stored result without re-running the callback.
60
+ return { orderId, reserved: true };
61
+ });
62
+
63
+ await this.sleep("payment-window", 60_000);
64
+
65
+ const payment = await this.wait<{ chargeId: string }>("capture-payment", "payment.received", {
66
+ timeoutAt: Date.now() + 86_400_000
67
+ });
68
+
69
+ await this.run("fulfill", async () => {
70
+ return { orderId, chargeId: payment.chargeId };
71
+ });
72
+ }
73
+ }
74
+
75
+ export default {
76
+ async fetch(request, env) {
77
+ const url = new URL(request.url);
78
+
79
+ if (url.pathname === "/orders") {
80
+ const orderId = "new-order";
81
+ const stub = env.ORDER_WORKFLOW.getByName(orderId);
82
+ await stub.create({ definitionVersion: "2026-04-01", input: { orderId } });
83
+ return Response.json({ id: orderId });
84
+ }
85
+
86
+ return new Response(null, { status: 404 });
87
+ }
88
+ } satisfies ExportedHandler<Env>;
89
+ ```
90
+
91
+ Workflow input is **`this.ctx.props.input`**, populated from **`create({ input })`**. Use a **stable `definitionVersion`** string per deploy you want long-running instances to keep using; add a new version in **`getDefinition`** when you ship breaking definition changes.
92
+
93
+ ## How it works
94
+
95
+ The library separates concerns into two main layers:
96
+
97
+ - **Runtime Layer**: Managed by the `WorkflowRuntime` Durable Object, this layer is responsible for orchestration and state persistence. It maintains all workflow metadata, tracks which steps have completed, what the workflow is currently waiting on, and stores any inbound events that may arrive while the workflow is paused. The runtime ensures workflow continuity across multiple invocations and restarts by persisting its state in SQLite via Durable Object storage.
98
+
99
+ - **Definition Layer**: This is where you author your workflow logic by subclassing `WorkflowDefinition` and implementing the `execute()` method. Here you describe, in a sequence of durable and replayable operations, how each step in your workflow should proceed. You use helpers like `run` (to perform an idempotent unit of work), `sleep` (to pause execution for a specific duration), and `wait` (to suspend progress until an inbound event or timeout). Each time the runtime's loop advances, your full `execute()` method is replayed deterministically, and the workflow engine ensures side effects are only performed when workflow state transitions allow.
100
+
101
+ ### Replay and side effects
102
+
103
+ Each time the runtime advances, it calls `next()` on your `WorkflowDefinition`, which **runs `execute()` from the beginning again**. Steps that have already completed durably (`run`, elapsed `sleep`, resolved `wait`, and so on) **replay from stored state**: their callbacks are not re-invoked, and recorded results are returned as-is. New side effects happen only when the engine reaches a step that is not yet complete and the durable state allows that transition.
104
+
105
+ ### When the loop runs and when it stops
106
+
107
+ The `WorkflowRuntime` Durable Object drives a **run loop** that repeatedly invokes `next()` until one of these happens:
108
+
109
+ - **Terminal**: `next()` reports the workflow is **done** (`completed` or `failed`). The loop exits and the watchdog alarm is cleared.
110
+ - **Immediate resume**: `next()` asks to **continue immediately** (for example, so another step in the same logical “tick” can run). The loop continues without leaving the Durable Object invocation.
111
+ - **Suspended**: `next()` asks to **suspend**—for example, a step is waiting on a **retry backoff**, a **sleep** until a future time, or a **wait** for an inbound event. The loop exits; the runtime relies on **alarms** and/or **incoming events** to call back into the run loop. A long **watchdog alarm** also exists as a safety net if progress stalls.
112
+
113
+ ### Step kinds
114
+
115
+ - **`run`**: A named, durable unit of work. Outcomes are persisted; failures can be **retried** with backoff up to a configured maximum number of attempts.
116
+ - **`sleep`**: Pauses until a **scheduled wake time** stored in SQLite; the Durable Object is woken by an **alarm** when that time is reached.
117
+ - **`wait`**: Pauses until a matching **inbound event** (by name) or an optional **timeout**. Resolution is recorded in durable state so replay does not double-apply the branch that handled the event.
118
+
119
+ ### Alarms
120
+
121
+ Alarms are the primary mechanism for waking the `WorkflowRuntime` Durable Object back up after it suspends. There are three kinds of precise alarm, each tied to a specific step, plus a long-running watchdog that acts as a safety net.
122
+
123
+ **Sleep wake-up.** When `execute()` calls `this.sleep("id", duration)`, the runtime records a `sleep` step in SQLite with a `wake_at` timestamp and immediately schedules an alarm for that exact moment. When the alarm fires, the run loop replays `execute()` from the top, reaches the sleep step, sees the wake time has passed, marks the step `elapsed`, and continues forward.
124
+
125
+ ```ts
126
+ async execute(): Promise<void> {
127
+ await this.run("charge", async () => { /* ... */ });
128
+
129
+ // Schedules a Durable Object alarm 24 hours from now.
130
+ // The DO hibernates; no CPU is consumed until the alarm fires.
131
+ await this.sleep("cooling-off-period", 24 * 60 * 60 * 1_000);
132
+
133
+ await this.run("ship", async () => { /* ... */ });
134
+ }
135
+ ```
136
+
137
+ **Retry backoff.** When a `run` step fails but has attempts remaining, the runtime computes an exponential backoff delay (`250 ms → 500 ms → 1 s → 2 s → 4 s → 8 s → 10 s`), records `next_attempt_at` in SQLite, and schedules an alarm for that time. The DO goes idle; the run loop resumes only when the alarm fires.
138
+
139
+ ```ts
140
+ await this.run(
141
+ "call-payment-api",
142
+ async () => {
143
+ const res = await fetch("https://payments.example.com/charge", { method: "POST" });
144
+ if (!res.ok) throw new Error(`Payment failed: ${res.status}`);
145
+ return res.json();
146
+ },
147
+ { maxAttempts: 5 } // retries up to 4 more times with exponential backoff
148
+ );
149
+ ```
150
+
151
+ **Wait timeout.** When `this.wait` is called with a `timeoutAt`, the runtime schedules an alarm for that deadline. If no matching inbound event has arrived by then, the alarm fires, the step transitions to `timed_out`, and execution continues past the wait.
152
+
153
+ ```ts
154
+ // Suspend until "payment.received" is delivered or 24 hours elapse.
155
+ const payment = await this.wait<{ chargeId: string }>("capture-payment", "payment.received", {
156
+ timeoutAt: Date.now() + 86_400_000
157
+ });
158
+ ```
159
+
160
+ #### The watchdog alarm
161
+
162
+ In addition to these precise alarms, the runtime sets a **30-minute watchdog alarm at the start of every run-loop iteration**, before delegating to the workflow definition. When an iteration completes normally whether the workflow finishes, suspends on a sleep, or suspends on a wait - that alarm is either deleted or overwritten with the more specific alarm for the next resumption point. The watchdog only fires if something goes wrong in the middle.
163
+
164
+ The problem it guards against is a `run` step that gets stuck in the `running` state. Before the user's callback executes, the runtime durably writes `state = 'running'` to SQLite. That write is intentional: it ensures that a later replay does not try to start a second concurrent attempt for the same step. But it creates a gap:
165
+
166
+ ```
167
+ 1. Runtime writes state = 'running' to SQLite. ← durable
168
+ 2. User's callback starts executing.
169
+ 3. Durable Object is evicted or crashes. ← no outcome recorded
170
+ 4. SQLite still shows state = 'running'. ← step is stuck
171
+ ```
172
+
173
+ At this point there is no sleep alarm, no retry alarm, and no wait-timeout alarm; nothing scheduled to wake the runtime back up. Without the watchdog the workflow would stall indefinitely. The watchdog fires 30 minutes later, calls back into the run loop, replays `execute()`, reaches the stuck step, re-runs the callback, and records a proper outcome.
174
+
175
+ There is also a guard for the case where an alarm fires while the run loop is already active — for example, a sleep's precise alarm arriving while the loop is processing another step in the same Durable Object invocation. In that situation the alarm handler simply reschedules the watchdog for another 30 minutes rather than starting a second concurrent loop, keeping the safety net in place until the active loop finishes.
176
+
177
+ ### Versioning
178
+
179
+ `create({ definitionVersion, input })` **pins** the definition version and input in SQLite the first time the instance is initialized. **The version cannot be changed later** for that Durable Object id; attempting a different version throws. Every subsequent `next()` resolves the worker implementation via **`getDefinition(version)`** using that pinned value, so **long-lived workflows keep running the definition lineage they started with**, while new instances can use newer version strings you add to `getDefinition`.
180
+
181
+ ## Why this exists
182
+
183
+ Cloudflare Workflows is a strong managed option, and for many use cases it is the right tradeoff. I built `workerflow` for cases where I wanted tighter control over runtime behavior, definition versioning, and state projection than the managed model naturally gives me.
184
+
185
+ 1. Explicit ownership of workflow state and lifecycle
186
+ 2. A clear story for versioning workflow definitions
187
+ 3. Separation between workflow execution and external state synchronization
188
+ 4. Extension points for streaming, WebSockets, and custom hooks
189
+ 5. Fewer surprises around long-lived execution and error handling
190
+
191
+ ### Versioning workflow definitions
192
+
193
+ One of the biggest concerns in long-running workflows is definition drift. A normal Worker request is typically bound to a single in-flight execution on one deployed version, but a Workflow is durable: it persists state and resumes across multiple executions over time. A workflow begin executing on version of its definition and resume later after a new deploy has changed or removed a step. That means the next invocation of the workflow entry point could repeat steps unsafely or leave the runtime in an invalid state.
194
+
195
+ Versioning does not eliminate these problems, but it makes the risk explicit. It forces you to think about compatibility, migration, and long-lived execution up front. Cloudflare Workflows can support version-aware workflows by passing a version token in the immutable per-instance parameters and branching in workflow code or by maintianing a version mapping in an external database, but both are conventions that your application is responsible for maintaining.
196
+
197
+ `workerflow` takes a different approach: the runtime pins a definition version when the instance is created and resolves future execution against that pinned version. The goal is not to make compatibility problems disappear, but to make the version boundary explicit in the runtime rather than implicit in workflow input and application code.
198
+
199
+ ### Keeping workflow execution separate from state projection
200
+
201
+ In most real applications, workflows do not live in isolation. You usually have an external database that you want to keep in sync with workflow state so your application can query status, render UI, or trigger related behavior. One way to handle that is to model synchronization as a workflow step. In practice, that typically pushes you toward a top-level `try/catch`:
202
+
203
+ ```ts
204
+ export class MyWorkflow extends WorkflowEntrypoint {
205
+ async run(event: Event, step: WorkflowStep) {
206
+ try {
207
+ await step.do("1", async () => {
208
+ return 1;
209
+ });
210
+
211
+ await step.do("sync success", async () => {
212
+ //
213
+ });
214
+ } catch {
215
+ await step.do("sync error", async () => {
216
+ //
217
+ });
218
+ }
219
+ }
220
+ }
221
+ ```
222
+
223
+ This looks reasonable at first, but it creates an important failure-mode problem. If the actual business steps all succeed, but the final “sync success” step fails, then the workflow as a whole is now treated as failed. At that point, workflow execution and application-state projection have become tightly coupled, even though they are not really the same concern.
224
+
225
+ I think a cleaner design is to keep synchronization logic out of workflow steps entirely. Instead, the runtime can expose lifecycle hooks that fire when workflow state changes, and synchronization can happen there.
226
+
227
+ ```ts
228
+ export class MyWorkflowRuntime extends WorkflowRuntime {
229
+ onStatusChange(status) {
230
+ // Update your database, or push to a queue for streaming
231
+ }
232
+ }
233
+ ```
234
+
235
+ That design keeps synchronization off the critical path of workflow completion. If the synchronization fails, that failure does not retroactively redefine the workflow’s business outcome. You can recover independently, for example by retrying asynchronously or running a scheduled reconciliation job that polls workflow state and replays missed updates.
236
+
237
+ That is not the only valid approach, but I think it produces a better separation of concerns: the workflow runtime determines workflow outcome, and projection mechanisms consume that outcome.
238
+
239
+ ### Error handling
240
+
241
+ Another friction point in Cloudflare Workflows is error handling. My understanding, based on using it in production and reading the [announcement materials](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers/), is that the workflow runtime creates a step context and passes it into the Worker entry point. That step context is the step object you call methods like `do` and `sleep` on.
242
+
243
+ The `do` method is effectively an RPC call that accepts a step name, a callback, and optional configuration. It is invoked from the workflow entry point, but it runs inside the Worker where it was created. Since functions can be passed over RPC through stubs, the result is a chain of calls that crosses boundaries multiple times: the workflow engine Durable Object calls the Worker entry point, the Worker calls back into the Durable Object to update step state, and the Durable Object may then call back into the Worker again. Some of this is unavoidable, but it does have an unfortunate consequence: if you catch an error outside `step.do`, it is not necessarily the same error instance that was originally thrown inside the step, because it had to cross an RPC boundary. That might sound like an implementation detail, but in practice it affects how errors can be classified, rethrown, or inspected.
244
+
245
+ ## Tradeoffs
246
+
247
+ Owning the runtime buys flexibility, but it also means giving up some of the benefits of a managed workflow product. The most obvious trade-off is the cost model. Cloudflare Workflows is priced like Workers Standard pricing: you are billed for workflow invocations, CPU time, and storage, and idle periods, such as waiting on an API response, do not consume CPU billing. Durable Objects have a different cost model. They are billed for requests, storage, and compute duration measured as wall-clock time while the object is active or idle in memory but unable to hibernate.
248
+ You also give up a fair amount of first-party tooling. Cloudflare Workflows comes with built-in observability and debugging, dashboard metrics, and a visualizer that can render your workflow definition as a diagram directly in the dashboard. It is possible to recreate in user land; in fact, a custom implementation could build a more application-specific control plane, but now you are responsible for building and maintaining it yourself.
package/demo/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>my-react-app</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "demo",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "npm run build && vite preview",
11
+ "wrangler:deploy": "npm run build && wrangler deploy",
12
+ "wrangler:types": "wrangler types worker-configuration.d.ts --config ./wrangler.jsonc"
13
+ },
14
+ "dependencies": {
15
+ "react": "^19.2.0",
16
+ "react-dom": "^19.2.0",
17
+ "workerflow": "*"
18
+ },
19
+ "devDependencies": {
20
+ "@cloudflare/vite-plugin": "^1.31.0",
21
+ "@cloudflare/vitest-pool-workers": "^0.14.1",
22
+ "@tailwindcss/vite": "^0.0.0-insiders.e01946f",
23
+ "@types/node": "^25.5.2",
24
+ "@types/react": "^19.2.14",
25
+ "@types/react-dom": "^19.2.3",
26
+ "@vitejs/plugin-react": "^6.0.1",
27
+ "tailwindcss": "^4.2.2",
28
+ "typescript": "^6.0.2",
29
+ "vite": "^8.0.3",
30
+ "vitest": "^4.1.2",
31
+ "wrangler": "^4.80.0"
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
File without changes
@@ -0,0 +1,9 @@
1
+ import "./App.css";
2
+
3
+ export default function App() {
4
+ return (
5
+ <div>
6
+ <h1>Hello World</h1>
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1,51 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 822.8 355.5" style="enable-background:new 0 0 822.8 355.5;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{fill:#FFFFFF;}
7
+ .st1{fill:#FBAE40;}
8
+ .st2{fill:#F58220;}
9
+ </style>
10
+ <g id="Page-1">
11
+ <path id="CLOUDFLARE-_xAE_" class="st0" d="M772.2,252.6c-3.4,0-6.1-2.7-6.1-6.1c0-3.3,2.7-6.1,6.1-6.1c3.3,0,6.1,2.7,6.1,6.1
12
+ C778.3,249.8,775.5,252.6,772.2,252.6L772.2,252.6z M772.2,241.6c-2.7,0-4.9,2.2-4.9,4.9s2.2,4.9,4.9,4.9c2.7,0,4.9-2.2,4.9-4.9
13
+ S774.9,241.6,772.2,241.6L772.2,241.6z M775.3,249.7h-1.4l-1.2-2.3h-1.6v2.3h-1.3V243h3.2c1.4,0,2.3,0.9,2.3,2.2c0,1-0.6,1.7-1.4,2
14
+ L775.3,249.7z M772.9,246.2c0.5,0,1-0.3,1-1c0-0.8-0.4-1-1-1h-2v2H772.9z M136.7,239.8h15.6v42.5h27.1v13.6h-42.7V239.8z
15
+ M195.5,268v-0.2c0-16.1,13-29.2,30.3-29.2s30.1,12.9,30.1,29v0.2c0,16.1-13,29.2-30.3,29.2S195.5,284.1,195.5,268z M240.1,268
16
+ v-0.2c0-8.1-5.8-15.1-14.4-15.1c-8.5,0-14.2,6.9-14.2,15v0.2c0,8.1,5.8,15.1,14.3,15.1C234.4,283,240.1,276.1,240.1,268z
17
+ M275,271.3v-31.5h15.8V271c0,8.1,4.1,11.9,10.3,11.9c6.2,0,10.3-3.7,10.3-11.5v-31.6h15.8v31.1c0,18.1-10.3,26-26.3,26
18
+ C285,296.9,275,288.9,275,271.3z M351,239.8h21.6c20,0,31.7,11.5,31.7,27.7v0.2c0,16.2-11.8,28.2-32,28.2H351V239.8z M372.9,282.1
19
+ c9.3,0,15.5-5.1,15.5-14.2v-0.2c0-9-6.2-14.2-15.5-14.2h-6.3V282L372.9,282.1L372.9,282.1z M426.9,239.8h44.9v13.6h-29.4v9.6H469
20
+ v12.9h-26.6v20h-15.5V239.8z M493.4,239.8h15.5v42.5h27.2v13.6h-42.7V239.8z M576.7,239.4h15l23.9,56.5h-16.7l-4.1-10h-21.6l-4,10
21
+ h-16.3L576.7,239.4z M590.4,273.8l-6.2-15.9l-6.3,15.9H590.4z M635.6,239.8h26.5c8.6,0,14.5,2.2,18.3,6.1c3.3,3.2,5,7.5,5,13.1v0.2
22
+ c0,8.6-4.6,14.3-11.5,17.2l13.4,19.6h-18L658,279h-6.8v17h-15.6V239.8z M661.4,266.7c5.3,0,8.3-2.6,8.3-6.6v-0.2
23
+ c0-4.4-3.2-6.6-8.4-6.6h-10.2v13.4H661.4z M707.8,239.8h45.1V253h-29.7v8.5h26.9v12.3h-26.9v8.9h30.1v13.2h-45.5V239.8z
24
+ M102.7,274.6c-2.2,4.9-6.8,8.4-12.8,8.4c-8.5,0-14.3-7.1-14.3-15.1v-0.2c0-8.1,5.7-15,14.2-15c6.4,0,11.3,3.9,13.3,9.3h16.4
25
+ c-2.6-13.4-14.4-23.3-29.6-23.3c-17.3,0-30.3,13.1-30.3,29.2v0.2c0,16.1,12.8,29,30.1,29c14.8,0,26.4-9.6,29.4-22.4L102.7,274.6z"
26
+ />
27
+ <path id="flare" class="st0" d="M734.5,150.4l-40.7-24.7c-0.6-0.1-4.4,0.3-6.4-0.7c-1.4-0.7-2.5-1.9-3.2-4c-3.2,0-175.5,0-175.5,0
28
+ v91.8h225.8V150.4z"/>
29
+ <path id="right-cloud" class="st1" d="M692.2,125.8c-0.8,0-1.5,0.6-1.8,1.4l-4.8,16.7c-2.1,7.2-1.3,13.8,2.2,18.7
30
+ c3.2,4.5,8.6,7.1,15.1,7.4l26.2,1.6c0.8,0,1.5,0.4,1.9,1c0.4,0.6,0.5,1.5,0.3,2.2c-0.4,1.2-1.6,2.1-2.9,2.2l-27.3,1.6
31
+ c-14.8,0.7-30.7,12.6-36.3,27.2l-2,5.1c-0.4,1,0.3,2,1.4,2H758c1.1,0,2.1-0.7,2.4-1.8c1.6-5.8,2.5-11.9,2.5-18.2
32
+ c0-37-30.2-67.2-67.3-67.2C694.5,125.7,693.3,125.7,692.2,125.8z"/>
33
+ <path id="left-cloud" class="st2" d="M656.4,204.6c2.1-7.2,1.3-13.8-2.2-18.7c-3.2-4.5-8.6-7.1-15.1-7.4L516,176.9
34
+ c-0.8,0-1.5-0.4-1.9-1c-0.4-0.6-0.5-1.4-0.3-2.2c0.4-1.2,1.6-2.1,2.9-2.2l124.2-1.6c14.7-0.7,30.7-12.6,36.3-27.2l7.1-18.5
35
+ c0.3-0.8,0.4-1.6,0.2-2.4c-8-36.2-40.3-63.2-78.9-63.2c-35.6,0-65.8,23-76.6,54.9c-7-5.2-15.9-8-25.5-7.1
36
+ c-17.1,1.7-30.8,15.4-32.5,32.5c-0.4,4.4-0.1,8.7,0.9,12.7c-27.9,0.8-50.2,23.6-50.2,51.7c0,2.5,0.2,5,0.5,7.5
37
+ c0.2,1.2,1.2,2.1,2.4,2.1h227.2c1.3,0,2.5-0.9,2.9-2.2L656.4,204.6z"/>
38
+ </g>
39
+ <g>
40
+ </g>
41
+ <g>
42
+ </g>
43
+ <g>
44
+ </g>
45
+ <g>
46
+ </g>
47
+ <g>
48
+ </g>
49
+ <g>
50
+ </g>
51
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ },
27
+ "include": ["src"]
28
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.app.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.node.json"
9
+ },
10
+ {
11
+ "path": "./tsconfig.worker.json"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedSideEffectImports": true
23
+ },
24
+ "include": ["vite.config.ts"]
25
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "./tsconfig.node.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo",
5
+ "lib": ["ES2024"],
6
+ "types": [
7
+ "./worker-configuration.d.ts",
8
+ "vite/client",
9
+ "@cloudflare/vitest-pool-workers/types"
10
+ ]
11
+ },
12
+ "include": ["worker"]
13
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import { cloudflare } from "@cloudflare/vite-plugin";
5
+
6
+ // https://vite.dev/config/
7
+ export default defineConfig({
8
+ plugins: [react(), cloudflare(), tailwindcss()]
9
+ });
@@ -0,0 +1,16 @@
1
+ import { WorkflowDefinition, WorkflowRuntime } from "workerflow";
2
+
3
+ export class DemoWorkflowRuntime extends WorkflowRuntime {
4
+ protected getDefinition() {
5
+ return this.ctx.exports.DemoWorkflowDefinition;
6
+ }
7
+ }
8
+ export class DemoWorkflowDefinition extends WorkflowDefinition {
9
+ async execute(): Promise<void> {}
10
+ }
11
+
12
+ export default {
13
+ fetch() {
14
+ return new Response(null);
15
+ }
16
+ } satisfies ExportedHandler<Env>;