Skip to content
This repository has been archived by the owner on Jan 5, 2025. It is now read-only.

Commit

Permalink
a bunch of stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
threepointone committed Dec 9, 2024
1 parent 2ba6a3d commit f08769a
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 105 deletions.
157 changes: 89 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,80 +9,101 @@ Sophisticated scheduler for durable tasks, built on Durable Object Alarms.
- query tasks by description or id (or by time range?)
- cancel tasks

Bonus: This will be particularly useful when wired up with an LLM agent, so you'll be able to schedule tasks by describing them in natural language. Like "remind me to call my friend every monday at 10:00"
The killer app: This will be particularly useful when wired up with an LLM agent, so you'll be able to schedule tasks by describing them in natural language. Like "remind me to call my friend every monday at 10:00"

```ts
import { Scheduler } from "durable-scheduler";

type Task = {
id: string;
description: string;
time: Date;
} & (
| {
time: Date;
type: "scheduled";
}
| {
delayInSeconds: number;
type: "delayed";
}
| {
cron: string;
type: "cron";
}
);

class MyClass extends Scheduler {
foo() {
// schedule at specific time
this.scheduler.scheduleTask({
description: "my-task",
time: new Date(Date.now() + 1000),
});

// schedule after a certain amount of time
this.scheduler.scheduleTask({
description: "my-task",
delayInSeconds: 1000, // in ms? s?
});

// schedule to run periodically
this.scheduler.scheduleTask({
description: "my-task",
cron: "*/1 * * * *", // every minute
});

// you can also specify an id
this.scheduler.scheduleTask({
id: "my-task",
time: new Date(Date.now() + 1000),
});

// ids must be unique

// if you don't provide an id, it will default to a random uuid
// if you try to schedule a task with an id that already exists,
// it will overwrite the existing task

// query for tasks
const tasks = this.scheduler.query({
// by description
// by id
// by time range
// some kind of sql syntax here? dunno..
});

// cancel a task
tasks.forEach((task) => {
this.scheduler.cancelTask(task.id);
});
}
}
export { Scheduler };
// also setup wrangler.toml to create a durable object binding
// let's say you've done it this way:

// [[durable_objects.bindings]]
// name = "SCHEDULER"
// class_name = "Scheduler"

export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
// access a scheduler instance
const id = env.SCHEDULER.idFromName("my-scheduler");
const scheduler = env.SCHEDULER.get(id);
// now you can use the scheduler
},
};
```

A task has a few parts:

- **description**: which is a string that you can use to identify the task
- **payload**: which is a JSON object that is passed to the task
- **type**: which is one of "delayed", "cron", or "scheduled"
- **`type: delayed`**: the task will be run after a delay **`delayInSeconds`**
- **`type: cron`**: the task will be run on a cron schedule **`cron`**
- **`type: scheduled`**: the task will be run at a specific date **`time`**
- **callback**: which is a function that is called when the task is run. It can be of type `webhook`, ` durable-object` or `service`
- **`type: webhook`**: the task will be run by POSTing to a **`url`**
- **`type: durable-object`**: the task will be run by calling a function on a durable object
- **`type: service`**: the task will be run by calling a function on a service

Here are some examples:

- This will schedule a task to be run after a delay of 60 seconds, and call a webhook at `https://example.com/webhook` with the payload `{ message: "Hello, world!" }`

```ts
scheduler.scheduleTask({
description: "my-task",
type: "delayed",
delayInSeconds: 60,
payload: {
message: "Hello, world!",
},
callback: {
type: "webhook",
url: "https://example.com/webhook",
},
});
```

- This will schedule a task to be run every Friday at 6pm, and call a durable object binding `MYDURABLE` of id "some-id" with the function `myFunction` with the payload `{ message: "Hello, world!" }`

```ts
scheduler.scheduleTask({
description: "my-task",
type: "cron",
cron: "0 18 * * 5",
payload: {
message: "Hello, world!",
},
callback: {
type: "durable-object",
namespace: "MYDURABLE",
id: "some-id",
function: "myFunction",
},
});
```

- This will schedule a task to be run at a specific date and time, and call a service binding `MYSERVICE` with the function `myFunction` with the payload `{ message: "Hello, world!" }`

```ts
scheduler.scheduleTask({
description: "my-task",
type: "scheduled",
time: new Date("2024-01-01T12:00:00Z"),
payload: {
message: "Hello, world!",
},
callback: {
type: "service",
service: "MYSERVICE",
function: "myFunction",
},
});
```

## todo:

- replace with a decorator syntax
- add a dashboard for visualizing tasks and their status?
- what's a good api for actually running the tasks? (maybe just a simple http api? like a webhook?)
- how to handle errors?
- how to handle retries?
- testing story?
4 changes: 1 addition & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default [
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/require-await": "off",
"@typescript-eslint/await-thenable": "off",
"no-console": "off",
// React rules
...reactPlugin.configs.recommended.rules,
"react/react-in-jsx-scope": "off",
Expand All @@ -64,9 +65,6 @@ export default [
alphabetize: { order: "asc", caseInsensitive: true },
},
],

// General rules
"no-console": ["warn", { allow: ["warn", "error"] }],
},
},
{
Expand Down
14 changes: 6 additions & 8 deletions example/src/client/app.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useState } from "react";

import { SqlTask } from "../../../src";
import { Task } from "../../../src";

interface ToDo {
id: string;
inputText: string;
parsedTask: SqlTask | undefined;
task: Task | undefined;
completed: boolean;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ROOM_ID = "username"; // TODO: this will read a username from auth later
// const ROOM_ID = "username"; // TODO: this will read a username from auth later

export default function App() {
const [todos, setTodos] = useState<ToDo[]>([]);
Expand All @@ -24,7 +23,7 @@ export default function App() {
const newToDo: ToDo = {
id: crypto.randomUUID(),
inputText,
parsedTask: undefined,
task: undefined,
completed: false,
};

Expand All @@ -37,7 +36,6 @@ export default function App() {
body: inputText,
});
const parsedTask = await result.json();
// eslint-disable-next-line no-console
console.log("parsedTask", parsedTask);
// ok now let's schedule it

Expand Down Expand Up @@ -94,9 +92,9 @@ export default function App() {
/>
<div className="flex-1">
<p className={`text-gray-800 ${todo.completed ? "line-through" : ""}`}>
{todo.parsedTask?.description}
{todo.task?.description}
</p>
<p className="text-sm text-gray-500">Due: {todo.parsedTask?.time}</p>
<p className="text-sm text-gray-500">Due: {todo.task?.time.toISOString()}</p>
</div>
<button
onClick={() => void handleDeleteToDo(todo.id)}
Expand Down
4 changes: 1 addition & 3 deletions example/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export default {
async fetch(request: Request, env: Env, _ctx: ExecutionContext) {
const url = new URL(request.url);

// reroute vite dev server requests to the client
if (!url.pathname.startsWith("/api/")) {
return fetch(request.url.replace("http://localhost:8787", "http://localhost:5173"), request);
}
Expand All @@ -124,9 +125,6 @@ ${await request.text()}
`,
});

// eslint-disable-next-line no-console
console.log(result.object);

return new Response(JSON.stringify(result.object));
}
}
Expand Down
Loading

0 comments on commit f08769a

Please sign in to comment.