From 91b59f4822a8ef344c164eb5f7bc47bb8e6444f1 Mon Sep 17 00:00:00 2001 From: Professor Raghunath Date: Sat, 12 Mar 2016 10:41:49 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 37 ++--------- .jshintignore | 1 + .npmignore | 6 ++ README.md | 66 ++++++++++++++++++- bs-config.json | 10 +++ karma-test-shim.js | 47 +++++++++++++ karma.conf.js | 103 +++++++++++++++++++++++++++++ package.json | 57 ++++++++++++++++ src/store.config.ts | 15 +++++ src/store.service.spec.ts | 135 ++++++++++++++++++++++++++++++++++++++ src/store.service.ts | 83 +++++++++++++++++++++++ store.service.js | 114 ++++++++++++++++++++++++++++++++ tsconfig.json | 17 +++++ typings.json | 6 ++ 14 files changed, 665 insertions(+), 32 deletions(-) create mode 100644 .jshintignore create mode 100644 .npmignore create mode 100644 bs-config.json create mode 100644 karma-test-shim.js create mode 100644 karma.conf.js create mode 100644 package.json create mode 100644 src/store.config.ts create mode 100644 src/store.service.spec.ts create mode 100644 src/store.service.ts create mode 100644 store.service.js create mode 100644 tsconfig.json create mode 100644 typings.json diff --git a/.gitignore b/.gitignore index e920c16..5cdff08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,10 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory node_modules +npm-debug.log +typings -# Optional npm cache directory -.npm +Thumbs.db +.DS_Store -# Optional REPL history -.node_repl_history +src/**/*.js +*.map +*.d.ts diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..b43bf86 --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +README.md diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..61cb67e --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +# Node generated files +node_modules +npm-debug.log +# OS generated files +Thumbs.db +.DS_Store diff --git a/README.md b/README.md index 5011f06..f10e636 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,64 @@ -# ng2-data -Convential HTTP client for Model Data +# NOT READY! +## ng2-data +Conventional HTTP client for Model Data in Angular 2 + +## Examples +### Making Models +```javascript +// user.model.ts +import {Model, Type} from 'ng2-data'; + +exports Model.define('student', { + name: Type.string, + age: Type.number, + // classes: Type.has.many('courses'), + // school: Type.has.one('school'), + // nameAndAge: function (student) { + // return student.name + student.age; + // } +}); +``` + +### Loading Store +```javascript +import {StoreService, StoreConfig} from 'ng2-data'; + +export class AppComponent { + constructor(store: StoreService) { + store.init(new StoreConfig({baseUri: 'http://localhost:3003/api'})); + } +} +``` + + +### Using the store +```javascript +import {StoreService} from 'ng2-data'; + +export class MyComponent { + constructor(store: StoreService) { + // GET /users + store.find('users').subscribe(/*...*/); + + // GET /courses?key=value + store.find('courses', {/*query filter*/}).subscribe(/*...*/); // returns [user objects] + + // GET /users/1 + store.findOne('user', 1).subscribe(/*...*/); // returns user object + + // POST /users + store.create('user', {name:'bob'}).subscribe(/*...*/); + // OR + let bob = store.instance('user'); + bob.name = 'bob'; + //then + bob.save().subscribe(/*...*/); + + // PUT /users/1 + store.update('schools', 1, {/*with*/}).subscribe(/*...*/); // returns user object + + // DELETE /users/1 + store.destroy('schools', 1).subscribe(/*...*/); // Returns OK + } +} +``` diff --git a/bs-config.json b/bs-config.json new file mode 100644 index 0000000..0d5467f --- /dev/null +++ b/bs-config.json @@ -0,0 +1,10 @@ +{ + "port": 8000, + "files": ["./src/**/*"], + "server": { + "baseDir": "./src", + "routes": { + "/node_modules": "node_modules" + } + } +} diff --git a/karma-test-shim.js b/karma-test-shim.js new file mode 100644 index 0000000..e6449db --- /dev/null +++ b/karma-test-shim.js @@ -0,0 +1,47 @@ +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; + +__karma__.loaded = function () {}; + +System.config({ + packages: { + 'base/src': { + defaultExtension: false, + format: 'register', + map: Object.keys(window.__karma__.files) + .filter(onlyAppFiles) + .reduce(function (pathsMapping, appPath) { + var moduleName = appPath.replace(/^\/base\/src\//, './').replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath] + return pathsMapping; + }, {}) + } + } +}); + +System.import('angular2/testing').then(function (testing) { + return System.import('angular2/platform/testing/browser').then(function (providers) { + testing.setBaseTestProviders(providers.TEST_BROWSER_PLATFORM_PROVIDERS, + providers.TEST_BROWSER_APPLICATION_PROVIDERS); + }); +}).then(function () { + return Promise.all( + Object.keys(window.__karma__.files) + .filter(onlySpecFiles) + .map(function (moduleName) { + return System.import(moduleName); + })); +}).then(function () { + __karma__.start(); +}, function (error) { + __karma__.error(error.stack || error); +}); + +function onlyAppFiles(filePath) { + return /^\/base\/src\/(?!.*\.spec\.js$)([a-z0-9-_\.\/]+)\.js$/.test(filePath); +} + +function onlySpecFiles(path) { + return /\.spec\.js$/.test(path); +} diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..f1a264f --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,103 @@ +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-mocha-reporter') + ], + customLaunchers: { + // chrome setup for travis CI using chromium + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + }, + }, + files: [{ + pattern: 'node_modules/systemjs/dist/system-polyfills.js', + included: true, + watched: true + }, { + pattern: 'node_modules/systemjs/dist/system.src.js', + included: true, + watched: true + }, { + pattern: 'node_modules/es6-shim/es6-shim.js', + included: true, + watched: true + }, { + pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', + included: true, + watched: true + }, { + pattern: 'node_modules/rxjs/bundles/Rx.js', + included: true, + watched: true + }, { + pattern: 'node_modules/angular2/bundles/angular2.js', + included: true, + watched: true + }, { + pattern: 'node_modules/angular2/bundles/http.dev.js', + included: true, + watched: true + }, { + pattern: 'node_modules/angular2/bundles/router.dev.js', + included: true, + watched: true + }, { + pattern: 'node_modules/angular2/bundles/testing.dev.js', + included: true, + watched: true + }, { + pattern: 'karma-test-shim.js', + included: true, + watched: true + }, + + // paths loaded via module imports + { + pattern: 'src/**/*.js', + included: false, + watched: true + }, + + // paths loaded via Angular's component compiler + // (these paths need to be rewritten, see proxies section) + { + pattern: 'src/**/*.html', + included: false, + watched: true + }, { + pattern: 'src/**/*.css', + included: false, + watched: true + }, + + // paths to support debugging with source maps in dev tools + { + pattern: 'src/**/*.ts', + included: false, + watched: false + }, { + pattern: 'src/**/*.js.map', + included: false, + watched: false + } + ], + // proxies: { + // // required for component assets fetched by Angular's compiler + // "/src/": "/base/src/" + // }, + exclude: [], + preprocessors: {}, + reporters: ['mocha'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..819de6a --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "ng2-data", + "version": "0.0.1", + "description": "Convential HTTP client for Model Data in Angular 2", + "repository": { + "type": "git", + "url": "git+https://github.com/raghunat/ng2-data.git" + }, + "main": "src/ng2-data.js", + "scripts": { + "start": "concurrently \"npm run tsc:w\" \"npm run lite\" ", + "tsc": "tsc", + "tsc:w": "tsc -w", + "lite": "lite-server", + "typings": "typings", + "postinstall": "typings install", + "test": "./node_modules/karma/bin/karma start" + }, + "keywords": [ + "angular", + "angular2", + "data", + "orm", + "http" + ], + "author": "raghunat", + "license": "MIT", + "bugs": { + "url": "https://github.com/raghunat/ng2-data/issues" + }, + "typings": "./ng2-data.d.ts", + "homepage": "https://github.com/raghunat/ng2-data#readme", + "dependencies": { + "angular2": "2.0.0-beta.9", + "es6-promise": "^3.0.2", + "es6-shim": "^0.33.3", + "reflect-metadata": "0.1.2", + "rxjs": "5.0.0-beta.2", + "zone.js": "0.5.15" + }, + "devDependencies": { + "body-parser": "^1.15.0", + "concurrently": "^2.0.0", + "cors": "^2.7.1", + "express": "^4.13.4", + "glob": "^7.0.3", + "jasmine-core": "^2.4.1", + "jasmine-spec-reporter": "^2.4.0", + "karma": "^0.13.22", + "karma-chrome-launcher": "^0.2.2", + "karma-jasmine": "^0.3.7", + "karma-mocha-reporter": "^2.0.0", + "lite-server": "^2.1.0", + "systemjs": "~0.19.24", + "typescript": "~1.7.5" + } +} diff --git a/src/store.config.ts b/src/store.config.ts new file mode 100644 index 0000000..f372781 --- /dev/null +++ b/src/store.config.ts @@ -0,0 +1,15 @@ +/** + * Input Class + */ +export class StoreConfig { + public baseUri: string + constructor(config:any = {}) { + config = config || {}; + + // TODO + // Sanitze inputs + + // bulk assign + Object.assign(this, config); + } +} diff --git a/src/store.service.spec.ts b/src/store.service.spec.ts new file mode 100644 index 0000000..6df27eb --- /dev/null +++ b/src/store.service.spec.ts @@ -0,0 +1,135 @@ +import { +it, +iit, +describe, +ddescribe, +expect, +inject, +injectAsync, +TestComponentBuilder, +beforeEachProviders +} from 'angular2/testing'; +import {provide} from 'angular2/core'; +import {StoreService} from './store.service'; +import {StoreConfig} from './store.config'; +import {Headers, HTTP_PROVIDERS, BaseRequestOptions, XHRBackend, Response} from 'angular2/http'; +import {MockBackend} from 'angular2/http/testing'; +import {MockConnection} from 'angular2/src/http/backends/mock_backend'; +import {ResponseOptions} from 'angular2/http'; + +describe('StoreService Service', () => { + + beforeEachProviders(() => [StoreService, HTTP_PROVIDERS, provide(XHRBackend, { useClass: MockBackend })]); + + it('should find users', inject([XHRBackend, StoreService], (mockBackend: MockBackend, store: StoreService) => { + // prep + mockBackend.connections.subscribe( + (connection: MockConnection) => { + connection.mockRespond(new Response( + new ResponseOptions({ + body: { + users: [{ + id: 1, + name: 'stephen' + }] + } + }))); + }); + + // test + store.init(new StoreConfig({ baseUri: 'http://localhost' })); + store.find('user').subscribe(users => { + expect(users[0].id).toBe(1); + expect(users[0].name).toBe('stephen'); + }); + })); + + it('should find a specific user', inject([XHRBackend, StoreService], (mockBackend: MockBackend, store: StoreService) => { + // prep + mockBackend.connections.subscribe( + (connection: MockConnection) => { + connection.mockRespond(new Response( + new ResponseOptions({ + body: { + user: { + id: 1, + name: 'stephen' + } + } + }))); + }); + + // test + store.init(new StoreConfig({ baseUri: 'http://localhost' })); + store.findOne('user', 1).subscribe(user => { + expect(user.id).toBe(1); + expect(user.name).toBe('stephen'); + }); + })); + + it('should create a user', inject([XHRBackend, StoreService], (mockBackend: MockBackend, store: StoreService) => { + // prep + mockBackend.connections.subscribe( + (connection: MockConnection) => { + connection.mockRespond(new Response( + new ResponseOptions({ + body: { + user: { + id: 1, + name: 'stephen' + } + } + }))); + }); + + // test + store.init(new StoreConfig({ baseUri: 'http://localhost' })); + store.create('user', { name: 'stephen' }).subscribe(user => { + expect(user.id).toBe(1); + expect(user.name).toBe('stephen'); + }); + })); + + it('should update a user', inject([XHRBackend, StoreService], (mockBackend: MockBackend, store: StoreService) => { + // prep + mockBackend.connections.subscribe( + (connection: MockConnection) => { + connection.mockRespond(new Response( + new ResponseOptions({ + body: { + user: { + id: 1, + name: 'stephenA' + } + } + }))); + }); + + // test + store.init(new StoreConfig({ baseUri: 'http://localhost' })); + store.update('user', 1, { name: 'stephen' }).subscribe(user => { + expect(user.id).toBe(1); + expect(user.name).toBe('stephenA'); + }); + })); + + it('should destroy a user', inject([XHRBackend, StoreService], (mockBackend: MockBackend, store: StoreService) => { + // prep + mockBackend.connections.subscribe( + (connection: MockConnection) => { + connection.mockRespond(new Response( + new ResponseOptions({ + body: { + statusCode: 200 + } + }))); + }); + + // test + store.init(new StoreConfig({ baseUri: 'http://localhost' })); + store.destroy('user', 1).subscribe(res => { + expect(res.statusCode).toBe(200); + }); + })); + +}); diff --git a/src/store.service.ts b/src/store.service.ts new file mode 100644 index 0000000..9518048 --- /dev/null +++ b/src/store.service.ts @@ -0,0 +1,83 @@ +import {Injectable, Injector} from 'angular2/core'; +import {Http} from 'angular2/http'; +import {StoreConfig} from './store.config'; +import 'rxjs/Rx'; + + +@Injectable() +export class StoreService { + private static config: StoreConfig + + constructor(private http: Http) {} + + init(config: StoreConfig) { + StoreService.config = config; + } + + simplePluralize(noun: string) { + switch (noun[noun.length - 1]) { + case 's': + return noun + 'es'; + case 'y': + switch (noun[noun.length - 2]) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return noun + 's'; + default: + return noun.substring(0, noun.length - 2) + 'ies'; + } + default: + return noun + 's'; + } + } + + buildUri(model: string) { + return `${StoreService.config.baseUri}/${this.simplePluralize(model)}` + } + + makeRequest(method:string, uri:string, params:Object) { + return this.http[method](uri, params); + } + + /** + * GET /model + */ + find(model: string, params: Object = {}) { + return this.makeRequest('get', this.buildUri(model), params).map(r => r.json()[this.simplePluralize(model)]); + } + + /** + * GET /model/:id + */ + findOne(model: string, id: number) { + return this.http.get(`${this.buildUri(model)}/${id}`).map(r => r.json()[model]); + } + + /** + * POST /model + */ + create(model: string, body: Object) { + let data = {}; + data[model] = body; + return this.http.post(this.buildUri(model), JSON.stringify(data)).map(r => r.json()[model]); + } + + /** + * PUT /model/:id + */ + update(model: string, id:number, body: Object) { + let data = {}; + data[model] = body; + return this.http.put(`${this.buildUri(model)}/${id}`, JSON.stringify(data)).map(r => r.json()[model]); + } + + /** + * DELETE /model/:id + */ + destroy(model: string, id: number) { + return this.http.delete(`${this.buildUri(model)}/${id}`).map(r => r.json()); + } +} diff --git a/store.service.js b/store.service.js new file mode 100644 index 0000000..b2b3f7d --- /dev/null +++ b/store.service.js @@ -0,0 +1,114 @@ +System.register(['angular2/core', 'angular2/http', 'rxjs/Rx'], function(exports_1, context_1) { + "use strict"; + var __moduleName = context_1 && context_1.id; + var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; + }; + var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); + }; + var core_1, http_1; + var StoreService; + return { + setters:[ + function (core_1_1) { + core_1 = core_1_1; + }, + function (http_1_1) { + http_1 = http_1_1; + }, + function (_1) {}], + execute: function() { + StoreService = (function () { + function StoreService(http) { + this.http = http; + if (StoreService.instance) { + return StoreService.instance; + } + else { + StoreService.instance = this; + } + } + StoreService.prototype.init = function (options) { + this.config = options; + }; + StoreService.prototype.simplePluralize = function (noun) { + switch (noun[noun.length - 1]) { + case 's': + return noun + 'es'; + case 'y': + switch (noun[noun.length - 2]) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return noun + 's'; + default: + return noun.substring(0, noun.length - 2) + 'ies'; + } + default: + return noun + 's'; + } + }; + StoreService.prototype.buildUri = function (model) { + return this.config.baseUri + "/" + this.simplePluralize(model); + }; + StoreService.prototype.makeRequest = function (method, uri, params) { + return this.http[method](uri, params); + }; + /** + * GET /model + */ + StoreService.prototype.find = function (model, params) { + if (params === void 0) { params = {}; } + // let modelContainer = this.config.models.find(m => m.name === model); + return this.makeRequest('get', this.buildUri(model), params).map(function (r) { + var response = r.json(); + var result = []; + // response[this.simplePluralize(modelContainer.name)].forEach(i => { + // let item = new modelContainer.Model(); + // Object.assign(item, i); + // result.push(item); + // }); + return result; + }); + }; + /** + * GET /model/:id + */ + StoreService.prototype.findOne = function (model, id) { + return this.http.get(this.buildUri(model) + "/" + id).map(function (r) { return r.json(); }); + }; + /** + * POST /model + */ + StoreService.prototype.create = function (model, params) { + return this.http.post(this.buildUri(model), JSON.stringify(params)).map(function (r) { return r.json(); }); + }; + /** + * PUT /model + */ + StoreService.prototype.update = function (model, params) { + return this.http.put(this.buildUri(model), JSON.stringify(params)).map(function (r) { return r.json(); }); + }; + /** + * DELETE /model/:id + */ + StoreService.prototype.destroy = function (model, id) { + return this.http.delete(this.buildUri(model) + "/" + id).map(function (r) { return r.json(); }); + }; + StoreService = __decorate([ + core_1.Injectable(), + __metadata('design:paramtypes', [http_1.Http]) + ], StoreService); + return StoreService; + }()); + exports_1("StoreService", StoreService); + } + } +}); +//# sourceMappingURL=store.service.js.map \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9be71e4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": false + }, + "exclude": [ + "node_modules", + "typings/main", + "typings/main.d.ts" + ] +} diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..f5b42e6 --- /dev/null +++ b/typings.json @@ -0,0 +1,6 @@ +{ + "ambientDependencies": { + "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", + "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#d594ef506d1efe2fea15f8f39099d19b39436b71" + } +}