Skip to content

Commit d45825e

Browse files
kiakingryo-gk
andauthored
feat: add save and revive method (#62)
Co-authored-by: Kia King Ishii <[email protected]> Co-authored-by: Ryo_gk <[email protected]>
1 parent 9a93d0f commit d45825e

File tree

14 files changed

+707
-15
lines changed

14 files changed

+707
-15
lines changed

rollup.config.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ function createEntry(config) {
2424
plugins: [],
2525
output: {
2626
file: config.file,
27-
format: config.format,
28-
globals: {
29-
vue: 'Vue'
30-
}
27+
format: config.format
3128
},
3229
onwarn: (msg, warn) => {
3330
if (!/Circular/.test(msg)) {

src/connection/Connection.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Store } from 'vuex'
22
import { Element, Elements } from '../data/Data'
3-
import { Database } from '@/database/Database'
3+
import { Database } from '../database/Database'
4+
import { Model } from '../model/Model'
45

56
export interface ConnectionNamespace {
67
connection: string
78
entity: string
89
}
910

10-
export class Connection {
11+
export class Connection<M extends Model> {
1112
/**
1213
* The store instance.
1314
*/
@@ -21,29 +22,31 @@ export class Connection {
2122
/**
2223
* The entity name.
2324
*/
24-
entity: string
25+
model: M
2526

2627
/**
2728
* Create a new connection instance.
2829
*/
29-
constructor(database: Database, entity: string) {
30+
constructor(database: Database, model: M) {
3031
this.store = database.store
3132
this.connection = database.connection
32-
this.entity = entity
33+
this.model = model
3334
}
3435

3536
/**
3637
* Commit a namespaced store mutation.
3738
*/
3839
private commit(name: string, payload?: any): void {
39-
this.store.commit(`${this.connection}/${this.entity}/${name}`, payload)
40+
const type = `${this.connection}/${this.model.$entity()}/${name}`
41+
42+
this.store.commit(type, payload)
4043
}
4144

4245
/**
4346
* Get all existing records.
4447
*/
4548
get(): Elements {
46-
return this.store.state[this.connection][this.entity].data
49+
return this.store.state[this.connection][this.model.$entity()].data
4750
}
4851

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

59+
/**
60+
* Commit `save` mutation to the store.
61+
*/
62+
save(records: Elements): void {
63+
const newRecords = {} as Elements
64+
65+
const data = this.get()
66+
67+
for (const id in records) {
68+
const record = records[id]
69+
const existing = data[id]
70+
71+
newRecords[id] = existing
72+
? Object.assign({}, existing, this.model.$sanitize(record))
73+
: this.model.$sanitizeAndFill(record)
74+
}
75+
76+
this.commit('save', newRecords)
77+
}
78+
5679
/**
5780
* Commit `insert` mutation to the store.
5881
*/

src/interpreter/Interpreter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ export class Interpreter<M extends Model> {
2323
this.model = model
2424
}
2525

26+
/**
27+
* Perform interpretation for the given data and return normalized schema.
28+
*/
29+
processRecord(data: Element[]): [Element[], NormalizedData]
30+
processRecord(data: Element): [Element, NormalizedData]
31+
processRecord(
32+
data: Element | Element[]
33+
): [Element | Element[], NormalizedData] {
34+
const schema = isArray(data) ? [this.getSchema()] : this.getSchema()
35+
36+
const normalizedData = normalize(data, schema).entities as NormalizedData
37+
38+
return [data, normalizedData]
39+
}
40+
2641
/**
2742
* Perform interpretation for the given data.
2843
*/

src/model/Model.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,4 +597,53 @@ export class Model {
597597
? relation.map((model) => model.$toJson())
598598
: relation.$toJson()
599599
}
600+
601+
/**
602+
* Sanitize the given record. This method is similar to `$toJson` method, but
603+
* the difference is that it doesn't instantiate the full model. The method
604+
* is used to sanitize the record before persisting to the store.
605+
*
606+
* It removes fields that don't exist in the model field schema or if the
607+
* field is relationship fields.
608+
*
609+
* Note that this method only sanitizes existing fields in the given record.
610+
* It will not generate missing model fields. If you need to generate all
611+
* model fields, use `$sanitizeAndFill` method instead.
612+
*/
613+
$sanitize(record: Element): Element {
614+
const sanitizedRecord = {} as Element
615+
const attrs = this.$fields()
616+
617+
for (const key in record) {
618+
const attr = attrs[key]
619+
const value = record[key]
620+
621+
if (attr !== undefined && !(attr instanceof Relation)) {
622+
sanitizedRecord[key] = attr.make(value)
623+
}
624+
}
625+
626+
return sanitizedRecord
627+
}
628+
629+
/**
630+
* Same as `$sanitize` method, but it produces missing model fields with its
631+
* default value.
632+
*/
633+
$sanitizeAndFill(record: Element): Element {
634+
const sanitizedRecord = {} as Element
635+
636+
const attrs = this.$fields()
637+
638+
for (const key in attrs) {
639+
const attr = attrs[key]
640+
const value = record[key]
641+
642+
if (!(attr instanceof Relation)) {
643+
sanitizedRecord[key] = attr.make(value)
644+
}
645+
}
646+
647+
return sanitizedRecord
648+
}
600649
}

src/modules/Mutations.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@ import { Elements } from '../data/Data'
33
import { State } from './State'
44

55
export interface Mutations<S extends State> extends MutationTree<S> {
6+
save(state: S, records: Elements): void
67
insert(state: S, records: Elements): void
78
update(state: S, records: Elements): void
89
delete(state: S, ids: string[]): void
910
flush(state: S): void
1011
}
1112

13+
/**
14+
* Commit `save` change to the store.
15+
*/
16+
function save(state: State, records: Elements): void {
17+
state.data = records
18+
}
19+
1220
/**
1321
* Commit `insert` change to the store.
1422
*/
@@ -53,6 +61,7 @@ function flush(state: State): void {
5361
}
5462

5563
export const mutations = {
64+
save,
5665
insert,
5766
fresh,
5867
update,

src/query/Query.ts

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class Query<M extends Model = Model> {
5555
/**
5656
* The connection instance.
5757
*/
58-
protected connection: Connection
58+
protected connection: Connection<M>
5959

6060
/**
6161
* The where constraints for the query.
@@ -90,7 +90,7 @@ export class Query<M extends Model = Model> {
9090
this.model = model
9191

9292
this.interpreter = new Interpreter(database, model)
93-
this.connection = new Connection(database, model.$entity())
93+
this.connection = new Connection(database, model)
9494
}
9595

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

382+
/*
383+
* Retrieves the models from the store by following the given
384+
* normalized schema.
385+
*/
386+
revive(schema: Element[]): Collection<M>
387+
revive(schema: Element): Item<M>
388+
revive(schema: Element | Element[]): Item<M> | Collection<M> {
389+
return isArray(schema) ? this.reviveMany(schema) : this.reviveOne(schema)
390+
}
391+
392+
/**
393+
* Revive single model from the given schema.
394+
*/
395+
reviveOne(schema: Element): Item<M> {
396+
const id = schema.__id
397+
398+
if (!id) {
399+
return null
400+
}
401+
402+
const item = this.connection.find(id)
403+
404+
if (!item) {
405+
return null
406+
}
407+
408+
const model = this.hydrate(item)
409+
410+
this.reviveRelations(model, schema)
411+
412+
return model
413+
}
414+
415+
/**
416+
* Revive multiple models from the given schema.
417+
*/
418+
reviveMany(schema: Element[]): Collection<M> {
419+
return schema.reduce<Collection<M>>((collection, item) => {
420+
const model = this.reviveOne(item)
421+
422+
model && collection.push(model)
423+
424+
return collection
425+
}, [])
426+
}
427+
382428
/**
383429
* Create and persist model with default values.
384430
*/
@@ -390,6 +436,52 @@ export class Query<M extends Model = Model> {
390436
return model
391437
}
392438

439+
/**
440+
* Revive relations for the given schema and entity.
441+
*/
442+
protected reviveRelations(model: M, schema: Element) {
443+
const fields = this.model.$fields()
444+
445+
for (const key in schema) {
446+
const attr = fields[key]
447+
448+
if (!(attr instanceof Relation)) {
449+
continue
450+
}
451+
452+
const relatedSchema = schema[key]
453+
454+
model[key] = isArray(relatedSchema)
455+
? this.newQueryForRelation(attr).reviveMany(relatedSchema)
456+
: this.newQueryForRelation(attr).reviveOne(relatedSchema)
457+
}
458+
}
459+
460+
/**
461+
* Save the given records to the store with data normalization.
462+
*/
463+
save(records: Element[]): Element[]
464+
save(record: Element): Element
465+
save(records: Element | Element[]): Element | Element[] {
466+
const [data, entities] = this.interpreter.processRecord(records)
467+
468+
for (const entity in entities) {
469+
const query = this.newQuery(entity)
470+
const elements = entities[entity]
471+
472+
query.saveElements(elements)
473+
}
474+
475+
return data
476+
}
477+
478+
/**
479+
* Save the given records to the store.
480+
*/
481+
saveElements(records: Elements): void {
482+
this.connection.save(records)
483+
}
484+
393485
/**
394486
* Insert the given record to the store.
395487
*/

src/repository/Repository.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ export class Repository<M extends Model = Model> {
163163
return this.query().find(ids)
164164
}
165165

166+
/**
167+
* Retrieves the models from the store by following the given
168+
* normalized schema.
169+
*/
170+
revive(schema: Element[]): Collection<M>
171+
revive(schema: Element): Item<M>
172+
revive(schema: Element | Element[]): Item<M> | Collection<M> {
173+
return this.query().revive(schema)
174+
}
175+
166176
/**
167177
* Create a new model instance. This method will not save the model to the
168178
* store. It's pretty much the alternative to `new Model()`, but it injects
@@ -174,6 +184,15 @@ export class Repository<M extends Model = Model> {
174184
})
175185
}
176186

187+
/*
188+
* Save the given records to the store with data normalization.
189+
*/
190+
save(records: Element[]): Element[]
191+
save(record: Element): Element
192+
save(records: Element | Element[]): Element | Element[] {
193+
return this.query().save(records)
194+
}
195+
177196
/**
178197
* Create and persist model with default values.
179198
*/

src/schema/Schema.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,15 @@ export class Schema {
111111
}
112112
}
113113

114-
// Finally, obtain the index id and return.
115-
return model.$getIndexId(record)
114+
// Finally, obtain the index id, attach it to the current record at the
115+
// special `__id` key. The `__id` key is used when we try to retrieve
116+
// the models via the `revive` method using the data that is currently
117+
// being normalized.
118+
const id = model.$getIndexId(record)
119+
120+
record.__id = id
121+
122+
return id
116123
}
117124
}
118125

0 commit comments

Comments
 (0)