Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,3 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
108 changes: 108 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

19 changes: 17 additions & 2 deletions service/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ COPY prisma.config.ts ./prisma.config.ts
# Generate Prisma client
RUN bun prisma generate

# Ensure migrations match schema (requires a local shadow DB)
RUN apt-get update && \
apt-get install -y postgresql && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
PG_VERSION="$(pg_lsclusters --no-header | awk 'NR==1{print $1}')" && \
PG_CLUSTER="$(pg_lsclusters --no-header | awk 'NR==1{print $2}')" && \
pg_ctlcluster "$PG_VERSION" "$PG_CLUSTER" start && \
su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD 'postgres';\"" && \
su - postgres -c "createdb db_shadow" && \
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" \
SHADOW_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/db_shadow" \
bun prisma migrate diff --from-migrations ./prisma/migrations --to-schema ./prisma/schema.prisma --exit-code && \
pg_ctlcluster "$PG_VERSION" "$PG_CLUSTER" stop

# Copy source code
COPY src ./src
COPY tsconfig.json ./tsconfig.json
Expand Down Expand Up @@ -68,7 +83,7 @@ EXPOSE 52030

# Health check using curl
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:52030/ping || exit 1
CMD curl -f http://localhost:52030/ping && curl -f http://localhost:12121/ || exit 1

# Start command: push schema and start service
CMD ["sh", "-c", "bun prisma db push && bun src/server.ts"]
CMD ["sh", "-c", "bun prisma db push --accept-data-loss && bun src/server.ts"]
8 changes: 5 additions & 3 deletions service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"infra:down": "docker compose -p function-bay -f ./docker-compose.dev.yml --profile infra down",
"infra:logs": "docker compose -p function-bay -f ./docker-compose.dev.yml --profile infra logs -f",
"db:generate": "bun prisma generate",
"db:push:dev": "sh -c 'set -a; . .env; set +a; bun prisma db push'",
"db:push:test": "sh -c 'set -a; . .env.test; set +a; bun prisma db push'",
"db:push:dev": "sh -c 'set -a; . .env; set +a; bun prisma db push --accept-data-loss'",
"db:push:test": "sh -c 'set -a; . .env.test; set +a; bun prisma db push --accept-data-loss'",
"db:push": "bun run db:push:dev && bun run db:push:test",
"test": "bunx vitest run --passWithNoTests",
"test:watch": "bunx vitest watch",
Expand Down Expand Up @@ -48,10 +48,12 @@
"@lowerdeck/service": "^1.0.3",
"@lowerdeck/slugify": "^1.0.4",
"@lowerdeck/snowflake": "^1.0.2",
"@lowerdeck/sentry": "^1.0.2",
"@lowerdeck/validation": "^1.0.4",
"@metorial-services/forge-client": "^1.0.3",
"@prisma/adapter-pg": "^7.2.0",
"@prisma/client": "^7.2.0",
"@sentry/bun": "^10.38.0",
"date-fns": "^4.1.0",
"jszip": "^3.10.1",
"lodash": "4.17.21",
Expand All @@ -60,4 +62,4 @@
"prisma-json-types-generator": "^4.0.1",
"yaml": "^2.8.2"
}
}
}
1 change: 1 addition & 0 deletions service/prisma.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export default defineConfig({
},
datasource: {
url: process.env["DATABASE_URL"],
shadowDatabaseUrl: process.env["SHADOW_DATABASE_URL"],
},
});
271 changes: 271 additions & 0 deletions service/prisma/migrations/20260208195000_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "public";

-- CreateEnum
CREATE TYPE "FunctionStatus" AS ENUM ('active', 'deleted');

-- CreateEnum
CREATE TYPE "FunctionDeploymentStatus" AS ENUM ('pending', 'running', 'succeeded', 'failed');

-- CreateEnum
CREATE TYPE "FunctionDeploymentStepStatus" AS ENUM ('pending', 'running', 'succeeded', 'failed', 'canceled');

-- CreateEnum
CREATE TYPE "FunctionDeploymentStepType" AS ENUM ('deploy');

-- CreateEnum
CREATE TYPE "FunctionVersionStatus" AS ENUM ('active', 'deleted');

-- CreateEnum
CREATE TYPE "FunctionInvocationStatus" AS ENUM ('succeeded', 'failed');

-- CreateEnum
CREATE TYPE "FunctionBundleStatus" AS ENUM ('uploading', 'available', 'failed');

