Install
$ agentstack add skill-param087-saas-starter-skills-data-access-layer ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Data Access Layer
Overview
Don't scatter ORM calls across components and route handlers. Put every query behind a server-only data-access layer (DAL): small typed functions that take an explicit orgId and never return another tenant's rows. The rest of the app calls getProjects(orgId), not db.select(...). This centralizes tenant scoping, caching, and the place you write tests.
When to use
- Any read or write to the database.
- You catch yourself importing
dbinto a React component. - You want one place to enforce "every query is scoped to the current org".
The pattern
// src/server/services/projects.ts
import "server-only";
import { and, eq, desc } from "drizzle-orm";
import { db } from "@/server/db/client";
import { projects } from "@/server/db/schema";
export async function listProjects(orgId: string) {
return db.select().from(projects)
.where(eq(projects.orgId, orgId))
.orderBy(desc(projects.createdAt));
}
export async function getProject(orgId: string, id: string) {
const [row] = await db.select().from(projects)
.where(and(eq(projects.id, id), eq(projects.orgId, orgId))) // scope by BOTH
.limit(1);
return row ?? null;
}
export async function createProject(orgId: string, input: { name: string }) {
const [row] = await db.insert(projects).values({ orgId, ...input }).returning();
return row;
}
Callers (server components, actions, route handlers) pass the orgId resolved from the session — they never invent it.
Why a DAL
- Isolation by construction. Lookups filter on
id AND org_id, so a guessed UUID from another tenant returnsnull, not data. - Typed end-to-end.
returning()gives you the row type for free. - Testable. Mock or seed the DB and test functions directly, no HTTP needed.
- Cacheable. Wrap reads in
cache()/unstable_cachein one spot.
Pitfalls
- Calling
dbfrom components — couples UI to the schema and risks shipping queries to the client. Always go through the DAL. - Lookups by
idonly —where(eq(projects.id, id))withoutorgIdis a cross-tenant read. Scope by both. - Returning raw rows with secrets — strip sensitive columns (tokens, hashes) in the DAL.
- N+1 in loops — batch with
inArrayor a join instead of awaiting per item. - Forgetting
server-only— without it a refactor can pull DB code into the client bundle.
Hand-off
A typed, tenant-safe query layer. api-routes-and-validation and Server Actions call it after validating input; authorization-rbac gates which functions a role may invoke.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: param087
- Source: param087/saas-starter-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.