Skip to content

Commit 6b768d9

Browse files
authored
feat: add custom repository support (#4)
1 parent 4d4d5af commit 6b768d9

File tree

6 files changed

+173
-14
lines changed

6 files changed

+173
-14
lines changed

src/repository/Repository.ts

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Store } from 'vuex'
2+
import { error } from '../support/Utils'
23
import { Constructor } from '../types'
34
import { Element, Item, Collection, Collections } from '../data/Data'
45
import { Model } from '../model/Model'
@@ -9,7 +10,14 @@ import {
910
OrderDirection
1011
} from '../query/Options'
1112

12-
export class Repository<M extends Model> {
13+
export class Repository<M extends Model = Model> {
14+
/**
15+
* A special flag to indicate if this is the repository class or not. It's
16+
* used when retrieving repository instance from `store.$repo()` method to
17+
* determine whether the passed in class is either a repository or a model.
18+
*/
19+
static _isRepository: boolean = true
20+
1321
/**
1422
* The store instance.
1523
*/
@@ -18,21 +26,78 @@ export class Repository<M extends Model> {
1826
/**
1927
* The model instance.
2028
*/
21-
protected model: M
29+
protected model!: M
30+
31+
/**
32+
* The model object to be used for the custom repository.
33+
*/
34+
use?: typeof Model
2235

2336
/**
2437
* Create a new Repository instance.
2538
*/
26-
constructor(store: Store<any>, model: Constructor<M>) {
39+
constructor(store: Store<any>) {
2740
this.store = store
28-
this.model = new model().$setStore(store)
41+
}
42+
43+
/**
44+
* Initialize the repository by setting the model instance.
45+
*/
46+
initialize(model?: Constructor<M>): this {
47+
// If there's a model passed in, just use that and return immediately.
48+
if (model) {
49+
this.model = new model().$setStore(this.store)
50+
51+
return this
52+
}
53+
54+
// If no model was passed to the initializer, that means the user has
55+
// passed repository to the `store.$repo` method instead of a model.
56+
// In this case, we'll check if the user has set model to the `use`
57+
// property and instantiate that.
58+
if (this.use) {
59+
this.model = (new this.use() as M).$setStore(this.store)
60+
61+
return this
62+
}
63+
64+
// Else just return for now. If the user tries to call methods that require
65+
// a model, the error will be thrown at that time.
66+
return this
67+
}
68+
69+
/**
70+
* Get the model instance. If the model is not registered to the repository,
71+
* it will throw an error. It happens when users use a custom repository
72+
* without setting `use` property.
73+
*/
74+
getModel(): M {
75+
if (!this.model) {
76+
error([
77+
'The model is not registered. Please define the model to be used at',
78+
'`use` property of the repository class.'
79+
])
80+
}
81+
82+
return this.model
83+
}
84+
85+
/**
86+
* Create a new repository with the given model.
87+
*/
88+
repo<M extends Model>(model: Constructor<M>): Repository<M>
89+
repo<R extends Repository<any>>(repository: Constructor<R>): R
90+
repo(modelOrRepository: any): any {
91+
return modelOrRepository._isRepository
92+
? new modelOrRepository(this.store).initialize()
93+
: new Repository(this.store).initialize(modelOrRepository)
2994
}
3095

3196
/**
3297
* Create a new Query instance.
3398
*/
3499
query(): Query<M> {
35-
return new Query(this.store, this.model)
100+
return new Query(this.store, this.getModel())
36101
}
37102

38103
/**
@@ -91,7 +156,7 @@ export class Repository<M extends Model> {
91156
* the store instance to support model instance methods in SSR environment.
92157
*/
93158
make(attributes?: Element): M {
94-
return this.model.$newInstance(attributes, {
159+
return this.getModel().$newInstance(attributes, {
95160
relations: false
96161
})
97162
}

src/store/Store.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { Constructor } from '../types'
21
import { Store, Plugin } from 'vuex'
32
import { Database } from '../database/Database'
4-
import { Model } from '../model/Model'
53
import { Repository } from '../repository/Repository'
64
import { plugins, components } from '../plugin/Plugin'
75

@@ -84,9 +82,9 @@ function startDatabase(store: Store<any>): void {
8482
* Mixin repo function to the store.
8583
*/
8684
function mixinRepoFunction(store: Store<any>): void {
87-
store.$repo = function <M extends Model>(
88-
model: Constructor<M>
89-
): Repository<M> {
90-
return new Repository(this, model)
85+
store.$repo = function (modelOrRepository: any): any {
86+
return modelOrRepository._isRepository
87+
? new modelOrRepository(this).initialize()
88+
: new Repository(this).initialize(modelOrRepository)
9189
}
9290
}

src/support/Utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,19 @@ export function cloneDeep<T extends object>(target: T): T {
199199
return target
200200
}
201201

202+
/**
203+
* Throw a new error with the given message.
204+
*/
205+
export function error(message: string[]): never {
206+
throw new Error(['[Vuex ORM]'].concat(message).join(' '))
207+
}
208+
202209
/**
203210
* Check for the given condition, and if it's `false`, it will throw a new
204211
* error with the given message.
205212
*/
206213
export function assert(condition: boolean, message: string[]): void {
207214
if (!condition) {
208-
throw new Error(['[Vuex ORM]'].concat(message).join(' '))
215+
error(message)
209216
}
210217
}

src/types/vuex.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ declare module 'vuex' {
1414
* Get a new Repository instance for the given model.
1515
*/
1616
$repo<M extends Model>(model: Constructor<M>): Repository<M>
17+
$repo<R extends Repository<any>>(repository: Constructor<R>): R
1718
}
1819
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { createStore } from 'test/Helpers'
2+
import { Model, Attr, Str, Repository } from '@/index'
3+
4+
describe('feature/repository_custom/custom_repository', () => {
5+
it('can define a custom repository', async () => {
6+
class User extends Model {
7+
static entity = 'users'
8+
9+
@Attr() id!: any
10+
@Str('') name!: string
11+
}
12+
13+
class UserRepository extends Repository<User> {
14+
use: typeof User = User
15+
16+
custom(): number {
17+
return 1
18+
}
19+
}
20+
21+
const store = createStore([User])
22+
23+
const userRepo = store.$repo(UserRepository)
24+
25+
expect(userRepo.custom()).toBe(1)
26+
})
27+
28+
it('can define an abstract custom repository', async () => {
29+
class ARepository extends Repository {
30+
custom(): number {
31+
return 1
32+
}
33+
}
34+
35+
const store = createStore([])
36+
37+
const userRepo = store.$repo(ARepository)
38+
39+
expect(userRepo.custom()).toBe(1)
40+
})
41+
42+
it('throws if the user tries to access the model in abstract custom repository', async () => {
43+
class ARepository extends Repository {
44+
custom(): any {
45+
this.getModel()
46+
}
47+
}
48+
49+
const store = createStore([])
50+
51+
const userRepo = store.$repo(ARepository)
52+
53+
expect(() => {
54+
userRepo.custom()
55+
}).toThrow()
56+
})
57+
})

test/unit/repository/Repository.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createStore, assertModel } from 'test/Helpers'
2-
import { Model, Attr, Str } from '@/index'
2+
import { Model, Attr, Str, Repository } from '@/index'
33

44
describe('unit/repository/Repository', () => {
55
class User extends Model {
@@ -9,6 +9,13 @@ describe('unit/repository/Repository', () => {
99
@Str('John Doe') name!: string
1010
}
1111

12+
class Post extends Model {
13+
static entity = 'posts'
14+
15+
@Attr() id!: any
16+
@Str('Title 001') title!: string
17+
}
18+
1219
it('creates a new model instance', () => {
1320
const store = createStore([User])
1421

@@ -31,4 +38,28 @@ describe('unit/repository/Repository', () => {
3138
expect(user.$store).toBe(store)
3239
assertModel(user, { id: 1, name: 'Jane Doe' })
3340
})
41+
42+
it('can create a new repository from the model', () => {
43+
const store = createStore([User, Post])
44+
45+
const userRepo = store.$repo(User)
46+
47+
const postRepo = userRepo.repo(Post)
48+
49+
expect(postRepo.getModel()).toBeInstanceOf(Post)
50+
})
51+
52+
it('can create a new repository from the custom repository', () => {
53+
class PostRepository extends Repository<Post> {
54+
use: typeof Post = Post
55+
}
56+
57+
const store = createStore([User, Post])
58+
59+
const userRepo = store.$repo(User)
60+
61+
const postRepo = userRepo.repo(PostRepository)
62+
63+
expect(postRepo.getModel()).toBeInstanceOf(Post)
64+
})
3465
})

0 commit comments

Comments
 (0)