Skip to main content

stacks-orm

Use when querying or writing Stacks models, using transactions and relationships, preserving inferred model types, or enabling native model traits. Covers @stacksjs/orm, the model runtime, and 107 built-in models.

Informations de source

Dépôt
stacksjs/stacks
Dernière activité de la source
10 octobre 2026 à 18:30
Langue détectée de SKILL.md
anglais
Étoiles
633
Forks
20

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
stacks-orm
description
Use when querying or writing Stacks models, using transactions and relationships, preserving inferred model types, or enabling native model traits. Covers @stacksjs/orm, the model runtime, and 107 built-in models.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks ORM `defineModel` produces an immediately usable query surface over `bun-query-builder`. Model-driven migrations change the schema; there are no per-model ORM classes to generate. The framework declares 107 models on disk, but optional feature gates determine which built-ins load in a running app. ## Definitions and typed rows Read `stacks-models` for schema, trait, ownership and extension declarations. Import your app model explicitly when exact inference matters: ```ts import WorkItem from '../app/Models/WorkItem' import type { ModelRow, ModelCreateData } from '@stacksjs/orm' type WorkItemRow = ModelRow<typeof WorkItem> type NewWorkItem = ModelCreateData<typeof WorkItem> const item = await WorkItem.find(id) const open = await WorkItem.where('done', false).orderBy('id', 'desc').get() ``` `find` can return no row; `findOrFail` throws `ModelNotFoundError`. Do not invent `UserRequest` or a runtime `UserModel` global. The ORM barrel's model proxies load lazily; server boot waits for model initialization. A feature-disabled model is not made usable merely by an ambient declaration. ## Write behavior Ordinary create/update paths apply mass-assignment rules, declared validation, setters, casts, encryption and applicable lifecycle behavior. Partial updates validate only fields supplied. Quiet writes still validate. `ModelValidationError` contains a 422 status and per-field errors; use native error mapping at an HTTP boundary rather than returning raw database errors. Use instance `update`/`delete` or the supported static `update(id, data)` and `delete(id)` helpers for an identified row. `firstOrCreate` and `updateOrCreate` provide explicit lookup/write behavior. Do not confuse a model's static helper with a fluent builder's `update` terminator. `forceCreate`, `forceUpdate` and `forceFill` are trusted mass-assignment escape hatches. They do not mean "disable encryption and validation". Scoped `withoutValidation` is for deliberate imports/backfills; `withoutEvents` and quiet helpers suppress events rather than rules. Read [model capabilities](../stacks-models/references/model-capabilities.md) for source-backed details. ## Relationships and model instance methods Declare relationships in the model rather than maintaining foreign-key or pivot schema separately. The named belongs-to-many form can own pivot columns, defaults, timestamps and uniqueness. An instance can expose the named relation, such as `post.tags().sync(ids)`; eager-loaded relations are accessible without requiring callers to unwrap the raw builder's storage. Traits expose model/instance methods for tags, categories, comments, likes, soft deletion, audit/activity feeds, two-factor authentication, provider-neutral billing and search. Use the exact method exposed by the chosen trait instead of reaching into underscore-prefixed implementation objects. The runtime checks `commentable`, not a guessed plural trait name. Soft-deleted model queries are trait-aware; process-wide raw query-builder scoping is a separate setting. Restore/force-delete operations and relation cascade behavior need their own tests. CMS category records and commerce Category records are distinct domains, even when a relation name looks familiar. ## Transactions ```ts import { transaction } from '@stacksjs/orm' await transaction(async (tx) => { const created = await tx.insertInto('work_items') .values({ title: 'Verify the change', user_id: ownerId }) .returningAll() .executeTakeFirst() if (!created) throw new Error('Work item was not created') return created }) ``` The callback handle owns the transaction's connection. All statements that must commit or roll back together, including validation reads and readback, use `tx`. `Model.create`, model queries, instance updates and relation calls inside this callback are not automatically rebound to it. SQLite, PostgreSQL, MySQL and Turso have relevant executor/locking differences; do not assume a model call can observe an uncommitted raw write. `savepoint` supplies a nested rollback point and `transactional` wraps a function. Options include retries, isolation, read-only mode, `onRollback` and `afterRollback`. Read `transaction.ts` and the active driver's capabilities; not every dialect implements every option identically. Native transaction scope can defer supported queue/lifecycle side effects until commit. It does not rebind queries. A post-commit effect failure is separate from rolling back data that has already committed. ## Native pagination and search ORM terminals return the canonical full, simple or cursor paginator rather than an invented `{ paging }` envelope. HTTP context can derive page/limit and URLs; jobs and CLI callers should supply their values explicitly. See `stacks-pagination` and `orm/tests/paginator-request.test.ts`. Model `useSearch` provides a search builder and document projection. Search hits pagination differs from database pagination. Related dot paths need the appropriate relation data and hidden attributes remain excluded. Do not pass an ORM paginator to a search driver and expect identical wire shapes. ## Runtime and schema boundaries - Import `db` and request-time SQL helpers from `@stacksjs/database/runtime` in handlers where tooling is not needed; the root package includes migrations. - Import `defineModel` and schema helpers explicitly. Model globals exist after server boot, while arbitrary ambient helper declarations are not runtime proof. - `app/Models` replaces defaults by identity. `extendModel` is the additive path. - Generated CRUD ownership/policy enforcement is a route contract, not a promise that every raw query elsewhere is automatically tenant-scoped. - Casts/encryption/model serialization operate on model paths; raw database results bypass the model projection. - No chain should interpolate unchecked identifiers or operators from a request. Use allowlists and the query-builder validation helpers. ## Source and retained evidence Runtime: `core/orm/src/{define-model,extend-model,model-types,transaction, model-registry,ownership,auto-crud}.ts`, `traits/`, and `utils/`. Evidence includes `model-validation`, `mass-assignment`, `force-create-guarded`, `casting`, `belongs-to-many`, `observed-writes`, `model-policy-wiring`, `paginator-request`, and the inference type tests. Read their actual contracts when a workflow depends on a dialect, trait, or return shape. ## Scoped locked operations and stored link credentials `withLockedRow(table, scope, async (row, tx) => ..., { tx? })` locks and reads a scoped row on one reserved connection. It returns null for a missing row. Use the callback handle for every related read/write; `tx` reuses an enclosing transaction. A scope must identify one row; empty or non-unique scopes are refused. This is the query-builder seam: model casts, fillable filtering and lifecycle hooks remain the caller's responsibility. `rowToken({ table, scope, column, action, tx?, timestampColumn? })` manages a stored share/feed credential. Read returns the live token; enable preserves it; rotate replaces it; disable clears it. Mutations serialize under the row lock and join an enclosing transaction when supplied. Missing owners throw `RowTokenNotFoundError` (404). The default timestamp column is updated_at; pass false for a table without timestamps. Identifiers and scope must be application-selected, and the caller must authorize that owner. This is for stored public-link secrets, not hashed authentication tokens with expiry.
Voir sur GitHub