Skip to main content

stacks-orm

Use when working with the Stacks ORM - defining models with defineModel(), model relationships (hasOne, hasMany, belongsTo, belongsToMany, morphOne, hasManyThrough), attributes, traits, factories, computed properties, query building, transactions, or the 107 built-in models. Covers @stacksjs/orm, storage/framework/orm/, and storage/framework/defaults/app/Models/.

소스 정보

저장소
stacksjs/stacks
최근 소스 활동
2026년 10월 7일 18:12
감지된 SKILL.md 언어
영어
스타
631
포크
20

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
stacks-orm
description
Use when working with the Stacks ORM - defining models with defineModel(), model relationships (hasOne, hasMany, belongsTo, belongsToMany, morphOne, hasManyThrough), attributes, traits, factories, computed properties, query building, transactions, or the 107 built-in models. Covers @stacksjs/orm, storage/framework/orm/, and storage/framework/defaults/app/Models/.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript, SQLite >= 3.47.2
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks ORM ## Key Paths - Core ORM package: `storage/framework/core/orm/src/` - ORM implementation: `storage/framework/orm/` - Model definitions: `storage/framework/defaults/app/Models/` (107 models) - Application models: `app/Models/` - Default model templates: `storage/framework/defaults/app/Models/` - ORM type globals: `storage/framework/types/orm-globals.d.ts` - Attribute types: `storage/framework/types/attributes.ts` (240+ attributes) - Model events: `storage/framework/types/events.ts` ## Source Files ``` orm/src/ ├── define-model.ts # defineModel() + buildEventHooks() + buildTraitMethods() ├── index.ts # Re-exports orm/src + db + subquery + transaction + types ├── db.ts # Database query builder bridge ├── subquery.ts # Subquery support ├── transaction.ts # transaction(), savepoint(), transactional() ├── model-types.ts # ModelRow<T>, NewModelData<T>, UpdateModelData<T> ├── types.ts # ORM type definitions ├── utils.ts # modelTableName, getRelations, getFillableAttributes, etc. ├── builder.ts # Query builder integration ├── generated/ # Auto-generated model types and table traits │ ├── types.ts │ ├── index.ts │ └── table-traits.ts └── traits/ ├── index.ts # Re-exports all trait creators ├── taggable.ts # createTaggableMethods() ├── categorizable.ts # createCategorizableMethods() ├── commentable.ts # createCommentableMethods() ├── billable.ts # createBillableMethods() ├── likeable.ts # createLikeableMethods() └── two-factor.ts # createTwoFactorMethods() ``` ## defineModel() API (define-model.ts) Wraps bun-query-builder's `createModel()` with Stacks-specific enhancements: - Event dispatching via `traits.observe` (emits `{model}:created`, `{model}:updated`, `{model}:deleted` via `@stacksjs/events`) - Trait methods (billable, taggable, categorizable, commentable, likeable, 2FA) - Raw definition access for generators (`getDefinition()`, `_isStacksModel`) ```typescript import { defineModel } from '@stacksjs/orm' export default defineModel({ name: 'Product', table: 'products', primaryKey: 'id', // default: 'id' autoIncrement: true, // default: true traits: { useUuid: true, // adds uuid column useTimestamps: true, // adds created_at, updated_at useAuth: { usePasskey: true }, // adds auth columns + passkey support useSocials: ['github'], // social login providers useSearch: { // search engine indexing displayable: ['name', 'email'], searchable: ['name', 'email'], sortable: ['name', 'created_at'], filterable: ['status'] }, useSeeder: { count: 10 }, // or just `true` (defaults to 10) useApi: { uri: 'products', routes: ['index', 'store', 'show', 'update', 'destroy'] }, categorizable: true, // adds category relations via categorizable table taggable: true, // adds tag relations via taggable table commentables: true, // adds comment relations (NOTE: plural 's' in key) billable: true, // adds Stripe integration methods likeable: true, // or { table?: string, foreignKey?: string } observe: true, // emit model events (true = all, or array: ['create','update','delete']) }, // Relationships hasOne: ['Subscriber'], hasMany: ['Post', 'Order'], belongsTo: ['User', 'Category'], belongsToMany: ['Tag'], hasOneThrough: ['Profile'], morphOne: 'Image', // or { model, morphName?, type?, id? } indexes: [ { name: 'idx_email', columns: ['email'] }, { name: 'idx_composite', columns: ['email', 'name'] } ], attributes: { name: { order: 1, fillable: true, required: true, unique: false, validation: { rule: schema.string().max(100), message: { max: 'Name is too long' } }, factory: (faker) => faker.lorem.word() }, price: { fillable: true, required: true, validation: { rule: schema.number().min(1) }, factory: (faker) => faker.datatype.number({ min: 100, max: 10000 }) }, status: { fillable: true, default: 'draft', validation: { rule: schema.enum(['draft', 'published', 'archived']) } }, password: { hidden: true, // excluded from JSON serialization guarded: true, // not mass-assignable } }, // Computed properties (accessors) get: { fullName: (attrs) => `${attrs.first_name} ${attrs.last_name}`, formattedPrice: (attrs) => `$${(attrs.price / 100).toFixed(2)}` }, // Mutators (setters) set: { email: (attrs) => attrs.email?.toLowerCase() }, // Model hooks (lifecycle callbacks) hooks: { afterCreate: (model) => { /* ... */ }, afterUpdate: (model) => { /* ... */ }, afterDelete: (model) => { /* ... */ }, }, dashboard: { highlight: true } // highlight in admin dashboard } as const) ``` ### How defineModel() Works Internally 1. `buildEventHooks(definition)` -- if `traits.observe` is truthy, creates `afterCreate`/`afterUpdate`/`afterDelete` hooks that lazy-import `@stacksjs/events` and call `dispatch()` 2. Merges event hooks with any user-defined hooks 3. Calls `createModel(defWithHooks)` from bun-query-builder (provides typed query methods) 4. `buildTraitMethods(definition)` -- checks each trait flag and creates method objects 5. Returns `Object.assign(baseModel, traitMethods, definition)` + `getDefinition()` + `_isStacksModel` ## Transactions (transaction.ts) ```typescript import { transaction, savepoint, transactional } from '@stacksjs/orm' // Basic transaction -- auto-commit on success, auto-rollback on error const result = await transaction(async (tx) => { await tx.insertInto('users').values({ name: 'John' }).execute() await tx.insertInto('profiles').values({ user_id: 1 }).execute() return 'success' }) // With options await transaction(callback, { retries: 3, isolation: 'serializable', // 'read committed' | 'repeatable read' | 'serializable' readOnly: false, onRollback: (error) => console.error(error), afterRollback: () => { /* cleanup */ } }) // Savepoints (nested transactions) await transaction(async (tx) => { await tx.insertInto('users').values({ name: 'Bob' }).execute() await savepoint(async (sp) => { await sp.insertInto('logs').values({ action: 'created' }).execute() // If this fails, only this savepoint rolls back }) }) // Decorator-style -- wraps function to auto-run in transaction const createUser = transactional(async (tx, name: string, email: string) => { const user = await tx.insertInto('users').values({ name }).returningAll().executeTakeFirst() await tx.insertInto('profiles').values({ user_id: user.id }).execute() return user }) await createUser('Alice', 'alice@example.com') // auto-wrapped ``` Both `transaction()` and `savepoint()` delegate to `db.transaction()` and `db.savepoint()` from `@stacksjs/database`. ### Transaction executor boundary Every query that must commit or roll back together must use the callback handle (`tx` or `sp`), including validation reads, pivot writes, and the final readback. Do not mix `Model.find()`, `Model.create()`, instance `update()` / `delete()`, or instance relation calls into a raw query-builder transaction. The model executor is a separate execution surface and is not rebound to the callback handle. On SQLite it may use a separate connection, so it cannot observe an uncommitted row written through `tx`. `runInTransactionScope()` buffers supported side effects until commit, but it does not rebind model queries. For a transaction-backed custom action, use the model definition as the schema and relationship source of truth, then execute the complete persistence workflow through `tx`. Read the created or updated row through `tx` before returning so a readback failure also rolls back the mutation. ## Trait Methods (traits/) ### Taggable (when `traits.taggable: true`) Uses `taggable` table with polymorphic `taggable_type` + `taggable_id` columns. - `Model._taggable.tags(id: number): Promise<any[]>` - `Model._taggable.tagCount(id: number): Promise<number>` -- uses `count(*)` - `Model._taggable.addTag(id, { name, description? }): Promise<any>` -- auto-generates slug - `Model._taggable.activeTags(id): Promise<any[]>` -- filters `is_active = true` - `Model._taggable.inactiveTags(id): Promise<any[]>` -- filters `is_active = false` - `Model._taggable.removeTag(id, tagId): Promise<void>` ### Categorizable (when `traits.categorizable: true`) Uses `categorizable` + `categorizable_models` pivot table. - `Model._categorizable.categories(id): Promise<any[]>` -- joins through pivot - `Model._categorizable.categoryCount(id): Promise<number>` - `Model._categorizable.addCategory(id, { name, description? }): Promise<any>` -- creates category if not exists, then links - `Model._categorizable.activeCategories(id): Promise<any[]>` - `Model._categorizable.inactiveCategories(id): Promise<any[]>` - `Model._categorizable.removeCategory(id, categoryId): Promise<void>` -- removes pivot link ### Commentable (when `traits.commentables: true`) Uses `comments` table with `commentables_id` + `commentables_type` columns. - `Model._commentable.comments(id): Promise<any[]>` - `Model._commentable.commentCount(id): Promise<number>` - `Model._commentable.addComment(id, { title, body }): Promise<any>` -- status defaults to `'pending'` - `Model._commentable.approvedComments(id): Promise<any[]>` -- status = `'approved'` - `Model._commentable.pendingComments(id): Promise<any[]>` -- status = `'pending'` - `Model._commentable.rejectedComments(id): Promise<any[]>` -- status = `'rejected'` ### Likeable (when `traits.likeable: true` or `{ table?, foreignKey? }`) Table defaults to `{tableName}_likes`, FK defaults to `{singular}_id`. - `Model._likeable.likes(id): Promise<any[]>` - `Model._likeable.likeCount(id): Promise<number>` - `Model._likeable.like(id, userId): Promise<any>` - `Model._likeable.unlike(id, userId): Promise<void>` - `Model._likeable.isLiked(id, userId): Promise<boolean>` ### Billable (when `traits.billable: true`) -- the configured payment driver All methods lazy-import `@stacksjs/payments`. Full reference: `stacks-payments`. - Provider-neutral (`config.payment.driver`): `paymentCustomer(model)`, `charge(model, money, paymentMethodId, options)`, `createPayment(model, money, options)`, `checkout(model, { mode, lines, successUrl, cancelUrl })`, `paymentMethods(model)`, `removePaymentMethod(model, providerId)`, `newSubscription(model, type, price)` -> `SubscriptionSummary`, `cancelSubscription(model, providerId, { atPeriodEnd })` (ownership-checked), `activeSubscription(model)` -> `{ subscription, providerSubscription }` - Stripe only (throw `PaymentUnsupportedError` under another driver): `createStripeUser`, `updateStripeUser`, `deleteStripeUser`, `createOrGetStripeUser`, `retrieveStripeUser`, `syncStripeCustomerDetails`, `setDefaultPaymentMethod`, `addPaymentMethod`, `updateSubscription`, `createSetupIntent`, `subscriptionHistory`, Connect methods - Local: `defaultPaymentMethod(model)`, `storeTransaction(model, productId, options)`, `transactionHistory(model)` ### Two-Factor Auth (when `traits.useAuth.useTwoFactor: true`)
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기