Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f9c28ce
chore(deps): bump next from 16.0.10 to 16.1.5
dependabot[bot] Jan 29, 2026
3cb0c95
chore(deps): bump workflow to 4.1.0-beta.51 for Next.js 16.1.5 compat
suisuss Feb 3, 2026
bc4b830
fix: await start() for workflow 4.1.0-beta.51 compatibility
suisuss Feb 6, 2026
dc449f7
fix: update e2e invitation tests for new inline invite UI
suisuss Feb 6, 2026
13128be
fix: format e2e invitation test for lint compliance
suisuss Feb 6, 2026
3d40816
fix: create .workflow-data directory for SDK Local World storage
suisuss Feb 6, 2026
921f5e5
feat: KEEP-1371 switch Workflow SDK from Local World to Postgres World
suisuss Feb 9, 2026
60fe834
fix: include @workflow/world-postgres in Next.js standalone output
suisuss Feb 9, 2026
00f9c79
fix: add WORKFLOW_POSTGRES_URL to deploy configs
suisuss Feb 9, 2026
d7e05b7
test: KEEP-1371 add e2e test for Postgres World integration
suisuss Feb 9, 2026
238f16e
fix: resolve TypeScript strict mode errors in postgres-world e2e test
suisuss Feb 9, 2026
3838cb3
fix: URL-encode postgres passwords for postgres.js compatibility
suisuss Feb 9, 2026
42fd333
fix: declare workflow enum types in drizzle schema
suisuss Feb 9, 2026
d6ebe03
fix: include @workflow/world-postgres in standalone output tracing
suisuss Feb 9, 2026
5aa3c2f
fix: include pg-boss in standalone output tracing
suisuss Feb 9, 2026
9608918
fix: hoist workflow world-postgres transitive deps for standalone tra…
suisuss Feb 9, 2026
e6e68ee
fix: add @vercel/oidc and mixpart to standalone tracing
suisuss Feb 9, 2026
d35827e
fix: include shared deps in standalone tracing for world-postgres
suisuss Feb 10, 2026
db956db
fix: include complete transitive dep tree for world-postgres
suisuss Feb 10, 2026
ffee324
chore: add script to list world-postgres transitive deps
suisuss Feb 10, 2026
bb76283
fix: add postgres world config to production Helm values
suisuss Feb 10, 2026
cb1c43a
fix: lint errors in list-world-deps.mjs
suisuss Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ NEXT_PUBLIC_GITHUB_CLIENT_ID=

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
NEXT_PUBLIC_GOOGLE_CLIENT_ID=
NEXT_PUBLIC_GOOGLE_CLIENT_ID=

# Workflow SDK World (leave unset for local world auto-detection in dev)
# WORKFLOW_TARGET_WORLD=@workflow/world-postgres
# WORKFLOW_POSTGRES_URL=postgresql://postgres:postgres@localhost:5433/keeperhub
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# misc
.DS_Store
*.pem
.workflow-data/

