diff --git a/angular.json b/angular.json index 69621baa..2fe1baec 100644 --- a/angular.json +++ b/angular.json @@ -610,9 +610,40 @@ "styleext": "scss" } } + }, + "syndesi": { + "root": "libs/syndesi", + "sourceRoot": "libs/syndesi/src", + "projectType": "library", + "prefix": "sp", + "architect": { + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "libs/syndesi/tsconfig.lib.json", + "libs/syndesi/tsconfig.spec.json" + ], + "exclude": ["**/node_modules/**"] + } + }, + "test": { + "builder": "@nrwl/builders:jest", + "options": { + "jestConfig": "libs/syndesi/jest.config.js", + "tsConfig": "libs/syndesi/tsconfig.spec.json", + "setupFile": "libs/syndesi/src/test-setup.ts" + } + } + }, + "schematics": { + "@nrwl/schematics:component": { + "styleext": "scss" + } + } } }, - "defaultProject": "domain", + "defaultProject": "sparkles", "cli": { "warnings": { "typescriptMismatch": false @@ -621,13 +652,16 @@ "packageManager": "yarn" }, "schematics": { + "@nrwl/schematics:application": { + "unitTestRunner": "jest", + "e2eTestRunner": "cypress" + }, "@nrwl/schematics:library": { "unitTestRunner": "jest", "framework": "angular" }, - "@nrwl/schematics:application": { - "unitTestRunner": "jest", - "e2eTestRunner": "cypress" + "@nrwl/schematics:component": { + "styleext": "scss" }, "@nrwl/schematics:node-application": { "framework": "express" diff --git a/docs/GREEK.md b/docs/GREEK.md new file mode 100644 index 00000000..eae4fbc2 --- /dev/null +++ b/docs/GREEK.md @@ -0,0 +1,5 @@ + - sýndesi / σύνδεση + - https://en.wiktionary.org/wiki/%CF%83%CF%8D%CE%BD%CE%B4%CE%B5%CF%83%CE%B7 + - epafí / επαφή + - https://en.wiktionary.org/wiki/%CE%B5%CF%80%CE%B1%CF%86%CE%AE + diff --git a/libs/shared/src/lib/debug.ts b/libs/shared/src/lib/debug.ts index ace81463..37a33edc 100644 --- a/libs/shared/src/lib/debug.ts +++ b/libs/shared/src/lib/debug.ts @@ -43,6 +43,7 @@ import { unique } from './functional'; export class Debug { public environment: any = {}; + private enabled: string[] = []; public get isDevelop(): boolean { diff --git a/libs/syndesi/README.md b/libs/syndesi/README.md new file mode 100644 index 00000000..971ee7ac --- /dev/null +++ b/libs/syndesi/README.md @@ -0,0 +1,9 @@ +# Syndesi + +> A connected-media format. + +Syndesi is inspired by HAL, Siren, and so on. + +- http://stateless.co/hal_specification.html +- https://sookocheff.com/post/api/on-choosing-a-hypermedia-format/ +- https://tools.ietf.org/html/draft-kelly-json-hal-08 diff --git a/libs/syndesi/jest.config.js b/libs/syndesi/jest.config.js new file mode 100644 index 00000000..468f06bd --- /dev/null +++ b/libs/syndesi/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + name: 'syndesi', + preset: '../../jest.config.js', + coverageDirectory: '../../coverage/libs/syndesi' +}; diff --git a/libs/syndesi/src/index.ts b/libs/syndesi/src/index.ts new file mode 100644 index 00000000..bf064d49 --- /dev/null +++ b/libs/syndesi/src/index.ts @@ -0,0 +1,8 @@ +export * from './lib/api-client.service'; +export * from './lib/call'; +export * from './lib/embedded.functions'; +export * from './lib/link.functions'; +export * from './lib/resource.interfaces'; +export * from './lib/resource.functions'; +export * from './lib/uri'; +export * from './lib/syndesi.module'; diff --git a/libs/syndesi/src/lib/api-client.service.spec.ts b/libs/syndesi/src/lib/api-client.service.spec.ts new file mode 100644 index 00000000..2639e404 --- /dev/null +++ b/libs/syndesi/src/lib/api-client.service.spec.ts @@ -0,0 +1,51 @@ +import { async, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { ApiClient } from './api-client.service'; +import { Resource, ResourceCollection } from './resource.interfaces'; + +describe('ApiClient', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ HttpClientTestingModule ], + providers: [ ApiClient ] + }).compileComponents(); + })); + + it('should create', () => { + const service = TestBed.get(ApiClient); + expect(service).toBeTruthy(); + const backend = TestBed.get(HttpTestingController); + expect(backend).toBeTruthy(); + }); + + describe(`call()`, () => { + interface Foo { + what: string; + } + + const foo: Resource = { + _links: { + self: { href: '/foo' } + }, + what: 'foo!' + }; + + interface FooCollectionResource extends ResourceCollection {} + + it(`should return an Observable`, () => { + const api: ApiClient = TestBed.get(ApiClient); + const obs = api.call(foo); + expect(obs).toBeTruthy(); + }); + + xit(`should...`, () => { + const api: ApiClient = TestBed.get(ApiClient); + + api.call(foo).get('next').send().subscribe(next => { + const bar = next.resource; + + expect(bar).toBeTruthy(); + }); + }); + }); +}); diff --git a/libs/syndesi/src/lib/api-client.service.ts b/libs/syndesi/src/lib/api-client.service.ts new file mode 100644 index 00000000..4d31d8ad --- /dev/null +++ b/libs/syndesi/src/lib/api-client.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Call } from './call'; +import { Resource } from './resource.interfaces'; + +/** + * Inject `ApiClient` to start brosing a nicely crafted connected-media API. + * + * @experimental + */ +@Injectable({ providedIn: 'root' }) +export class ApiClient { + + constructor( + private http: HttpClient + ) {} + + /** + * Get the index page of the API at given `url` + * + * @param url URL of index resource, e.g. `/foo/bar/api.json` + * @return Emits a `Call` object for subsequent API calls + */ + public index (url: string): Observable> { + return this.http.get> (url).pipe( + map(res => new Call(this.http, res))); + } + + /** + * Get a subsequent call from a resource obtained prior. + * + * @param resource + * @return A `Call` object for subsequent API calls + */ + public call (resource: Resource): Call { + return new Call(this.http, resource); + } + +} diff --git a/libs/syndesi/src/lib/call.ts b/libs/syndesi/src/lib/call.ts new file mode 100644 index 00000000..0760e571 --- /dev/null +++ b/libs/syndesi/src/lib/call.ts @@ -0,0 +1,171 @@ +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Resource, ResourceMetadata } from './resource.interfaces'; +import { expand, UriParams } from './uri'; + +const toNextCall = (call: Call) => { + + return function (res: Resource | HttpResponse>) { + const json = res instanceof HttpResponse ? res.body : res; + const response = res instanceof HttpResponse ? res : undefined; + + return new Call(call['_http'], json, response); + }; +}; + +/** + * A `Call` is a single HTTP interaction for exchanging a resource between client and server. + * + * ### How To Use + * + * Inject `ApiClient` and obtain a call instance from a `Resource` obtained prior to this call: + * + * ```ts + * interface Entity { + * whatsUp: string; + * } + * + * @Injectable() + * export class EntityService { + * constructor(private api: ApiClient) + * + * fetchNextPage(res: Respurce) { + * return this.api.call(res).get('next').send<>(); + * } + * } + * ``` + * + * @param T Type declaration for a `Resource` obtained prior. + * @experimental + */ +export class Call { + private _http: HttpClient; + private _uri: string; + private _method: string; + private _options = {}; + + // TODO: public resource: T & ResourceMetadata + constructor( + http: HttpClient | Call, + public resource: Resource, + public response?: HttpResponse + ) { + if (http instanceof HttpClient) { + this._http = http; + } else { + this._http = http._http; + } + } + + public delete(rel: string, params?: UriParams, body?: any): Call { + this.uri(rel, params); + this.method('delete'); + + return this; + } + + public get(rel: string, params?: UriParams): Call { + this.uri(rel, params); + this.method('get'); + + return this; + } + + public head(rel: string, params?: UriParams): Call { + this.uri(rel, params); + this.method('head'); + + return this; + } + + public options(rel: string, params?: UriParams): Call { + this.uri(rel, params); + this.method('options'); + + return this; + } + + public patch(rel: string, params?: UriParams): Call { + this.uri(rel, params); + this.method('patch'); + + return this; + } + + public post(rel: string, params?: UriParams, body?: any): Call { + this.uri(rel, params); + this.method('post'); + this.body(body); + + return this; + } + + public put(rel: string, params?: UriParams, body?: any): Call { + this.uri(rel, params); + this.method('put'); + this.body(body); + + return this; + } + + public uri(rel: string, params?: UriParams): Call { + this._uri = this.expandLink(rel, params); + + return this; + } + + public method(verb: string): Call { + this._method = verb; + + return this; + } + + public opts(opts: any): Call { + this._options = opts; + + return this; + } + + public body(body: any): Call { + this._options = { + ...this._options, + body + }; + + return this; + } + + public send(): Observable> { + if (!this._method) { + throw new Error('method is a required parameter! Please set it with method() or one of the short-hands like get().'); + } + if (!this._uri) { + throw new Error('uri is a required parameter! Please set it with uri() or one of the short-hands like get().'); + } + + return this._http.request>( + this._method, + this._uri, + { + ...this._options, + observe: 'response', + responseType: 'json' + } + ).pipe(map(toNextCall(this))); + } + + private expandLink(rel: string, params?: UriParams): string { + const link = this.resource._links[rel]; + + if (!link) { + throw new Error(`Link with rel=${rel} does not exist`); + } + + if (link instanceof Array) { + throw new Error('Traversing arrays not implemented'); + } else { + return expand(link, params); + } + } +} diff --git a/libs/syndesi/src/lib/embedded.functions.spec.ts b/libs/syndesi/src/lib/embedded.functions.spec.ts new file mode 100644 index 00000000..308e43db --- /dev/null +++ b/libs/syndesi/src/lib/embedded.functions.spec.ts @@ -0,0 +1,57 @@ +import { Resource } from './resource.interfaces'; +import { embedded, embeddeds } from './embedded.functions'; + +describe(`embedded()`, () => { + + const test: Resource = { + _links: { + self: { href: '/test' } + }, + _embedded: { + foo: { + _links: { self: { href: '/foo'} }, + value: 'first' + }, + bar: { + _links: { self: { href: '/bar'} }, + value: 'second' + }, + } + }; + + it(`should return Resource for rel`, () => { + const r = embedded('foo', test); + expect(r).toBeTruthy(); + expect(r.value).toEqual('first'); + + const r2 = embedded('bar', test); + expect(r2).toBeTruthy(); + expect(r2.value).toEqual('second'); + }); +}); + +describe(`embeddeds()`, () => { + + const test: Resource = { + _links: { + self: { href: '/test' } + }, + _embedded: { + foo: [ + { + _links: { self: { href: '/foo'} }, + value: 'first' + }, + { + _links: { self: { href: '/bar'} }, + value: 'second' + } + ] + } + }; + + it(`should return Resource[] for rel`, () => { + const r = embeddeds('foo', test); + expect(r.length).toEqual(2); + }); +}); diff --git a/libs/syndesi/src/lib/embedded.functions.ts b/libs/syndesi/src/lib/embedded.functions.ts new file mode 100644 index 00000000..9e6b7cf4 --- /dev/null +++ b/libs/syndesi/src/lib/embedded.functions.ts @@ -0,0 +1,63 @@ +import { Resource, ResourceWithEmbedded } from './resource.interfaces'; +import { isResource, isResources } from './resource.functions'; + +export function hasEmbedded(value: any): value is ResourceWithEmbedded { + return value && typeof value._embedded === 'object'; +} + +export interface EmbeddedOpts { + guard?: (value: any) => value is T; + shouldThrow?: boolean; +} + +/** + * Return the embedded resource identified by relation `rel` from parent `res`. + * + * @param rel Relation name + * @param res Parent resource + * @param opts Set `shouldThrow`, if you want an error instead of an `undefined` return value + * @return Embedded resource or an `undefined` value + * @stable + */ +export function embedded(rel: string, res: any, opts?: EmbeddedOpts): Resource { + if (hasEmbedded(res)) { + const em = res._embedded[rel]; + const guard = opts && opts.guard ? opts.guard : (v: T): v is T => true; + + if (isResource(em) && guard(em)) { + return em; + } else if (opts && opts.shouldThrow) { + throw new Error(`rel=${rel} is not an embedded Resource on ${res}`); + } + } else if (opts && opts.shouldThrow) { + throw new Error(`${res} has no _embedded resources`); + } +} + +/** + * Return an array of embedded resources identified by relation `rel` from parent `res`. + * + * @param rel Relation name + * @param res Parent resource + * @param opts Set `shouldThrow`, if you want an error instead of an `undefined` return value + * @return Embedded resource or an `undefined` value + * @stable + */ +export function embeddeds(rel: string, res: any, opts?: EmbeddedOpts): Resource[] { + if (hasEmbedded(res)) { + const em = res._embedded[rel]; + const guard = opts && opts.guard ? opts.guard : (v: T): v is T => true; + + const isArrayOfT = (value: any): value is T[] => { + return value instanceof Array && value.every(val => guard(val)); + }; + + if (isResources(em) && isArrayOfT(em)) { + return em; + } else if (opts && opts.shouldThrow) { + throw new Error(`rel=${rel} is not an embedded Resource[] on ${res}`); + } + } else if (opts && opts.shouldThrow) { + throw new Error(`${res} has no _embedded resources`); + } +} diff --git a/libs/syndesi/src/lib/link.functions.spec.ts b/libs/syndesi/src/lib/link.functions.spec.ts new file mode 100644 index 00000000..fbd3a428 --- /dev/null +++ b/libs/syndesi/src/lib/link.functions.spec.ts @@ -0,0 +1,76 @@ +import { Resource } from './resource.interfaces'; +import { isLink, isLinks, links, link } from './link.functions'; + +describe(`isLink()`, () => { + + it(`should return true when property 'href' is string`, () => { + const foo = { href: '/bar' }; + expect(isLink(foo)).toBeTruthy(); + }); + + it(`should return false for undefined values`, () => { + expect(isLink(undefined)).toBeFalsy(); + expect(isLink(null)).toBeFalsy(); + }); + + it(`should return false for quirks`, () => { + expect(isLink([])).toBeFalsy(); + expect(isLink({})).toBeFalsy(); + }); + + it(`should be a type guard for Link`, () => { + const a = { + href: '/foo' + }; + + expect(isLink(a)).toBeTruthy(); + if (isLink(a)) { + expect(a.href).toEqual('/foo'); + } else { + fail('isLink() should return true for Link interface'); + } + }); +}); + +describe(`isLinks()`, () => { + + const foo = { href: '/foo' }; + const bar = { href: '/bar' }; + + it(`should return true for array of Link`, () => { + expect(isLinks([foo, bar])).toBeTruthy(); + }); +}); + +describe(`link()`, () => { + + const test: Resource = { + _links: { + self: { href: '/test' }, + foo: { href: '/bar' } + } + }; + + it(`should return Link for rel`, () => { + const l = link('self', test); + expect(l.href).toEqual('/test'); + }); +}); + +describe(`links()`, () => { + + const test: Resource = { + _links: { + self: { href: '/test' }, + foo: [ + { href: '/bar' }, + { href: '/bar1' } + ] + } + }; + + it(`should return Link for rel`, () => { + const l = links('foo', test); + expect(l.length).toEqual(2); + }); +}); diff --git a/libs/syndesi/src/lib/link.functions.ts b/libs/syndesi/src/lib/link.functions.ts new file mode 100644 index 00000000..d0a88847 --- /dev/null +++ b/libs/syndesi/src/lib/link.functions.ts @@ -0,0 +1,63 @@ +import { Resource, Link } from './resource.interfaces'; + +export function hasLinks(value: any): value is Resource { + return value && value._links; +} + +export function isLink(value: any): value is Link { + return value && typeof value.href === 'string'; +} + +export function isLinks(value: any): value is Link[] { + return value instanceof Array && value.every(val => isLink(val)); +} + +export interface LinkOpts { + shouldThrow?: boolean; +} + +/** + * Return the link identified by relation `rel` from resource `res`. + * + * @param rel Relation name + * @param res Parent resource + * @param opts Set `shouldThrow`, if you want an error instead of an `undefined` return value + * @return An object of type `Link` or an `undefined` value + * @stable + */ +export function link(rel: string, res: any, opts?: LinkOpts): Link { + if (hasLinks(res)) { + const linkForRel = res._links[rel]; + + if (isLink(linkForRel)) { + return linkForRel; + } else if (opts && opts.shouldThrow) { + throw new Error(`rel=${rel} is not a Link on Resource ${res}`); + } + } else if (opts && opts.shouldThrow) { + throw new Error(`Object ${res} is not a Resource`); + } +} + +/** + * Return an array of links identified by relation `rel` from resource `res`. + * + * @param rel Relation name + * @param res Parent resource + * @param opts Set `shouldThrow`, if you want an error instead of an `undefined` return value + * @return Array of `Link` or an `undefined` value + * @stable + */ +export function links(rel: string, res: any, opts?: LinkOpts): Link[] { + if (hasLinks(res)) { + const linkForRel = res._links[rel]; + + if (isLinks(linkForRel)) { + return linkForRel; + } else if (opts && opts.shouldThrow) { + throw new Error(`rel=${rel} is not an Link[] on Resource ${res}`); + } + } else if (opts && opts.shouldThrow) { + throw new Error(`Object ${res} is not a Resource`); + } +} diff --git a/libs/syndesi/src/lib/resource.functions.ts b/libs/syndesi/src/lib/resource.functions.ts new file mode 100644 index 00000000..5813a0ce --- /dev/null +++ b/libs/syndesi/src/lib/resource.functions.ts @@ -0,0 +1,10 @@ +import { Resource } from './resource.interfaces'; +import { hasLinks, isLink } from './link.functions'; + +export function isResource(value: any): value is Resource { + return hasLinks(value) && isLink(value._links.self); +} + +export function isResources(value: any): value is Resource[] { + return value instanceof Array && value.every(val => isResource(val)); +} diff --git a/libs/syndesi/src/lib/resource.interfaces.spec.ts b/libs/syndesi/src/lib/resource.interfaces.spec.ts new file mode 100644 index 00000000..5b6a68a3 --- /dev/null +++ b/libs/syndesi/src/lib/resource.interfaces.spec.ts @@ -0,0 +1,37 @@ +import { Resource } from './resource.interfaces'; + +describe(`Resource`, () => { + + it(`should extend existing interface types`, () => { + interface A { + b: number; + } + + const test: Resource = { + _links: { + self: { href: '/test' } + }, + b: 123 + }; + + expect(test.b).toEqual(123); + }); + + it(`should allow to declare custom interface types`, () => { + interface A { + id: number; + } + + interface AResource extends Resource {} + + const test: AResource = { + id: 123, + _links: { + self: { href: '/user/123' } + } + }; + + expect(test.id).toEqual(123); + expect(test._links.self.href).toEqual('/user/123'); + }); +}); diff --git a/libs/syndesi/src/lib/resource.interfaces.ts b/libs/syndesi/src/lib/resource.interfaces.ts new file mode 100644 index 00000000..e4d0df6d --- /dev/null +++ b/libs/syndesi/src/lib/resource.interfaces.ts @@ -0,0 +1,125 @@ +/** + * Link representation. + * + * @experimental + */ +export interface Link { + href: string; + templated?: boolean; +} + +/** + * Base type for resource representations. + * + * A resource must be identifiable by a `self` URI. + * + * The generic type parameter may be used to extend existing type information, thus allows + * to add resource-capabilities to existing code in an unobstrusive way. + * + * ### How To Use + * + * The first approach is to declare a value with generic type information: + * + * ```ts + * export interface User { + * id: number; + * name: string; + * } + * + * const foo: Resource = { + * _links: { + * self: { href: '/user/123' } + * }, + * id: 123, + * name: 'Theo Test' + * } + * ``` + * + * The second approach is to extend existing type declarations: + * + * ```ts + * export interface User { + * id: number; + * name: string; + * } + * + * export interface UserResource extends Resource { + * } + * + * const foo: UserResource = { + * _links: { + * self: { href: '/user/123' } + * }, + * id: 123, + * name: 'Theo Test' + * } + * ``` + * + * @stable + */ +export type Resource = T & ResourceMetadata; + +export interface ResourceMetadata { + _links: { + self: Link; + [key: string]: Link | Link[]; + }; + _embedded?: { + [key: string]: Resource | Resource[]; + }; +} + +/** + * A resource with `_embedded` resources. + * + * @stable + */ +export type ResourceWithEmbedded = Resource & { + _embedded: { + [key: string]: Resource | Resource[]; + } +}; + +/** + * Resource metadata for collections of resources. + * + * @stable + */ +export interface CollectionMetadata { + /** The number of items contained in this collection representation. */ + count: number; + /** The total count of items in the full collection. Optional. */ + totalCount?: number; + /** The index of this page. Optional, but required when paging is enabled. */ + page?: number; + /** The total count of pages. Optional, but required when paging is enabled. */ + totalPages?: number; + /** The number of items per each page. Optional, but required when paging is enabled. */ + pageSize?: number; +} + +/** + * A collection of resources. + * + * The embedded `content` property must be an array of resources. + * Each resource must be indentifiable by a `self` URI. + * + * `ResourceCollection` itself is a resource and must be identifiable by `self` URI. + * It has metadata information from `CollectionMetadata`, such as the amount of items or paging + * information. + * + * @stable + */ +export type ResourceCollection = Resource & { + _embedded: { + content: Resource[]; + [key: string]: any; + } +} + +/** + * A resource collection with simple collection metadata. + * + * @stable + */ +export interface ResourcesCollection extends ResourceCollection {} diff --git a/libs/syndesi/src/lib/syndesi.module.spec.ts b/libs/syndesi/src/lib/syndesi.module.spec.ts new file mode 100644 index 00000000..5d99ed05 --- /dev/null +++ b/libs/syndesi/src/lib/syndesi.module.spec.ts @@ -0,0 +1,14 @@ +import { async, TestBed } from '@angular/core/testing'; +import { SyndesiModule } from './syndesi.module'; + +describe('SyndesiModule', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [SyndesiModule] + }).compileComponents(); + })); + + it('should create', () => { + expect(SyndesiModule).toBeDefined(); + }); +}); diff --git a/libs/syndesi/src/lib/syndesi.module.ts b/libs/syndesi/src/lib/syndesi.module.ts new file mode 100644 index 00000000..9bf2553e --- /dev/null +++ b/libs/syndesi/src/lib/syndesi.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +/** @experimental */ +@NgModule({ + imports: [CommonModule] +}) +export class SyndesiModule {} diff --git a/libs/syndesi/src/lib/uri.ts b/libs/syndesi/src/lib/uri.ts new file mode 100644 index 00000000..2227cb8c --- /dev/null +++ b/libs/syndesi/src/lib/uri.ts @@ -0,0 +1,22 @@ +import { Link } from './resources'; + +export interface UriParams { + [key: string]: any +} + +export function expand(link: Link, params: UriParams): string { + let url: string; + if (link.templated) { + // TODO: expand uri template + // https://github.com/bramstein/url-template/blob/master/lib/url-template.js + // https://github.com/geraintluff/uri-templates + url = link.href; + Object.keys(params).forEach(key => { + url = url.replace(`{${key}}`, params[key]); + }); + } else { + url = link.href; + } + + return url; +} diff --git a/libs/syndesi/src/test-setup.ts b/libs/syndesi/src/test-setup.ts new file mode 100644 index 00000000..8d88704e --- /dev/null +++ b/libs/syndesi/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular'; diff --git a/libs/syndesi/tsconfig.json b/libs/syndesi/tsconfig.json new file mode 100644 index 00000000..e5decd5e --- /dev/null +++ b/libs/syndesi/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["**/*.ts"] +} diff --git a/libs/syndesi/tsconfig.lib.json b/libs/syndesi/tsconfig.lib.json new file mode 100644 index 00000000..3b81cfc3 --- /dev/null +++ b/libs/syndesi/tsconfig.lib.json @@ -0,0 +1,26 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/libs/syndesi", + "target": "es2015", + "module": "es2015", + "moduleResolution": "node", + "declaration": true, + "sourceMap": true, + "inlineSources": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "types": [], + "lib": ["dom", "es2018"] + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true, + "enableResourceInlining": true + }, + "exclude": ["src/test.ts", "**/*.spec.ts"] +} diff --git a/libs/syndesi/tsconfig.spec.json b/libs/syndesi/tsconfig.spec.json new file mode 100644 index 00000000..8e37b9f1 --- /dev/null +++ b/libs/syndesi/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/libs/syndesi", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/libs/syndesi/tslint.json b/libs/syndesi/tslint.json new file mode 100644 index 00000000..a8eb2fb8 --- /dev/null +++ b/libs/syndesi/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tslint.json", + "rules": { + "directive-selector": [true, "attribute", "sparkles", "camelCase"], + "component-selector": [true, "element", "sparkles", "kebab-case"] + } +} diff --git a/nx.json b/nx.json index 87b4ac73..a96cc855 100644 --- a/nx.json +++ b/nx.json @@ -47,7 +47,8 @@ }, "demos-app": { "tags": [] - } + }, + "syndesi": { "tags": [] } }, "implicitDependencies": { "package.json": "*", diff --git a/tsconfig.json b/tsconfig.json index f9913105..05903a82 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,7 +32,8 @@ "@sparkles/demos/component-demos": [ "libs/demos/component-demos/src/index.ts" ], - "@sparkles/demos/app": ["libs/demos/app/src/index.ts"] + "@sparkles/demos/app": ["libs/demos/app/src/index.ts"], + "@sparkles/syndesi": ["libs/syndesi/src/index.ts"] }, "module": "es2015" },