-- CreateTable
CREATE TABLE "Tenant" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"identifier" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Tenant_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "Provider" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"identifier" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Provider_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "Runtime" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"identifier" TEXT NOT NULL,
"name" TEXT NOT NULL,
"providerOid" BIGINT NOT NULL,
"configuration" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Runtime_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "RuntimeForgeWorkflow" (
"oid" BIGINT NOT NULL,
"runtimeOid" BIGINT NOT NULL,
"tenantOid" BIGINT NOT NULL,
"forgeWorkflowId" TEXT NOT NULL,
"forgeTenantId" TEXT NOT NULL,
"forgeWorkflowVersionId" TEXT NOT NULL,

CONSTRAINT "RuntimeForgeWorkflow_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "Function" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"status" "FunctionStatus" NOT NULL,
"identifier" TEXT NOT NULL,
"name" TEXT NOT NULL,
"currentVersionOid" BIGINT,
"tenantOid" BIGINT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"runtimeOid" BIGINT,

CONSTRAINT "Function_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "FunctionDeployment" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"status" "FunctionDeploymentStatus" NOT NULL,
"identifier" TEXT NOT NULL,
"name" TEXT NOT NULL,
"forgeRunId" TEXT,
"forgeWorkflowId" TEXT,
"functionVersionOid" BIGINT,
"functionBundleOid" BIGINT,
"runtimeOid" BIGINT NOT NULL,
"functionOid" BIGINT NOT NULL,
"encryptedEnvironmentVariables" TEXT NOT NULL,
"configuration" JSONB NOT NULL,
"errorCode" TEXT,
"errorMessage" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "FunctionDeployment_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "FunctionDeploymentStep" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"status" "FunctionDeploymentStepStatus" NOT NULL,
"type" "FunctionDeploymentStepType" NOT NULL,
"name" TEXT NOT NULL,
"output" TEXT NOT NULL,
"functionDeploymentOid" BIGINT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"startedAt" TIMESTAMP(3),
"endedAt" TIMESTAMP(3),

CONSTRAINT "FunctionDeploymentStep_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "FunctionVersion" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"status" "FunctionVersionStatus" NOT NULL,
"encryptedEnvironmentVariables" TEXT NOT NULL,
"identifier" TEXT NOT NULL,
"name" TEXT NOT NULL,
"functionOid" BIGINT NOT NULL,
"runtimeOid" BIGINT NOT NULL,
"functionBundleOid" BIGINT NOT NULL,
"configuration" JSONB NOT NULL,
"providerData" JSONB NOT NULL,
"manifest" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "FunctionVersion_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "FunctionInvocation" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"status" "FunctionInvocationStatus" NOT NULL,
"functionVersionOid" BIGINT NOT NULL,
"error" JSONB NOT NULL,
"logs" TEXT NOT NULL,
"computeTimeMs" INTEGER NOT NULL,
"billedTimeMs" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "FunctionInvocation_pkey" PRIMARY KEY ("oid")
);

-- CreateTable
CREATE TABLE "FunctionBundle" (
"oid" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"status" "FunctionBundleStatus" NOT NULL,
"functionOid" BIGINT NOT NULL,
"storageKey" TEXT,
"bucket" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "FunctionBundle_pkey" PRIMARY KEY ("oid")
);

-- CreateIndex
CREATE UNIQUE INDEX "Tenant_id_key" ON "Tenant"("id");

-- CreateIndex
CREATE UNIQUE INDEX "Tenant_identifier_key" ON "Tenant"("identifier");

-- CreateIndex
CREATE UNIQUE INDEX "Provider_id_key" ON "Provider"("id");

-- CreateIndex
CREATE UNIQUE INDEX "Provider_identifier_key" ON "Provider"("identifier");

-- CreateIndex
CREATE UNIQUE INDEX "Runtime_id_key" ON "Runtime"("id");

-- CreateIndex
CREATE UNIQUE INDEX "Runtime_identifier_key" ON "Runtime"("identifier");

-- CreateIndex
CREATE UNIQUE INDEX "RuntimeForgeWorkflow_runtimeOid_tenantOid_key" ON "RuntimeForgeWorkflow"("runtimeOid", "tenantOid");

-- CreateIndex
CREATE UNIQUE INDEX "Function_id_key" ON "Function"("id");

-- CreateIndex
CREATE UNIQUE INDEX "Function_identifier_tenantOid_key" ON "Function"("identifier", "tenantOid");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionDeployment_id_key" ON "FunctionDeployment"("id");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionDeployment_identifier_functionOid_key" ON "FunctionDeployment"("identifier", "functionOid");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionDeploymentStep_id_key" ON "FunctionDeploymentStep"("id");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionVersion_id_key" ON "FunctionVersion"("id");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionVersion_identifier_functionOid_key" ON "FunctionVersion"("identifier", "functionOid");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionInvocation_id_key" ON "FunctionInvocation"("id");

