Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make functions aware of their app #92

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
Next Next commit
Make functions aware of their app
  • Loading branch information
amh4r committed Feb 5, 2025
commit 3f9fd2d63c1f6b38f20121b1ae05211cbe76a854
57 changes: 31 additions & 26 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,47 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
)

var (
// DefaultClient represents the default, mutable, global client used
// within the `Send` function provided by this package.
//
// You should initialize this within an init() function using `NewClient`
// if you plan to use the `Send` function:
//
// func init() {
// inngestgo.DefaultClient = inngestgo.NewClient(
// "key",
// inngestgo.WithHTTPClient(&http.Client{Timeout: 10 * time.Second}),
// )
// }
//
// If this client is not set, Send will return an error.
DefaultClient Client
)

const (
defaultEndpoint = "https://inn.gs"
)

// Send uses the DefaultClient to send the given event.
func Send(ctx context.Context, e any) (string, error) {
if DefaultClient == nil {
return "", fmt.Errorf("no default client initialized for inngest")
client, ok := ctx.Value(clientCtxKey).(Client)
if !ok || client == nil {
return "", fmt.Errorf("client not found in context")
}
return DefaultClient.Send(ctx, e)
return client.Send(ctx, e)
}

// SendMany uses the DefaultClient to send the given event batch.
func SendMany(ctx context.Context, e []any) ([]string, error) {
if DefaultClient == nil {
return nil, fmt.Errorf("no default client initialized for inngest")
client, ok := ctx.Value(clientCtxKey).(Client)
if !ok || client == nil {
return nil, fmt.Errorf("client not found in context")
}
return DefaultClient.SendMany(ctx, e)
return client.SendMany(ctx, e)
}

// Client represents a client used to send events to Inngest.
type Client interface {
AppID() string

// Send sends the specific event to the ingest API.
Send(ctx context.Context, evt any) (string, error)
// Send sends a batch of events to the ingest API.
SendMany(ctx context.Context, evt []any) ([]string, error)
}

type ClientOpts struct {
AppID string

// HTTPClient is the HTTP client used to send events.
HTTPClient *http.Client
// EventKey is your Inngest event key for sending events. This defaults to the
Expand All @@ -70,9 +59,21 @@ type ClientOpts struct {
Env *string
}

func (c ClientOpts) validate() error {
if c.AppID == "" {
return errors.New("app id is required")
}
return nil
}

// NewClient returns a concrete client initialized with the given ingest key,
// which can immediately send events to the ingest API.
func NewClient(opts ClientOpts) Client {
func NewClient(opts ClientOpts) (Client, error) {
err := opts.validate()
if err != nil {
return nil, err
}

c := &apiClient{
ClientOpts: opts,
}
Expand All @@ -81,7 +82,7 @@ func NewClient(opts ClientOpts) Client {
c.ClientOpts.HTTPClient = http.DefaultClient
}

return c
return c, nil
}

// apiClient is a concrete implementation of Client that uses the given HTTP client
Expand All @@ -90,6 +91,10 @@ type apiClient struct {
ClientOpts
}

func (a apiClient) AppID() string {
return a.ClientOpts.AppID
}

func (a apiClient) GetEnv() string {
if a.Env == nil {
return os.Getenv("INNGEST_ENV")
Expand Down
5 changes: 3 additions & 2 deletions connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package inngestgo
import (
"context"
"fmt"
"net/url"

"github.com/inngest/inngest/pkg/execution/state"
"github.com/inngest/inngest/pkg/publicerr"
"github.com/inngest/inngestgo/connect"
"github.com/inngest/inngestgo/internal/sdkrequest"
"net/url"
)

func (h *handler) Connect(ctx context.Context) error {
Expand Down Expand Up @@ -70,7 +71,7 @@ func (h *handler) getServableFunctionBySlug(slug string) ServableFunction {
h.l.RLock()
var fn ServableFunction
for _, f := range h.funcs {
if f.Slug(h.appName) == slug {
if f.FullyQualifiedID() == slug {
fn = f
break
}
Expand Down
17 changes: 13 additions & 4 deletions examples/connect/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,41 @@ package main
import (
"context"
"fmt"
"os"

"github.com/inngest/inngest/pkg/logger"
"github.com/inngest/inngestgo"
"os"
)

func main() {
ctx := context.Background()

key := "signkey-test-12345678"
h := inngestgo.NewHandler("connect-test", inngestgo.HandlerOpts{
c, err := inngestgo.NewClient(inngestgo.ClientOpts{AppID: "connect-test"})
if err != nil {
panic(err)
}
h := inngestgo.NewHandler(c, inngestgo.HandlerOpts{
Logger: logger.StdlibLogger(ctx),
SigningKey: &key,
InstanceId: inngestgo.Ptr("example-worker"),
BuildId: nil,
Dev: inngestgo.BoolPtr(true),
})

f := inngestgo.CreateFunction(
f, err := inngestgo.CreateFunction(
c,
inngestgo.FunctionOpts{ID: "conntest", Name: "connect test"},
inngestgo.EventTrigger("test/connect.run", nil),
testRun,
)
if err != nil {
panic(err)
}

h.Register(f)

err := h.Connect(ctx)
err = h.Connect(ctx)
if err != nil {
fmt.Printf("ERROR: %#v\n", err)
os.Exit(1)
Expand Down
7 changes: 6 additions & 1 deletion examples/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
)

func main() {
c := inngestgo.NewClient(inngestgo.ClientOpts{

Check failure on line 17 in examples/http/main.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

assignment mismatch: 1 variable but inngestgo.NewClient returns 2 values
AppID: "billing",
})

h := inngestgo.NewHandler("billing", inngestgo.HandlerOpts{})

Check failure on line 21 in examples/http/main.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

cannot use "billing" (constant of type string) as inngestgo.Client value in argument to inngestgo.NewHandler: string does not implement inngestgo.Client (missing method AppID)

// CreateFunction is a factory method which creates new Inngest functions (step functions,
// or workflows) with a specific configuration.
f := inngestgo.CreateFunction(
f, err := inngestgo.CreateFunction(

Check failure on line 25 in examples/http/main.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

declared and not used: err (typecheck)
c,
inngestgo.FunctionOpts{
ID: "account-created",
Name: "Account creation flow",
Expand Down
51 changes: 33 additions & 18 deletions funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package inngestgo

import (
"context"
"errors"
"fmt"
"reflect"
"time"

"github.com/gosimple/slug"
"github.com/inngest/inngest/pkg/inngest"
)

Expand Down Expand Up @@ -47,6 +48,14 @@ type FunctionOpts struct {
BatchEvents *inngest.EventBatchConfig
}

func (f FunctionOpts) validate() error {
var err error
if f.ID == "" {
err = errors.Join(err, errors.New("id is required"))
}
return err
}

// GetRateLimit returns the inngest.RateLimit for function configuration. The
// SDK's RateLimit type is incompatible with the inngest.RateLimit type signature
// for ease of definition.
Expand Down Expand Up @@ -181,26 +190,34 @@ func (t Timeouts) Convert() *inngest.Timeouts {
// },
// )
func CreateFunction[T any](
c Client,
fc FunctionOpts,
trigger inngest.Trigger,
f SDKFunction[T],
) ServableFunction {
) (ServableFunction, error) {
// Validate that the input type is a concrete type, and not an interface.
//
// The only exception is `any`, when users don't care about the input event
// eg. for cron based functions.

err := fc.validate()
if err != nil {
return nil, err
}

sf := servableFunc{
appID: c.AppID(),
fc: fc,
trigger: trigger,
f: f,
}

zt := sf.ZeroType()
if zt.Interface() == nil && zt.NumMethod() > 0 {
panic("You cannot use an interface type as the input within an Inngest function.")
return nil, errors.New("You cannot use an interface type as the input within an Inngest function.")
}
return sf

return sf, nil
}

func EventTrigger(name string, expression *string) inngest.Trigger {
Expand Down Expand Up @@ -237,8 +254,9 @@ type SDKFunction[T any] func(ctx context.Context, input Input[T]) (any, error)
//
// This is created via CreateFunction in this package.
type ServableFunction interface {
// Slug returns the function's human-readable ID, such as "sign-up-flow".
Slug(appName string) string
FullyQualifiedID() string

ID() string

// Name returns the function name.
Name() string
Expand Down Expand Up @@ -275,6 +293,7 @@ type InputCtx struct {
}

type servableFunc struct {
appID string
fc FunctionOpts
trigger inngest.Trigger
f any
Expand All @@ -284,22 +303,18 @@ func (s servableFunc) Config() FunctionOpts {
return s.fc
}

func (s servableFunc) Slug(appName string) string {
fnSlug := s.fc.ID
if fnSlug == "" {
fnSlug = slug.Make(s.fc.Name)
}

// Old format which only includes the fn slug
// This should no longer be used.
if appName == "" {
return fnSlug
}
func (s servableFunc) ID() string {
return s.fc.ID
}

return appName + "-" + fnSlug
func (s servableFunc) FullyQualifiedID() string {
return fmt.Sprintf("%s-%s", s.appID, s.ID())
}

func (s servableFunc) Name() string {
if s.fc.Name == "" {
return s.ID()
}
return s.fc.Name
}

Expand Down
Loading
Loading