# OS files
Thumbs.db
Expand Down
35 changes: 35 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Force-hoist the full transitive dependency tree of @workflow/world-postgres.
# Next.js standalone output tracing cannot follow the dynamic
# require(process.env.WORKFLOW_TARGET_WORLD) in the workflow SDK.
# pnpm strict mode keeps transitive deps in .pnpm/ where the
# outputFileTracingIncludes globs in next.config.ts can't reach them.
#
# Regenerate with: node scripts/list-world-deps.mjs
public-hoist-pattern[]=@vercel/oidc
public-hoist-pattern[]=@vercel/queue
public-hoist-pattern[]=@workflow/*
public-hoist-pattern[]=async-sema
public-hoist-pattern[]=cbor-x
public-hoist-pattern[]=cbor-extract
public-hoist-pattern[]=cron-parser
public-hoist-pattern[]=luxon
public-hoist-pattern[]=mixpart
public-hoist-pattern[]=ms
public-hoist-pattern[]=pg
public-hoist-pattern[]=pg-boss
public-hoist-pattern[]=pg-connection-string
public-hoist-pattern[]=pg-int8
public-hoist-pattern[]=pg-pool
public-hoist-pattern[]=pg-protocol
public-hoist-pattern[]=pg-types
public-hoist-pattern[]=pgpass
public-hoist-pattern[]=postgres-array
public-hoist-pattern[]=postgres-bytea
public-hoist-pattern[]=postgres-date
public-hoist-pattern[]=postgres-interval
public-hoist-pattern[]=serialize-error
public-hoist-pattern[]=split2
public-hoist-pattern[]=type-fest
public-hoist-pattern[]=ulid
public-hoist-pattern[]=undici
public-hoist-pattern[]=xtend
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ When you must use an ignore comment:
- **Database**: PostgreSQL + Drizzle ORM
- **Testing**: Vitest (unit/integration), Playwright (E2E)
- **AI**: Vercel AI SDK with Anthropic/OpenAI
- **Workflow**: Workflow DevKit 4.0.1-beta.17
- **Workflow**: Workflow DevKit 4.1.0-beta.51
- **Package Manager**: pnpm

## Project Structure
Expand Down
6 changes: 3 additions & 3 deletions app/api/workflow/[workflowId]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ async function executeWorkflowBackground(
});

// Use start() from workflow/api to properly execute the workflow
start(executeWorkflow, [
const run = await start(executeWorkflow, [
{
nodes,
edges,
triggerInput: input,
executionId,
workflowId, // Pass workflow ID so steps can fetch credentials
workflowId,
},
]);

console.log("[Workflow Execute] Workflow started successfully");
console.log("[Workflow Execute] Workflow started, runId:", run.runId);
} catch (error) {
console.error("[Workflow Execute] Error during execution:", error);
console.error(
Expand Down
4 changes: 2 additions & 2 deletions app/api/workflows/[workflowId]/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async function executeWorkflowBackground(
workflowId,
});

start(executeWorkflow, [
const run = await start(executeWorkflow, [
{
nodes,
edges,
Expand All @@ -105,7 +105,7 @@ async function executeWorkflowBackground(
},
]);

console.log("[Webhook] Workflow started successfully");
console.log("[Webhook] Workflow started, runId:", run.runId);
} catch (error) {
console.error("[Webhook] Error during execution:", error);
console.error(
Expand Down
21 changes: 19 additions & 2 deletions deploy/keeperhub/prod/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ shared_env: &shared_env
type: parameterStore
name: db-url
parameter_name: /eks/maker-prod/keeperhub/db-url
WORKFLOW_POSTGRES_URL:
type: parameterStore
name: workflow-postgres-url
parameter_name: /eks/maker-prod/keeperhub/db-url
CHAIN_RPC_CONFIG:
type: parameterStore
name: chain-rpc-config
Expand Down Expand Up @@ -50,6 +54,19 @@ deployment:
- -c
args:
- |
echo "Setting up workflow postgres tables..."
export WORKFLOW_POSTGRES_URL=$(node -e "
const u = process.env.WORKFLOW_POSTGRES_URL || process.env.DATABASE_URL || '';
try { new URL(u); console.log(u); } catch {
const s = u.indexOf('://') + 3;
const a = u.lastIndexOf('@');
const c = u.indexOf(':', s);
if (s > 3 && a > c && c > s) {
console.log(u.slice(0, s) + encodeURIComponent(u.slice(s, c)) + ':' + encodeURIComponent(u.slice(c + 1, a)) + u.slice(a));
} else { console.log(u); }
}
")
pnpm exec workflow-postgres-setup
echo "Running database migrations..."
pnpm db:push
echo "Seeding chains..."
Expand Down Expand Up @@ -123,9 +140,9 @@ env:
PARA_ENVIRONMENT:
type: kv
value: "prod"
WORKFLOW_EMBEDDED_DATA_DIR:
WORKFLOW_TARGET_WORLD:
type: kv
value: "/tmp/workflow-data"
value: "@workflow/world-postgres"
NEXT_PUBLIC_API_URL:
type: kv
value: "http://localhost:3000"
Expand Down
21 changes: 19 additions & 2 deletions deploy/keeperhub/staging/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ shared_env: &shared_env
type: parameterStore
name: db-url
parameter_name: /eks/maker-staging/keeperhub/db-url
WORKFLOW_POSTGRES_URL:
type: parameterStore
name: workflow-postgres-url
parameter_name: /eks/maker-staging/keeperhub/db-url
CHAIN_RPC_CONFIG:
type: parameterStore
name: chain-rpc-config
Expand Down Expand Up @@ -50,6 +54,19 @@ deployment:
- -c
args:
- |
echo "Setting up workflow postgres tables..."
export WORKFLOW_POSTGRES_URL=$(node -e "
const u = process.env.WORKFLOW_POSTGRES_URL || process.env.DATABASE_URL || '';
try { new URL(u); console.log(u); } catch {
const s = u.indexOf('://') + 3;
const a = u.lastIndexOf('@');
const c = u.indexOf(':', s);
if (s > 3 && a > c && c > s) {
console.log(u.slice(0, s) + encodeURIComponent(u.slice(s, c)) + ':' + encodeURIComponent(u.slice(c + 1, a)) + u.slice(a));
} else { console.log(u); }
}
")
pnpm exec workflow-postgres-setup
echo "Running database migrations..."
pnpm db:push
echo "Seeding chains..."
Expand Down Expand Up @@ -114,9 +131,9 @@ env:
PARA_ENVIRONMENT:
type: kv
value: "beta"
WORKFLOW_EMBEDDED_DATA_DIR:
WORKFLOW_TARGET_WORLD:
type: kv
value: "/tmp/workflow-data"
value: "@workflow/world-postgres"
NEXT_PUBLIC_API_URL:
type: kv
value: "http://localhost:3000"
Expand Down
20 changes: 18 additions & 2 deletions deploy/pr-environment/values.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ shared_env: &shared_env
DATABASE_URL:
type: kv
value: "postgresql://keeperhub:${DB_PASSWORD}@keeperhub-pr-${PR_NUMBER}-db-rw.pr-${PR_NUMBER}.svc.cluster.local:5432/keeperhub"
WORKFLOW_POSTGRES_URL:
type: kv
value: "postgresql://keeperhub:${DB_PASSWORD}@keeperhub-pr-${PR_NUMBER}-db-rw.pr-${PR_NUMBER}.svc.cluster.local:5432/keeperhub"
CHAIN_RPC_CONFIG:
type: parameterStore
name: chain-rpc-config
Expand Down Expand Up @@ -52,6 +55,19 @@ deployment:
- -c
args:
- |
echo "Setting up workflow postgres tables..."
export WORKFLOW_POSTGRES_URL=$(node -e "
const u = process.env.WORKFLOW_POSTGRES_URL || process.env.DATABASE_URL || '';
try { new URL(u); console.log(u); } catch {
const s = u.indexOf('://') + 3;
const a = u.lastIndexOf('@');
const c = u.indexOf(':', s);
if (s > 3 && a > c && c > s) {
console.log(u.slice(0, s) + encodeURIComponent(u.slice(s, c)) + ':' + encodeURIComponent(u.slice(c + 1, a)) + u.slice(a));
} else { console.log(u); }
}
")
pnpm exec workflow-postgres-setup
echo "Running database migrations..."
pnpm db:push
echo "Seeding chains..."
Expand Down Expand Up @@ -105,9 +121,9 @@ env:
PARA_ENVIRONMENT:
type: kv
value: "beta"
WORKFLOW_EMBEDDED_DATA_DIR:
WORKFLOW_TARGET_WORLD:
type: kv
value: "/tmp/workflow-data"
value: "@workflow/world-postgres"
NEXT_PUBLIC_API_URL:
type: kv
value: "http://localhost:3000"
Expand Down
42 changes: 42 additions & 0 deletions instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@
* https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
*/