-- CreateIndex
CREATE UNIQUE INDEX "FunctionBundle_id_key" ON "FunctionBundle"("id");

-- AddForeignKey
ALTER TABLE "Runtime" ADD CONSTRAINT "Runtime_providerOid_fkey" FOREIGN KEY ("providerOid") REFERENCES "Provider"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RuntimeForgeWorkflow" ADD CONSTRAINT "RuntimeForgeWorkflow_runtimeOid_fkey" FOREIGN KEY ("runtimeOid") REFERENCES "Runtime"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RuntimeForgeWorkflow" ADD CONSTRAINT "RuntimeForgeWorkflow_tenantOid_fkey" FOREIGN KEY ("tenantOid") REFERENCES "Tenant"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Function" ADD CONSTRAINT "Function_currentVersionOid_fkey" FOREIGN KEY ("currentVersionOid") REFERENCES "FunctionVersion"("oid") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Function" ADD CONSTRAINT "Function_tenantOid_fkey" FOREIGN KEY ("tenantOid") REFERENCES "Tenant"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Function" ADD CONSTRAINT "Function_runtimeOid_fkey" FOREIGN KEY ("runtimeOid") REFERENCES "Runtime"("oid") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionDeployment" ADD CONSTRAINT "FunctionDeployment_functionVersionOid_fkey" FOREIGN KEY ("functionVersionOid") REFERENCES "FunctionVersion"("oid") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionDeployment" ADD CONSTRAINT "FunctionDeployment_functionBundleOid_fkey" FOREIGN KEY ("functionBundleOid") REFERENCES "FunctionBundle"("oid") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionDeployment" ADD CONSTRAINT "FunctionDeployment_runtimeOid_fkey" FOREIGN KEY ("runtimeOid") REFERENCES "Runtime"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionDeployment" ADD CONSTRAINT "FunctionDeployment_functionOid_fkey" FOREIGN KEY ("functionOid") REFERENCES "Function"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionDeploymentStep" ADD CONSTRAINT "FunctionDeploymentStep_functionDeploymentOid_fkey" FOREIGN KEY ("functionDeploymentOid") REFERENCES "FunctionDeployment"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionVersion" ADD CONSTRAINT "FunctionVersion_functionOid_fkey" FOREIGN KEY ("functionOid") REFERENCES "Function"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionVersion" ADD CONSTRAINT "FunctionVersion_runtimeOid_fkey" FOREIGN KEY ("runtimeOid") REFERENCES "Runtime"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionVersion" ADD CONSTRAINT "FunctionVersion_functionBundleOid_fkey" FOREIGN KEY ("functionBundleOid") REFERENCES "FunctionBundle"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionInvocation" ADD CONSTRAINT "FunctionInvocation_functionVersionOid_fkey" FOREIGN KEY ("functionVersionOid") REFERENCES "FunctionVersion"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FunctionBundle" ADD CONSTRAINT "FunctionBundle_functionOid_fkey" FOREIGN KEY ("functionOid") REFERENCES "Function"("oid") ON DELETE RESTRICT ON UPDATE CASCADE;

1 change: 1 addition & 0 deletions service/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
provider = "postgresql"
11 changes: 4 additions & 7 deletions service/src/controllers/tests/function.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { testDb, cleanDatabase } from '../../test/setup';
import { fixtures } from '../../test/fixtures';
import { functionBayClient } from '../../test/client';
import { times } from 'lodash';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { functionBayClient } from '../../test/client';
import { fixtures } from '../../test/fixtures';
import { cleanDatabase, testDb } from '../../test/setup';

const providerMocks = vi.hoisted(() => ({
invokeFunction: vi.fn()
Expand Down Expand Up @@ -31,7 +31,6 @@ describe('function:upsert E2E', () => {
});

expect(result).toMatchObject({
object: 'function_bay#function',
id: expect.any(String),
identifier: 'my-function',
name: 'My Function',
Expand Down Expand Up @@ -99,7 +98,6 @@ describe('function:list E2E', () => {
const [presented] = result.items;
expect(presented).toBeDefined();
expect(presented).toMatchObject({
object: 'function_bay#function',
id: expect.any(String),
identifier: expect.any(String),
name: expect.any(String),
Expand All @@ -125,7 +123,6 @@ describe('function:get E2E', () => {
});

expect(result).toMatchObject({
object: 'function_bay#function',
id: func.id,
identifier: func.identifier,
name: func.name
Expand Down
Loading