Skip to main content
Alpha

Workspace data

Your app can sign users in, but it has nothing of its own to store yet. This chapter gives the workspace its own data: a second, isolated database with its own Prisma schema, migration history, and generated client — separate from the primary Postgres the auth plugin migrated into. The reason to isolate it is the same reason teams isolate any domain: an independent lifecycle you can migrate, scale, and back up on its own.

  1. 1 · Scaffold
  2. 2 · Auth
  3. 3 · Workspace data
  4. 4 · Provision job
  5. 5 · Route authz
  6. 6 · Deploy

What you will build

A second Postgres datasource named workspace, scaffolded by netscript db add, with its own Member and Workspace models, its own migration, and its own typed Prisma client. By the end you can import that client at database/workspace/schema/.generated/client.server.ts and query workspace records — fully independent from the primary database and the auth tables.

Before you begin

You need the auth layer from chapter 2 and aspire startning. The second database is provisioned as its own container in the Aspire graph, so Docker must be up too. Confirm the primary is migrated and Aspire is live:

# In my-workspace/, with `aspire start` up in another terminal
netscript db status        # primary datasource is migrated (chapter 2)
docker info                # Docker engine is running

Step 1 — Scaffold the second database

From the workspace root, run netscript db add <engine>. The --name flag sets the config key the datasource is registered under — use workspace so it does not collide with the primary postgres:

# Add a second Postgres for workspace records. Registered under
# NetScript.Databases.workspace, scaffolded into database/workspace/.
netscript db add postgres --name workspace

In one pass, db add scaffolds a workspace at database/workspace/ (its own schema/schema.prisma, prisma.config.ts, and scripts/), registers the datasource in appsettings.json under NetScript.Databases.workspace, adds it as a project member, and regenerates the Aspire config so the new container joins the resource graph.

Step 2 — Restart Aspire so the container joins the graph

Because db add regenerated the Aspire config, the new database becomes a container in the resource graph. Restart the AppHost so it provisions:

cd aspire
aspire start

Open the dashboard at http://localhost:18888 and confirm the new workspace resource goes green alongside the existing postgres and redis.

Step 3 — Define the workspace models

Edit the second datasource's schema to hold workspace records. A Workspace row is a team; a Member row links a signed-in user (by their auth subject) to a workspace:

// database/workspace/schema/schema.prisma — add these models
model Workspace {
  id        String   @id @default(cuid())
  name      String
  createdAt DateTime @default(now())
  members   Member[]
}

model Member {
  id          String    @id @default(cuid())
  // The auth Principal.subject from chapter 2 — the stable user identifier.
  subject     String
  role        String    @default("member")
  workspace   Workspace @relation(fields: [workspaceId], references: [id])
  workspaceId String
  createdAt   DateTime  @default(now())

  @@unique([workspaceId, subject])
}

The subject column is the link back to auth: it stores the Principal.subject value the auth backend resolves for a signed-in user, so a workspace member is "this auth identity, in this workspace."

Step 4 — Migrate and generate the second datasource

The netscript db operations are multi-database aware: every one takes a --db <target> flag, where the target is a config key, a database name, or all. Point each command at the workspace datasource. Run these from the workspace root with aspire start up:

# Create + apply the first migration for the SECOND datasource only.
netscript db init --db workspace --name init
# Generate the Deno-runtime Prisma client + zod schemas into
# database/workspace/schema/.generated for the workspace datasource.
netscript db generate --db workspace
# Confirm the workspace datasource is migrated and in sync.
netscript db status --db workspace

Each datasource keeps its own migration history and generated client. Migrating --db workspace never touches the primary Postgres (or the auth tables), and vice versa.

Step 5 — Query the workspace client

After db generate --db workspace, the second datasource has its own typed client. Import it exactly like the primary — just from the new path. It is an independent PrismaClient, typed off the Workspace/Member models:

// services/workspace/src/db.ts
// The SECOND datasource generates its OWN client. Import it from its path.
import { PrismaClient as WorkspacePrisma } from '../../database/workspace/schema/.generated/client.server.ts';

export const workspaceDb = new WorkspacePrisma();

// Fully typed off the workspace schema — separate from the primary client.
const teams = await workspaceDb.workspace.findMany({ take: 20 });
console.log(teams.length);

Extend — app-level org scoping

The data model above is single-tenant: every Member belongs to a Workspace, but there is no framework-managed notion of an organization that owns many workspaces. NetScript does not ship orgs, tenants, or RBAC roles — if you want multi-tenant scoping, you add it yourself, in your own schema and your own queries.

Verify your progress

Confirm the second datasource is migrated and its client generated:

netscript db status --db workspace
ls database/workspace/schema/.generated/client.server.ts

db status --db workspace should report it migrated and in sync, and the generated client file should exist.

  • [ ] netscript db add postgres --name workspace scaffolded database/workspace/.
  • [ ] The workspace resource is green in the Aspire dashboard.
  • [ ] database/workspace/schema/schema.prisma defines Workspace and Member.
  • [ ] netscript db status --db workspace reports migrated and in sync.
  • [ ] The generated client exists at database/workspace/schema/.generated/client.server.ts.

What you built

Your workspace now owns its data: a second, isolated Postgres datasource with Workspace and Member models, its own migration history, and its own typed client — cleanly separate from auth and the primary database. You also saw the boundary: org scoping is app-level, not a framework primitive. Next you provision new members off the request path with a background job.