skills/strataljs/stratal/stratal-framework

stratal-framework

SKILL.md

@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
Weekly Installs
5
GitHub Stars
7
First Seen
8 days ago
Installed on
opencode5
claude-code5
github-copilot5
codex5
kimi-cli5
gemini-cli5