Skip to content

Commit

Permalink
feat: add save and revive method (#62)
Browse files Browse the repository at this point in the history
Co-authored-by: Kia King Ishii <kia.king.08@gmail.com>
Co-authored-by: Ryo_gk <62658104+ryo-gk@users.noreply.github.com>
  • Loading branch information
kiaking and ryo-gk committed May 17, 2021
1 parent 9a93d0f commit d45825e
Show file tree
Hide file tree
Showing 14 changed files with 707 additions and 15 deletions.
5 changes: 1 addition & 4 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ function createEntry(config) {
plugins: [],
output: {
file: config.file,
format: config.format,
globals: {
vue: 'Vue'
}
format: config.format
},
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
Expand Down
37 changes: 30 additions & 7 deletions src/connection/Connection.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Store } from 'vuex'
import { Element, Elements } from '../data/Data'
import { Database } from '@/database/Database'
import { Database } from '../database/Database'
import { Model } from '../model/Model'

export interface ConnectionNamespace {
connection: string
entity: string
}

export class Connection {
export class Connection<M extends Model> {
/**
* The store instance.
*/
Expand All @@ -21,29 +22,31 @@ export class Connection {
/**
* The entity name.
*/
entity: string
model: M

/**
* Create a new connection instance.
*/
constructor(database: Database, entity: string) {
constructor(database: Database, model: M) {
this.store = database.store
this.connection = database.connection
this.entity = entity
this.model = model
}

/**
* Commit a namespaced store mutation.
*/
private commit(name: string, payload?: any): void {
this.store.commit(`${this.connection}/${this.entity}/${name}`, payload)
const type = `${this.connection}/${this.model.$entity()}/${name}`

this.store.commit(type, payload)
}

/**
* Get all existing records.
*/
get(): Elements {
return this.store.state[this.connection][this.entity].data
return this.store.state[this.connection][this.model.$entity()].data
}

/**
Expand All @@ -53,6 +56,26 @@ export class Connection {
return this.get()[id] ?? null
}

/**
* Commit `save` mutation to the store.
*/
save(records: Elements): void {
const newRecords = {} as Elements

const data = this.get()

for (const id in records) {
const record = records[id]
const existing = data[id]

newRecords[id] = existing
? Object.assign({}, existing, this.model.$sanitize(record))
: this.model.$sanitizeAndFill(record)
}

this.commit('save', newRecords)
}

/**
* Commit `insert` mutation to the store.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/interpreter/Interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ export class Interpreter<M extends Model> {
this.model = model
}

/**
* Perform interpretation for the given data and return normalized schema.
*/
processRecord(data: Element[]): [Element[], NormalizedData]
processRecord(data: Element): [Element, NormalizedData]
processRecord(
data: Element | Element[]
): [Element | Element[], NormalizedData] {
const schema = isArray(data) ? [this.getSchema()] : this.getSchema()

const normalizedData = normalize(data, schema).entities as NormalizedData

return [data, normalizedData]
}

/**
* Perform interpretation for the given data.
*/
Expand Down
49 changes: 49 additions & 0 deletions src/model/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,4 +597,53 @@ export class Model {
? relation.map((model) => model.$toJson())
: relation.$toJson()
}

/**
* Sanitize the given record. This method is similar to `$toJson` method, but
* the difference is that it doesn't instantiate the full model. The method
* is used to sanitize the record before persisting to the store.
*
* It removes fields that don't exist in the model field schema or if the
* field is relationship fields.
*
* Note that this method only sanitizes existing fields in the given record.
* It will not generate missing model fields. If you need to generate all
* model fields, use `$sanitizeAndFill` method instead.
*/
$sanitize(record: Element): Element {
const sanitizedRecord = {} as Element
const attrs = this.$fields()

for (const key in record) {
const attr = attrs[key]
const value = record[key]

if (attr !== undefined && !(attr instanceof Relation)) {
sanitizedRecord[key] = attr.make(value)
}
}

return sanitizedRecord
}

/**
* Same as `$sanitize` method, but it produces missing model fields with its
* default value.
*/
$sanitizeAndFill(record: Element): Element {
const sanitizedRecord = {} as Element

const attrs = this.$fields()

for (const key in attrs) {
const attr = attrs[key]
const value = record[key]

if (!(attr instanceof Relation)) {
sanitizedRecord[key] = attr.make(value)
}
}

return sanitizedRecord
}
}
9 changes: 9 additions & 0 deletions src/modules/Mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { Elements } from '../data/Data'
import { State } from './State'

export interface Mutations<S extends State> extends MutationTree<S> {
save(state: S, records: Elements): void
insert(state: S, records: Elements): void
update(state: S, records: Elements): void
delete(state: S, ids: string[]): void
flush(state: S): void
}

/**
* Commit `save` change to the store.
*/
function save(state: State, records: Elements): void {
state.data = records
}

/**
* Commit `insert` change to the store.
*/
Expand Down Expand Up @@ -53,6 +61,7 @@ function flush(state: State): void {
}

export const mutations = {
save,
insert,
fresh,
update,
Expand Down
96 changes: 94 additions & 2 deletions src/query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Query<M extends Model = Model> {
/**
* The connection instance.
*/
protected connection: Connection
protected connection: Connection<M>

/**
* The where constraints for the query.
Expand Down Expand Up @@ -90,7 +90,7 @@ export class Query<M extends Model = Model> {
this.model = model

this.interpreter = new Interpreter(database, model)
this.connection = new Connection(database, model.$entity())
this.connection = new Connection(database, model)
}

/**
Expand Down Expand Up @@ -379,6 +379,52 @@ export class Query<M extends Model = Model> {
return this.model.$getRelation(name)
}

/*
* Retrieves the models from the store by following the given
* normalized schema.
*/
revive(schema: Element[]): Collection<M>
revive(schema: Element): Item<M>
revive(schema: Element | Element[]): Item<M> | Collection<M> {
return isArray(schema) ? this.reviveMany(schema) : this.reviveOne(schema)
}

/**
* Revive single model from the given schema.
*/
reviveOne(schema: Element): Item<M> {
const id = schema.__id

if (!id) {
return null
}

const item = this.connection.find(id)

if (!item) {
return null
}

const model = this.hydrate(item)

this.reviveRelations(model, schema)

return model
}

/**
* Revive multiple models from the given schema.
*/
reviveMany(schema: Element[]): Collection<M> {
return schema.reduce<Collection<M>>((collection, item) => {
const model = this.reviveOne(item)

model && collection.push(model)

return collection
}, [])
}

/**
* Create and persist model with default values.
*/
Expand All @@ -390,6 +436,52 @@ export class Query<M extends Model = Model> {
return model
}

/**
* Revive relations for the given schema and entity.
*/
protected reviveRelations(model: M, schema: Element) {
const fields = this.model.$fields()

for (const key in schema) {
const attr = fields[key]

if (!(attr instanceof Relation)) {
continue
}

const relatedSchema = schema[key]

model[key] = isArray(relatedSchema)
? this.newQueryForRelation(attr).reviveMany(relatedSchema)
: this.newQueryForRelation(attr).reviveOne(relatedSchema)
}
}

/**
* Save the given records to the store with data normalization.
*/
save(records: Element[]): Element[]
save(record: Element): Element
save(records: Element | Element[]): Element | Element[] {
const [data, entities] = this.interpreter.processRecord(records)

for (const entity in entities) {
const query = this.newQuery(entity)
const elements = entities[entity]

query.saveElements(elements)
}

return data
}

/**
* Save the given records to the store.
*/
saveElements(records: Elements): void {
this.connection.save(records)
}

/**
* Insert the given record to the store.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/repository/Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ export class Repository<M extends Model = Model> {
return this.query().find(ids)
}

/**
* Retrieves the models from the store by following the given
* normalized schema.
*/
revive(schema: Element[]): Collection<M>
revive(schema: Element): Item<M>
revive(schema: Element | Element[]): Item<M> | Collection<M> {
return this.query().revive(schema)
}

/**
* Create a new model instance. This method will not save the model to the
* store. It's pretty much the alternative to `new Model()`, but it injects
Expand All @@ -174,6 +184,15 @@ export class Repository<M extends Model = Model> {
})
}

/*
* Save the given records to the store with data normalization.
*/
save(records: Element[]): Element[]
save(record: Element): Element
save(records: Element | Element[]): Element | Element[] {
return this.query().save(records)
}

/**
* Create and persist model with default values.
*/
Expand Down
11 changes: 9 additions & 2 deletions src/schema/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,15 @@ export class Schema {
}
}

// Finally, obtain the index id and return.
return model.$getIndexId(record)
// Finally, obtain the index id, attach it to the current record at the
// special `__id` key. The `__id` key is used when we try to retrieve
// the models via the `revive` method using the data that is currently
// being normalized.
const id = model.$getIndexId(record)

record.__id = id

return id
}
}

Expand Down

0 comments on commit d45825e

Please sign in to comment.