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.
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 workspacescaffoldeddatabase/workspace/. - [ ] The
workspaceresource is green in the Aspire dashboard. - [ ]
database/workspace/schema/schema.prismadefinesWorkspaceandMember. - [ ]
netscript db status --db workspacereports 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.