Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/content/2.api/3.query/update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: 'update()'
description: 'Update the record matching the query chain.'
---

# `update()`

## Usage

````ts
import { useRepo } from 'pinia-orm'
import User from './models/User'

const userRepo = useRepo(User)

// update all records by query with the given properties
userRepo.where('name', 'Jane Doe').update({ age: 50 })
````

## Typescript Declarations

````ts
function update(record: Element): Collection<M>
````
30 changes: 30 additions & 0 deletions docs/content/2.api/4.repository/update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: 'update()'
description: 'Tries to update given record or records by their id.'
---

# `update()`

## Usage

````ts
import { useRepo } from 'pinia-orm'
import User from './models/User'

const userRepo = useRepo(User)

// update specific record
userRepo.update({ id: 1, age: 50 })

// update specific records
userRepo.update([{ id: 1, age: 50 }, { id: 2, age: 50 }])

// throws an warning if the record to update is not found
userRepo.update({ id: 999, age: 50 })
````

## Typescript Declarations

````ts
function update(records: Element | Element[]): M | M[]
````
10 changes: 0 additions & 10 deletions packages/pinia-orm/src/composables/useStoreActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,6 @@ export function useStoreActions() {
fresh(this: DataStore, records: Elements) {
this.data = records
},
destroy(this: DataStore, ids: string[]): void {
const data: Elements = {}

for (const id in this.data) {
if (!ids.includes(id))
data[id] = this.data[id]
}

this.data = data
},
/**
* Commit `delete` change to the store.
*/
Expand Down
75 changes: 50 additions & 25 deletions packages/pinia-orm/src/query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
isArray,
isEmpty,
isFunction,
orderBy,
orderBy, throwWarning,
} from '../support/Utils'
import type { Collection, Element, Elements, GroupedCollection, Item, NormalizedData } from '../data/Data'
import type { Database } from '../database/Database'
Expand Down Expand Up @@ -33,6 +33,8 @@ import type {
WhereSecondaryClosure,
} from './Options'

export type SaveModes = Array<'create' | 'update' | 'insert'>

