-
Notifications
You must be signed in to change notification settings - Fork 498
Workflows #873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Workflows #873
Changes from all commits
809a4de
a7e983d
694425c
cc04bc4
5894613
a56d3d3
4c46d23
0f4ea9f
48f8d82
e610ec9
437bafa
7adeee4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| -- CreateTable | ||
| CREATE TABLE "WorkflowTriggerToken" ( | ||
| "tenancyId" UUID NOT NULL, | ||
| "id" UUID NOT NULL, | ||
| "tokenHash" TEXT NOT NULL, | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
| "expiresAt" TIMESTAMP(3) NOT NULL, | ||
|
|
||
| CONSTRAINT "WorkflowTriggerToken_pkey" PRIMARY KEY ("tenancyId","id") | ||
| ); | ||
|
|
||
| -- CreateTable | ||
| CREATE TABLE "WorkflowTrigger" ( | ||
| "tenancyId" UUID NOT NULL, | ||
| "id" UUID NOT NULL, | ||
| "executionId" UUID NOT NULL, | ||
| "triggerData" JSONB NOT NULL, | ||
| "scheduledAt" TIMESTAMP(3), | ||
| "output" JSONB, | ||
| "error" JSONB, | ||
| "compiledWorkflowId" UUID, | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
|
|
||
| CONSTRAINT "WorkflowTrigger_pkey" PRIMARY KEY ("tenancyId","id") | ||
| ); | ||
|
|
||
| -- CreateTable | ||
| CREATE TABLE "WorkflowExecution" ( | ||
| "tenancyId" UUID NOT NULL, | ||
| "id" UUID NOT NULL, | ||
| "workflowId" TEXT NOT NULL, | ||
| "triggerIds" TEXT[], | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
|
|
||
| CONSTRAINT "WorkflowExecution_pkey" PRIMARY KEY ("tenancyId","id") | ||
| ); | ||
|
|
||
| -- CreateTable | ||
| CREATE TABLE "CurrentlyCompilingWorkflow" ( | ||
| "tenancyId" UUID NOT NULL, | ||
| "workflowId" TEXT NOT NULL, | ||
| "compilationVersion" INTEGER NOT NULL, | ||
| "sourceHash" TEXT NOT NULL, | ||
| "startedCompilingAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
|
|
||
| CONSTRAINT "CurrentlyCompilingWorkflow_pkey" PRIMARY KEY ("tenancyId","workflowId","compilationVersion","sourceHash") | ||
| ); | ||
|
|
||
| -- CreateTable | ||
| CREATE TABLE "CompiledWorkflow" ( | ||
| "tenancyId" UUID NOT NULL, | ||
| "id" UUID NOT NULL, | ||
| "workflowId" TEXT NOT NULL, | ||
| "compilationVersion" INTEGER NOT NULL, | ||
| "sourceHash" TEXT NOT NULL, | ||
| "compiledCode" TEXT, | ||
| "compileError" TEXT, | ||
| "compiledAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "registeredTriggers" TEXT[], | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
|
|
||
| CONSTRAINT "CompiledWorkflow_pkey" PRIMARY KEY ("tenancyId","id") | ||
| ); | ||
|
|
||
| -- CreateIndex | ||
| CREATE UNIQUE INDEX "WorkflowTriggerToken_tenancyId_tokenHash_key" ON "WorkflowTriggerToken"("tenancyId", "tokenHash"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE UNIQUE INDEX "CompiledWorkflow_tenancyId_workflowId_compilationVersion_so_key" ON "CompiledWorkflow"("tenancyId", "workflowId", "compilationVersion", "sourceHash"); | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "WorkflowTrigger" ADD CONSTRAINT "WorkflowTrigger_tenancyId_compiledWorkflowId_fkey" FOREIGN KEY ("tenancyId", "compiledWorkflowId") REFERENCES "CompiledWorkflow"("tenancyId", "id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "WorkflowTrigger" ADD CONSTRAINT "WorkflowTrigger_tenancyId_executionId_fkey" FOREIGN KEY ("tenancyId", "executionId") REFERENCES "WorkflowExecution"("tenancyId", "id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { ensureTeamMembershipExists, ensureUserExists } from "@/lib/request-chec | |
| import { Tenancy, getSoleTenancyFromProjectBranch, getTenancy } from "@/lib/tenancies"; | ||
| import { PrismaTransaction } from "@/lib/types"; | ||
| import { sendTeamMembershipDeletedWebhook, sendUserCreatedWebhook, sendUserDeletedWebhook, sendUserUpdatedWebhook } from "@/lib/webhooks"; | ||
| import { triggerWorkflows } from "@/lib/workflows"; | ||
| import { RawQuery, getPrismaClientForSourceOfTruth, getPrismaClientForTenancy, getPrismaSchemaForSourceOfTruth, getPrismaSchemaForTenancy, globalPrismaClient, rawQuery, retryTransaction, sqlQuoteIdent } from "@/prisma-client"; | ||
| import { createCrudHandlers } from "@/route-handlers/crud-handler"; | ||
| import { uploadAndGetUrl } from "@/s3"; | ||
|
|
@@ -648,6 +649,14 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC | |
|
|
||
| await createPersonalTeamIfEnabled(prisma, auth.tenancy, result); | ||
|
|
||
| // if the user is not an anonymous user, trigger onSignUp workflows | ||
| if (!result.is_anonymous) { | ||
| await triggerWorkflows(auth.tenancy, { | ||
| type: "sign-up", | ||
| userId: result.id, | ||
| }); | ||
| } | ||
|
Comment on lines
+652
to
+658
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Don’t block user creation on workflow execution; make it fire-and-forget. If any workflow throws, the endpoint 500s even though the user is already persisted. This couples signup UX to arbitrary workflow code and increases latency. Mirror the webhook pattern and dispatch asynchronously. Apply this diff: - if (!result.is_anonymous) {
- await triggerWorkflows(auth.tenancy, {
- type: "sign-up",
- userId: result.id,
- });
- }
+ if (!result.is_anonymous) {
+ // Run in the background to avoid failing user creation on workflow errors
+ runAsynchronouslyAndWaitUntil(
+ triggerWorkflows(auth.tenancy, {
+ type: "sign-up",
+ userId: result.id,
+ })
+ );
+ }
🤖 Prompt for AI Agents |
||
|
|
||
| runAsynchronouslyAndWaitUntil(sendUserCreatedWebhook({ | ||
| projectId: auth.project.id, | ||
| data: result, | ||
|
|
@@ -948,8 +957,15 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC | |
| } | ||
| } | ||
|
|
||
| // if we went from anonymous to non-anonymous, rename the personal team | ||
| // if we went from anonymous to non-anonymous: | ||
| if (oldUser.isAnonymous && data.is_anonymous === false) { | ||
| // trigger onSignUp workflows | ||
| await triggerWorkflows(auth.tenancy, { | ||
| type: "sign-up", | ||
| userId: params.user_id, | ||
| }); | ||
|
|
||
| // rename the personal team | ||
|
Comment on lines
+960
to
+968
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainCritical: Triggering workflows inside retryTransaction causes duplicate side effects and long transactions.
Apply this diff: const prisma = await getPrismaClientForTenancy(auth.tenancy);
- const { user } = await retryTransaction(prisma, async (tx) => {
+ let shouldTriggerSignUp = false;
+ const { user } = await retryTransaction(prisma, async (tx) => {
@@
- if (oldUser.isAnonymous && data.is_anonymous === false) {
- // trigger onSignUp workflows
- await triggerWorkflows(auth.tenancy, {
- type: "sign-up",
- userId: params.user_id,
- });
-
- // rename the personal team
+ if (oldUser.isAnonymous && data.is_anonymous === false) {
+ // mark for post-commit workflow trigger
+ shouldTriggerSignUp = true;
+ // rename the personal team
await tx.team.updateMany({
where: {
tenancyId: auth.tenancy.id,
@@
return {
user,
};
});
+ if (shouldTriggerSignUp) {
+ // Dispatch asynchronously to avoid blocking the update path and duplicate side effects on retries
+ runAsynchronouslyAndWaitUntil(
+ triggerWorkflows(auth.tenancy, {
+ type: "sign-up",
+ userId: params.user_id,
+ })
+ );
+ }Run to confirm the trigger type is valid and to surface other call sites: 🏁 Script executed: #!/bin/bash
# Verify 'sign-up' is an allowed WorkflowTrigger type and find all usages
rg -nP --type=ts '\bWorkflowTrigger\b' -C2
rg -nP --type=ts "(\"|')sign-up(\"|')" -C2Length of output: 8641 🏁 Script executed: #!/bin/bash
# Show all retryTransaction usages in the users CRUD file with context
rg -nP --type=ts 'retryTransaction\(' -C4 apps/backend/src/app/api/latest/users/crud.tsxLength of output: 1414 🏁 Script executed: #!/bin/bash
# Locate definition of retryTransaction
rg -nP --type=ts "async function retryTransaction" -C3 .
rg -nP --type=ts "retryTransaction\\s*=" -C3 .Length of output: 732 🏁 Script executed: #!/bin/bash
sed -n '115,180p' apps/backend/src/prisma-client.tsxLength of output: 3778 🏁 Script executed: #!/bin/bash
# Extract create path around retryTransaction and subsequent triggerWorkflows
sed -n '630,700p' apps/backend/src/app/api/latest/users/crud.tsxLength of output: 2300 Move triggerWorkflows call out of retryTransaction in onUpdate runAsynchronouslyAndWaitUntil(
triggerWorkflows(auth.tenancy, { type: "sign-up", userId: params.user_id })
);🤖 Prompt for AI Agents |
||
| await tx.team.updateMany({ | ||
| where: { | ||
| tenancyId: auth.tenancy.id, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove triggerIds array to avoid drift; rely on FK from WorkflowTrigger
WorkflowTrigger already has executionId -> WorkflowExecution. Keeping triggerIds TEXT[] duplicates the relationship without FK guarantees and can desynchronize, causing data integrity issues.
Apply (either edit this migration before apply, or follow-up migration to drop the column):
CREATE TABLE "WorkflowExecution" ( "tenancyId" UUID NOT NULL, "id" UUID NOT NULL, "workflowId" TEXT NOT NULL, - "triggerIds" TEXT[], "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, CONSTRAINT "WorkflowExecution_pkey" PRIMARY KEY ("tenancyId","id") );Optional: add an index for common lookups:
🤖 Prompt for AI Agents