Skip to main content 홈 크리에이터 stacksjs postline stacks-orm
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 50+ built-in models. Covers @stacksjs/orm, storage/framework/orm/, and storage/framework/defaults/app/Models/.
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/stacksjs/postline --skill stacks-orm명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중...
Use when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.
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 50+ 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/ (50+ 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)
import { defineModel } from '@stacksjs/orm'
export default defineModel ({
name : 'Product' ,
table : 'products' ,
primaryKey : 'id' ,
autoIncrement : true ,
traits : {
useUuid : true ,
useTimestamps : true ,
useAuth : { usePasskey : true },
useSocials : ['github' ],
useSearch : {
displayable : ['name' , 'email' ],
searchable : ['name' , 'email' ],
sortable : ['name' , 'created_at' ],
filterable : ['status' ]
},
useSeeder : { count : 10 },
useApi : {
uri : 'products' ,
routes : ['index' , 'store' , 'show' , 'update' , 'destroy' ]
},
categorizable : true ,
taggable : true ,
commentables : true ,
billable : true ,
likeable : true ,
observe : true ,
},
hasOne : ['Subscriber' ],
hasMany : ['Post' , 'Order' ],
belongsTo : ['User' , 'Category' ],
belongsToMany : ['Tag' ],
hasOneThrough : ['Profile' ],
morphOne : 'Image' ,
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 ().maxLength (100 ),
message : { maxLength : '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 ,
guarded : true ,
}
},
get : {
fullName : (attrs ) => `${attrs.first_name} ${attrs.last_name} ` ,
formattedPrice : (attrs ) => `$${(attrs.price / 100 ).toFixed(2 )} `
},
set : {
email : (attrs ) => attrs.email ?.toLowerCase ()
},
hooks : {
afterCreate : (model ) => { },
afterUpdate : (model ) => { },
afterDelete : (model ) => { },
},
dashboard : { highlight : true }
} as const )
How defineModel() Works Internally
buildEventHooks(definition) -- if traits.observe is truthy, creates afterCreate/afterUpdate/afterDelete hooks that lazy-import @stacksjs/events and call dispatch()
Merges event hooks with any user-defined hooks
Calls createModel(defWithHooks) from bun-query-builder (provides typed query methods)
buildTraitMethods(definition) -- checks each trait flag and creates method objects
Returns Object.assign(baseModel, traitMethods, definition) + getDefinition() + _isStacksModel
Transactions (transaction.ts) import { transaction, savepoint, transactional } from '@stacksjs/orm'
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'
})
await transaction (callback, {
retries : 3 ,
isolation : 'serializable' ,
readOnly : false ,
onRollback : (error ) => console .error (error),
afterRollback : () => { }
})
await transaction (async (tx) => {
await tx.insertInto ('users' ).values ({ name : 'Bob' }).execute ()
await savepoint (async (sp) => {
await sp.insertInto ('logs' ).values ({ action : 'created' }).execute ()
})
})
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' )
Both transaction() and savepoint() delegate to db.transaction() and db.savepoint() from @stacksjs/database.
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) -- Stripe integration All methods lazy-import @stacksjs/payments.
createStripeUser(model, options), updateStripeUser(model, options), deleteStripeUser(model)
createOrGetStripeUser(model, options), retrieveStripeUser(model)
defaultPaymentMethod(model), setDefaultPaymentMethod(model, pmId), addPaymentMethod(model, paymentMethodId), paymentMethods(model, cardType?)
newSubscription(model, type, lookupKey, options) -- returns { subscription, paymentIntent }
updateSubscription(model, type, lookupKey, options), cancelSubscription(model, providerId, options)
activeSubscription(model) -- queries subscriptions table for provider_status = 'active', then retrieves from Stripe
checkout(model, priceIds[], options) -- supports enableTax, allowPromotions options
createSetupIntent(model, options), subscriptionHistory(model), transactionHistory(model)
Two-Factor Auth (when traits.useAuth.useTwoFactor: true)
Model._twoFactor.generateTwoFactorForModel(model) -- generates secret, calls model.update()
Model._twoFactor.verifyTwoFactorCode(model, code): Promise<boolean>
Auto-Generated System Fields
id -- primary key (auto-increment)
created_at, updated_at -- when useTimestamps: true
uuid -- when useUuid: true
deleted_at -- when soft deletes enabled
stripe_id -- when billable: true
two_factor_secret, public_key -- when useAuth: { usePasskey: true }
Naming Conventions
Model: PascalCase (ProductVariant)
Table: snake_case plural (product_variants)
Column: snake_case (first_name)
Foreign key: {singular_model}_id (user_id)
Pivot table: alphabetical sort of both table names (category_product)
ORM Utility Types (model-types.ts) type Def <T> = T extends { getDefinition : () => infer D } ? D : never
type BelongsToForeignKeys <TDef >
type ModelRow <T> = ModelAttributes <Def <T>> & BelongsToForeignKeys <Def <T>>
type NewModelData <T> = Partial <InferModelAttributes <Def <T>> & BelongsToForeignKeys <Def <T>>>
type UpdateModelData <T> = Partial <InferModelAttributes <Def <T>> & BelongsToForeignKeys <Def <T>>>
ORM Utility Functions (utils.ts)
modelTableName(model: Model | string): Promise<string> -- uses model.table or converts model.name to snake_case plural
getModelName(model, modelPath): string -- from definition or filename
getTableName(model, modelPath): TableNames -- from definition or snake_case plural of name
getPivotTableName(modelA, modelB): string -- alphabetical sort + join with _
getRelations(model, name): Promise<RelationConfig[]> -- processes hasOne, hasMany, belongsTo, hasOneThrough, belongsToMany, morphOne
getHiddenAttributes(attrs): string[] -- filters for hidden: true
getGuardedAttributes(model): string[] -- filters for guarded: true, returns snake_case
getFillableAttributes(model, relations): string[] -- filters for fillable: true, adds FK columns, stripe_id, uuid, etc.
extractFields(model, file): Promise<ModelElement[]> -- parses model file for field metadata
findCoreModel(name): string -- searches storage/framework/defaults/app/Models/ recursively
findUserModel(name): string -- searches app/Models/
fetchOtherModelRelations(modelName?): Promise<RelationConfig[]> -- scans all models for relations pointing to this model
formatDate(date): string -- ISO format YYYY-MM-DD HH:MM:SS
Relationship Processing (utils.ts) Each relationship type is processed into a RelationConfig object:
hasOne / hasMany : FK = {parent_snake}_id, model key = {related_snake}_id
belongsTo : FK is empty string (set on the owning model's side), supports custom foreignKey
belongsToMany : the legacy array form auto-creates a conventional pivot and supports pivotTable, firstForeignKey, and secondForeignKey; the named object form supports table, foreignKey, relatedKey, and pivot metadata (columns, timestamps, uniques)
hasOneThrough : includes throughModel and throughForeignKey
morphOne : uses {modelName}able pattern, generates _type and _id columns
Named many-to-many relations are callable on model instances. Use their native
relation builder for pivot writes:
const post = await Post .find (id)
await post.categories ().sync (categoryIds)
await post.tags ().attach (tagId)
await post.tags ().detach ()
Declaring custom pivot columns in the model is required when those columns have
defaults that attach() and sync() must write. pivot.timestamps: true
causes both timestamps to be generated and maintained.
Model Events (when traits.observe: true) observe: true emits all three events. observe: ['create', 'update'] emits only those.
'{modelname}:created' -- via afterCreate hook, lazy-imports @stacksjs/events
'{modelname}:updated' -- via afterUpdate hook
'{modelname}:deleted' -- via afterDelete hook
If @stacksjs/events is not available (browser, tests), errors are caught and silently ignored.
Stub Types in index.ts The ORM exports stub types for commonly used models to keep typecheck green before code generation:
UserModel, NewUser, User (class stub with static where, find, create, all)
Job, FailedJob (query stubs)
PaymentMethod (CRUD stubs)
CategorizableTable, CategorizableModelsTable, CommentablesTable, TaggableTable
All 50+ Framework Models Content: Author, Page, Post, Comment, Tag, Category
Users: User, Customer, Driver, Subscriber, SubscriberEmail
Commerce: Product, ProductVariant, ProductUnit, Cart, CartItem, Order, OrderItem, Coupon, GiftCard, Manufacturer, Review, LicenseKey, DigitalDelivery, WaitlistProduct, WaitlistRestaurant
Payments: Payment, PaymentMethod, PaymentProduct, PaymentTransaction, Subscription, Transaction, Receipt
Shipping: ShippingMethod, ShippingRate, ShippingZone, DeliveryRoute
Loyalty: LoyaltyPoint, LoyaltyReward, TaxRate
System: Job, FailedJob, Error, Log, Notification, Activity, Request, Websocket, PrintDevice
Marketing: Campaign, EmailList, SocialPost
CLI Commands
buddy make:migration -- create migration for model changes
buddy generate:migrations -- generate migrations from model diffs
buddy migrate -- run pending migrations
Gotchas
Models work directly via the dynamic ORM — no code generation step needed
defineModel() calls createModel() from bun-query-builder at runtime, providing all typed query methods immediately
Two ORM locations: storage/framework/core/orm/ (package) and storage/framework/orm/ (implementation)
Factories use @stacksjs/faker -- each attribute can have a factory function
The hidden attribute flag excludes fields from JSON serialization (e.g., passwords)
The guarded flag prevents mass assignment
The fillable flag explicitly allows mass assignment
Pivot tables for belongsToMany are auto-created using alphabetical naming of both table names
Model events are only emitted when observe: true (or array) trait is set
The trait key for commentable is commentables (with 's'), not commentable
Trait methods are accessed via underscore-prefixed properties: _taggable, _categorizable, _commentable, _billable, _likeable, _twoFactor
The useAuth.useTwoFactor check (not usePasskey) determines if two-factor methods are added
defineModel() calls createModel() from bun-query-builder which returns the typed query builder interface at runtime
Model file loading uses findUserModel() (app/Models/) with fallback to findCoreModel() (storage/framework/defaults/app/Models/)