Skip to content

Commit 470e212

Browse files
author
Lee Richmond
committed
initial commit
0 parents  commit 470e212

File tree

12 files changed

+367
-0
lines changed

12 files changed

+367
-0
lines changed

.gitignore

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
*.pid.lock
11+
12+
# Directory for instrumented libs generated by jscoverage/JSCover
13+
lib-cov
14+
15+
# Coverage directory used by tools like istanbul
16+
coverage
17+
18+
# nyc test coverage
19+
.nyc_output
20+
21+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22+
.grunt
23+
24+
# node-waf configuration
25+
.lock-wscript
26+
27+
# Compiled binary addons (http://nodejs.org/api/addons.html)
28+
build/Release
29+
30+
# Dependency directories
31+
node_modules
32+
jspm_packages
33+
34+
# Optional npm cache directory
35+
.npm
36+
37+
# Optional eslint cache
38+
.eslintcache
39+
40+
# Optional REPL history
41+
.node_repl_history
42+
43+
# Output of 'npm pack'
44+
*.tgz
45+
46+
# Yarn Integrity file
47+
.yarn-integrity
48+
*.swp
49+
*.swn
50+
*.swo
51+
52+
tmp/*
53+
!tmp/.keepme

gulpfile.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const gulp = require('gulp');
2+
const mocha = require('gulp-mocha');
3+
const clean = require('gulp-clean');
4+
const webpack = require('webpack-stream');
5+
const ts = require('gulp-typescript');
6+
7+
var tsProject = ts.createProject('tsconfig.json');
8+
9+
gulp.task('test_clean', function () {
10+
//return gulp.src('./tmp/test/**/*', { read: false })
11+
//.pipe(clean());
12+
});
13+
14+
gulp.task('test', ['test_clean'], () =>
15+
gulp
16+
.src(['./index.d.ts.', './src/**/*.ts', './test/test-helper.ts', './test/**/*-test.ts'], { base: '.' })
17+
.pipe(tsProject())
18+
.pipe(gulp.dest('tmp/test'))
19+
.pipe(mocha())
20+
);
21+
22+
// Use webpack, not tsProject, for browserification
23+
gulp.task('build', function () {
24+
return gulp.src("src/main.ts")
25+
.pipe(webpack(require('./webpack.config.js') ))
26+
.pipe(gulp.dest('dist/'))
27+
});

index.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
declare var expect: any;
2+
declare var asyncAssert: any;
3+
4+
// todo deglobalize?, split test
5+
6+
interface japiDocArray {
7+
data: Array<japiResource>;
8+
}
9+
10+
interface japiDoc {
11+
data: japiResource;
12+
}
13+
14+
interface japiResource {
15+
id: string;
16+
type: string;
17+
}

package.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "jsorm",
3+
"version": "1.0.0",
4+
"description": "Javascript ORM",
5+
"main": "./dist/main.js",
6+
"types": "./main.d.ts",
7+
"scripts": {
8+
"test": "gulp test"
9+
},
10+
"author": "Lee Richmond",
11+
"license": "MIT",
12+
"devDependencies": {
13+
"@types/chai": "^3.4.34",
14+
"@types/chai-as-promised": "0.0.29",
15+
"@types/es6-promise": "0.0.32",
16+
"@types/isomorphic-fetch": "0.0.31",
17+
"@types/mocha": "^2.2.33",
18+
"@types/node": "^6.0.52",
19+
"chai": "^3.5.0",
20+
"chai-as-promised": "^6.0.0",
21+
"chai-things": "^0.2.0",
22+
"fetch-mock": "^5.6.2",
23+
"gulp": "^3.9.1",
24+
"gulp-clean": "^0.3.2",
25+
"gulp-mocha": "^3.0.1",
26+
"gulp-typescript": "^3.1.3",
27+
"mocha": "^3.2.0",
28+
"sinon": "^1.17.6",
29+
"typescript": "^2.1.4",
30+
"webpack-stream": "^3.2.0"
31+
},
32+
"dependencies": {
33+
"es6-promise": "^4.0.5",
34+
"isomorphic-fetch": "^2.2.1",
35+
"ts-loader": "^1.3.2"
36+
}
37+
}

src/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path="../index.d.ts" />
2+
3+
import * as es6Promise from 'es6-promise';
4+
import 'isomorphic-fetch';
5+
es6Promise.polyfill();
6+
7+
import Model from "./model";
8+
9+
export { Model };

