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. It also bounds the blast radius — a bad migration to your team schema cannot take the auth tables down with it, and vice versa.
What makes this a NetScript capability and not just "run two Postgres containers" is the typed
data layer: one netscript db add scaffolds the datasource, joins it to the Aspire graph, and —
after a single migrate/generate loop — hands you a fully typed Prisma client scoped to that
datasource, with its own migration history. The isolation is real at the type level, not just the
connection string, and the whole db toolchain is multi-database-aware, so every command takes a
--db target rather than leaving you to juggle two schemas by hand.
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 running. 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 https://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 — the same catalog-versus-domain split a
real NetScript app runs on, cleanly separate from auth and the primary database. You also saw the
boundary: org scoping is app-level, not a framework primitive. Next comes the moment this data model
exists for — adding a member to a team — and you do it off the request path with a background job.