Skip to content

Commit 30ce60e

Browse files
committed
feat(repository): add KVRepository impl using legacy juggler
1 parent 791e015 commit 30ce60e

15 files changed

Lines changed: 273 additions & 56 deletions

packages/repository/examples/juggler-bridge/note-with-repo-class.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ import {
1414
ModelDefinition,
1515
} from '../../';
1616

17-
class NoteController {
18-
@repository('noteRepo') public noteRepo: EntityCrudRepository<Entity, number>;
19-
}
20-
2117
const ds: juggler.DataSource = new juggler.DataSource({
2218
name: 'db',
2319
connector: 'memory',
@@ -28,6 +24,13 @@ class Note extends Entity {
2824
name: 'note',
2925
properties: {title: 'string', content: 'string'},
3026
});
27+
28+
title: string;
29+
content?: string;
30+
}
31+
32+
class NoteController {
33+
@repository('noteRepo') public noteRepo: EntityCrudRepository<Note, number>;
3134
}
3235

3336
class MyNoteRepository extends DefaultCrudRepository<Note, string> {

packages/repository/examples/juggler-bridge/note-with-repo-instance.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,28 @@ import {
1616
ModelDefinition,
1717
} from '../../';
1818

19+
const ds: juggler.DataSource = new juggler.DataSource({
20+
name: 'db',
21+
connector: 'memory',
22+
});
23+
24+
class Note extends Entity {
25+
static definition = new ModelDefinition({
26+
name: 'note',
27+
properties: {title: 'string', content: 'string'},
28+
});
29+
30+
title: string;
31+
content?: string;
32+
}
33+
1934
// The Controller for Note
2035
class NoteController {
2136
constructor(
22-
@repository('noteRepo')
23-
public noteRepo: EntityCrudRepository<Entity, number>,
37+
@repository('noteRepo') public noteRepo: EntityCrudRepository<Note, number>,
2438
) {}
2539

26-
create(data: DataObject<Entity>, options?: Options) {
40+
create(data: DataObject<Note>, options?: Options) {
2741
return this.noteRepo.create(data, options);
2842
}
2943

@@ -32,18 +46,6 @@ class NoteController {
3246
}
3347
}
3448

35-
const ds: juggler.DataSource = new juggler.DataSource({
36-
name: 'db',
37-
connector: 'memory',
38-
});
39-
40-
class Note extends Entity {
41-
static definition = new ModelDefinition({
42-
name: 'note',
43-
properties: {title: 'string', content: 'string'},
44-
});
45-
}
46-
4749
async function main() {
4850
// Create a context
4951
const ctx = new Context();

packages/repository/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@loopback/core": "^0.10.3",
3333
"@loopback/dist-util": "^0.3.3",
3434
"lodash": "^4.17.10",
35-
"loopback-datasource-juggler": "^3.22.1"
35+
"loopback-datasource-juggler": "^3.23.0"
3636
},
3737
"files": [
3838
"README.md",

packages/repository/src/common-types.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,15 @@ export interface AnyObject {
4444
}
4545

4646
/**
47-
* Type alias for T or any object
47+
* An extension of the built-in Partial<T> type which allows partial values
48+
* in deeply nested properties too.
4849
*/
49-
export type DataObject<T> = T | AnyObject;
50+
export type DeepPartial<T> = {[P in keyof T]?: DeepPartial<T[P]>};
51+
52+
/**
53+
* Type alias for strongly or weakly typed objects of T
54+
*/
55+
export type DataObject<T> = T | DeepPartial<T>;
5056

5157
/**
5258
* Type alias for Node.js options object

packages/repository/src/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export abstract class Model {
177177
return obj;
178178
}
179179

180-
constructor(data?: Partial<Model>) {
180+
constructor(data?: DataObject<Model>) {
181181
Object.assign(this, data);
182182
}
183183
}

packages/repository/src/repositories/constraint-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function constrainWhere(
5151
*/
5252
export function constrainDataObject<T extends Entity>(
5353
originalData: DataObject<T>,
54-
constraint: Partial<T>,
54+
constraint: DataObject<T>,
5555
): DataObject<T> {
5656
const constrainedData = cloneDeep(originalData);
5757
for (const c in constraint) {

packages/repository/src/repositories/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
export * from './kv.repository';
77
export * from './legacy-juggler-bridge';
8+
export * from './kv.repository.bridge';
89
export * from './repository';
910
export * from './relation.factory';
1011
export * from './relation.repository';
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
2+
// Node module: @loopback/repository
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import * as legacy from 'loopback-datasource-juggler';
7+
8+
import {Options, DataObject} from '../common-types';
9+
import {Model} from '../model';
10+
11+
import {KVRepository, AsyncIterator, KVFilter} from './kv.repository';
12+
13+
import {juggler, ensurePromise} from './legacy-juggler-bridge';
14+
15+
/**
16+
* An implementation of KVRepository based on legacy loopback-datasource-juggler
17+
*/
18+
export class DefaultKVRepository<T extends Model> implements KVRepository<T> {
19+
kvModelClass: typeof juggler.KeyValueModel;
20+
21+
constructor(kvModelClass: typeof juggler.KeyValueModel) {
22+
this.kvModelClass = kvModelClass;
23+
}
24+
25+
delete(key: string, options?: Options): Promise<void> {
26+
return ensurePromise(this.kvModelClass.delete(key, options));
27+
}
28+
29+
deleteAll(options?: Options): Promise<void> {
30+
return ensurePromise(this.kvModelClass.deleteAll(options));
31+
}
32+
33+
get(key: string, options?: Options): Promise<T> {
34+
const val = this.kvModelClass.get(key, options) as legacy.PromiseOrVoid<T>;
35+
return ensurePromise<T>(val);
36+
}
37+
38+
set(key: string, value: DataObject<T>, options?: Options): Promise<void> {
39+
return ensurePromise<void>(this.kvModelClass.set(key, value, options));
40+
}
41+
42+
expire(key: string, ttl: number, options?: Options): Promise<void> {
43+
return ensurePromise<void>(this.kvModelClass.expire(key, ttl, options));
44+
}
45+
46+
ttl(key: string, options?: Options): Promise<number> {
47+
return ensurePromise<number>(this.kvModelClass.ttl(key, options));
48+
}
49+
50+
keys(filter?: KVFilter, options?: Options): Promise<string[]> {
51+
return ensurePromise<string[]>(this.kvModelClass.keys(filter, options));
52+
}
53+
54+
iterateKeys(filter?: KVFilter, options?: Options): AsyncIterator<string> {
55+
return new AsyncKeyIteratorImpl(
56+
this.kvModelClass.iterateKeys(filter, options),
57+
);
58+
}
59+
}
60+
61+
class AsyncKeyIteratorImpl implements AsyncIterator<string> {
62+
constructor(private keys: legacy.AsyncKeyIterator) {}
63+
next() {
64+
const key = ensurePromise<string | undefined>(this.keys.next());
65+
return key.then(k => {
66+
return {done: k === undefined, value: k || ''};
67+
});
68+
}
69+
}

packages/repository/src/repositories/kv.repository.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,28 @@
66
import {Repository} from './repository';
77
import {Options, DataObject} from '../common-types';
88
import {Model} from '../model';
9-
import {Filter} from '../query';
9+
10+
/**
11+
* Filter for keys
12+
*/
13+
export type KVFilter = {
14+
/**
15+
* Glob string to use to filter returned keys (i.e. `userid.*`). All
16+
* connectors are required to support `*` and `?`. They may also support
17+
* additional special characters that are specific to the backing database.
18+
*/
19+
match: string;
20+
};
21+
22+
/**
23+
* Polyfill for AsyncIterator before es.next is ready
24+
*/
25+
// tslint:disable:no-any
26+
export interface AsyncIterator<T> {
27+
next(value?: any): Promise<IteratorResult<T>>;
28+
return?(value?: any): Promise<IteratorResult<T>>;
29+
throw?(e?: any): Promise<IteratorResult<T>>;
30+
}
1031

1132
/**
1233
* Key/Value operations for connector implementations
@@ -17,19 +38,16 @@ export interface KVRepository<T extends Model> extends Repository<T> {
1738
*
1839
* @param key Key for the entry
1940
* @param options Options for the operation
20-
* @returns Promise<true> if an entry is deleted for the key, otherwise
21-
* Promise<false>
2241
*/
23-
delete(key: string, options?: Options): Promise<boolean>;
42+
delete(key: string, options?: Options): Promise<void>;
2443

2544
/**
2645
* Delete all entries
2746
*
2847
* @param key Key for the entry
2948
* @param options Options for the operation
30-
* @returns A promise of the number of entries deleted
3149
*/
32-
deleteAll(options?: Options): Promise<number>;
50+
deleteAll(options?: Options): Promise<void>;
3351

3452
/**
3553
* Get an entry by key
@@ -46,20 +64,16 @@ export interface KVRepository<T extends Model> extends Repository<T> {
4664
* @param key Key for the entry
4765
* @param value Value for the entry
4866
* @param options Options for the operation
49-
* @returns Promise<true> if an entry is set for the key, otherwise
50-
* Promise<false>
5167
*/
52-
set(key: string, value: DataObject<T>, options?: Options): Promise<boolean>;
68+
set(key: string, value: DataObject<T>, options?: Options): Promise<void>;
5369

5470
/**
5571
* Set up ttl for an entry by key
5672
*
5773
* @param key Key for the entry
5874
* @param options Options for the operation
59-
* @returns Promise<true> if an entry is set for the key, otherwise
60-
* Promise<false>
6175
*/
62-
expire(key: string, ttl: number, options?: Options): Promise<boolean>;
76+
expire(key: string, ttl: number, options?: Options): Promise<void>;
6377

6478
/**
6579
* Get ttl for an entry by key
@@ -68,23 +82,28 @@ export interface KVRepository<T extends Model> extends Repository<T> {
6882
* @param options Options for the operation
6983
* @returns A promise of the TTL value
7084
*/
71-
ttl?(key: string, ttl: number, options?: Options): Promise<number>;
85+
ttl?(key: string, options?: Options): Promise<number>;
7286

7387
/**
7488
* Fetch all keys
7589
*
76-
* @param key Key for the entry
90+
* @param filter Filter for keys
7791
* @param options Options for the operation
7892
* @returns A promise of an array of keys for all entries
7993
*/
80-
keys?(options?: Options): Promise<string[]>;
94+
keys?(filter?: KVFilter, options?: Options): Promise<string[]>;
8195

8296
/**
8397
* Get an Iterator for matching keys
8498
*
8599
* @param filter Filter for keys
86100
* @param options Options for the operation
87-
* @returns A promise of an iterator of entries
101+
* @returns An iterator of keys.
102+
*
103+
* FIXME(rfeng): It's probably better to return an object that supports async
104+
* iteration ("iterable" if it has a Symbol.asyncIterator method that returns
105+
* an AsyncIterator object) so that the return value can be used with
106+
* `for-await-of`.
88107
*/
89-
iterateKeys?(filter?: Filter, options?: Options): Promise<Iterator<T>>;
108+
iterateKeys?(filter?: KVFilter, options?: Options): AsyncIterator<string>;
90109
}

packages/repository/src/repositories/legacy-juggler-bridge.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Command,
1414
NamedParameters,
1515
PositionalParameters,
16+
DataObject,
1617
} from '../common-types';
1718
import {Entity, ModelDefinition} from '../model';
1819
import {Filter, Where} from '../query';
@@ -31,6 +32,7 @@ export namespace juggler {
3132
export import ModelBase = legacy.ModelBase;
3233
export import ModelBaseClass = legacy.ModelBaseClass;
3334
export import PersistedModel = legacy.PersistedModel;
35+
export import KeyValueModel = legacy.KeyValueModel;
3436
export import PersistedModelClass = legacy.PersistedModelClass;
3537
}
3638

@@ -55,7 +57,7 @@ export function bindModel<T extends juggler.ModelBaseClass>(
5557
* @param p Promise or void
5658
*/
5759
/* tslint:disable-next-line:no-any */
58-
function ensurePromise<T>(p: legacy.PromiseOrVoid<T>): Promise<T> {
60+
export function ensurePromise<T>(p: legacy.PromiseOrVoid<T>): Promise<T> {
5961
if (p && isPromiseLike(p)) {
6062
// Juggler uses promise-like Bluebird instead of native Promise
6163
// implementation. We need to convert the promise returned by juggler
@@ -161,12 +163,12 @@ export class DefaultCrudRepository<T extends Entity, ID>
161163
);
162164
}
163165

164-
async create(entity: Partial<T>, options?: Options): Promise<T> {
166+
async create(entity: DataObject<T>, options?: Options): Promise<T> {
165167
const model = await ensurePromise(this.modelClass.create(entity, options));
166168
return this.toEntity(model);
167169
}
168170

169-
async createAll(entities: Partial<T>[], options?: Options): Promise<T[]> {
171+
async createAll(entities: DataObject<T>[], options?: Options): Promise<T[]> {
170172
const models = await ensurePromise(
171173
this.modelClass.create(entities, options),
172174
);
@@ -215,7 +217,7 @@ export class DefaultCrudRepository<T extends Entity, ID>
215217
}
216218

217219
updateAll(
218-
data: Partial<T>,
220+
data: DataObject<T>,
219221
where?: Where,
220222
options?: Options,
221223
): Promise<number> {
@@ -224,14 +226,18 @@ export class DefaultCrudRepository<T extends Entity, ID>
224226
);
225227
}
226228

227-
updateById(id: ID, data: Partial<T>, options?: Options): Promise<boolean> {
229+
updateById(id: ID, data: DataObject<T>, options?: Options): Promise<boolean> {
228230
const idProp = this.modelClass.definition.idName();
229231
const where = {} as Where;
230232
where[idProp] = id;
231233
return this.updateAll(data, where, options).then(count => count > 0);
232234
}
233235

234-
replaceById(id: ID, data: Partial<T>, options?: Options): Promise<boolean> {
236+
replaceById(
237+
id: ID,
238+
data: DataObject<T>,
239+
options?: Options,
240+
): Promise<boolean> {
235241
return ensurePromise(this.modelClass.replaceById(id, data, options)).then(
236242
result => !!result,
237243
);

0 commit comments

Comments
 (0)