stratal-framework
@stratal/framework
Higher-level framework modules for Stratal: authentication (Better Auth), database ORM (ZenStack), RBAC (Casbin), authorization guards, and test data factories. Full documentation at stratal.dev/framework.
Authentication (AuthModule)
Docs: Auth
@Module({
imports: [
AuthModule.forRootAsync({
inject: [authConfig.KEY],
useFactory: (config) => ({
secret: config.secret,
baseURL: config.baseURL,
// ...Better Auth options
}),
}),
],
})
export class AppModule {}
AuthContext is request-scoped and available via @inject(DI_TOKENS.AuthContext). Key methods: isAuthenticated(), getUserId(), requireUserId(), getAuthContext(). AuthModule auto-registers session verification middleware.
Database (DatabaseModule)
Docs: Database · Database Events
Each connection brings its own ZenStack schema. Users write per-connection .zmodel files, run zenstack generate independently, and pass each connection's schema in the config.
import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres';
import { Pool } from 'pg';
import { schema } from '../db/main/schema';
DatabaseModule.forRootAsync({
inject: [DI_TOKENS.CloudflareEnv],
useFactory: (env: StratalEnv) => ({
default: 'main',
connections: [
{
name: 'main',
schema,
dialect: () => new PostgresDialect({
pool: new Pool({ connectionString: env.DB.connectionString, max: 1 }),
}),
},
],
}),
})
Type augmentation:
declare module '@stratal/framework/database' {
interface StratalDatabase {
schemas: {
main: MainSchemaType;
analytics: AnalyticsSchemaType;
};
defaultConnection: 'main';
}
}
Inject with @inject(DI_TOKENS.Database) (default connection) or @InjectDB('name') (named). Plugins: EventEmitterPlugin, SchemaSwitcherPlugin, ErrorHandlerPlugin.
Database events follow the pattern {phase}.{Model}.{operation} — e.g., after.User.create. Augment CustomEventRegistry with DatabaseEvents<ConnectionName> for type safety.
Multi-Connection Per-Connection Schemas
Each connection has its own .zmodel file and its own zenstack generate output. For shared models, use ZenStack's native import/extends between schema files.
Per-connection schema setup:
db/
main/
schema.zmodel # Main connection models
schema.ts # Generated by zenstack generate
analytics/
schema.zmodel # Analytics connection models
schema.ts # Generated by zenstack generate
Config with per-connection schemas:
import { schema as mainSchema } from '../db/main/schema';
import { schema as analyticsSchema } from '../db/analytics/schema';
DatabaseModule.forRootAsync({
inject: [DI_TOKENS.CloudflareEnv],
useFactory: (env: StratalEnv) => ({
default: 'main',
connections: [
{ name: 'main', schema: mainSchema, dialect: () => ... },
{ name: 'analytics', schema: analyticsSchema, dialect: () => ... },
],
}),
})
Migrations — use standard ZenStack commands per connection:
zenstack db push --schema db/main/schema.zmodel
zenstack db push --schema db/analytics/schema.zmodel
RBAC (RbacModule)
Docs: RBAC
RbacModule.forRoot({
model: casbinModel,
defaultPolicies: [['admin', 'users:*', '.*']],
roleHierarchy: [['super_admin', 'admin']],
})
CasbinService is request-scoped. Key methods: hasPermission(), currentUserHasPermission(), hasAnyPermission(), currentUserHasAnyPermission(), addRoleForUser(), getRolesForUser(), getCurrentUserRoles(). Requires a CasbinRule model in your ZenStack schema.
AuthGuard
Docs: AuthGuard
// Auth only — checks isAuthenticated()
@UseGuards(AuthGuard())
// Auth + permissions — checks isAuthenticated() + CasbinService
@UseGuards(AuthGuard({ scopes: ['users:read'] }))
Throws UserNotAuthenticatedError (401) or InsufficientPermissionsError (403). Apply at class or method level.
Test Factories
Docs: Factories
export class UserFactory extends Factory<User, UserCreateInput> {
protected model = 'user';
protected definition() {
return {
email: this.faker.internet.email(),
name: this.faker.person.fullName(),
};
}
admin() {
return this.state((attrs) => ({ ...attrs, role: 'admin' }));
}
}
// Usage
const user = await new UserFactory().create(db);
const admins = await new UserFactory().admin().count(5).createManyAndReturn(db);
Methods: make() (build without saving), create() (persist), makeMany(), createMany(), createManyAndReturn(). Use state() for variants and count() for batch creation.
Sub-path Imports
| Path | Key Exports |
|---|---|
@stratal/framework/auth |
AuthModule, AuthService, AuthContext |
@stratal/framework/database |
DatabaseModule, DatabaseService, @InjectDB, databaseI18n |
@stratal/framework/rbac |
RbacModule, CasbinService |
@stratal/framework/guards |
AuthGuard |
@stratal/framework/factory |
Factory, Sequence |
@stratal/framework/context |
RequestContext |