diff --git a/.vendor/with/cmd/example-dbos/client.go b/.vendor/with/cmd/example-dbos/client.go new file mode 100644 index 0000000..db2b3cb --- /dev/null +++ b/.vendor/with/cmd/example-dbos/client.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "flag" + "log" + "os" + + "gitea.bel.blue/bel/with" + "gitea.bel.blue/bel/with/cmd/example-dbos/work" + "gitea.bel.blue/bel/with/dbos" +) + +func main() { + if err := with.Context(run); err != nil { + log.Fatal(err) + } +} + +func run(ctx context.Context) error { + fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + conn := fs.String("c", "", "postgresql://u:p@h:p/db") + gdedupe := fs.String("k1", "global-deduplication-key", "prevent ever re-evaluating this execution") + cdedupe := fs.String("k2", "concurrent-deduplication-key", "prevent re-evaluating this execution concurrently") + if err := fs.Parse(os.Args[1:]); err != nil { + return err + } + + return dbos.QueueClient(ctx, *conn, "queue", func(c *dbos.Client) error { + return dbos.Go(ctx, c, work.Workflow, 1, *gdedupe, *cdedupe) + }) +} diff --git a/.vendor/with/cmd/example-dbos/work/work.go b/.vendor/with/cmd/example-dbos/work/work.go new file mode 100644 index 0000000..ffc620e --- /dev/null +++ b/.vendor/with/cmd/example-dbos/work/work.go @@ -0,0 +1,12 @@ +package work + +import ( + "log" + + "github.com/dbos-inc/dbos-transact-golang/dbos" +) + +func Workflow(ctx dbos.DBOSContext, arg any) (any, error) { + log.Printf("WORKFLOW with %v!", arg) + return nil, nil +} diff --git a/.vendor/with/cmd/example-dbos/worker.go b/.vendor/with/cmd/example-dbos/worker.go new file mode 100644 index 0000000..3b6c09b --- /dev/null +++ b/.vendor/with/cmd/example-dbos/worker.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "flag" + "log" + "os" + + "gitea.bel.blue/bel/with" + "gitea.bel.blue/bel/with/cmd/example-dbos/work" + mydbos "gitea.bel.blue/bel/with/dbos" +) + +func main() { + if err := with.Context(run); err != nil { + log.Fatal(err) + } +} + +func run(ctx context.Context) error { + fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + conn := fs.String("c", "", "postgresql://u:p@h:p/db") + if err := fs.Parse(os.Args[1:]); err != nil { + return err + } + + return mydbos.QueueWorker(ctx, *conn, "queue", func(w *mydbos.Worker) error { + mydbos.Can(w, work.Workflow) + return w.Listen(ctx) + }) +} diff --git a/.vendor/with/ctx.go b/.vendor/with/ctx.go new file mode 100644 index 0000000..1bedbdd --- /dev/null +++ b/.vendor/with/ctx.go @@ -0,0 +1,24 @@ +package with + +import ( + "context" + "os/signal" + "syscall" + "time" +) + +func Context(foo func(context.Context) error) error { + ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) + defer can() + + if err := foo(ctx); err != nil && ctx.Err() == nil { + return err + } + return nil +} + +func Timeout(ctx context.Context, d time.Duration, foo func(context.Context) error) error { + ctx, can := context.WithTimeout(ctx, d) + defer can() + return foo(ctx) +} diff --git a/.vendor/with/dbos/client.go b/.vendor/with/dbos/client.go new file mode 100644 index 0000000..f04e0ea --- /dev/null +++ b/.vendor/with/dbos/client.go @@ -0,0 +1,75 @@ +package dbos + +import ( + "context" + "fmt" + "log" + "reflect" + "runtime" + "strings" + "time" + + "github.com/dbos-inc/dbos-transact-golang/dbos" +) + +type Client struct { + c dbos.Client + q string +} + +func QueueClient(ctx context.Context, conn, q string, foo func(*Client) error) error { + connctx, conncan := context.WithTimeout(ctx, 10*time.Second) + defer conncan() + c, err := dbos.NewClient(connctx, dbos.ClientConfig{ + DatabaseURL: conn, + }) + if err != nil { + return err + } + defer c.Shutdown(5 * time.Second) + return foo(&Client{c: c, q: q}) +} + +func (c *Client) WithQueue(q string) *Client { + c2 := *c + c2.q = q + return &c2 +} + +func Go[P any, R any](ctx context.Context, c *Client, foo dbos.Workflow[P, R], input P, globalDedupe, concurrentDedupe string) error { + log.Printf("[client] going to %s#%s#%s", fooToName(foo), globalDedupe, concurrentDedupe) + + result := make(chan error) + go func() { + defer close(result) + _, err := dbos.Enqueue[P, R](c.c, c.q, fooToName(foo), input, dbos.WithEnqueueDeduplicationID(concurrentDedupe), dbos.WithEnqueueApplicationVersion("latest"), dbos.WithEnqueueWorkflowID(globalDedupe)) + select { + case <-ctx.Done(): + case result <- err: + } + }() + select { + case err := <-result: + return err + case <-ctx.Done(): + } + return ctx.Err() +} + +// https://github.com/dbos-inc/dbos-transact-golang/blob/0d755bda26e3162c823f3e316aa0f88677b8b246/dbos/workflow.go#L577 +func fooToName[P any, R any](fn dbos.Workflow[P, R]) string { + ptr := reflect.ValueOf(fn).Pointer() + fqn := runtime.FuncForPC(ptr).Name() + + // If this is a generic workflow, append the actual types to the FQN + if strings.Contains(fqn, "[") { + fqn = strings.Split(fqn, "[")[0] + fqn = fmt.Sprintf("%s[%s,%s]", + fqn, + reflect.TypeFor[P]().String(), + reflect.TypeFor[R]().String(), + ) + } + + return fqn +} diff --git a/.vendor/with/dbos/dbos_test.go b/.vendor/with/dbos/dbos_test.go new file mode 100644 index 0000000..eace7b7 --- /dev/null +++ b/.vendor/with/dbos/dbos_test.go @@ -0,0 +1,122 @@ +package dbos + +import ( + "context" + "os" + "regexp" + "runtime" + "strings" + "sync" + "testing" + "time" + + "github.com/dbos-inc/dbos-transact-golang/dbos" +) + +func TestDBOS(t *testing.T) { + conn := os.Getenv("CONN_URL") + if conn == "" { + t.Skip("no $CONN_URL") + } + + routine := func() string { + buf := make([]byte, 128_000) + n := runtime.Stack(buf[:], false) + buf = buf[:n] + + myRoutine := strings.Fields(strings.TrimPrefix(string(buf), "goroutine"))[0] + + re := regexp.MustCompile(`created by .*? goroutine [0-9]+`) + chain := []string{myRoutine} + for _, match := range re.FindAllString(string(buf), -1) { + fields := strings.Fields(match) + chain = append(chain, fields[len(fields)-1]) + } + return strings.Join(chain, "-") + } + + ctx, can := context.WithTimeout(context.Background(), 19*time.Second) + defer can() + + wg := &sync.WaitGroup{} + defer wg.Wait() + wg.Add(3) + waitScheduled := &sync.Once{} + + onDemandWorkflow := func(ctx dbos.DBOSContext, arg string) (string, error) { + t.Logf("[%s] on demand...", routine()) + defer t.Logf("[%s] /on demand", routine()) + + dbos.Sleep(ctx, 1*time.Millisecond) + return dbos.RunAsStep(ctx, func(ctx context.Context) (string, error) { + t.Logf("[%s] on demand step...", routine()) + defer t.Logf("[%s] /on demand step", routine()) + + t.Logf("[%s] step on demand", routine()) + wg.Done() + return "my step string", nil + }) + } + + scheduledWorkflow := func(dbos.DBOSContext, time.Time) (int, error) { + t.Logf("[%s] scheduled...", routine()) + defer t.Logf("[%s] /scheduled", routine()) + + waitScheduled.Do(wg.Done) + return -1, nil + } + + t.Run("worker", func(t *testing.T) { + go func() { + if err := QueueWorker(ctx, conn, "queue", func(w *Worker) error { + t.Logf("[%s] dbosc...", routine()) + defer t.Logf("[%s] /dbosc", routine()) + + t.Logf("[%s] dbosc.can...", routine()) + Can(w, onDemandWorkflow) + t.Logf("[%s] dbosc.every...", routine()) + Every(w, scheduledWorkflow, "* * * * * *") + t.Logf("[%s] dbosc go do...", routine()) + go func() { + t.Logf("[%s] dbos do...", routine()) + defer t.Logf("[%s] /dbos do", routine()) + + time.Sleep(2 * time.Second) + + res, err := Do(ctx, w, onDemandWorkflow, "arg") + if err != nil { + t.Fatal(err) + } + t.Logf("onDemandWorkflow result: %q", res) + }() + + t.Logf("[%s] dbosc listen...", routine()) + defer t.Logf("[%s] /dbosc listen", routine()) + return w.Listen(ctx) + }); err != nil { + t.Fatal(err) + } + }() + }) + + t.Run("client", func(t *testing.T) { + if err := QueueClient(ctx, conn, "queue", func(w *Client) error { + t.Logf("[%s] dbosc...", routine()) + defer t.Logf("[%s] /dbosc", routine()) + + t.Logf("[%s] dbos go...", routine()) + defer t.Logf("[%s] /dbos go", routine()) + + err := Go(ctx, w, onDemandWorkflow, "arg", time.Now().String()) + if err != nil { + t.Fatal(err) + } + t.Logf("[%s] onDemandWorkflow enqueued result", routine()) + + return nil + }); err != nil { + t.Fatal(err) + } + }) + +} diff --git a/.vendor/with/dbos/worker.go b/.vendor/with/dbos/worker.go new file mode 100644 index 0000000..c41cc88 --- /dev/null +++ b/.vendor/with/dbos/worker.go @@ -0,0 +1,103 @@ +package dbos + +import ( + "context" + "log" + "os" + "slices" + "time" + + "github.com/dbos-inc/dbos-transact-golang/dbos" +) + +type Worker struct { + dbos dbos.DBOSContext + q string + qs []dbos.WorkflowQueue +} + +func QueueWorker(ctx context.Context, conn, q string, foo func(*Worker) error) error { + return NewWorker(ctx, conn, func(w *Worker) error { + return foo(w.WithQueue(q)) + }) +} + +func NewWorker(ctx context.Context, conn string, foo func(*Worker) error) error { + type result struct { + ctx dbos.DBOSContext + err error + } + ch := make(chan result) + go func() { + defer close(ch) + dbosctx, err := dbos.NewDBOSContext(ctx, dbos.Config{ + AppName: "with", + DatabaseURL: conn, + AdminServer: os.Getenv("WITH_DBOS_ADMIN_SERVER") == "true", + ApplicationVersion: "latest", + }) + select { + case ch <- result{ctx: dbosctx, err: err}: + case <-ctx.Done(): + } + }() + select { + case result := <-ch: + if err := result.err; err != nil { + return err + } + return foo(&Worker{dbos: result.ctx}) + case <-ctx.Done(): + } + return ctx.Err() +} + +func (c *Worker) Listen(ctx context.Context) error { + dbos := c.dbos + //dbos.ListenQueues(c.dbos, c.qs...) + log.Printf("[worker] listening to %+v", c.q) + if err := dbos.Launch(); err != nil { + return err + } + defer dbos.Shutdown(5 * time.Second) + <-ctx.Done() + return nil +} + +func (c *Worker) WithQueue(q string) *Worker { + c2 := *c + c2.q = q + c2.qs = slices.Clone(c2.qs) + + dbosq := dbos.NewWorkflowQueue(c2.dbos, q) + //dbos.WithWorkerConcurrency(5), // per worker + //dbos.WithGlobalConcurrency(10), // per all workers + //dbos.WithRateLimiter(&dbos.RateLimiter{Limit: 100, Period: time.Second}), + //dbos.WithPriorityEnabled(), + // dbos.WithPartitionQueue(), // WithQueuePartitionKey but has limit per-partiiton + + c2.qs = append(c2.qs, dbosq) + + return &c2 +} + +func Can[P any, R any](c *Worker, foo dbos.Workflow[P, R]) { + log.Printf("[worker] registering %s", fooToName(foo)) + dbos.RegisterWorkflow(c.dbos, foo, dbos.WithWorkflowName(fooToName(foo))) +} + +func Every[P any, R any](w *Worker, foo dbos.Workflow[P, R], cron string) { + log.Printf("[worker] registering cron '%s' %s", cron, fooToName(foo)) + dbos.RegisterWorkflow(w.dbos, foo, dbos.WithSchedule(cron), dbos.WithWorkflowName(fooToName(foo))) +} + +func Do[P any, R any](ctx context.Context, w *Worker, foo dbos.Workflow[P, R], input P) (R, error) { + dbosctx := dbos.From(w.dbos, ctx) + log.Printf("[worker] doing %s", fooToName(foo)) + handle, err := dbos.RunWorkflow(dbosctx, foo, input) //, options...) + var some R + if err != nil { + return some, err + } + return handle.GetResult() +} diff --git a/.vendor/with/every.go b/.vendor/with/every.go new file mode 100644 index 0000000..25bce14 --- /dev/null +++ b/.vendor/with/every.go @@ -0,0 +1,34 @@ +package with + +import ( + "context" + "time" +) + +func GoEvery(ctx context.Context, d time.Duration, foo func()) { + every(ctx, d, foo, true) +} + +func Every(ctx context.Context, d time.Duration, foo func()) { + every(ctx, d, foo, false) +} + +func every(ctx context.Context, d time.Duration, foo func(), async bool) { + ticker := time.NewTicker(d) + defer ticker.Stop() + for ctx.Err() == nil { + everyTry(ctx, foo, async) + select { + case <-ctx.Done(): + case <-ticker.C: + } + } +} + +func everyTry(ctx context.Context, foo func(), async bool) { + if async { + go foo() + } else { + foo() + } +} diff --git a/.vendor/with/go.mod b/.vendor/with/go.mod new file mode 100644 index 0000000..0fa40d4 --- /dev/null +++ b/.vendor/with/go.mod @@ -0,0 +1,31 @@ +module gitea.bel.blue/bel/with + +go 1.25.0 + +require ( + github.com/dbos-inc/dbos-transact-golang v0.15.0 + github.com/lib/pq v1.11.2 + modernc.org/sqlite v1.46.1 +) + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + modernc.org/libc v1.67.6 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) diff --git a/.vendor/with/go.sum b/.vendor/with/go.sum new file mode 100644 index 0000000..73ffc52 --- /dev/null +++ b/.vendor/with/go.sum @@ -0,0 +1,89 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dbos-inc/dbos-transact-golang v0.15.0 h1:h4RBiUv5AED6vSmM/CPAPAC8uAfBw+USz8xgULbzrdk= +github.com/dbos-inc/dbos-transact-golang v0.15.0/go.mod h1:Q9UxpOFyrSBYQywcFyy4nDHCFThQwwNuQvSi8S9LDZY= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ= +github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= +modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= +modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/.vendor/with/http.go b/.vendor/with/http.go new file mode 100644 index 0000000..06c3a0c --- /dev/null +++ b/.vendor/with/http.go @@ -0,0 +1,140 @@ +package with + +import ( + "bytes" + "context" + "database/sql" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" +) + +type RoundTripper struct { + http.RoundTripper + kv SQLKV +} + +func CachedHTTP(ctx context.Context, foo func(*http.Client) error) error { + return Sqlite(ctx, ":memory:", func(db *sql.DB) error { + return KV(ctx, db, func(kv SQLKV) error { + return foo(&http.Client{ + Timeout: time.Minute, + Transport: RoundTripper{ + RoundTripper: &http.Transport{ + DisableKeepAlives: true, + }, + kv: kv, + }, + }) + }) + }) +} + +func (c RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + req := newCacheableRequest(r) + if v, err := c.kv.Get(r.Context(), req.cacheK()); err != nil { + } else if resp := parseCacheableResponse(v); resp != nil { + return resp.response(), nil + } + + resp, err := c.RoundTripper.RoundTrip(r) + if err != nil { + return nil, err + } + c.kv.Set(r.Context(), req.cacheK(), newCacheableResponse(resp).cacheV()) + + return resp, nil +} + +type cacheableRequest struct { + URLHost string + URLPath string + URLQuery cacheableHTTPHeader + Header cacheableHTTPHeader + Body string +} + +func newCacheableRequest(r *http.Request) cacheableRequest { + defer r.Body.Close() + b, _ := io.ReadAll(r.Body) + r.Body = io.NopCloser(bytes.NewReader(b)) + return cacheableRequest{ + URLHost: r.URL.Host, + URLPath: r.URL.Path, + URLQuery: newCacheableHTTPHeader(r.URL.Query()), + Header: newCacheableHTTPHeader(r.Header), + Body: string(b), + } +} + +func (c cacheableRequest) cacheK() string { + return fmt.Sprint(c) +} + +type cacheableResponse struct { + Code int + Header cacheableHTTPHeader + Body string +} + +func newCacheableResponse(resp *http.Response) cacheableResponse { + defer resp.Body.Close() + b, _ := io.ReadAll(resp.Body) + resp.Body = io.NopCloser(bytes.NewReader(b)) + return cacheableResponse{ + Code: resp.StatusCode, + Header: newCacheableHTTPHeader(resp.Header), + Body: string(b), + } +} + +func parseCacheableResponse(b []byte) *cacheableResponse { + var c cacheableResponse + if err := json.Unmarshal(b, &c); err != nil { + return nil + } + return &c +} + +func (c cacheableResponse) cacheV() []byte { + b, _ := json.Marshal(c) + return b +} + +func (c cacheableResponse) response() *http.Response { + return &http.Response{ + StatusCode: c.Code, + Header: c.Header.header(), + Body: io.NopCloser(bytes.NewReader([]byte(c.Body))), + } +} + +type cacheableHTTPHeader [][]string + +func newCacheableHTTPHeader(m map[string][]string) cacheableHTTPHeader { + result := [][]string{} + for k, v := range m { + result = append(result, append([]string{k}, v...)) + } + return result +} + +func (c cacheableHTTPHeader) header() http.Header { + return http.Header(c.m()) +} + +func (c cacheableHTTPHeader) query() url.Values { + return url.Values(c.m()) +} + +func (c cacheableHTTPHeader) m() map[string][]string { + m := map[string][]string{} + for _, v := range c { + v := v + m[v[0]] = v[1:] + } + return m +} diff --git a/.vendor/with/http_test.go b/.vendor/with/http_test.go new file mode 100644 index 0000000..908dcef --- /dev/null +++ b/.vendor/with/http_test.go @@ -0,0 +1,119 @@ +package with_test + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "slices" + "strings" + "testing" + + "gitea.bel.blue/bel/with" +) + +func TestCachedHTTP(t *testing.T) { + called := false + code := http.StatusAccepted + headers := http.Header{ + "K": []string{"v"}, + "K2": []string{"v2a", "v2b"}, + } + body := "response body" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + for k, v := range headers { + for _, subv := range v { + w.Header().Add(k, subv) + } + } + w.WriteHeader(code) + w.Write([]byte(body)) + })) + defer server.Close() + + testResp := func(resp *http.Response) { + if resp.StatusCode != code { + t.Errorf("initial request wrong status code: %d", resp.StatusCode) + } else if !slices.Equal(resp.Header["K"], headers["K"]) { + t.Errorf("initial request wrong headers[k]: %+v in %+v", resp.Header["K"], resp.Header) + } else if !slices.Equal(resp.Header["K2"], headers["K2"]) { + t.Errorf("initial request wrong headers[k2]: %+v in %+v", resp.Header["K2"], resp.Header) + } else if b, _ := io.ReadAll(resp.Body); string(b) != body { + t.Errorf("initial request wrong resp body: %q", b) + } + } + + if err := with.CachedHTTP(context.Background(), func(c *http.Client) error { + req := func() *http.Request { + req, _ := http.NewRequest(http.MethodGet, server.URL+"/my/path", strings.NewReader("my body")) + return req + } + + if resp, err := c.Do(req()); err != nil { + t.Fatalf("failed initial request: %v", err) + } else if !called { + t.Errorf("initial request didnt hit server") + } else { + testResp(resp) + } + + called = false + if resp, err := c.Do(req()); err != nil { + t.Fatalf("failed second request: %v", err) + } else if called { + t.Errorf("second request didnt hit cache") + } else { + testResp(resp) + } + + called = false + reqDiffURLPath := req() + reqDiffURLPath.URL.Path += "/teehee" + if resp, err := c.Do(reqDiffURLPath); err != nil { + t.Fatalf("failed diff url path request: %v", err) + } else if !called { + t.Errorf("new initial hit cache") + } else { + testResp(resp) + } + + called = false + reqDiffURLQuery := req() + reqDiffURLQuery.URL.RawQuery += "hello=world" + if resp, err := c.Do(reqDiffURLQuery); err != nil { + t.Fatalf("failed diff url query request: %v", err) + } else if !called { + t.Errorf("new initial hit cache") + } else { + testResp(resp) + } + + called = false + reqDiffHeader := req() + reqDiffHeader.Header.Set("Misc", "Misc") + if resp, err := c.Do(reqDiffHeader); err != nil { + t.Fatalf("failed diff header request: %v", err) + } else if !called { + t.Errorf("new initial hit cache") + } else { + testResp(resp) + } + + called = false + reqDiffBody := req() + reqDiffBody.Body = io.NopCloser(strings.NewReader("diff")) + reqDiffBody.ContentLength = int64(len("diff")) + if resp, err := c.Do(reqDiffBody); err != nil { + t.Fatalf("failed diff body request: %v", err) + } else if !called { + t.Errorf("new initial hit cache") + } else { + testResp(resp) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} diff --git a/.vendor/with/kv.go b/.vendor/with/kv.go new file mode 100644 index 0000000..31f0c6c --- /dev/null +++ b/.vendor/with/kv.go @@ -0,0 +1,46 @@ +package with + +import ( + "context" + "database/sql" +) + +type SQLKV struct { + db *sql.DB +} + +func KV(ctx context.Context, db *sql.DB, foo func(SQLKV) error) error { + if _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS with_kv( + k TEXT PRIMARY KEY + , v TEXT + ) + `); err != nil { + return err + } + + return foo(SQLKV{db: db}) +} + +func (kv SQLKV) Get(ctx context.Context, k string) ([]byte, error) { + row := kv.db.QueryRowContext(ctx, ` + SELECT v FROM with_kv WHERE k=$1 + `, k) + + var v []byte + if err := row.Scan(&v); err != nil { + return nil, err + } + + return v, row.Err() +} + +func (kv SQLKV) Set(ctx context.Context, k string, v []byte) error { + _, err := kv.db.ExecContext(ctx, ` + INSERT INTO with_kv + (k, v) VALUES ($1, $2) + ON CONFLICT DO UPDATE + SET v=$2 WHERE k=$1 + `, k, v) + return err +} diff --git a/.vendor/with/kv_test.go b/.vendor/with/kv_test.go new file mode 100644 index 0000000..9eee6f2 --- /dev/null +++ b/.vendor/with/kv_test.go @@ -0,0 +1,44 @@ +package with_test + +import ( + "context" + "database/sql" + "testing" + + "gitea.bel.blue/bel/with" +) + +func TestKV(t *testing.T) { + ctx := context.Background() + if err := with.Sqlite(ctx, ":memory:", func(db *sql.DB) error { + return with.KV(ctx, db, func(kv with.SQLKV) error { + if _, err := kv.Get(ctx, "k"); err == nil { + t.Error("404 no err") + } + + if err := kv.Set(ctx, "k", []byte("v")); err != nil { + t.Error("err on insert", err) + } + + if v, err := kv.Get(ctx, "k"); err != nil { + t.Error("err on get", err) + } else if string(v) != "v" { + t.Errorf("expected 'v' but got %q", v) + } + + if err := kv.Set(ctx, "k", []byte("v2")); err != nil { + t.Error("err on update", err) + } + + if v, err := kv.Get(ctx, "k"); err != nil { + t.Error("err on get updated", err) + } else if string(v) != "v2" { + t.Errorf("expected 'v2' but got %q", v) + } + + return nil + }) + }); err != nil { + t.Fatal(err) + } +} diff --git a/.vendor/with/pg.go b/.vendor/with/pg.go new file mode 100644 index 0000000..4f2946c --- /dev/null +++ b/.vendor/with/pg.go @@ -0,0 +1,12 @@ +package with + +import ( + "context" + "database/sql" + + _ "github.com/lib/pq" +) + +func PSQL(ctx context.Context, conn string, foo func(*sql.DB) error) error { + return _sql(ctx, "postgres", conn, foo) +} diff --git a/.vendor/with/sql.go b/.vendor/with/sql.go new file mode 100644 index 0000000..a50d022 --- /dev/null +++ b/.vendor/with/sql.go @@ -0,0 +1,76 @@ +package with + +import ( + "context" + "database/sql" + "fmt" + "log" + "net/url" + "time" + + _ "modernc.org/sqlite" +) + +func SQL(ctx context.Context, conn string, foo func(*sql.DB) error) error { + u, err := url.Parse(conn) + if err != nil { + return err + } + switch u.Scheme { + case "sqlite": + return Sqlite(ctx, conn, foo) + case "postgres", "postgresql": + return PSQL(ctx, conn, foo) + } + return fmt.Errorf("unknown sql scheme %q", u.Scheme) +} + +func _sql(ctx context.Context, engine, conn string, foo func(db *sql.DB) error) error { + log.Printf("opening %s %s...", engine, conn) + db, err := sql.Open(engine, conn) + if err != nil { + return err + } + defer func() { + log.Println("closed:", db.Close()) + }() + + if err := Timeout(ctx, 10*time.Second, func(ctx context.Context) error { + pinged := make(chan bool) + defer close(pinged) + for ctx.Err() == nil { + log.Println("pinging...") + go func() { + defer func() { recover() }() + err := db.PingContext(ctx) + if err != nil { + log.Println("!", err) + } + ok := err == nil + select { + case pinged <- ok: + case <-ctx.Done(): + case <-time.After(time.Second * 5): + } + }() + select { + case <-ctx.Done(): + break + case ok := <-pinged: + if ok { + return nil + } + } + select { + case <-ctx.Done(): + case <-time.After(time.Second): + } + } + return ctx.Err() + }); err != nil { + return err + } + log.Println("connected") + + return foo(db) +} diff --git a/.vendor/with/sqlite.go b/.vendor/with/sqlite.go new file mode 100644 index 0000000..ef6797f --- /dev/null +++ b/.vendor/with/sqlite.go @@ -0,0 +1,23 @@ +package with + +import ( + "context" + "database/sql" + "strings" + "time" + + _ "modernc.org/sqlite" +) + +func Sqlite(ctx context.Context, conn string, foo func(db *sql.DB) error) error { + conn = strings.TrimPrefix(conn, "sqlite://") + return _sql(ctx, "sqlite", conn, func(db *sql.DB) error { + if err := Timeout(ctx, 2*time.Second, func(ctx context.Context) error { + _, err := db.ExecContext(ctx, `PRAGMA journal_mode=WAL`) + return err + }); err != nil { + return err + } + return foo(db) + }) +} diff --git a/.vendor/with/sqlite_test.go b/.vendor/with/sqlite_test.go new file mode 100644 index 0000000..3d41d4d --- /dev/null +++ b/.vendor/with/sqlite_test.go @@ -0,0 +1,21 @@ +package with_test + +import ( + "context" + "database/sql" + "fmt" + "path" + "testing" + + "gitea.bel.blue/bel/with" +) + +func TestSqlite(t *testing.T) { + d := t.TempDir() + myErr := fmt.Errorf("my err") + if err := with.Sqlite(context.Background(), "sqlite://"+path.Join(d, "db"), func(db *sql.DB) error { + return myErr + }); err != myErr { + t.Fatal(err) + } +} diff --git a/go.mod b/go.mod index 9ed718b..1879f7e 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,9 @@ module gitea/lobby-irl go 1.25.0 +require gitea.bel.blue/bel/with v0.0.0-20260527141310-eadcabb7fe54 + require ( - gitea.bel.blue/bel/with v0.0.0-20260527141310-eadcabb7fe54 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/lib/pq v1.11.2 // indirect @@ -17,3 +18,5 @@ require ( modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.46.1 // indirect ) + +replace gitea.bel.blue/bel/with => ./.vendor/with diff --git a/go.sum b/go.sum index 4443fb9..ddc0b7f 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,11 @@ -gitea.bel.blue/bel/with v0.0.0-20260527141310-eadcabb7fe54 h1:4a3/ryr2O4qlKebPJ+hrXQzmCXsQ3u2AE0FbCtkM8Wk= -gitea.bel.blue/bel/with v0.0.0-20260527141310-eadcabb7fe54/go.mod h1:9/uv9/wJdSRErentSOb2flc8rgnRXlOIdOUdaVPUl/c= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -14,14 +16,40 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=