// start custom keeperhub code //
/**
* Ensure special characters in postgres URL passwords are percent-encoded.
* postgres.js parses connection strings via new URL() which requires encoding.
* CNPG-generated passwords often contain +/= (base64) that break URL parsing.
*/
function encodePostgresPassword(url: string): string {
try {
new URL(url);
return url;
} catch {
const schemeEnd = url.indexOf("://") + 3;
const atIdx = url.lastIndexOf("@");
const colonIdx = url.indexOf(":", schemeEnd);
if (schemeEnd > 3 && atIdx > colonIdx && colonIdx > schemeEnd) {
const user = url.slice(schemeEnd, colonIdx);
const pass = url.slice(colonIdx + 1, atIdx);
return `${url.slice(0, schemeEnd)}${encodeURIComponent(user)}:${encodeURIComponent(pass)}${url.slice(atIdx)}`;
}
return url;
}
}
// end keeperhub code //

export async function register() {
// Patch console with LOG_LEVEL support
// This must be imported dynamically to ensure it runs at startup
Expand Down Expand Up @@ -32,6 +56,24 @@ export async function register() {
console.log("[Metrics] Prometheus dual-write collector initialized");
}

// start custom keeperhub code //
// Initialize Workflow Postgres World (pg-boss queue polling)
if (process.env.WORKFLOW_TARGET_WORLD === "@workflow/world-postgres") {
const rawUrl =
process.env.WORKFLOW_POSTGRES_URL || process.env.DATABASE_URL;
if (rawUrl) {
process.env.WORKFLOW_POSTGRES_URL = encodePostgresPassword(rawUrl);
}

const { getWorld } = await import("workflow/runtime");
const world = getWorld();
if (world.start) {
await world.start();
console.log("[Workflow] Postgres World initialized");
}
}
// end keeperhub code //

// Catch unhandled promise rejections (would otherwise be silent)
process.on("unhandledRejection", (reason) => {
console.error(
Expand Down
21 changes: 21 additions & 0 deletions lib/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
index,
integer,
jsonb,
pgEnum,
pgTable,
text,
timestamp,
Expand All @@ -12,6 +13,26 @@ import {
import type { IntegrationType } from "../types/integration";
import { generateId } from "../utils/id";

// start custom keeperhub code //
// These enums are created by @workflow/world-postgres migrations in the public
// schema and referenced by workflow.workflow_runs / workflow.workflow_steps.
// Declaring them here prevents drizzle-kit from trying to drop them.
export const workflowRunStatus = pgEnum("status", [
"pending",
"running",
"completed",
"failed",
"cancelled",
]);
export const workflowStepStatus = pgEnum("step_status", [
"pending",
"running",
"completed",
"failed",
"cancelled",
]);
// end keeperhub code //

// Better Auth tables
export const users = pgTable("users", {
id: text("id").primaryKey(),
Expand Down
Loading