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
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Forge is a build orchestration service that wraps different cloud build provider
- **Log Management**: Centralized log collection and retrieval for all build runs
- **Environment Variables**: Encrypted environment variable storage and injection
- **Build Versioning**: Track and manage different versions of your workflows
- **Instance Isolation**: Multi-tenant support with instance-based resource isolation
- **Tenant Isolation**: Multi-tenant architecture for isolated projects

## Quick Start

Expand Down Expand Up @@ -152,20 +152,20 @@ let client = createForgeClient({

### Core API Examples

#### 1. Instance Management
#### 1. Tenant Management

Instances represent isolated tenants or projects:
Tenants represent isolated tenants or projects:

```typescript
// Create/update an instance
let instance = await client.instance.upsert({
// Create/update an tenant
let tenant = await client.tenant.upsert({
name: 'My Project',
identifier: 'my-project',
});

// Get an instance
let retrievedInstance = await client.instance.get({
instanceId: instance.id,
// Get an tenant
let retrievedTenant = await client.tenant.get({
tenantId: tenant.id,
});
```

Expand All @@ -176,34 +176,34 @@ Workflows define build processes:
```typescript
// Create/update a workflow
let workflow = await client.workflow.upsert({
instanceId: instance.id,
tenantId: tenant.id,
name: 'Build and Test',
identifier: 'build-test',
});

// List workflows
let workflows = await client.workflow.list({
instanceId: instance.id,
tenantId: tenant.id,
limit: 10,
order: 'desc',
});

// Get a specific workflow
let workflowDetails = await client.workflow.get({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
});

// Update a workflow
let updated = await client.workflow.update({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
name: 'Build, Test, and Deploy',
});

// Delete a workflow
await client.workflow.delete({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
});
```
Expand All @@ -215,7 +215,7 @@ Versions allow you to define the actual build steps:
```typescript
// Create a workflow version with steps
let version = await client.workflowVersion.create({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
name: 'v1.0.0',
steps: [
Expand All @@ -242,14 +242,14 @@ let version = await client.workflowVersion.create({

// List versions
let versions = await client.workflowVersion.list({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
limit: 10,
});

// Get version details
let versionDetails = await client.workflowVersion.get({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
});
```
Expand All @@ -261,7 +261,7 @@ Execute workflows with environment variables and files:
```typescript
// Create a workflow run
let run = await client.workflowRun.create({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
env: {
NODE_ENV: 'production',
Expand All @@ -287,15 +287,15 @@ console.log('Steps:', run.steps);

// List workflow runs
let runs = await client.workflowRun.list({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
limit: 20,
order: 'desc',
});

// Get detailed run information
let runDetails = await client.workflowRun.get({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
workflowRunId: run.id,
});
Expand All @@ -312,7 +312,7 @@ Retrieve build logs for workflow runs:
```typescript
// Get all step outputs for a run
let outputs = await client.workflowRun.getOutput({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
workflowRunId: run.id,
});
Expand All @@ -326,7 +326,7 @@ for (let output of outputs) {

// Get output for a specific step
let stepOutput = await client.workflowRun.getOutputForStep({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
workflowRunId: run.id,
workflowRunStepId: run.steps[0].id,
Expand All @@ -342,7 +342,7 @@ Access build artifacts generated during workflow runs:
```typescript
// List artifacts for a workflow
let artifacts = await client.workflowArtifact.list({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
limit: 10,
});
Expand All @@ -355,7 +355,7 @@ for (let artifact of artifacts.items) {

// Get a specific artifact
let artifact = await client.workflowArtifact.get({
instanceId: instance.id,
tenantId: tenant.id,
workflowId: workflow.id,
});

Expand Down
2 changes: 1 addition & 1 deletion clients/typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metorial-services/forge-client",
"version": "1.0.2",
"version": "1.0.3",
"publishConfig": {
"access": "public"
},
Expand Down
8 changes: 4 additions & 4 deletions service/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ datasource db {
provider = "postgresql"
}

model Instance {
model Tenant {
oid BigInt @id
id String @unique

Expand Down Expand Up @@ -52,8 +52,8 @@ model Workflow {
identifier String
name String

instanceOid BigInt
instance Instance @relation(fields: [instanceOid], references: [oid])
tenantOid BigInt
tenant Tenant @relation(fields: [tenantOid], references: [oid])

providerOid BigInt
provider Provider @relation(fields: [providerOid], references: [oid])
Expand All @@ -69,7 +69,7 @@ model Workflow {
artifacts WorkflowArtifact[]
runs WorkflowRun[]

@@unique([instanceOid, identifier])
@@unique([tenantOid, identifier])
}

model WorkflowVersion {
Expand Down
4 changes: 2 additions & 2 deletions service/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { apiMux } from '@lowerdeck/api-mux';
import { createServer, rpcMux, type InferClient } from '@lowerdeck/rpc-server';
import { app } from './_app';
import { instanceController } from './instance';
import { providerController } from './provider';
import { tenantController } from './tenant';
import { workflowController } from './workflow';
import { workflowArtifactController } from './workflowArtifact';
import { workflowRunController } from './workflowRun';
import { workflowVersionController } from './workflowVersion';

export let rootController = app.controller({
instance: instanceController,
tenant: tenantController,
provider: providerController,
workflow: workflowController,
workflowArtifact: workflowArtifactController,
Expand Down
42 changes: 0 additions & 42 deletions service/src/controllers/instance.ts

This file was deleted.

42 changes: 42 additions & 0 deletions service/src/controllers/tenant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { v } from '@lowerdeck/validation';
import { tenantPresenter } from '../presenters/tenant';
import { tenantService } from '../services';
import { app } from './_app';

export let tenantApp = app.use(async ctx => {
let tenantId = ctx.body.tenantId;
if (!tenantId) throw new Error('Tenant ID is required');

let tenant = await tenantService.getTenantById({ id: tenantId });

return { tenant };
});

export let tenantController = app.controller({
upsert: app
.handler()
.input(
v.object({
name: v.string(),
identifier: v.string()
})
)
.do(async ctx => {
let tenant = await tenantService.upsertTenant({
input: {
name: ctx.input.name,
identifier: ctx.input.identifier
}
});
return tenantPresenter(tenant);
}),

get: tenantApp
.handler()
.input(
v.object({
tenantId: v.string()
})
)
.do(async ctx => tenantPresenter(ctx.tenant))
});
24 changes: 12 additions & 12 deletions service/src/controllers/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,34 @@ import { v } from '@lowerdeck/validation';
import { workflowPresenter } from '../presenters/workflow';
import { workflowService } from '../services';
import { app } from './_app';
import { instanceApp } from './instance';
import { tenantApp } from './tenant';

export let workflowApp = instanceApp.use(async ctx => {
export let workflowApp = tenantApp.use(async ctx => {
let workflowId = ctx.body.workflowId;
if (!workflowId) throw new Error('Workflow ID is required');

let workflow = await workflowService.getWorkflowById({
id: workflowId,
instance: ctx.instance
tenant: ctx.tenant
});

return { workflow };
});

export let workflowController = app.controller({
upsert: instanceApp
upsert: tenantApp
.handler()
.input(
v.object({
instanceId: v.string(),
tenantId: v.string(),

name: v.string(),
identifier: v.string()
})
)
.do(async ctx => {
let workflow = await workflowService.upsertWorkflow({
instance: ctx.instance,
tenant: ctx.tenant,
input: {
name: ctx.input.name,
identifier: ctx.input.identifier
Expand All @@ -39,18 +39,18 @@ export let workflowController = app.controller({
return workflowPresenter(workflow);
}),

list: instanceApp
list: tenantApp
.handler()
.input(
Paginator.validate(
v.object({
instanceId: v.string()
tenantId: v.string()
})
)
)
.do(async ctx => {
let paginator = await workflowService.listWorkflows({
instance: ctx.instance
tenant: ctx.tenant
});

let list = await paginator.run(ctx.input);
Expand All @@ -62,7 +62,7 @@ export let workflowController = app.controller({
.handler()
.input(
v.object({
instanceId: v.string(),
tenantId: v.string(),
workflowId: v.string()
})
)
Expand All @@ -72,7 +72,7 @@ export let workflowController = app.controller({
.handler()
.input(
v.object({
instanceId: v.string(),
tenantId: v.string(),
workflowId: v.string()
})
)
Expand All @@ -88,7 +88,7 @@ export let workflowController = app.controller({
.handler()
.input(
v.object({
instanceId: v.string(),
tenantId: v.string(),
workflowId: v.string(),

name: v.optional(v.string())
Expand Down
Loading