diff --git a/cmd/workers-assets-gen/assets/common/shim.mjs b/cmd/workers-assets-gen/assets/common/shim.mjs index 9432c2b..cadc0be 100644 --- a/cmd/workers-assets-gen/assets/common/shim.mjs +++ b/cmd/workers-assets-gen/assets/common/shim.mjs @@ -22,3 +22,8 @@ export async function fetch(req, env, ctx) { await run(); return handleRequest(req, { env, ctx }); } + +export async function scheduled(event, env, ctx) { + await run(); + return runScheduler(event, { env, ctx }); +} diff --git a/cmd/workers-assets-gen/assets/common/worker.mjs b/cmd/workers-assets-gen/assets/common/worker.mjs index d529e11..3472c98 100644 --- a/cmd/workers-assets-gen/assets/common/worker.mjs +++ b/cmd/workers-assets-gen/assets/common/worker.mjs @@ -3,4 +3,4 @@ import mod from "./app.wasm"; imports.init(mod); -export default { fetch: imports.fetch } +export default { fetch: imports.fetch, scheduled: imports.scheduled } diff --git a/cron.go b/cron.go new file mode 100644 index 0000000..a320d2c --- /dev/null +++ b/cron.go @@ -0,0 +1,84 @@ +package workers + +import ( + "context" + "errors" + "fmt" + "syscall/js" + "time" + + "github.com/syumai/workers/internal/jsutil" + "github.com/syumai/workers/internal/runtimecontext" +) + +// CronEvent represents information about the Cron that invoked this worker. +type CronEvent struct { + // Type string + Cron string + ScheduledTime time.Time +} + +// toGo converts JS Object to CronEvent +func (ce *CronEvent) toGo(obj js.Value) error { + if obj.IsUndefined() { + return errors.New("event is null") + } + cronVal := obj.Get("cron").String() + ce.Cron = cronVal + scheduledTimeVal := obj.Get("scheduledTime").Float() + ce.ScheduledTime = time.Unix(int64(scheduledTimeVal)/1000, 0).UTC() + + return nil +} + +type CronFunc func(ctx context.Context, event CronEvent) error + +var cronTask CronFunc + +// ScheduleTask sets the CronFunc to be executed +func ScheduleTask(task CronFunc) { + cronTask = task + jsutil.Global.Call("ready") + select {} +} + +func runScheduler(eventObj js.Value, runtimeCtxObj js.Value) error { + ctx := runtimecontext.New(context.Background(), runtimeCtxObj) + event := CronEvent{} + err := event.toGo(eventObj) + if err != nil { + return err + } + err = cronTask(ctx, event) + if err != nil { + return err + } + return nil +} + +func init() { + runSchedulerCallback := js.FuncOf(func(_ js.Value, args []js.Value) any { + if len(args) != 2 { + panic(fmt.Errorf("invalid number of arguments given to runScheduler: %d", len(args))) + } + event := args[0] + runtimeCtx := args[1] + + var cb js.Func + cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any { + defer cb.Release() + resolve := pArgs[0] + go func() { + err := runScheduler(event, runtimeCtx) + if err != nil { + panic(err) + } + resolve.Invoke(js.Undefined()) + }() + return js.Undefined() + }) + + return jsutil.NewPromise(cb) + }) + jsutil.Global.Set("runScheduler", runSchedulerCallback) +} diff --git a/examples/cron/.gitignore b/examples/cron/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/examples/cron/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/examples/cron/Makefile b/examples/cron/Makefile new file mode 100644 index 0000000..94ac704 --- /dev/null +++ b/examples/cron/Makefile @@ -0,0 +1,12 @@ +.PHONY: dev +dev: + wrangler dev + +.PHONY: build +build: + go run ../../cmd/workers-assets-gen + tinygo build -o ./build/app.wasm -target wasm ./... + +.PHONY: publish +publish: + wrangler publish diff --git a/examples/cron/README.md b/examples/cron/README.md new file mode 100644 index 0000000..80c52af --- /dev/null +++ b/examples/cron/README.md @@ -0,0 +1,20 @@ +# [Cron](https://developers.cloudflare.com/workers/platform/triggers/cron-triggers/) + +* Create a worker to be triggered by Cron. + +## Development + +### Requirements + +This project requires these tools to be installed globally. + +* wrangler +* tinygo + +### Commands + +``` +make dev # run dev server +make build # build Go Wasm binary +make publish # publish worker +``` diff --git a/examples/cron/go.mod b/examples/cron/go.mod new file mode 100644 index 0000000..6e2c460 --- /dev/null +++ b/examples/cron/go.mod @@ -0,0 +1,7 @@ +module github.com/syumai/workers/examples/scheduled + +go 1.18 + +require github.com/syumai/workers v0.0.0 + +replace github.com/syumai/workers => ../../ diff --git a/examples/cron/go.sum b/examples/cron/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/examples/cron/main.go b/examples/cron/main.go new file mode 100644 index 0000000..be86cd3 --- /dev/null +++ b/examples/cron/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + "errors" + "fmt" + + "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare" +) + +func task(ctx context.Context, event workers.CronEvent) error { + fmt.Println(cloudflare.Getenv(ctx, "HELLO")) + + if event.ScheduledTime.Minute()%2 == 0 { + return errors.New("even numbers cause errors") + } + + return nil +} + +func main() { + workers.ScheduleTask(task) +} diff --git a/examples/cron/wrangler.toml b/examples/cron/wrangler.toml new file mode 100644 index 0000000..c6d714e --- /dev/null +++ b/examples/cron/wrangler.toml @@ -0,0 +1,13 @@ +name = "scheduled" +main = "./build/worker.mjs" +compatibility_date = "2023-02-24" +workers_dev = false + +[vars] +HELLO = "hello, world!" + +[triggers] +crons = ["* * * * *"] + +[build] +command = "make build"