src/model.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/// <reference path="../index.d.ts" />
2+
3+
import Scope from './scope';
4+
5+
export default class Model {
6+
static baseUrl = process.env.BROWSER? '': 'http://localhost:9999'
7+
static endpoint = 'define-in-subclass';
8+
static apiNamespace = '/';
9+
10+
id: string;
11+
12+
private static _scope: Scope;
13+
14+
static scope(): Scope {
15+
return this._scope || new Scope(this);
16+
}
17+
18+
constructor(attributes?: Object) {
19+
for(var key in attributes) {
20+
this[key] = attributes[key];
21+
}
22+
}
23+
24+
static all() : Promise<Array<Model>> {
25+
return this.scope().all();
26+
}
27+
28+
static find(id) : Promise<Model> {
29+
return this.scope().find(id);
30+
}
31+
32+
// private
33+
34+
private static _url(id?: string) : string {
35+
let base = `${this.baseUrl}${this.apiNamespace}${this.endpoint}`;
36+
37+
if (id) {
38+
base = `${base}/${id}`;
39+
}
40+
41+
return base;
42+
}
43+
44+
private static _fetch(url) : Promise<Object> {
45+
return new Promise((resolve, reject) => {
46+
fetch(url).then((response) => {
47+
response.json().then((json) => {
48+
resolve(json);
49+
});
50+
});
51+
});
52+
}
53+
54+
}

src/scope.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Model from './model';
2+
3+
export default class Scope {
4+
model: typeof Model;
5+
6+
constructor(model : typeof Model) {
7+
this.model = model;
8+
}
9+
10+
all() : Promise<Array<Model>> {
11+
return new Promise((resolve, reject) => {
12+
this._fetch(this._url()).then((json : japiDocArray) => {
13+
let records = json.data.map((datum : japiResource) => {
14+
return this._newFromJSON(datum);
15+
});
16+
resolve(records);
17+
});
18+
});
19+
}
20+
21+
find(id) : Promise<Model> {
22+
return new Promise((resolve, reject) => {
23+
this._fetch(this._url(id)).then((json : japiDoc) => {
24+
resolve(this._newFromJSON(json.data));
25+
});
26+
});
27+
}
28+
29+
// private
30+
31+
private _newFromJSON(resource : japiResource) : Model {
32+
let instance = new this.model({ id: resource.id });
33+
return instance;
34+
}
35+
36+
private _url(id?: string) : string {
37+
let base = this.model.baseUrl;
38+
base = base + this.model.apiNamespace;
39+
base = base + this.model.endpoint;
40+
41+
if (id) {
42+
base = `${base}/${id}`;
43+
}
44+
45+
return base;
46+
}
47+
48+
private _fetch(url) : Promise<Object> {
49+
return new Promise((resolve, reject) => {
50+
fetch(url).then((response) => {
51+
response.json().then((json) => {
52+
resolve(json);
53+
});
54+
});
55+
});
56+
}
57+
}

test/model-test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import '../test/test-helper';
2+
import { Model } from '../src/main';
3+
4+
class Person extends Model {
5+
static baseUrl = 'http://example.com';
6+
static apiNamespace = '/api';
7+
static endpoint = '/v1/people';
8+
}
9+
10+
let fetchMock = require('fetch-mock');
11+
let mockResponse = {};
12+
13+
before(function() {
14+
fetchMock.get('http://example.com/api/v1/people/1', {
15+
data: {
16+
id: '1'
17+
}
18+
});
19+
});
20+
21+
after(function () {
22+
fetchMock.restore();
23+
});
24+
25+
describe('Model', function() {
26+
describe('#find()', function() {
27+
before(function () {
28+
fetchMock.get('http://example.com/api/v1/people/1', {
29+
data: {
30+
id: '1'
31+
}
32+
});
33+
});
34+
35+
it('returns a promise that resolves the correct instance', function() {
36+
return expect(Person.find(1)).to.eventually
37+
.be.instanceof(Person).and
38+
.have.property('id', '1');
39+
});
40+
41+
describe('when API response returns a different type than the caller', function() {
42+
it('resolves to the correct class', function() {
43+
});
44+
});
45+
});
46+
47+
describe('#all()', function() {
48+
before(function () {
49+
fetchMock.get('http://example.com/api/v1/people', {
50+
data: [
51+
{ id: '1' }
52+
]
53+
});
54+
});
55+
56+
it('returns a promise that resolves the correct instances', function() {
57+
return expect(Person.all()).to.eventually
58+
.all.be.instanceof(Person)
59+
.all.have.property('id', '1').and
60+
});
61+
});
62+
});

test/test-helper.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path="../index.d.ts" />
2+
3+
import * as es6Promise from 'es6-promise';
4+
import 'isomorphic-fetch';
5+
import * as sinon from 'sinon';
6+
es6Promise.polyfill();
7+
8+
import * as chai from 'chai';
9+
import * as chaiAsPromised from 'chai-as-promised';
10+
import * as chaiThings from 'chai-things';
11+
12+
// MUST be in this order
13+
chai.use(chaiThings);
14+
chai.use(chaiAsPromised);
15+
16+
global['expect'] = chai.expect;
17+
global['sinon'] = sinon;
18+
19+
global['asyncAssert'] = function(done, callback) {
20+
try {
21+
if (callback() === false) {
22+
done('Failed!');
23+
} else {
24+
done();
25+
}
26+
} catch(e) {
27+
done(e);
28+
}
29+
}

tmp/.keepme

Whitespace-only changes.

0 commit comments

Comments
 (0)