Migration to 2.0.0

This guide covers all breaking changes when upgrading from pinia-orm 1.x to 2.0.0.

Requirements

  • pinia >= 3
  • TypeScript >= 5.2 with standard decorator semantics (see below)
  • A build toolchain that transforms standard ECMAScript decorators (esbuild >= 0.21, current Vite/Nuxt versions)

Standard ECMAScript decorators

The property decorators are now standard TC39 decorators instead of the legacy experimentalDecorators implementation.

1. Update your tsconfig

Remove the legacy flags — TypeScript uses standard decorators by default:

{
  "compilerOptions": {
-   "experimentalDecorators": true,
-   "useDefineForClassFields": false,
  }
}

Also remove any experimentalDecorators entries from vite.esbuild.tsconfigRaw in your Nuxt/Vite config.

2. Migrate your models

Standard decorators cannot be applied to declare fields. Replace declare with the definite assignment assertion (!) on decorated fields:

class User extends Model {
  static entity = 'users'
- @Uid() declare id: string
- @Str('') declare name: string
+ @Uid() id!: string
+ @Str('') name!: string
  @HasMany(() => Post, 'userId')
- declare posts: Post[]
+ posts!: Post[]
}

Rules of thumb:

  • decorated field → field!: Type (or keep field?: Type for optional relations)
  • undecorated typing for fields() based models → keep (or change to) declare field: Type
  • a subclass overriding a decorated field of its base class needs an initializer: @Attr('dog') type = 'dog'
  • @NonEnumerable now requires the accessor keyword: @NonEnumerable accessor pivot!: Pivot

Automated migration

A codemod that applies the model changes automatically ships with the repository:

curl -O https://raw.githubusercontent.com/CodeDredd/pinia-orm/main/scripts/migrate-standard-decorators.mjs
node migrate-standard-decorators.mjs --dry ./src   # preview
node migrate-standard-decorators.mjs ./src         # apply

It only touches declare fields that are decorated, so fields() based models stay untouched.

Bonus: decorator inheritance

Decorated fields are now registered through decorator metadata, which is inherited along the class hierarchy. Derived STI models see the decorated fields of their base classes without re-declaring them — this did not work in 1.x.

fresh() fires lifecycle hooks

fresh() now fires the saving/creating and saved/created hooks for every record. Pass { raw: true } as second argument to restore the old silent behaviour:

useRepo(User).fresh(records, { raw: true })

Casts run before validation on save

When saving, casts are applied before the field type check. If a field has both a set-mutator and a cast, the mutator now receives the casted value.

Decorator state is scoped per entity

Mutators, casts and hidden fields registered via decorators or setMutator()/setCast()/setHidden() no longer leak to other entities with same-named fields.