Skip to content

Commit

Permalink
Feature: Json Ripper and Data Providers (#221)
Browse files Browse the repository at this point in the history
* fix/removeRelationshipLikeJsonApiSpecification
* Json Ripper and Data Provider tested. Package json updated, last version of Jest used
* jest types updated
* getAll done for new JsonRipper
* ripper ready for get()
* circle ci require 1 node
* json ripper fixed for hasOne = null
* proposal: collection have resources[] or basicresources[] (#224)
* Feature/without memory service (#226)
* ContentType added (#227)
* feature/dexie-work-with-elements-and-collections (#228)
* Improve typing and test cahememory (#229)
* fix/get-with-include (#230)
  • Loading branch information
pablorsk authored Oct 4, 2019
1 parent 537ed0d commit b1e09f1
Show file tree
Hide file tree
Showing 58 changed files with 2,000 additions and 1,255 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class AuthorsComponent {

#### Collection sort

Ex: `name` is a authors attribute, and makes a query like `/authors?sort=name,job_title`
Example: `name` is a authors attribute, and makes a query like `/authors?sort=name,job_title`

```typescript
let authors = authorsService.all({
Expand Down
6 changes: 6 additions & 0 deletions demo/app/authors/components/authors.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ <h3>Authors</h3>
<th>Date of birth</th>
<th>Date of dead</th>
<th>Books</th>
<th>Photos</th>
</tr>
</thead>
<tr *ngFor="let author of authors.data; trackBy: authors.trackBy">
<td>{{ author.id }}</td>
<td>
<a [routerLink]="['/authors', author.id]">{{ author.attributes.name }}</a>
<code title="Cache last update"><small>{{ author.cache_last_update | date:'H:MM:SS' }}</small></code>
<code><small>{{ author.source }}</small></code>
</td>
<td>{{ author.attributes.date_of_birth | date }}</td>
<td>{{ author.attributes.date_of_death | date }}</td>
Expand All @@ -24,6 +27,9 @@ <h3>Authors</h3>
and {{ author.relationships.books.data.length}} more...
</span>
</td>
<td>
{{ author.relationships.photos.data.length }}
</td>
<!-- <td><button (click)="delete(author)">Delete</button></td> -->
</tr>
</table>
Expand Down
2 changes: 1 addition & 1 deletion demo/app/authors/components/authors.component.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Photo } from './../../../../src/tests/factories/photos.service';
import { BooksService } from './../../books/books.service';
import { Component } from '@angular/core';
import { DocumentCollection } from 'ngx-jsonapi';
Expand All @@ -23,7 +24,6 @@ export class AuthorsComponent {
.subscribe(
authors => {
this.authors = authors;
console.info('success authors controller', authors, 'page', page || 1, authors.page.number);
},
error => console.error('Could not load authors :(', error)
);
Expand Down
2 changes: 1 addition & 1 deletion demo/app/books/components/book.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class BookComponent {
private route: ActivatedRoute
) {
route.params.subscribe(({ id }) => {
let book$ = booksService.get(id, { include: ['author', 'photos'] }).subscribe(
booksService.get(id, { include: ['author', 'photos'] }).subscribe(
book => {
this.book = book;
console.log('success book', this.book);
Expand Down
10 changes: 7 additions & 3 deletions demo/app/books/components/books.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ <h3>Books</h3>
</tr>
</thead>
<!-- Use authors example. @deprected since version 2.0.0 -->
<tr *ngFor="let book of books.data">
<tr *ngFor="let book of books.data; trackBy: books.trackBy">
<td>{{ book.id }}</td>
<td>
<a [routerLink]="['/books', book.id]">{{ book.attributes.title }}</a>
</td>
<td>{{ book.attributes.date_published | date }}</td>
<td>{{ book.relationships.author.data.attributes.name }} #{{ book.relationships.author.data.id }}</td>
<td><span *ngFor="let photo of book.relationships.photos.data">{{ photo.id }}, </span></td>
<td>
<a *ngIf="book.relationships.author.builded" [routerLink]="['/authors', book.relationships.author.data.id]">{{ book.relationships.author.data.attributes.name }}</a>
</td>
<td>
<span *ngFor="let photo of book.relationships.photos.data">{{ photo.id }}, </span>
</td>
<td><a ng-click="delete(book)" title="not supported by demo server"><small>DELETE</small></a></td>
</tr>
</table>
Expand Down
11 changes: 7 additions & 4 deletions demo/app/books/components/books.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ export class BooksComponent {
page: { number: page || 1 },
include: ['author', 'photos']
})
.subscribe(books => {
this.books = books;
console.info('success books controll', this.books);
}, (error): void => console.info('error books controll', error));
.subscribe(
books => {
this.books = books;
// console.info('success books controll', this.books);
},
(error): void => console.info('error books controll', error)
);
});
}

Expand Down
2 changes: 1 addition & 1 deletion jest.base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
'<rootDir>/setup-jest.ts'
],
setupFiles: [
'<rootDir>/src/test/globals-test.ts'
'<rootDir>/src/tests/globals-test.ts'
],
transform: {
'^.+\\.(ts|js|html)$': 'ts-jest'
Expand Down
9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"demo:test": "jest --config ./jest.demo.config.js",
"demo:build": "yarn build && yarn run copy:dist && yarn cli build --prod --base-href \"/\" --output-path \"./demo-dist\"",
"demo:release": "yarn demo:build && ts-node ./build/demo-release.ts",
"ci": "yarn run build && yarn run test --coverage && cat ./coverage/lcov.info | coveralls",
"ci": "yarn run build && yarn run test -w 1 --coverage && cat ./coverage/lcov.info | coveralls",
"watch:tests": "chokidar 'src/**/*.ts' --initial -c 'nyc --reporter=text --reporter=html yarn test'",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"release": "yarn run build && cd dist && yarn publish",
Expand Down Expand Up @@ -107,9 +107,7 @@
"@types/faker": "^4.1.5",
"@types/fs-extra": "^2.1.0",
"@types/glob": "^5.0.33",
"@types/jasmine": "^2.8.8",
"@types/jasminewd2": "^2.0.2",
"@types/jest": "^20.0.2",
"@types/jest": "^24.0.18",
"@types/lodash": "^4.14.80",
"@types/node": "^7.0.5",
"@types/ora": "^1.3.1",
Expand Down Expand Up @@ -158,6 +156,5 @@
"dependencies": {
"dexie": "^2.0.4",
"rxjs": "^6.2"
},
"version": "0.0.0"
}
}
34 changes: 30 additions & 4 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,49 @@ import { ICacheable } from './interfaces/cacheable';
import { Core } from './core';
import { DocumentResource } from './document-resource';
import { DocumentCollection } from './document-collection';
import { Resource } from './resource';

export function isLive(cacheable: ICacheable, ttl: number = null): boolean {
let ttl_in_seconds = typeof ttl === 'number' ? ttl : cacheable.ttl || 0;
export function isLive(cacheable: ICacheable, ttl?: number): boolean {
let ttl_in_seconds = ttl && typeof ttl === 'number' ? ttl : cacheable.ttl || 0;

return Date.now() <= cacheable.cache_last_update + ttl_in_seconds * 1000;
return Date.now() < cacheable.cache_last_update + ttl_in_seconds * 1000;
}

// @todo test required for hasMany and hasOne
export function relationshipsAreBuilded(resource: Resource, includes: Array<string>): boolean {
if (includes.length === 0) {
return true;
}

for (let relationship_alias in resource.relationships) {
if (includes.includes(relationship_alias) && !resource.relationships[relationship_alias].builded) {
return false;
}
}

return true;
}

export function isCollection(document: DocumentResource | DocumentCollection): document is DocumentCollection {
if (!document.data) {
return false;
}

return !('id' in document.data);
}

export function isResource(document: DocumentResource | DocumentCollection): document is DocumentResource {
if (!document.data) {
return false;
}

return 'id' in document.data;
}

// NOTE: Checks that the service passed to the method is registered (method needs to have service's type or a resource as first arg)
export function serviceIsRegistered(target: Object, key: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | null {
// changes "PropertyDescriptor | null" type for "any" to avoid typescript error in decorators property decorators
// (see https://stackoverflow.com/questions/37694322/typescript-ts1241-unable-to-resolve-signature-of-method-decorator-when-called-a)
export function serviceIsRegistered(target: Object, key: string | symbol, descriptor: PropertyDescriptor): any {
const original = descriptor.value;

descriptor.value = function() {
Expand Down
2 changes: 1 addition & 1 deletion src/core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class CustomResourceService extends Service<CustomResource> {
describe('core methods', () => {
let core: Core;
it('should crete core service instance', () => {
spyOn(JsonapiStore.prototype, 'constructor');
spyOn<any>(JsonapiStore.prototype, 'constructor');
core = new Core(
new JsonapiConfig(),
new JsonapiStore(),
Expand Down
43 changes: 31 additions & 12 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CacheMemory } from './services/cachememory';
import { Injectable, Optional, isDevMode } from '@angular/core';
import { serviceIsRegistered } from './common';
import { PathBuilder } from './services/path-builder';
Expand All @@ -6,7 +7,7 @@ import { Resource } from './resource';
import { JsonapiConfig } from './jsonapi-config';
import { Http as JsonapiHttpImported } from './sources/http.service';
import { StoreService as JsonapiStore } from './sources/store.service';
import { IDataObject } from './interfaces/data-object';
import { IDocumentResource } from './interfaces/data-object';
import { noop } from 'rxjs/internal/util/noop';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
Expand Down Expand Up @@ -51,7 +52,12 @@ export class Core {
return Core.exec(path, 'get');
}

public static exec(path: string, method: string, data?: IDataObject, call_loadings_error: boolean = true): Observable<IDocumentData> {
public static exec(
path: string,
method: string,
data?: IDocumentResource,
call_loadings_error: boolean = true
): Observable<IDocumentData> {
Core.me.refreshLoadings(1);

return Core.injectedServices.JsonapiHttp.exec(path, method, data).pipe(
Expand Down Expand Up @@ -85,35 +91,43 @@ export class Core {
}

// @todo this function could return an empty value, fix required
public getResourceService(type: string): Service {
public getResourceService(type: string): Service | undefined {
return this.resourceServices[type];
}

public getResourceServiceOrFail(type: string): Service {
let service = this.resourceServices[type];
if (!service) {
throw new Error('The requested service has not been registered, please use register() method or @Autoregister() decorator');
}

return service;
}

@serviceIsRegistered
public static removeCachedResource(resource_type: string, resource_id: string): void {
Core.me.getResourceService(resource_type).cachememory.removeResource(resource_id);
CacheMemory.getInstance().removeResource(resource_type, resource_id);
if (Core.injectedServices.rsJsonapiConfig.cachestore_support) {
// TODO: FE-85 ---> agregar removeResource en cacheStorage
Core.me.getResourceService(resource_type).cachestore.removeResource(resource_id, resource_type);
// TODO: FE-85 ---> add method on JsonRipper
}
}

@serviceIsRegistered
public static setCachedResource(resource: Resource): void {
Core.me.getResourceService(resource.type).cachememory.setResource(resource, true);
CacheMemory.getInstance().setResource(resource, true);
if (Core.injectedServices.rsJsonapiConfig.cachestore_support) {
Core.me.getResourceService(resource.type).cachestore.setResource(resource);
// TODO: FE-85 ---> add method on JsonRipper
}
}

@serviceIsRegistered
public static deprecateCachedCollections(type: string): void {
let service = Core.me.getResourceService(type);
let service = Core.me.getResourceServiceOrFail(type);
let path = new PathBuilder();
path.applyParams(service);
service.cachememory.deprecateCollections(path.getForCache());
CacheMemory.getInstance().deprecateCollections(path.getForCache());
if (Core.injectedServices.rsJsonapiConfig.cachestore_support) {
service.cachestore.deprecateCollections(path.getForCache());
// TODO: FE-85 ---> add method on JsonRipper
}
}

Expand All @@ -134,13 +148,18 @@ export class Core {

// just an helper
public duplicateResource<R extends Resource>(resource: R, ...relations_alias_to_duplicate_too: Array<string>): R {
let newresource = <R>this.getResourceService(resource.type).new();
let newresource = <R>this.getResourceServiceOrFail(resource.type).new();
newresource.id = 'new_' + Math.floor(Math.random() * 10000).toString();
newresource.attributes = { ...newresource.attributes, ...resource.attributes };

for (const alias in resource.relationships) {
let relationship = resource.relationships[alias];

if (!relationship.data) {
newresource.relationships[alias] = resource.relationships[alias];
continue;
}

if ('id' in relationship.data) {
// relation hasOne
if (relations_alias_to_duplicate_too.indexOf(alias) > -1) {
Expand Down
17 changes: 17 additions & 0 deletions src/data-providers/data-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface IObject {
[key: string]: any;
}

export interface IElement {
key: string;
content: IObject;
}

export type TableNameType = 'collections' | 'elements';

export interface IDataProvider {
getElement(key: string, table_name: TableNameType): Promise<IObject | Array<IObject>>;
getElements(keys: Array<string>, table_name: TableNameType): Promise<Array<IObject>>;
saveElements(elements: Array<IElement>, table_name: TableNameType): Promise<void>;
updateElements(key_start_with: string, new_data: IObject, table_name: TableNameType): Promise<void>;
}
73 changes: 73 additions & 0 deletions src/data-providers/dexie-data-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { IDataProvider, IObject, IElement } from './data-provider';
import Dexie from 'dexie';

export class DexieDataProvider implements IDataProvider {
private static db: Dexie;

public constructor() {
if (DexieDataProvider.db) {
return;
}
DexieDataProvider.db = new Dexie('dexie_data_provider');
DexieDataProvider.db.version(1).stores({
collections: '',
elements: ''
});
}

public async getElement(key: string, table_name = 'elements'): Promise<IObject | Array<IObject>> {
await DexieDataProvider.db.open();
const data = await DexieDataProvider.db.table(table_name).get(key);
if (data === undefined) {
throw new Error(key + ' not found.');
}

return data;
}

public async getElements(keys: Array<string>, table_name = 'elements'): Promise<Array<IObject>> {
let data = {};
await DexieDataProvider.db
.table(table_name)
.where(':id')
.anyOf(keys)
.each(element => {
data[element.data.type + '.' + element.data.id] = element;
});

// we need to maintain same order, database return ordered by key
return keys.map(key => {
return data[key];
});
}

// @todo implement dexie.modify(changes)
// @todo test
public async updateElements(key_start_with: string, changes: IObject, table_name = 'elements'): Promise<void> {
return DexieDataProvider.db.open().then(async () => {
if (key_start_with === '') {
return DexieDataProvider.db.table(table_name).clear();
} else {
return DexieDataProvider.db
.table(table_name)
.where(':id')
.startsWith(key_start_with)
.delete()
.then(() => undefined);
}
});
}

public async saveElements(elements: Array<IElement>, table_name = 'elements'): Promise<void> {
let keys: Array<string> = [];
let items = elements.map(element => {
keys.push(element.key);

return element.content;
});

return DexieDataProvider.db.open().then(() => {
DexieDataProvider.db.table(table_name).bulkPut(items, keys);
});
}
}
2 changes: 0 additions & 2 deletions src/decorators/autoregister.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Service } from '../service';

export function Autoregister() {
return (target): any => {
const original = target;
Expand Down
Loading

0 comments on commit b1e09f1

Please sign in to comment.