mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2026-02-18 06:09:24 -06:00
feat: bulk inserts of events (#887)
* progress commit of bulk inserts * in_flight: Add changes to metering finish the bulk insert * remove an attempt to overide enforce limits * merge in PR fixes * update docs to add in an additional section in the User guide to describe pushing single events and pushing multiple events * run lint fix --------- Co-authored-by: Sean Reilly <sean@hatchet.run>
This commit is contained in:
@@ -7,6 +7,8 @@ import "google/protobuf/timestamp.proto";
|
||||
service EventsService {
|
||||
rpc Push(PushEventRequest) returns (Event) {}
|
||||
|
||||
rpc BulkPush(BulkPushEventRequest) returns (Events) {}
|
||||
|
||||
rpc ReplaySingleEvent(ReplayEventRequest) returns (Event) {}
|
||||
|
||||
rpc PutLog(PutLogRequest) returns (PutLogResponse) {}
|
||||
@@ -35,6 +37,10 @@ message Event {
|
||||
|
||||
}
|
||||
|
||||
message Events {
|
||||
repeated Event events = 1;
|
||||
}
|
||||
|
||||
message PutLogRequest {
|
||||
// the step run id for the request
|
||||
string stepRunId = 1;
|
||||
@@ -70,6 +76,12 @@ message PutStreamEventRequest {
|
||||
|
||||
message PutStreamEventResponse {}
|
||||
|
||||
|
||||
message BulkPushEventRequest {
|
||||
|
||||
repeated PushEventRequest events = 1;
|
||||
}
|
||||
|
||||
message PushEventRequest {
|
||||
// the key for the event
|
||||
string key = 1;
|
||||
|
||||
@@ -80,6 +80,10 @@ EventData:
|
||||
$ref: "./event.yaml#/EventData"
|
||||
CreateEventRequest:
|
||||
$ref: "./event.yaml#/CreateEventRequest"
|
||||
BulkCreateEventRequest:
|
||||
$ref: "./event.yaml#/BulkCreateEventRequest"
|
||||
BulkCreateEventResponse:
|
||||
$ref: "./event.yaml#/Events"
|
||||
EventWorkflowRunSummary:
|
||||
$ref: "./event.yaml#/EventWorkflowRunSummary"
|
||||
EventOrderByField:
|
||||
|
||||
@@ -22,6 +22,19 @@ Event:
|
||||
- key
|
||||
- tenantId
|
||||
|
||||
Events:
|
||||
properties:
|
||||
metadata:
|
||||
$ref: "./metadata.yaml#/APIResourceMeta"
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/Event"
|
||||
description: The events.
|
||||
required:
|
||||
- metadata
|
||||
- events
|
||||
|
||||
EventData:
|
||||
properties:
|
||||
data:
|
||||
@@ -45,6 +58,15 @@ CreateEventRequest:
|
||||
- key
|
||||
- data
|
||||
|
||||
BulkCreateEventRequest:
|
||||
properties:
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/CreateEventRequest"
|
||||
required:
|
||||
- events
|
||||
|
||||
ReplayEventRequest:
|
||||
properties:
|
||||
eventIds:
|
||||
|
||||
@@ -94,6 +94,8 @@ paths:
|
||||
$ref: "./paths/tenant/tenant.yaml#/getQueueMetrics"
|
||||
/api/v1/tenants/{tenant}/events:
|
||||
$ref: "./paths/event/event.yaml#/withTenant"
|
||||
/api/v1/tenants/{tenant}/events/bulk:
|
||||
$ref: "./paths/event/event.yaml#/bulkCreateEvents"
|
||||
/api/v1/tenants/{tenant}/events/replay:
|
||||
$ref: "./paths/event/event.yaml#/replayEvents"
|
||||
/api/v1/tenants/{tenant}/events/cancel:
|
||||
|
||||
@@ -159,6 +159,59 @@ withTenant:
|
||||
tags:
|
||||
- Event
|
||||
|
||||
|
||||
bulkCreateEvents:
|
||||
post:
|
||||
x-resources: ["tenant"]
|
||||
description: Bulk creates new events.
|
||||
operationId: event:create:bulk
|
||||
parameters:
|
||||
- description: The tenant id
|
||||
in: path
|
||||
name: tenant
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
maxLength: 36
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../../components/schemas/_index.yaml#/BulkCreateEventRequest"
|
||||
description: The events to create
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../../components/schemas/_index.yaml#/BulkCreateEventResponse"
|
||||
description: Successfully created the events
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../../components/schemas/_index.yaml#/APIErrors"
|
||||
description: A malformed or bad request
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../../components/schemas/_index.yaml#/APIErrors"
|
||||
description: Forbidden
|
||||
"429":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../../components/schemas/_index.yaml#/APIErrors"
|
||||
description: Resource limit exceeded
|
||||
summary: Bulk Create events
|
||||
tags:
|
||||
- Event
|
||||
|
||||
|
||||
withEvent:
|
||||
get:
|
||||
x-resources: ["tenant", "event"]
|
||||
|
||||
62
api/v1/server/handlers/events/bulk_create.go
Normal file
62
api/v1/server/handlers/events/bulk_create.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/api/v1/server/oas/apierrors"
|
||||
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
|
||||
"github.com/hatchet-dev/hatchet/api/v1/server/oas/transformers"
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository"
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository/metered"
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/db"
|
||||
)
|
||||
|
||||
func (t *EventService) EventCreateBulk(ctx echo.Context, request gen.EventCreateBulkRequestObject) (gen.EventCreateBulkResponseObject, error) {
|
||||
tenant := ctx.Get("tenant").(*db.TenantModel)
|
||||
|
||||
eventOpts := make([]*repository.CreateEventOpts, len(request.Body.Events))
|
||||
|
||||
for i, event := range request.Body.Events {
|
||||
dataBytes, err := json.Marshal(event.Data)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var additionalMetadata []byte
|
||||
|
||||
if event.AdditionalMetadata != nil {
|
||||
additionalMetadata, err = json.Marshal(event.AdditionalMetadata)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
eventOpts[i] = &repository.CreateEventOpts{
|
||||
TenantId: tenant.ID,
|
||||
Key: event.Key,
|
||||
Data: dataBytes,
|
||||
AdditionalMetadata: additionalMetadata,
|
||||
}
|
||||
}
|
||||
events, err := t.config.Ingestor.BulkIngestEvent(ctx.Request().Context(), tenant.ID, eventOpts)
|
||||
|
||||
if err != nil {
|
||||
|
||||
if err == metered.ErrResourceExhausted {
|
||||
return gen.EventCreateBulk429JSONResponse(
|
||||
apierrors.NewAPIErrors("Event limit exceeded"),
|
||||
), nil
|
||||
}
|
||||
|
||||
return gen.EventCreateBulk400JSONResponse{}, err
|
||||
|
||||
}
|
||||
|
||||
return gen.EventCreateBulk200JSONResponse{
|
||||
Events: transformers.ToEventList(events)}, nil
|
||||
|
||||
}
|
||||
@@ -264,6 +264,18 @@ type AcceptInviteRequest struct {
|
||||
Invite string `json:"invite" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
// BulkCreateEventRequest defines model for BulkCreateEventRequest.
|
||||
type BulkCreateEventRequest struct {
|
||||
Events []CreateEventRequest `json:"events"`
|
||||
}
|
||||
|
||||
// BulkCreateEventResponse defines model for BulkCreateEventResponse.
|
||||
type BulkCreateEventResponse struct {
|
||||
// Events The events.
|
||||
Events []Event `json:"events"`
|
||||
Metadata APIResourceMeta `json:"metadata"`
|
||||
}
|
||||
|
||||
// CancelEventRequest defines model for CancelEventRequest.
|
||||
type CancelEventRequest struct {
|
||||
EventIds []openapi_types.UUID `json:"eventIds"`
|
||||
@@ -1462,6 +1474,9 @@ type ApiTokenCreateJSONRequestBody = CreateAPITokenRequest
|
||||
// EventCreateJSONRequestBody defines body for EventCreate for application/json ContentType.
|
||||
type EventCreateJSONRequestBody = CreateEventRequest
|
||||
|
||||
// EventCreateBulkJSONRequestBody defines body for EventCreateBulk for application/json ContentType.
|
||||
type EventCreateBulkJSONRequestBody = BulkCreateEventRequest
|
||||
|
||||
// EventUpdateCancelJSONRequestBody defines body for EventUpdateCancel for application/json ContentType.
|
||||
type EventUpdateCancelJSONRequestBody = CancelEventRequest
|
||||
|
||||
@@ -1587,6 +1602,9 @@ type ServerInterface interface {
|
||||
// Create event
|
||||
// (POST /api/v1/tenants/{tenant}/events)
|
||||
EventCreate(ctx echo.Context, tenant openapi_types.UUID) error
|
||||
// Bulk Create events
|
||||
// (POST /api/v1/tenants/{tenant}/events/bulk)
|
||||
EventCreateBulk(ctx echo.Context, tenant openapi_types.UUID) error
|
||||
// Replay events
|
||||
// (POST /api/v1/tenants/{tenant}/events/cancel)
|
||||
EventUpdateCancel(ctx echo.Context, tenant openapi_types.UUID) error
|
||||
@@ -2353,6 +2371,26 @@ func (w *ServerInterfaceWrapper) EventCreate(ctx echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// EventCreateBulk converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) EventCreateBulk(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "tenant" -------------
|
||||
var tenant openapi_types.UUID
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "tenant", runtime.ParamLocationPath, ctx.Param("tenant"), &tenant)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tenant: %s", err))
|
||||
}
|
||||
|
||||
ctx.Set(BearerAuthScopes, []string{})
|
||||
|
||||
ctx.Set(CookieAuthScopes, []string{})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.EventCreateBulk(ctx, tenant)
|
||||
return err
|
||||
}
|
||||
|
||||
// EventUpdateCancel converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) EventUpdateCancel(ctx echo.Context) error {
|
||||
var err error
|
||||
@@ -3669,6 +3707,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
router.POST(baseURL+"/api/v1/tenants/:tenant/api-tokens", wrapper.ApiTokenCreate)
|
||||
router.GET(baseURL+"/api/v1/tenants/:tenant/events", wrapper.EventList)
|
||||
router.POST(baseURL+"/api/v1/tenants/:tenant/events", wrapper.EventCreate)
|
||||
router.POST(baseURL+"/api/v1/tenants/:tenant/events/bulk", wrapper.EventCreateBulk)
|
||||
router.POST(baseURL+"/api/v1/tenants/:tenant/events/cancel", wrapper.EventUpdateCancel)
|
||||
router.GET(baseURL+"/api/v1/tenants/:tenant/events/keys", wrapper.EventKeyList)
|
||||
router.POST(baseURL+"/api/v1/tenants/:tenant/events/replay", wrapper.EventUpdateReplay)
|
||||
@@ -4607,6 +4646,51 @@ func (response EventCreate429JSONResponse) VisitEventCreateResponse(w http.Respo
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type EventCreateBulkRequestObject struct {
|
||||
Tenant openapi_types.UUID `json:"tenant"`
|
||||
Body *EventCreateBulkJSONRequestBody
|
||||
}
|
||||
|
||||
type EventCreateBulkResponseObject interface {
|
||||
VisitEventCreateBulkResponse(w http.ResponseWriter) error
|
||||
}
|
||||
|
||||
type EventCreateBulk200JSONResponse BulkCreateEventResponse
|
||||
|
||||
func (response EventCreateBulk200JSONResponse) VisitEventCreateBulkResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(200)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type EventCreateBulk400JSONResponse APIErrors
|
||||
|
||||
func (response EventCreateBulk400JSONResponse) VisitEventCreateBulkResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type EventCreateBulk403JSONResponse APIErrors
|
||||
|
||||
func (response EventCreateBulk403JSONResponse) VisitEventCreateBulkResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(403)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type EventCreateBulk429JSONResponse APIErrors
|
||||
|
||||
func (response EventCreateBulk429JSONResponse) VisitEventCreateBulkResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(429)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type EventUpdateCancelRequestObject struct {
|
||||
Tenant openapi_types.UUID `json:"tenant"`
|
||||
Body *EventUpdateCancelJSONRequestBody
|
||||
@@ -6751,6 +6835,8 @@ type StrictServerInterface interface {
|
||||
|
||||
EventCreate(ctx echo.Context, request EventCreateRequestObject) (EventCreateResponseObject, error)
|
||||
|
||||
EventCreateBulk(ctx echo.Context, request EventCreateBulkRequestObject) (EventCreateBulkResponseObject, error)
|
||||
|
||||
EventUpdateCancel(ctx echo.Context, request EventUpdateCancelRequestObject) (EventUpdateCancelResponseObject, error)
|
||||
|
||||
EventKeyList(ctx echo.Context, request EventKeyListRequestObject) (EventKeyListResponseObject, error)
|
||||
@@ -7527,6 +7613,37 @@ func (sh *strictHandler) EventCreate(ctx echo.Context, tenant openapi_types.UUID
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventCreateBulk operation middleware
|
||||
func (sh *strictHandler) EventCreateBulk(ctx echo.Context, tenant openapi_types.UUID) error {
|
||||
var request EventCreateBulkRequestObject
|
||||
|
||||
request.Tenant = tenant
|
||||
|
||||
var body EventCreateBulkJSONRequestBody
|
||||
if err := ctx.Bind(&body); err != nil {
|
||||
return err
|
||||
}
|
||||
request.Body = &body
|
||||
|
||||
handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
|
||||
return sh.ssi.EventCreateBulk(ctx, request.(EventCreateBulkRequestObject))
|
||||
}
|
||||
for _, middleware := range sh.middlewares {
|
||||
handler = middleware(handler, "EventCreateBulk")
|
||||
}
|
||||
|
||||
response, err := handler(ctx, request)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if validResponse, ok := response.(EventCreateBulkResponseObject); ok {
|
||||
return validResponse.VisitEventCreateBulkResponse(ctx.Response())
|
||||
} else if response != nil {
|
||||
return fmt.Errorf("Unexpected response type: %T", response)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventUpdateCancel operation middleware
|
||||
func (sh *strictHandler) EventUpdateCancel(ctx echo.Context, tenant openapi_types.UUID) error {
|
||||
var request EventUpdateCancelRequestObject
|
||||
@@ -8992,178 +9109,180 @@ func (sh *strictHandler) WorkflowVersionGet(ctx echo.Context, workflow openapi_t
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/db"
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/dbsqlc"
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/sqlchelpers"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
@@ -21,6 +22,24 @@ func ToEvent(event *db.EventModel) *gen.Event {
|
||||
return res
|
||||
}
|
||||
|
||||
func ToEventList(events []*dbsqlc.Event) []gen.Event {
|
||||
res := make([]gen.Event, len(events))
|
||||
|
||||
for i, event := range events {
|
||||
res[i] = dbslqEventToEvent(event)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func dbslqEventToEvent(event *dbsqlc.Event) gen.Event {
|
||||
return gen.Event{
|
||||
Metadata: *toAPIMetadata(sqlchelpers.UUIDToStr(event.ID), event.CreatedAt.Time, event.UpdatedAt.Time),
|
||||
Key: event.Key,
|
||||
TenantId: pgUUIDToStr(event.TenantId),
|
||||
}
|
||||
}
|
||||
|
||||
func ToEventFromSQLC(eventRow *dbsqlc.ListEventsRow) (*gen.Event, error) {
|
||||
event := eventRow.Event
|
||||
|
||||
|
||||
113
examples/bulk_imports/main.go
Normal file
113
examples/bulk_imports/main.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/hatchet-dev/hatchet/pkg/client"
|
||||
"github.com/hatchet-dev/hatchet/pkg/worker"
|
||||
)
|
||||
|
||||
type userCreateEvent struct {
|
||||
Username string `json:"username"`
|
||||
UserID string `json:"user_id"`
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
|
||||
type stepOneOutput struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func run() (func() error, error) {
|
||||
c, err := client.New()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating client: %w", err)
|
||||
}
|
||||
|
||||
w, err := worker.NewWorker(
|
||||
worker.WithClient(
|
||||
c,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating worker: %w", err)
|
||||
}
|
||||
|
||||
testSvc := w.NewService("test")
|
||||
|
||||
err = testSvc.RegisterWorkflow(
|
||||
&worker.WorkflowJob{
|
||||
On: worker.Events("user:create:bulk"),
|
||||
Name: "bulk",
|
||||
Description: "This runs after an update to the user model.",
|
||||
Steps: []*worker.WorkflowStep{
|
||||
worker.Fn(func(ctx worker.HatchetContext) (result *stepOneOutput, err error) {
|
||||
input := &userCreateEvent{}
|
||||
|
||||
err = ctx.WorkflowInput(input)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("step-one")
|
||||
|
||||
return &stepOneOutput{
|
||||
Message: "Username is: " + input.Username,
|
||||
}, nil
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error registering workflow: %w", err)
|
||||
}
|
||||
|
||||
var events []client.EventWithMetadata
|
||||
|
||||
for i := 0; i < 20000; i++ {
|
||||
testEvent := userCreateEvent{
|
||||
Username: "echo-test",
|
||||
UserID: "1234 " + fmt.Sprint(i),
|
||||
Data: map[string]string{
|
||||
"test": "test " + fmt.Sprint(i),
|
||||
},
|
||||
}
|
||||
events = append(events, client.EventWithMetadata{
|
||||
Event: testEvent,
|
||||
AdditionalMetadata: map[string]string{"hello": "world " + fmt.Sprint(i)},
|
||||
Key: "user:create:bulk",
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("pushing event user:create:bulk")
|
||||
|
||||
err = c.Event().BulkPush(
|
||||
context.Background(),
|
||||
events,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error pushing event: %w", err))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
APIError,
|
||||
APIErrors,
|
||||
APIMeta,
|
||||
BulkCreateEventRequest,
|
||||
BulkCreateEventResponse,
|
||||
CancelEventRequest,
|
||||
CreateAPITokenRequest,
|
||||
CreateAPITokenResponse,
|
||||
@@ -888,6 +890,25 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Bulk creates new events.
|
||||
*
|
||||
* @tags Event
|
||||
* @name EventCreateBulk
|
||||
* @summary Bulk Create events
|
||||
* @request POST:/api/v1/tenants/{tenant}/events/bulk
|
||||
* @secure
|
||||
*/
|
||||
eventCreateBulk = (tenant: string, data: BulkCreateEventRequest, params: RequestParams = {}) =>
|
||||
this.request<BulkCreateEventResponse, APIErrors>({
|
||||
path: `/api/v1/tenants/${tenant}/events/bulk`,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
secure: true,
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Replays a list of events.
|
||||
*
|
||||
|
||||
@@ -434,6 +434,16 @@ export interface CreateEventRequest {
|
||||
additionalMetadata?: object;
|
||||
}
|
||||
|
||||
export interface BulkCreateEventRequest {
|
||||
events: CreateEventRequest[];
|
||||
}
|
||||
|
||||
export interface BulkCreateEventResponse {
|
||||
metadata: APIResourceMeta;
|
||||
/** The events. */
|
||||
events: Event[];
|
||||
}
|
||||
|
||||
export interface EventWorkflowRunSummary {
|
||||
/**
|
||||
* The number of pending runs.
|
||||
|
||||
@@ -109,9 +109,161 @@ w.RegisterWorkflow(
|
||||
|
||||
Hatchet supports various event sources that can trigger workflows. Some common event sources include:
|
||||
|
||||
1. **Internal Events**: Hatchet allows you to generate internal events using our SDKs from within your existing APIs. For example, you can push an event when a specific user request is made.
|
||||
### Internal Events
|
||||
|
||||
2. **Webhooks**: Hatchet can expose webhook endpoints that listen for incoming HTTP requests. When a webhook is triggered, it generates an event that can be used to start a workflow.
|
||||
Hatchet allows you to generate internal events using our SDKs from within your existing APIs. For example, you can push an event when a specific user request is made.
|
||||
|
||||
Here is an example of how to push a single event:
|
||||
|
||||
<Tabs items={['Python', 'Typescript', 'Golang']}>
|
||||
<Tabs.Tab>
|
||||
```python
|
||||
from hatchet_sdk import Hatchet
|
||||
|
||||
hatchet = Hatchet()
|
||||
|
||||
hatchet.client.event.push(
|
||||
"user:create",
|
||||
{
|
||||
"test": "test"
|
||||
}
|
||||
)
|
||||
|
||||
````
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```typescript
|
||||
import Hatchet from "@hatchet-dev/typescript-sdk";
|
||||
|
||||
const hatchet = Hatchet.init();
|
||||
|
||||
hatchet.event.push("user:create", {
|
||||
test: "test",
|
||||
});
|
||||
````
|
||||
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```go
|
||||
c, err := client.New(
|
||||
client.WithHostPort("127.0.0.1", 7077),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Event().Push(
|
||||
context.Background(),
|
||||
"test-called",
|
||||
&events.TestEvent{
|
||||
Name: "testing",
|
||||
},
|
||||
)
|
||||
|
||||
````
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
Here is an example of how to push multiple events at once:
|
||||
|
||||
<Tabs items={['Python', 'Typescript', 'Golang']}>
|
||||
<Tabs.Tab>
|
||||
```python
|
||||
from hatchet_sdk import Hatchet
|
||||
|
||||
hatchet = Hatchet()
|
||||
|
||||
events: List[BulkPushEventWithMetadata] = [
|
||||
{
|
||||
"key": "event1",
|
||||
"payload": {"message": "This is event 1"},
|
||||
"additional_metadata": {"source": "test", "user_id": "user123"},
|
||||
},
|
||||
{
|
||||
"key": "event2",
|
||||
"payload": {"message": "This is event 2"},
|
||||
"additional_metadata": {"source": "test", "user_id": "user456"},
|
||||
},
|
||||
{
|
||||
"key": "event3",
|
||||
"payload": {"message": "This is event 3"},
|
||||
"additional_metadata": {"source": "test", "user_id": "user789"},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
result =
|
||||
hatchet.client.event.bulk_push(
|
||||
events
|
||||
)
|
||||
````
|
||||
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```typescript
|
||||
import Hatchet from "@hatchet-dev/typescript-sdk";
|
||||
|
||||
const hatchet = Hatchet.init();
|
||||
|
||||
const events = [
|
||||
{
|
||||
payload: { test: 'test1' },
|
||||
additionalMetadata: { user_id: 'user1', source: 'test' },
|
||||
},
|
||||
{
|
||||
payload: { test: 'test2' },
|
||||
additionalMetadata: { user_id: 'user2', source: 'test' },
|
||||
},
|
||||
{
|
||||
payload: { test: 'test3' },
|
||||
additionalMetadata: { user_id: 'user3', source: 'test' },
|
||||
},
|
||||
];
|
||||
|
||||
hatchet.event.bulkPush('user:create:bulk', events);
|
||||
|
||||
````
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```go
|
||||
c, err := client.New(
|
||||
client.WithHostPort("127.0.0.1", 7077),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
events := []client.EventWithMetadata{
|
||||
{
|
||||
Event: &events.TestEvent{
|
||||
Name: "testing",
|
||||
},
|
||||
AdditionalMetadata: map[string]string{"hello": "world1"},
|
||||
Key: "event1",
|
||||
},
|
||||
{
|
||||
Event: &events.TestEvent{
|
||||
Name: "testing2",
|
||||
},
|
||||
AdditionalMetadata: map[string]string{"hello": "world2"},
|
||||
Key: "event2",
|
||||
},
|
||||
}
|
||||
|
||||
c.Event().BulkPush(
|
||||
context.Background(),
|
||||
events,
|
||||
)
|
||||
````
|
||||
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
### Webhooks
|
||||
|
||||
Hatchet can expose webhook endpoints that listen for incoming HTTP requests. When a webhook is triggered, it generates an event that can be used to start a workflow.
|
||||
|
||||
## Event-Driven Best Practices
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Pushing Events
|
||||
|
||||
## Pushing Single Events
|
||||
|
||||
Events can be pushed via the client's `Event().Push` method:
|
||||
|
||||
```go
|
||||
@@ -21,3 +23,39 @@ c.Event().Push(
|
||||
```
|
||||
|
||||
Events are marshalled/unmarshalled using the `encoding/json` package, so any event type must be JSON serializable.
|
||||
|
||||
## Pushing Multiple Events
|
||||
|
||||
Multiple events can be pushed at once using the client's `Event().BulkPush` method:
|
||||
|
||||
```go
|
||||
c, err := client.New(
|
||||
client.WithHostPort("127.0.0.1", 7077),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
events := []client.EventWithMetadata{
|
||||
{
|
||||
Event: &events.TestEvent{
|
||||
Name: "testing",
|
||||
},
|
||||
AdditionalMetadata: map[string]string{"hello": "world1"},
|
||||
Key: "event1",
|
||||
},
|
||||
{
|
||||
Event: &events.TestEvent{
|
||||
Name: "testing2",
|
||||
},
|
||||
AdditionalMetadata: map[string]string{"hello": "world2"},
|
||||
Key: "event2",
|
||||
},
|
||||
}
|
||||
|
||||
c.Event().BulkPush(
|
||||
context.Background(),
|
||||
events,
|
||||
)
|
||||
```
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Running Workflows via Events
|
||||
|
||||
## Pushing Single Events
|
||||
|
||||
For workflows with event triggers, you can push events to the Hatchet API with the `client.event.push` method:
|
||||
|
||||
```py
|
||||
@@ -16,3 +18,37 @@ hatchet.client.event.push(
|
||||
```
|
||||
|
||||
The event's input data will be passed to the workflow run as the input, and is retrievable via the `context.workflow_input()` method.
|
||||
|
||||
## Pushing Multiple Events
|
||||
|
||||
Sometimes we would like to push many events at the same time. You can use the bulk_push `client.event.bulk_push` method:
|
||||
|
||||
```py
|
||||
from hatchet_sdk import Hatchet
|
||||
|
||||
hatchet = Hatchet()
|
||||
|
||||
events: List[BulkPushEventWithMetadata] = [
|
||||
{
|
||||
"key": "event1",
|
||||
"payload": {"message": "This is event 1"},
|
||||
"additional_metadata": {"source": "test", "user_id": "user123"},
|
||||
},
|
||||
{
|
||||
"key": "event2",
|
||||
"payload": {"message": "This is event 2"},
|
||||
"additional_metadata": {"source": "test", "user_id": "user456"},
|
||||
},
|
||||
{
|
||||
"key": "event3",
|
||||
"payload": {"message": "This is event 3"},
|
||||
"additional_metadata": {"source": "test", "user_id": "user789"},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
result =
|
||||
hatchet.client.event.bulk_push(
|
||||
events
|
||||
)
|
||||
```
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Running Workflows via Events
|
||||
|
||||
## Pushing Single Events
|
||||
|
||||
For workflows with event triggers, you can push events to the Hatchet API with the `hatchet.event.push` method:
|
||||
|
||||
```ts
|
||||
@@ -13,3 +15,30 @@ hatchet.event.push("user:create", {
|
||||
```
|
||||
|
||||
The event's input data will be passed to the workflow run as the input, and is retrievable via the `context.workflow_input()` method.
|
||||
|
||||
## Pushing Multiple Events
|
||||
|
||||
Sometimes we would like to push many events at the same time. You can use the bulk_push `hatchet.event.bulk_push` method:
|
||||
|
||||
```ts
|
||||
import Hatchet from "@hatchet-dev/typescript-sdk";
|
||||
|
||||
const hatchet = Hatchet.init();
|
||||
|
||||
const events = [
|
||||
{
|
||||
payload: { test: "test1" },
|
||||
additionalMetadata: { user_id: "user1", source: "test" },
|
||||
},
|
||||
{
|
||||
payload: { test: "test2" },
|
||||
additionalMetadata: { user_id: "user2", source: "test" },
|
||||
},
|
||||
{
|
||||
payload: { test: "test3" },
|
||||
additionalMetadata: { user_id: "user3", source: "test" },
|
||||
},
|
||||
];
|
||||
|
||||
hatchet.event.bulkPush("user:create:bulk", events);
|
||||
```
|
||||
|
||||
@@ -114,6 +114,53 @@ func (x *Event) GetAdditionalMetadata() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type Events struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Events []*Event `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Events) Reset() {
|
||||
*x = Events{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Events) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Events) ProtoMessage() {}
|
||||
|
||||
func (x *Events) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Events.ProtoReflect.Descriptor instead.
|
||||
func (*Events) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Events) GetEvents() []*Event {
|
||||
if x != nil {
|
||||
return x.Events
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PutLogRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -134,7 +181,7 @@ type PutLogRequest struct {
|
||||
func (x *PutLogRequest) Reset() {
|
||||
*x = PutLogRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[1]
|
||||
mi := &file_events_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -147,7 +194,7 @@ func (x *PutLogRequest) String() string {
|
||||
func (*PutLogRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PutLogRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[1]
|
||||
mi := &file_events_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -160,7 +207,7 @@ func (x *PutLogRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PutLogRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PutLogRequest) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{1}
|
||||
return file_events_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *PutLogRequest) GetStepRunId() string {
|
||||
@@ -207,7 +254,7 @@ type PutLogResponse struct {
|
||||
func (x *PutLogResponse) Reset() {
|
||||
*x = PutLogResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[2]
|
||||
mi := &file_events_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -220,7 +267,7 @@ func (x *PutLogResponse) String() string {
|
||||
func (*PutLogResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PutLogResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[2]
|
||||
mi := &file_events_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -233,7 +280,7 @@ func (x *PutLogResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PutLogResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PutLogResponse) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{2}
|
||||
return file_events_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
type PutStreamEventRequest struct {
|
||||
@@ -254,7 +301,7 @@ type PutStreamEventRequest struct {
|
||||
func (x *PutStreamEventRequest) Reset() {
|
||||
*x = PutStreamEventRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[3]
|
||||
mi := &file_events_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -267,7 +314,7 @@ func (x *PutStreamEventRequest) String() string {
|
||||
func (*PutStreamEventRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PutStreamEventRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[3]
|
||||
mi := &file_events_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -280,7 +327,7 @@ func (x *PutStreamEventRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PutStreamEventRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PutStreamEventRequest) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{3}
|
||||
return file_events_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *PutStreamEventRequest) GetStepRunId() string {
|
||||
@@ -320,7 +367,7 @@ type PutStreamEventResponse struct {
|
||||
func (x *PutStreamEventResponse) Reset() {
|
||||
*x = PutStreamEventResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[4]
|
||||
mi := &file_events_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -333,7 +380,7 @@ func (x *PutStreamEventResponse) String() string {
|
||||
func (*PutStreamEventResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PutStreamEventResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[4]
|
||||
mi := &file_events_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -346,7 +393,54 @@ func (x *PutStreamEventResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PutStreamEventResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PutStreamEventResponse) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{4}
|
||||
return file_events_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
type BulkPushEventRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Events []*PushEventRequest `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
|
||||
}
|
||||
|
||||
func (x *BulkPushEventRequest) Reset() {
|
||||
*x = BulkPushEventRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *BulkPushEventRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BulkPushEventRequest) ProtoMessage() {}
|
||||
|
||||
func (x *BulkPushEventRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BulkPushEventRequest.ProtoReflect.Descriptor instead.
|
||||
func (*BulkPushEventRequest) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *BulkPushEventRequest) GetEvents() []*PushEventRequest {
|
||||
if x != nil {
|
||||
return x.Events
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PushEventRequest struct {
|
||||
@@ -367,7 +461,7 @@ type PushEventRequest struct {
|
||||
func (x *PushEventRequest) Reset() {
|
||||
*x = PushEventRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[5]
|
||||
mi := &file_events_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -380,7 +474,7 @@ func (x *PushEventRequest) String() string {
|
||||
func (*PushEventRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PushEventRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[5]
|
||||
mi := &file_events_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -393,7 +487,7 @@ func (x *PushEventRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PushEventRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PushEventRequest) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{5}
|
||||
return file_events_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *PushEventRequest) GetKey() string {
|
||||
@@ -436,7 +530,7 @@ type ReplayEventRequest struct {
|
||||
func (x *ReplayEventRequest) Reset() {
|
||||
*x = ReplayEventRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_events_proto_msgTypes[6]
|
||||
mi := &file_events_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -449,7 +543,7 @@ func (x *ReplayEventRequest) String() string {
|
||||
func (*ReplayEventRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ReplayEventRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_events_proto_msgTypes[6]
|
||||
mi := &file_events_proto_msgTypes[8]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -462,7 +556,7 @@ func (x *ReplayEventRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ReplayEventRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ReplayEventRequest) Descriptor() ([]byte, []int) {
|
||||
return file_events_proto_rawDescGZIP(), []int{6}
|
||||
return file_events_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ReplayEventRequest) GetEventId() string {
|
||||
@@ -493,68 +587,77 @@ var file_events_proto_rawDesc = []byte{
|
||||
0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x61,
|
||||
0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||
0x61, 0x88, 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xc2, 0x01, 0x0a, 0x0d,
|
||||
0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a,
|
||||
0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x63,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61,
|
||||
0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
|
||||
0x19, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
|
||||
0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65,
|
||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65,
|
||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c,
|
||||
0x22, 0x10, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0xa5, 0x01, 0x0a, 0x15, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x18, 0x0a, 0x16, 0x50, 0x75,
|
||||
0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0xce, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70,
|
||||
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61,
|
||||
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x33, 0x0a, 0x12, 0x61, 0x64, 0x64,
|
||||
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x15,
|
||||
0x0a, 0x13, 0x5f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74,
|
||||
0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2e, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x49, 0x64, 0x32, 0xda, 0x01, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x12,
|
||||
0x11, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x11,
|
||||
0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x12, 0x13, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00,
|
||||
0x12, 0x2b, 0x0a, 0x06, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x0e, 0x2e, 0x50, 0x75, 0x74,
|
||||
0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x50, 0x75, 0x74,
|
||||
0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a,
|
||||
0x0e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12,
|
||||
0x16, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72,
|
||||
0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74,
|
||||
0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65,
|
||||
0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x28, 0x0a, 0x06, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xc2, 0x01, 0x0a, 0x0d, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52,
|
||||
0x75, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70,
|
||||
0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
|
||||
0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x65, 0x76,
|
||||
0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65,
|
||||
0x6c, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||
0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x10, 0x0a, 0x0e, 0x50, 0x75,
|
||||
0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa5, 0x01, 0x0a,
|
||||
0x15, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75,
|
||||
0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52,
|
||||
0x75, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
|
||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
|
||||
0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61,
|
||||
0x64, 0x61, 0x74, 0x61, 0x22, 0x18, 0x0a, 0x16, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41,
|
||||
0x0a, 0x14, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x22, 0xce, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c,
|
||||
0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f,
|
||||
0x61, 0x64, 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x33, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c,
|
||||
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||
0x74, 0x61, 0x22, 0x2e, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x49, 0x64, 0x32, 0x88, 0x02, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72,
|
||||
0x76, 0x69, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x12, 0x11, 0x2e, 0x50,
|
||||
0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x08, 0x42, 0x75, 0x6c,
|
||||
0x6b, 0x50, 0x75, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x75, 0x73, 0x68,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x07, 0x2e, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6c, 0x61,
|
||||
0x79, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x52,
|
||||
0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x50,
|
||||
0x75, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x0e, 0x2e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x53,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x50, 0x75, 0x74,
|
||||
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x17, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x47, 0x5a,
|
||||
0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63,
|
||||
0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -569,35 +672,41 @@ func file_events_proto_rawDescGZIP() []byte {
|
||||
return file_events_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_events_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_events_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_events_proto_goTypes = []interface{}{
|
||||
(*Event)(nil), // 0: Event
|
||||
(*PutLogRequest)(nil), // 1: PutLogRequest
|
||||
(*PutLogResponse)(nil), // 2: PutLogResponse
|
||||
(*PutStreamEventRequest)(nil), // 3: PutStreamEventRequest
|
||||
(*PutStreamEventResponse)(nil), // 4: PutStreamEventResponse
|
||||
(*PushEventRequest)(nil), // 5: PushEventRequest
|
||||
(*ReplayEventRequest)(nil), // 6: ReplayEventRequest
|
||||
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
|
||||
(*Events)(nil), // 1: Events
|
||||
(*PutLogRequest)(nil), // 2: PutLogRequest
|
||||
(*PutLogResponse)(nil), // 3: PutLogResponse
|
||||
(*PutStreamEventRequest)(nil), // 4: PutStreamEventRequest
|
||||
(*PutStreamEventResponse)(nil), // 5: PutStreamEventResponse
|
||||
(*BulkPushEventRequest)(nil), // 6: BulkPushEventRequest
|
||||
(*PushEventRequest)(nil), // 7: PushEventRequest
|
||||
(*ReplayEventRequest)(nil), // 8: ReplayEventRequest
|
||||
(*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
|
||||
}
|
||||
var file_events_proto_depIdxs = []int32{
|
||||
7, // 0: Event.eventTimestamp:type_name -> google.protobuf.Timestamp
|
||||
7, // 1: PutLogRequest.createdAt:type_name -> google.protobuf.Timestamp
|
||||
7, // 2: PutStreamEventRequest.createdAt:type_name -> google.protobuf.Timestamp
|
||||
7, // 3: PushEventRequest.eventTimestamp:type_name -> google.protobuf.Timestamp
|
||||
5, // 4: EventsService.Push:input_type -> PushEventRequest
|
||||
6, // 5: EventsService.ReplaySingleEvent:input_type -> ReplayEventRequest
|
||||
1, // 6: EventsService.PutLog:input_type -> PutLogRequest
|
||||
3, // 7: EventsService.PutStreamEvent:input_type -> PutStreamEventRequest
|
||||
0, // 8: EventsService.Push:output_type -> Event
|
||||
0, // 9: EventsService.ReplaySingleEvent:output_type -> Event
|
||||
2, // 10: EventsService.PutLog:output_type -> PutLogResponse
|
||||
4, // 11: EventsService.PutStreamEvent:output_type -> PutStreamEventResponse
|
||||
8, // [8:12] is the sub-list for method output_type
|
||||
4, // [4:8] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
9, // 0: Event.eventTimestamp:type_name -> google.protobuf.Timestamp
|
||||
0, // 1: Events.events:type_name -> Event
|
||||
9, // 2: PutLogRequest.createdAt:type_name -> google.protobuf.Timestamp
|
||||
9, // 3: PutStreamEventRequest.createdAt:type_name -> google.protobuf.Timestamp
|
||||
7, // 4: BulkPushEventRequest.events:type_name -> PushEventRequest
|
||||
9, // 5: PushEventRequest.eventTimestamp:type_name -> google.protobuf.Timestamp
|
||||
7, // 6: EventsService.Push:input_type -> PushEventRequest
|
||||
6, // 7: EventsService.BulkPush:input_type -> BulkPushEventRequest
|
||||
8, // 8: EventsService.ReplaySingleEvent:input_type -> ReplayEventRequest
|
||||
2, // 9: EventsService.PutLog:input_type -> PutLogRequest
|
||||
4, // 10: EventsService.PutStreamEvent:input_type -> PutStreamEventRequest
|
||||
0, // 11: EventsService.Push:output_type -> Event
|
||||
1, // 12: EventsService.BulkPush:output_type -> Events
|
||||
0, // 13: EventsService.ReplaySingleEvent:output_type -> Event
|
||||
3, // 14: EventsService.PutLog:output_type -> PutLogResponse
|
||||
5, // 15: EventsService.PutStreamEvent:output_type -> PutStreamEventResponse
|
||||
11, // [11:16] is the sub-list for method output_type
|
||||
6, // [6:11] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_events_proto_init() }
|
||||
@@ -619,7 +728,7 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PutLogRequest); i {
|
||||
switch v := v.(*Events); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -631,7 +740,7 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PutLogResponse); i {
|
||||
switch v := v.(*PutLogRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -643,7 +752,7 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PutStreamEventRequest); i {
|
||||
switch v := v.(*PutLogResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -655,7 +764,7 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PutStreamEventResponse); i {
|
||||
switch v := v.(*PutStreamEventRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -667,7 +776,7 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PushEventRequest); i {
|
||||
switch v := v.(*PutStreamEventResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -679,6 +788,30 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*BulkPushEventRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PushEventRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReplayEventRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -692,15 +825,15 @@ func file_events_proto_init() {
|
||||
}
|
||||
}
|
||||
file_events_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
||||
file_events_proto_msgTypes[1].OneofWrappers = []interface{}{}
|
||||
file_events_proto_msgTypes[5].OneofWrappers = []interface{}{}
|
||||
file_events_proto_msgTypes[2].OneofWrappers = []interface{}{}
|
||||
file_events_proto_msgTypes[7].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_events_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumMessages: 9,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ const _ = grpc.SupportPackageIsVersion7
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type EventsServiceClient interface {
|
||||
Push(ctx context.Context, in *PushEventRequest, opts ...grpc.CallOption) (*Event, error)
|
||||
BulkPush(ctx context.Context, in *BulkPushEventRequest, opts ...grpc.CallOption) (*Events, error)
|
||||
ReplaySingleEvent(ctx context.Context, in *ReplayEventRequest, opts ...grpc.CallOption) (*Event, error)
|
||||
PutLog(ctx context.Context, in *PutLogRequest, opts ...grpc.CallOption) (*PutLogResponse, error)
|
||||
PutStreamEvent(ctx context.Context, in *PutStreamEventRequest, opts ...grpc.CallOption) (*PutStreamEventResponse, error)
|
||||
@@ -45,6 +46,15 @@ func (c *eventsServiceClient) Push(ctx context.Context, in *PushEventRequest, op
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eventsServiceClient) BulkPush(ctx context.Context, in *BulkPushEventRequest, opts ...grpc.CallOption) (*Events, error) {
|
||||
out := new(Events)
|
||||
err := c.cc.Invoke(ctx, "/EventsService/BulkPush", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eventsServiceClient) ReplaySingleEvent(ctx context.Context, in *ReplayEventRequest, opts ...grpc.CallOption) (*Event, error) {
|
||||
out := new(Event)
|
||||
err := c.cc.Invoke(ctx, "/EventsService/ReplaySingleEvent", in, out, opts...)
|
||||
@@ -77,6 +87,7 @@ func (c *eventsServiceClient) PutStreamEvent(ctx context.Context, in *PutStreamE
|
||||
// for forward compatibility
|
||||
type EventsServiceServer interface {
|
||||
Push(context.Context, *PushEventRequest) (*Event, error)
|
||||
BulkPush(context.Context, *BulkPushEventRequest) (*Events, error)
|
||||
ReplaySingleEvent(context.Context, *ReplayEventRequest) (*Event, error)
|
||||
PutLog(context.Context, *PutLogRequest) (*PutLogResponse, error)
|
||||
PutStreamEvent(context.Context, *PutStreamEventRequest) (*PutStreamEventResponse, error)
|
||||
@@ -90,6 +101,9 @@ type UnimplementedEventsServiceServer struct {
|
||||
func (UnimplementedEventsServiceServer) Push(context.Context, *PushEventRequest) (*Event, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Push not implemented")
|
||||
}
|
||||
func (UnimplementedEventsServiceServer) BulkPush(context.Context, *BulkPushEventRequest) (*Events, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method BulkPush not implemented")
|
||||
}
|
||||
func (UnimplementedEventsServiceServer) ReplaySingleEvent(context.Context, *ReplayEventRequest) (*Event, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReplaySingleEvent not implemented")
|
||||
}
|
||||
@@ -130,6 +144,24 @@ func _EventsService_Push_Handler(srv interface{}, ctx context.Context, dec func(
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EventsService_BulkPush_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(BulkPushEventRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EventsServiceServer).BulkPush(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/EventsService/BulkPush",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EventsServiceServer).BulkPush(ctx, req.(*BulkPushEventRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EventsService_ReplaySingleEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ReplayEventRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -195,6 +227,10 @@ var EventsService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Push",
|
||||
Handler: _EventsService_Push_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "BulkPush",
|
||||
Handler: _EventsService_BulkPush_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ReplaySingleEvent",
|
||||
Handler: _EventsService_ReplaySingleEvent_Handler,
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
type Ingestor interface {
|
||||
contracts.EventsServiceServer
|
||||
IngestEvent(ctx context.Context, tenantId, eventName string, data []byte, metadata []byte) (*dbsqlc.Event, error)
|
||||
BulkIngestEvent(ctx context.Context, tenantID string, eventOpts []*repository.CreateEventOpts) ([]*dbsqlc.Event, error)
|
||||
IngestReplayedEvent(ctx context.Context, tenantId string, replayedEvent *dbsqlc.Event) (*dbsqlc.Event, error)
|
||||
}
|
||||
|
||||
@@ -145,6 +146,41 @@ func (i *IngestorImpl) IngestEvent(ctx context.Context, tenantId, key string, da
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func (i *IngestorImpl) BulkIngestEvent(ctx context.Context, tenantId string, eventOpts []*repository.CreateEventOpts) ([]*dbsqlc.Event, error) {
|
||||
ctx, span := telemetry.NewSpan(ctx, "bulk-ingest-event")
|
||||
defer span.End()
|
||||
|
||||
events, err := i.eventRepository.BulkCreateEvent(ctx, &repository.BulkCreateEventOpts{
|
||||
Events: eventOpts,
|
||||
TenantId: tenantId,
|
||||
})
|
||||
|
||||
if err == metered.ErrResourceExhausted {
|
||||
return nil, metered.ErrResourceExhausted
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create events: %w", err)
|
||||
}
|
||||
|
||||
// TODO any attributes we want to add here? could jam in all the event ids? but could be a lot
|
||||
|
||||
// telemetry.WithAttributes(span, telemetry.AttributeKV{
|
||||
// Key: "event_id",
|
||||
// Value: event.ID,
|
||||
// })
|
||||
|
||||
for _, event := range events.Events {
|
||||
err = i.mq.AddMessage(context.Background(), msgqueue.EVENT_PROCESSING_QUEUE, eventToTask(event))
|
||||
fmt.Printf("event: %+v\n", event)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not add event to task queue: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return events.Events, nil
|
||||
}
|
||||
|
||||
func (i *IngestorImpl) IngestReplayedEvent(ctx context.Context, tenantId string, replayedEvent *dbsqlc.Event) (*dbsqlc.Event, error) {
|
||||
ctx, span := telemetry.NewSpan(ctx, "ingest-replayed-event")
|
||||
defer span.End()
|
||||
|
||||
@@ -48,6 +48,67 @@ func (i *IngestorImpl) Push(ctx context.Context, req *contracts.PushEventRequest
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (i *IngestorImpl) BulkPush(ctx context.Context, req *contracts.BulkPushEventRequest) (*contracts.Events, error) {
|
||||
tenant := ctx.Value("tenant").(*dbsqlc.Tenant)
|
||||
|
||||
tenantId := sqlchelpers.UUIDToStr(tenant.ID)
|
||||
|
||||
events := make([]*repository.CreateEventOpts, 0)
|
||||
|
||||
for _, e := range req.Events {
|
||||
var additionalMeta []byte
|
||||
if e.AdditionalMetadata != nil {
|
||||
additionalMeta = []byte(*e.AdditionalMetadata)
|
||||
}
|
||||
events = append(events, &repository.CreateEventOpts{
|
||||
TenantId: tenantId,
|
||||
Key: e.Key,
|
||||
Data: []byte(e.Payload),
|
||||
AdditionalMetadata: additionalMeta,
|
||||
})
|
||||
}
|
||||
|
||||
opts := &repository.BulkCreateEventOpts{
|
||||
TenantId: tenantId,
|
||||
Events: events,
|
||||
}
|
||||
|
||||
if err := i.v.Validate(opts); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid request: %s", err)
|
||||
}
|
||||
|
||||
for _, e := range opts.Events {
|
||||
|
||||
if err := i.v.Validate(e); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid request: events failing validation %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
createdEvents, err := i.BulkIngestEvent(ctx, tenantId, events)
|
||||
|
||||
if err == metered.ErrResourceExhausted {
|
||||
return nil, status.Errorf(codes.ResourceExhausted, "resource exhausted: event limit exceeded for tenant")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var contractEvents []*contracts.Event
|
||||
for _, e := range createdEvents {
|
||||
|
||||
contractEvent, err := toEvent(e)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contractEvents = append(contractEvents, contractEvent)
|
||||
|
||||
}
|
||||
|
||||
return &contracts.Events{Events: contractEvents}, nil
|
||||
}
|
||||
|
||||
func (i *IngestorImpl) ReplaySingleEvent(ctx context.Context, req *contracts.ReplayEventRequest) (*contracts.Event, error) {
|
||||
tenant := ctx.Value("tenant").(*dbsqlc.Tenant)
|
||||
|
||||
|
||||
@@ -14,14 +14,24 @@ import (
|
||||
|
||||
type PushOpFunc func(*eventcontracts.PushEventRequest) error
|
||||
|
||||
type BulkPushOpFunc func(*eventcontracts.BulkPushEventRequest) error
|
||||
|
||||
type EventClient interface {
|
||||
Push(ctx context.Context, eventKey string, payload interface{}, options ...PushOpFunc) error
|
||||
|
||||
BulkPush(ctx context.Context, payloads []EventWithMetadata, options ...BulkPushOpFunc) error
|
||||
|
||||
PutLog(ctx context.Context, stepRunId, msg string) error
|
||||
|
||||
PutStreamEvent(ctx context.Context, stepRunId string, message []byte) error
|
||||
}
|
||||
|
||||
type EventWithMetadata struct {
|
||||
Event interface{} `json:"event"`
|
||||
AdditionalMetadata map[string]string `json:"metadata"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type eventClientImpl struct {
|
||||
client eventcontracts.EventsServiceClient
|
||||
|
||||
@@ -93,6 +103,43 @@ func (a *eventClientImpl) Push(ctx context.Context, eventKey string, payload int
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *eventClientImpl) BulkPush(ctx context.Context, payload []EventWithMetadata, options ...BulkPushOpFunc) error {
|
||||
|
||||
request := eventcontracts.BulkPushEventRequest{}
|
||||
|
||||
var events []*eventcontracts.PushEventRequest
|
||||
|
||||
for _, p := range payload {
|
||||
|
||||
ePayload, err := json.Marshal(p.Event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eMetadata, err := json.Marshal(p.AdditionalMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eMetadataString := string(eMetadata)
|
||||
|
||||
events = append(events, &eventcontracts.PushEventRequest{
|
||||
Key: a.namespace + p.Key,
|
||||
EventTimestamp: timestamppb.Now(),
|
||||
Payload: string(ePayload),
|
||||
AdditionalMetadata: &eMetadataString,
|
||||
})
|
||||
}
|
||||
|
||||
request.Events = events
|
||||
|
||||
_, err := a.client.BulkPush(a.ctx.newContext(ctx), &request)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *eventClientImpl) PutLog(ctx context.Context, stepRunId, msg string) error {
|
||||
_, err := a.client.PutLog(a.ctx.newContext(ctx), &eventcontracts.PutLogRequest{
|
||||
CreatedAt: timestamppb.Now(),
|
||||
|
||||
@@ -261,6 +261,18 @@ type AcceptInviteRequest struct {
|
||||
Invite string `json:"invite" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
// BulkCreateEventRequest defines model for BulkCreateEventRequest.
|
||||
type BulkCreateEventRequest struct {
|
||||
Events []CreateEventRequest `json:"events"`
|
||||
}
|
||||
|
||||
// BulkCreateEventResponse defines model for BulkCreateEventResponse.
|
||||
type BulkCreateEventResponse struct {
|
||||
// Events The events.
|
||||
Events []Event `json:"events"`
|
||||
Metadata APIResourceMeta `json:"metadata"`
|
||||
}
|
||||
|
||||
// CancelEventRequest defines model for CancelEventRequest.
|
||||
type CancelEventRequest struct {
|
||||
EventIds []openapi_types.UUID `json:"eventIds"`
|
||||
@@ -1459,6 +1471,9 @@ type ApiTokenCreateJSONRequestBody = CreateAPITokenRequest
|
||||
// EventCreateJSONRequestBody defines body for EventCreate for application/json ContentType.
|
||||
type EventCreateJSONRequestBody = CreateEventRequest
|
||||
|
||||
// EventCreateBulkJSONRequestBody defines body for EventCreateBulk for application/json ContentType.
|
||||
type EventCreateBulkJSONRequestBody = BulkCreateEventRequest
|
||||
|
||||
// EventUpdateCancelJSONRequestBody defines body for EventUpdateCancel for application/json ContentType.
|
||||
type EventUpdateCancelJSONRequestBody = CancelEventRequest
|
||||
|
||||
@@ -1667,6 +1682,11 @@ type ClientInterface interface {
|
||||
|
||||
EventCreate(ctx context.Context, tenant openapi_types.UUID, body EventCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// EventCreateBulkWithBody request with any body
|
||||
EventCreateBulkWithBody(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
EventCreateBulk(ctx context.Context, tenant openapi_types.UUID, body EventCreateBulkJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// EventUpdateCancelWithBody request with any body
|
||||
EventUpdateCancelWithBody(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@@ -2237,6 +2257,30 @@ func (c *Client) EventCreate(ctx context.Context, tenant openapi_types.UUID, bod
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) EventCreateBulkWithBody(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewEventCreateBulkRequestWithBody(c.Server, tenant, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) EventCreateBulk(ctx context.Context, tenant openapi_types.UUID, body EventCreateBulkJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewEventCreateBulkRequest(c.Server, tenant, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) EventUpdateCancelWithBody(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewEventUpdateCancelRequestWithBody(c.Server, tenant, contentType, body)
|
||||
if err != nil {
|
||||
@@ -4326,6 +4370,53 @@ func NewEventCreateRequestWithBody(server string, tenant openapi_types.UUID, con
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewEventCreateBulkRequest calls the generic EventCreateBulk builder with application/json body
|
||||
func NewEventCreateBulkRequest(server string, tenant openapi_types.UUID, body EventCreateBulkJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewEventCreateBulkRequestWithBody(server, tenant, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewEventCreateBulkRequestWithBody generates requests for EventCreateBulk with any type of body
|
||||
func NewEventCreateBulkRequestWithBody(server string, tenant openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "tenant", runtime.ParamLocationPath, tenant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v1/tenants/%s/events/bulk", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewEventUpdateCancelRequest calls the generic EventUpdateCancel builder with application/json body
|
||||
func NewEventUpdateCancelRequest(server string, tenant openapi_types.UUID, body EventUpdateCancelJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
@@ -6994,6 +7085,11 @@ type ClientWithResponsesInterface interface {
|
||||
|
||||
EventCreateWithResponse(ctx context.Context, tenant openapi_types.UUID, body EventCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*EventCreateResponse, error)
|
||||
|
||||
// EventCreateBulkWithBodyWithResponse request with any body
|
||||
EventCreateBulkWithBodyWithResponse(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EventCreateBulkResponse, error)
|
||||
|
||||
EventCreateBulkWithResponse(ctx context.Context, tenant openapi_types.UUID, body EventCreateBulkJSONRequestBody, reqEditors ...RequestEditorFn) (*EventCreateBulkResponse, error)
|
||||
|
||||
// EventUpdateCancelWithBodyWithResponse request with any body
|
||||
EventUpdateCancelWithBodyWithResponse(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EventUpdateCancelResponse, error)
|
||||
|
||||
@@ -7784,6 +7880,31 @@ func (r EventCreateResponse) StatusCode() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type EventCreateBulkResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *BulkCreateEventResponse
|
||||
JSON400 *APIErrors
|
||||
JSON403 *APIErrors
|
||||
JSON429 *APIErrors
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r EventCreateBulkResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r EventCreateBulkResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type EventUpdateCancelResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@@ -9384,6 +9505,23 @@ func (c *ClientWithResponses) EventCreateWithResponse(ctx context.Context, tenan
|
||||
return ParseEventCreateResponse(rsp)
|
||||
}
|
||||
|
||||
// EventCreateBulkWithBodyWithResponse request with arbitrary body returning *EventCreateBulkResponse
|
||||
func (c *ClientWithResponses) EventCreateBulkWithBodyWithResponse(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EventCreateBulkResponse, error) {
|
||||
rsp, err := c.EventCreateBulkWithBody(ctx, tenant, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseEventCreateBulkResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) EventCreateBulkWithResponse(ctx context.Context, tenant openapi_types.UUID, body EventCreateBulkJSONRequestBody, reqEditors ...RequestEditorFn) (*EventCreateBulkResponse, error) {
|
||||
rsp, err := c.EventCreateBulk(ctx, tenant, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseEventCreateBulkResponse(rsp)
|
||||
}
|
||||
|
||||
// EventUpdateCancelWithBodyWithResponse request with arbitrary body returning *EventUpdateCancelResponse
|
||||
func (c *ClientWithResponses) EventUpdateCancelWithBodyWithResponse(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EventUpdateCancelResponse, error) {
|
||||
rsp, err := c.EventUpdateCancelWithBody(ctx, tenant, contentType, body, reqEditors...)
|
||||
@@ -10945,6 +11083,53 @@ func ParseEventCreateResponse(rsp *http.Response) (*EventCreateResponse, error)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseEventCreateBulkResponse parses an HTTP response from a EventCreateBulkWithResponse call
|
||||
func ParseEventCreateBulkResponse(rsp *http.Response) (*EventCreateBulkResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &EventCreateBulkResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest BulkCreateEventResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
|
||||
var dest APIErrors
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON400 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403:
|
||||
var dest APIErrors
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON403 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429:
|
||||
var dest APIErrors
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON429 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseEventUpdateCancelResponse parses an HTTP response from a EventUpdateCancelWithResponse call
|
||||
func ParseEventUpdateCancelResponse(rsp *http.Response) (*EventUpdateCancelResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
|
||||
@@ -8,6 +8,11 @@ import (
|
||||
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/dbsqlc"
|
||||
)
|
||||
|
||||
type BulkCreateEventOpts struct {
|
||||
TenantId string `validate:"required,uuid"`
|
||||
Events []*CreateEventOpts
|
||||
}
|
||||
|
||||
type CreateEventOpts struct {
|
||||
// (required) the tenant id
|
||||
TenantId string `validate:"required,uuid"`
|
||||
@@ -65,6 +70,10 @@ type ListEventResult struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
type BulkCreateEventResult struct {
|
||||
Events []*dbsqlc.Event
|
||||
}
|
||||
|
||||
type EventAPIRepository interface {
|
||||
// ListEvents returns all events for a given tenant.
|
||||
ListEvents(ctx context.Context, tenantId string, opts *ListEventOpts) (*ListEventResult, error)
|
||||
@@ -85,6 +94,9 @@ type EventEngineRepository interface {
|
||||
// CreateEvent creates a new event for a given tenant.
|
||||
CreateEvent(ctx context.Context, opts *CreateEventOpts) (*dbsqlc.Event, error)
|
||||
|
||||
// CreateEvent creates a new event for a given tenant.
|
||||
BulkCreateEvent(ctx context.Context, opts *BulkCreateEventOpts) (*BulkCreateEventResult, error)
|
||||
|
||||
// GetEventForEngine returns an event for the engine by id.
|
||||
GetEventForEngine(ctx context.Context, tenantId, id string) (*dbsqlc.Event, error)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewMetered(entitlements repository.EntitlementsRepository, l *zerolog.Logge
|
||||
|
||||
var ErrResourceExhausted = fmt.Errorf("resource exhausted")
|
||||
|
||||
func MakeMetered[T any](ctx context.Context, m *Metered, resource dbsqlc.LimitResource, tenantId string, f func() (*string, *T, error)) (*T, error) {
|
||||
func MakeMetered[T any](ctx context.Context, m *Metered, resource dbsqlc.LimitResource, tenantId string, numberOfResources int32, f func() (*string, *T, error)) (*T, error) {
|
||||
|
||||
var key = fmt.Sprintf("%s:%s", resource, tenantId)
|
||||
|
||||
@@ -45,7 +45,7 @@ func MakeMetered[T any](ctx context.Context, m *Metered, resource dbsqlc.LimitRe
|
||||
}
|
||||
|
||||
if canCreate == nil {
|
||||
c, percent, err := m.entitlements.TenantLimit().CanCreate(ctx, resource, tenantId)
|
||||
c, percent, err := m.entitlements.TenantLimit().CanCreate(ctx, resource, tenantId, numberOfResources)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not check tenant limit: %w", err)
|
||||
@@ -70,7 +70,7 @@ func MakeMetered[T any](ctx context.Context, m *Metered, resource dbsqlc.LimitRe
|
||||
}
|
||||
|
||||
deferredMeter := func() {
|
||||
limit, err := m.entitlements.TenantLimit().Meter(ctx, resource, tenantId)
|
||||
limit, err := m.entitlements.TenantLimit().Meter(ctx, resource, tenantId, numberOfResources)
|
||||
|
||||
if limit != nil && (percent <= 50 || percent >= 100) {
|
||||
m.c.Set(key, limit.Value < limit.LimitValue)
|
||||
|
||||
47
pkg/repository/prisma/dbsqlc/copyfrom.go
Normal file
47
pkg/repository/prisma/dbsqlc/copyfrom.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.24.0
|
||||
// source: copyfrom.go
|
||||
|
||||
package dbsqlc
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// iteratorForCreateEvents implements pgx.CopyFromSource.
|
||||
type iteratorForCreateEvents struct {
|
||||
rows []CreateEventsParams
|
||||
skippedFirstNextCall bool
|
||||
}
|
||||
|
||||
func (r *iteratorForCreateEvents) Next() bool {
|
||||
if len(r.rows) == 0 {
|
||||
return false
|
||||
}
|
||||
if !r.skippedFirstNextCall {
|
||||
r.skippedFirstNextCall = true
|
||||
return true
|
||||
}
|
||||
r.rows = r.rows[1:]
|
||||
return len(r.rows) > 0
|
||||
}
|
||||
|
||||
func (r iteratorForCreateEvents) Values() ([]interface{}, error) {
|
||||
return []interface{}{
|
||||
r.rows[0].ID,
|
||||
r.rows[0].Key,
|
||||
r.rows[0].TenantId,
|
||||
r.rows[0].ReplayedFromId,
|
||||
r.rows[0].Data,
|
||||
r.rows[0].AdditionalMetadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r iteratorForCreateEvents) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queries) CreateEvents(ctx context.Context, db DBTX, arg []CreateEventsParams) (int64, error) {
|
||||
return db.CopyFrom(ctx, []string{"Event"}, []string{"id", "key", "tenantId", "replayedFromId", "data", "additionalMetadata"}, &iteratorForCreateEvents{rows: arg})
|
||||
}
|
||||
@@ -15,6 +15,7 @@ type DBTX interface {
|
||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error)
|
||||
SendBatch(context.Context, *pgx.Batch) pgx.BatchResults
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,32 @@ INSERT INTO "Event" (
|
||||
@additionalMetadata::jsonb
|
||||
) RETURNING *;
|
||||
|
||||
-- name: CreateEvents :copyfrom
|
||||
INSERT INTO "Event" (
|
||||
"id",
|
||||
"key",
|
||||
"tenantId",
|
||||
"replayedFromId",
|
||||
"data",
|
||||
"additionalMetadata"
|
||||
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6
|
||||
);
|
||||
|
||||
|
||||
-- name: GetInsertedEvents :many
|
||||
|
||||
SELECT * FROM "Event"
|
||||
WHERE xmin::text = (txid_current() % (2^32)::bigint)::text
|
||||
ORDER BY id;
|
||||
|
||||
|
||||
-- name: ListEvents :many
|
||||
WITH filtered_events AS (
|
||||
SELECT
|
||||
|
||||
@@ -201,6 +201,15 @@ func (q *Queries) CreateEvent(ctx context.Context, db DBTX, arg CreateEventParam
|
||||
return &i, err
|
||||
}
|
||||
|
||||
type CreateEventsParams struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Key string `json:"key"`
|
||||
TenantId pgtype.UUID `json:"tenantId"`
|
||||
ReplayedFromId pgtype.UUID `json:"replayedFromId"`
|
||||
Data []byte `json:"data"`
|
||||
AdditionalMetadata []byte `json:"additionalMetadata"`
|
||||
}
|
||||
|
||||
const getEventForEngine = `-- name: GetEventForEngine :one
|
||||
SELECT
|
||||
id, "createdAt", "updatedAt", "deletedAt", key, "tenantId", "replayedFromId", data, "additionalMetadata"
|
||||
@@ -268,6 +277,43 @@ func (q *Queries) GetEventsForRange(ctx context.Context, db DBTX) ([]*GetEventsF
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getInsertedEvents = `-- name: GetInsertedEvents :many
|
||||
|
||||
SELECT id, "createdAt", "updatedAt", "deletedAt", key, "tenantId", "replayedFromId", data, "additionalMetadata" FROM "Event"
|
||||
WHERE xmin::text = (txid_current() % (2^32)::bigint)::text
|
||||
ORDER BY id
|
||||
`
|
||||
|
||||
func (q *Queries) GetInsertedEvents(ctx context.Context, db DBTX) ([]*Event, error) {
|
||||
rows, err := db.Query(ctx, getInsertedEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []*Event
|
||||
for rows.Next() {
|
||||
var i Event
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.DeletedAt,
|
||||
&i.Key,
|
||||
&i.TenantId,
|
||||
&i.ReplayedFromId,
|
||||
&i.Data,
|
||||
&i.AdditionalMetadata,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, &i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listEvents = `-- name: ListEvents :many
|
||||
WITH filtered_events AS (
|
||||
SELECT
|
||||
|
||||
@@ -78,7 +78,7 @@ SET
|
||||
WHEN ("customValueMeter" = true OR ("window" IS NOT NULL AND "window" != '' AND NOW() - "lastRefill" >= "window"::INTERVAL)) THEN
|
||||
0 -- Refill to 0 since the window has passed
|
||||
ELSE
|
||||
"value" + 1 -- Increment the current value within the window
|
||||
"value" + @numResources::int -- Increment the current value within the window by the number of resources
|
||||
END,
|
||||
"lastRefill" = CASE
|
||||
WHEN ("window" IS NOT NULL AND "window" != '' AND NOW() - "lastRefill" >= "window"::INTERVAL) THEN
|
||||
|
||||
@@ -128,7 +128,7 @@ SET
|
||||
WHEN ("customValueMeter" = true OR ("window" IS NOT NULL AND "window" != '' AND NOW() - "lastRefill" >= "window"::INTERVAL)) THEN
|
||||
0 -- Refill to 0 since the window has passed
|
||||
ELSE
|
||||
"value" + 1 -- Increment the current value within the window
|
||||
"value" + $1::int -- Increment the current value within the window by the number of resources
|
||||
END,
|
||||
"lastRefill" = CASE
|
||||
WHEN ("window" IS NOT NULL AND "window" != '' AND NOW() - "lastRefill" >= "window"::INTERVAL) THEN
|
||||
@@ -136,18 +136,19 @@ SET
|
||||
ELSE
|
||||
"lastRefill" -- Keep the lastRefill unchanged if within the window
|
||||
END
|
||||
WHERE "tenantId" = $1::uuid
|
||||
AND "resource" = $2::"LimitResource"
|
||||
WHERE "tenantId" = $2::uuid
|
||||
AND "resource" = $3::"LimitResource"
|
||||
RETURNING id, "createdAt", "updatedAt", resource, "tenantId", "limitValue", "alarmValue", value, "window", "lastRefill", "customValueMeter"
|
||||
`
|
||||
|
||||
type MeterTenantResourceParams struct {
|
||||
Tenantid pgtype.UUID `json:"tenantid"`
|
||||
Resource NullLimitResource `json:"resource"`
|
||||
Numresources int32 `json:"numresources"`
|
||||
Tenantid pgtype.UUID `json:"tenantid"`
|
||||
Resource NullLimitResource `json:"resource"`
|
||||
}
|
||||
|
||||
func (q *Queries) MeterTenantResource(ctx context.Context, db DBTX, arg MeterTenantResourceParams) (*TenantResourceLimit, error) {
|
||||
row := db.QueryRow(ctx, meterTenantResource, arg.Tenantid, arg.Resource)
|
||||
row := db.QueryRow(ctx, meterTenantResource, arg.Numresources, arg.Tenantid, arg.Resource)
|
||||
var i TenantResourceLimit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -243,7 +244,7 @@ func (r *eventEngineRepository) GetEventForEngine(ctx context.Context, tenantId,
|
||||
}
|
||||
|
||||
func (r *eventEngineRepository) CreateEvent(ctx context.Context, opts *repository.CreateEventOpts) (*dbsqlc.Event, error) {
|
||||
return metered.MakeMetered(ctx, r.m, dbsqlc.LimitResourceEVENT, opts.TenantId, func() (*string, *dbsqlc.Event, error) {
|
||||
return metered.MakeMetered(ctx, r.m, dbsqlc.LimitResourceEVENT, opts.TenantId, 1, func() (*string, *dbsqlc.Event, error) {
|
||||
|
||||
ctx, span := telemetry.NewSpan(ctx, "db-create-event")
|
||||
defer span.End()
|
||||
@@ -283,6 +284,93 @@ func (r *eventEngineRepository) CreateEvent(ctx context.Context, opts *repositor
|
||||
return &id, e, nil
|
||||
})
|
||||
}
|
||||
func (r *eventEngineRepository) BulkCreateEvent(ctx context.Context, opts *repository.BulkCreateEventOpts) (*repository.BulkCreateEventResult, error) {
|
||||
|
||||
numberOfResources := len(opts.Events)
|
||||
if numberOfResources < math.MinInt32 || numberOfResources > math.MaxInt32 {
|
||||
return nil, fmt.Errorf("number of resources is out of range")
|
||||
}
|
||||
|
||||
return metered.MakeMetered(ctx, r.m, dbsqlc.LimitResourceEVENT, opts.TenantId, int32(numberOfResources), func() (*string, *repository.BulkCreateEventResult, error) {
|
||||
|
||||
ctx, span := telemetry.NewSpan(ctx, "db-bulk-create-event")
|
||||
defer span.End()
|
||||
|
||||
if err := r.v.Validate(opts); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
params := make([]dbsqlc.CreateEventsParams, len(opts.Events))
|
||||
|
||||
for i, event := range opts.Events {
|
||||
|
||||
params[i] = dbsqlc.CreateEventsParams{
|
||||
ID: sqlchelpers.UUIDFromStr(uuid.New().String()),
|
||||
Key: event.Key,
|
||||
TenantId: sqlchelpers.UUIDFromStr(event.TenantId),
|
||||
Data: event.Data,
|
||||
AdditionalMetadata: event.AdditionalMetadata,
|
||||
}
|
||||
|
||||
if event.ReplayedEvent != nil {
|
||||
params[i].ReplayedFromId = sqlchelpers.UUIDFromStr(*event.ReplayedEvent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// start a transaction
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer deferRollback(ctx, r.l, tx.Rollback)
|
||||
|
||||
insertCount, err := r.queries.CreateEvents(
|
||||
ctx,
|
||||
tx,
|
||||
params,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not create events: %w", err)
|
||||
}
|
||||
|
||||
r.l.Info().Msgf("inserted %d events", insertCount)
|
||||
|
||||
events, err := r.queries.GetInsertedEvents(ctx, tx)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not retrieve inserted events: %w", err)
|
||||
}
|
||||
err = tx.Commit(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not commit transaction: %w", err)
|
||||
}
|
||||
|
||||
var returnString string
|
||||
|
||||
for _, e := range events {
|
||||
|
||||
for _, cb := range r.callbacks {
|
||||
err = cb.Do(e)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not execute callback: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(events) > 0 {
|
||||
|
||||
returnString = sqlchelpers.UUIDToStr(events[0].ID)
|
||||
}
|
||||
|
||||
// TODO is this return string important?
|
||||
return &returnString, &repository.BulkCreateEventResult{Events: events}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *eventEngineRepository) ListEventsByIds(ctx context.Context, tenantId string, ids []string) ([]*dbsqlc.Event, error) {
|
||||
pgIds := make([]pgtype.UUID, len(ids))
|
||||
|
||||
@@ -202,7 +202,7 @@ func (t *tenantLimitRepository) GetLimits(ctx context.Context, tenantId string)
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
func (t *tenantLimitRepository) CanCreate(ctx context.Context, resource dbsqlc.LimitResource, tenantId string) (bool, int, error) {
|
||||
func (t *tenantLimitRepository) CanCreate(ctx context.Context, resource dbsqlc.LimitResource, tenantId string, numberOfResources int32) (bool, int, error) {
|
||||
|
||||
if !t.config.EnforceLimits {
|
||||
return true, 0, nil
|
||||
@@ -243,18 +243,20 @@ func (t *tenantLimitRepository) CanCreate(ctx context.Context, resource dbsqlc.L
|
||||
|
||||
}
|
||||
|
||||
if value >= limit.LimitValue {
|
||||
// subtract 1 for backwards compatibility
|
||||
|
||||
if value+numberOfResources-1 >= limit.LimitValue {
|
||||
return false, 100, nil
|
||||
}
|
||||
|
||||
return true, calcPercent(value, limit.LimitValue), nil
|
||||
return true, calcPercent(value+numberOfResources, limit.LimitValue), nil
|
||||
}
|
||||
|
||||
func calcPercent(value int32, limit int32) int {
|
||||
return int((float64(value) / float64(limit)) * 100)
|
||||
}
|
||||
|
||||
func (t *tenantLimitRepository) Meter(ctx context.Context, resource dbsqlc.LimitResource, tenantId string) (*dbsqlc.TenantResourceLimit, error) {
|
||||
func (t *tenantLimitRepository) Meter(ctx context.Context, resource dbsqlc.LimitResource, tenantId string, numberOfResources int32) (*dbsqlc.TenantResourceLimit, error) {
|
||||
if !t.config.EnforceLimits {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -265,6 +267,7 @@ func (t *tenantLimitRepository) Meter(ctx context.Context, resource dbsqlc.Limit
|
||||
LimitResource: resource,
|
||||
Valid: true,
|
||||
},
|
||||
Numresources: numberOfResources,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -219,7 +219,7 @@ func (w *workerEngineRepository) GetWorkerForEngine(ctx context.Context, tenantI
|
||||
}
|
||||
|
||||
func (w *workerEngineRepository) CreateNewWorker(ctx context.Context, tenantId string, opts *repository.CreateWorkerOpts) (*dbsqlc.Worker, error) {
|
||||
return metered.MakeMetered(ctx, w.m, dbsqlc.LimitResourceWORKER, tenantId, func() (*string, *dbsqlc.Worker, error) {
|
||||
return metered.MakeMetered(ctx, w.m, dbsqlc.LimitResourceWORKER, tenantId, 1, func() (*string, *dbsqlc.Worker, error) {
|
||||
if err := w.v.Validate(opts); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (w *workflowRunEngineRepository) GetWorkflowRunInputData(tenantId, workflow
|
||||
}
|
||||
|
||||
func (w *workflowRunAPIRepository) CreateNewWorkflowRun(ctx context.Context, tenantId string, opts *repository.CreateWorkflowRunOpts) (*dbsqlc.WorkflowRun, error) {
|
||||
return metered.MakeMetered(ctx, w.m, dbsqlc.LimitResourceWORKFLOWRUN, tenantId, func() (*string, *dbsqlc.WorkflowRun, error) {
|
||||
return metered.MakeMetered(ctx, w.m, dbsqlc.LimitResourceWORKFLOWRUN, tenantId, 1, func() (*string, *dbsqlc.WorkflowRun, error) {
|
||||
if err := w.v.Validate(opts); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -425,7 +425,7 @@ func (w *workflowRunEngineRepository) PopWorkflowRunsRoundRobin(ctx context.Cont
|
||||
|
||||
func (w *workflowRunEngineRepository) CreateNewWorkflowRun(ctx context.Context, tenantId string, opts *repository.CreateWorkflowRunOpts) (string, error) {
|
||||
|
||||
wfr, err := metered.MakeMetered(ctx, w.m, dbsqlc.LimitResourceWORKFLOWRUN, tenantId, func() (*string, *dbsqlc.WorkflowRun, error) {
|
||||
wfr, err := metered.MakeMetered(ctx, w.m, dbsqlc.LimitResourceWORKFLOWRUN, tenantId, 1, func() (*string, *dbsqlc.WorkflowRun, error) {
|
||||
|
||||
if err := w.v.Validate(opts); err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -25,10 +25,10 @@ type TenantLimitRepository interface {
|
||||
GetLimits(ctx context.Context, tenantId string) ([]*dbsqlc.TenantResourceLimit, error)
|
||||
|
||||
// CanCreateWorkflowRun checks if the tenant can create a resource
|
||||
CanCreate(ctx context.Context, resource dbsqlc.LimitResource, tenantId string) (bool, int, error)
|
||||
CanCreate(ctx context.Context, resource dbsqlc.LimitResource, tenantId string, numberOfResources int32) (bool, int, error)
|
||||
|
||||
// MeterWorkflowRun increments the tenant's resource count
|
||||
Meter(ctx context.Context, resource dbsqlc.LimitResource, tenantId string) (*dbsqlc.TenantResourceLimit, error)
|
||||
Meter(ctx context.Context, resource dbsqlc.LimitResource, tenantId string, numberOfResources int32) (*dbsqlc.TenantResourceLimit, error)
|
||||
|
||||
// Create new Tenant Resource Limits for a tenant
|
||||
SelectOrInsertTenantLimits(ctx context.Context, tenantId string, plan *string) error
|
||||
|
||||
Reference in New Issue
Block a user