export class Query<M extends Model = Model> {
/**
* The database instance.
Expand Down Expand Up @@ -701,8 +703,14 @@ export class Query<M extends Model = Model> {
* Save the given records to the store with data normalization.
*/
save(records: Element[]): M[]
save(record: Element): M
save(records: Element): M
save(records: Element | Element[]): M | M[] {
return this.processSavingElements(records)
}

processSavingElements(records: Element[], modes?: SaveModes): M[]
processSavingElements(records: Element, modes?: SaveModes): M
processSavingElements(records: Element | Element[], modes: SaveModes = ['create', 'update', 'insert']): M | M[] {
let processedData: [Element | Element[], NormalizedData] = this.newInterpreter().process(records)
const modelTypes = this.model.$types()
const isChildEntity = this.model.$baseEntity() !== this.model.$entity()
Expand Down Expand Up @@ -735,39 +743,60 @@ export class Query<M extends Model = Model> {
const query = this.newQuery(entity)
const elements = entities[entity]

query.saveElements(elements)
query.saveElements(elements, modes)
}
return this.revive(data) as M | M[]
}

/**
* Save the given elements to the store.
*/
saveElements(elements: Elements): void {
protected saveElements(elements: Elements, modes: SaveModes): void {
const newData = {} as Elements
const currentData = this.commit('all')
const afterSavingHooks = []

for (const id in elements) {
const record = elements[id]
const existing = currentData[id]
const model = existing
? this.hydrate({ ...existing, ...record }, { operation: 'set', action: 'update' })
: this.hydrate(record, { operation: 'set', action: 'save' })
let model: M | null = null
if (existing) {
if (modes.includes('update'))
model = this.hydrate({ ...existing, ...record }, { operation: 'set', action: 'update' })

const isSaving = model.$self().saving(model, record)
const isUpdatingOrCreating = existing ? model.$self().updating(model, record) : model.$self().creating(model, record)
if (isSaving === false || isUpdatingOrCreating === false)
continue
if (!model && modes.includes('insert'))
throwWarning(['Inserting a record which already exist.'], 'Existing', existing, 'New Record', record)
}
else {
if (modes.includes('create') || modes.includes('insert'))
model = this.hydrate(record, { operation: 'set', action: 'save' })

if (!model && modes.includes('update'))
throwWarning(['Updating a record which does not exist.'], record)
}

afterSavingHooks.push(() => model.$self().saved(model, record))
afterSavingHooks.push(() => existing ? model.$self().updated(model, record) : model.$self().created(model, record))
newData[id] = model.$getAttributes()
if (Object.values(model.$types()).length > 0 && !newData[id][model.$typeKey()])
newData[id][model.$typeKey()] = record[model.$typeKey()]
if (model) {
const isSaving = model.$self().saving(model, record)
const isUpdatingOrCreating = existing ? model.$self().updating(model, record) : model.$self().creating(model, record)
if (isSaving === false || isUpdatingOrCreating === false)
continue

// @ts-expect-error model is not null
afterSavingHooks.push(() => model.$self().saved(model, record))
// @ts-expect-error model is not null
afterSavingHooks.push(() => existing ? model.$self().updated(model, record) : model.$self().created(model, record))
newData[id] = model.$getAttributes()
if (Object.values(model.$types()).length > 0 && !newData[id][model.$typeKey()])
newData[id][model.$typeKey()] = record[model.$typeKey()]
}
}

if (Object.keys(newData).length > 0) {
this.commit('save', newData)
if (modes.length === 1)
this.commit(modes[0], newData)
else
this.commit('save', newData)

afterSavingHooks.forEach(hook => hook())
}
}
Expand All @@ -778,11 +807,7 @@ export class Query<M extends Model = Model> {
insert(records: Element[]): Collection<M>
insert(record: Element): M
insert(records: Element | Element[]): M | Collection<M> {
const models = this.hydrate(records)

this.commit('insert', this.compile(models))

return models
return this.processSavingElements(records, ['insert'])
}

/**
Expand All @@ -799,7 +824,7 @@ export class Query<M extends Model = Model> {
}

/**
* Update the reocrd matching the query chain.
* Update the record matching the query chain.
*/
update(record: Element): Collection<M> {
const models = this.get(false)
Expand Down Expand Up @@ -842,7 +867,7 @@ export class Query<M extends Model = Model> {

const [afterHooks, removeIds] = this.dispatchDeleteHooks(model)
if (!removeIds.includes(model.$getIndexId())) {
this.commit('destroy', [model.$getIndexId()])
this.commit('delete', [model.$getIndexId()])
afterHooks.forEach(hook => hook())
}

Expand All @@ -858,7 +883,7 @@ export class Query<M extends Model = Model> {
const [afterHooks, removeIds] = this.dispatchDeleteHooks(models)
const checkedIds = this.getIndexIdsFromCollection(models).filter(id => !removeIds.includes(id))

this.commit('destroy', checkedIds)
this.commit('delete', checkedIds)
afterHooks.forEach(hook => hook())

return models
Expand Down
11 changes: 10 additions & 1 deletion packages/pinia-orm/src/repository/Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,19 @@ export class Repository<M extends Model = Model> {
*/
save(records: Element[]): M[]
save(record: Element): M
public save(records: Element | Element[]): M | M[] {
save(records: Element | Element[]): M | M[] {
return this.query().save(records)
}

/**
* Tries to update given record or records by their id.
*/
update(records: Element[]): M[]
update(record: Element): M
update(records: Element | Element[]): M | M[] {
return this.query().processSavingElements(records, ['update'])
}

/**
* Create and persist model with default values.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/pinia-orm/src/support/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ export function throwError(
throw new Error(['[Pinia ORM]'].concat(message).join(' '))
}

export function throwWarning(message: string[], ...variables: any) {
// eslint-disable-next-line no-console
console.warn(['[Pinia ORM]'].concat(message).join(' '), ...variables)
}

/**
* Asserts that the condition is truthy, throwing immediately if not.
*/
Expand Down
51 changes: 46 additions & 5 deletions packages/pinia-orm/tests/feature/repository/insert.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { describe, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'

import { Model, useRepo } from '../../../src'
import { Attr, Str } from '../../../src/decorators'
import { Attr, HasMany, Num, Str } from '../../../src/decorators'
import { assertState } from '../../helpers'

describe('feature/repository/insert', () => {
class User extends Model {
static entity = 'users'

@Attr() id!: any
@Str('') name!: string
@Attr() declare id: any
@Str('') declare name: string
@HasMany(() => Post, 'userId') declare posts: Post[]
}

class Post extends Model {
static entity = 'posts'

@Num(0) declare id: number
@Attr() declare userId: number
@Str('') declare title: string
}

it('inserts a record to the store', () => {
Expand Down Expand Up @@ -44,6 +53,38 @@ describe('feature/repository/insert', () => {
const userRepo = useRepo(User)

userRepo.insert([])
assertState({ users: {} })
assertState({})
})

it('inserts with relation', () => {
const userRepo = useRepo(User)

userRepo.insert({ id: 1, name: 'John Doe', posts: [{ id: 1, title: 'New Post' }] })
assertState({
users: {
1: { id: 1, name: 'John Doe' },
},
posts: {
1: { id: 1, userId: 1, title: 'New Post' },
},
})
})

it('throws a warning if the same ids are inserted', () => {
const userRepo = useRepo(User)
const logger = vi.spyOn(console, 'warn')

userRepo.insert({ id: 1, name: 'John Doe', posts: [{ id: 1, title: 'New Post' }] })
userRepo.insert({ id: 1, name: 'John Doe2', posts: [{ id: 1, title: 'New Post 2' }] })

expect(logger).toBeCalledTimes(2)
assertState({
users: {
1: { id: 1, name: 'John Doe' },
},
posts: {
1: { id: 1, userId: 1, title: 'New Post' },
},
})
})
})
49 changes: 48 additions & 1 deletion packages/pinia-orm/tests/feature/repository/update.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'

import { Model, useRepo } from '../../../src'
import { Attr, Num, Str } from '../../../src/decorators'
Expand Down Expand Up @@ -81,4 +81,51 @@ describe('feature/repository/update', () => {
},
})
})

it('updates with repository update', () => {
const userRepo = useRepo(User)

fillState({
users: {
1: { id: 1, name: 'John Doe', age: 40 },
2: { id: 2, name: 'Jane Doe', age: 30 },
3: { id: 3, name: 'Johnny Doe', age: 20 },
},
})

userRepo.update({ id: 1, age: 50 })

assertState({
users: {
1: { id: 1, name: 'John Doe', age: 50 },
2: { id: 2, name: 'Jane Doe', age: 30 },
3: { id: 3, name: 'Johnny Doe', age: 20 },
},
})
})

it('throws warning if no updatable record found', () => {
const userRepo = useRepo(User)
const logger = vi.spyOn(console, 'warn')

fillState({
users: {
1: { id: 1, name: 'John Doe', age: 40 },
2: { id: 2, name: 'Jane Doe', age: 30 },
3: { id: 3, name: 'Johnny Doe', age: 20 },
},
})

userRepo.update({ id: 4, age: 50 })

expect(logger).toBeCalledTimes(1)

assertState({
users: {
1: { id: 1, name: 'John Doe', age: 40 },
2: { id: 2, name: 'Jane Doe', age: 30 },
3: { id: 3, name: 'Johnny Doe', age: 20 },
},
})
})
})