├── packages ├── rev-api │ ├── .npmignore │ ├── .npmrc │ ├── src │ │ ├── untyped.d.ts │ │ ├── decorators │ │ │ ├── index.ts │ │ │ └── decorators.ts │ │ ├── index.ts │ │ ├── graphql │ │ │ ├── types │ │ │ │ ├── NoModels.ts │ │ │ │ └── ApiReadMeta.ts │ │ │ ├── types.ts │ │ │ ├── mutation │ │ │ │ ├── mutation.ts │ │ │ │ └── methods.ts │ │ │ └── __tests__ │ │ │ │ └── api.tests.ts │ │ └── api │ │ │ └── types.ts │ ├── tsconfig.json │ ├── .vscode │ │ ├── tasks.json │ │ └── launch.json │ ├── LICENSE │ └── package.json ├── rev-ui │ ├── .npmignore │ ├── .npmrc │ ├── .vscode │ │ ├── settings.json │ │ ├── tasks.json │ │ └── launch.json │ ├── typedoc.js │ ├── src │ │ ├── __test_utils__ │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── deepCopy.ts │ │ │ └── props.ts │ │ ├── actions │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── views │ │ │ ├── withDetailViewContext.tsx │ │ │ ├── withSearchViewContext.tsx │ │ │ └── __tests__ │ │ │ │ ├── DetailView │ │ │ │ └── withDetailViewContext.tests.tsx │ │ │ │ └── SearchView │ │ │ │ └── withSearchViewContext.tests.tsx │ │ ├── provider │ │ │ ├── withModelManager.tsx │ │ │ ├── ModelProvider.ts │ │ │ └── __tests__ │ │ │ │ ├── ModelProvider.tests.tsx │ │ │ │ └── withModelManager.tests.tsx │ │ ├── __fixtures__ │ │ │ └── models.ts │ │ └── config.ts │ ├── tsconfig.json │ ├── testsetup.js │ ├── LICENSE │ └── package.json ├── rev-models │ ├── .npmignore │ ├── .npmrc │ ├── src │ │ ├── decorators │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ └── extends.tests.ts │ │ ├── backends │ │ │ ├── index.ts │ │ │ ├── inmemory │ │ │ │ ├── sort.ts │ │ │ │ └── __tests__ │ │ │ │ │ └── testdata.ts │ │ │ ├── testsuite │ │ │ │ ├── index.ts │ │ │ │ └── modeldata.ts │ │ │ └── backend.ts │ │ ├── utils │ │ │ ├── types.ts │ │ │ ├── index.ts │ │ │ └── __tests__ │ │ │ │ └── utils.tests.ts │ │ ├── validation │ │ │ ├── validators │ │ │ │ ├── index.ts │ │ │ │ └── __tests__ │ │ │ │ │ └── utils.ts │ │ │ ├── __tests__ │ │ │ │ └── validationerror.tests.ts │ │ │ ├── validationerror.ts │ │ │ └── validationmsg.ts │ │ ├── types.ts │ │ ├── __test_utils__ │ │ │ └── index.ts │ │ ├── queries │ │ │ ├── nodes │ │ │ │ ├── index.ts │ │ │ │ ├── value.ts │ │ │ │ ├── query.ts │ │ │ │ ├── conjunction.ts │ │ │ │ ├── valuelist.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── value.tests.ts │ │ │ │ │ ├── conjunction.tests.ts │ │ │ │ │ └── valuelist.tests.ts │ │ │ │ └── field.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── fields │ │ │ ├── index.ts │ │ │ └── datetimefields.ts │ │ ├── index.ts │ │ ├── operations │ │ │ ├── operation.ts │ │ │ ├── utils.ts │ │ │ ├── hydrate.ts │ │ │ ├── remove.ts │ │ │ ├── __tests__ │ │ │ │ └── utils.tests.ts │ │ │ ├── create.ts │ │ │ └── validate.ts │ │ ├── models │ │ │ └── utils.ts │ │ └── polyfills.ts │ ├── typedoc.js │ ├── tsconfig.json │ ├── LICENSE │ └── package.json ├── rev-api-client │ ├── .npmignore │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ └── client │ │ │ ├── __test_utils__ │ │ │ └── mockHttpClient.ts │ │ │ ├── __tests__ │ │ │ └── backend.tests.ts │ │ │ └── __fixtures__ │ │ │ └── models.ts │ ├── .vscode │ │ ├── settings.json │ │ ├── tasks.json │ │ └── launch.json │ ├── typedoc.js │ ├── tsconfig.json │ ├── LICENSE │ └── package.json ├── rev-ui-materialui │ ├── .npmignore │ ├── .npmrc │ ├── src │ │ ├── fields │ │ │ ├── __tests__ │ │ │ │ └── MUIRelatedModelField.tests.tsx │ │ │ ├── utils.ts │ │ │ ├── MUIBooleanField.tsx │ │ │ ├── MUIDateField.tsx │ │ │ └── MUITextField.tsx │ │ ├── views │ │ │ ├── MUISearchView.tsx │ │ │ ├── MUIDetailView.tsx │ │ │ └── __tests__ │ │ │ │ ├── MUIDetailView.tests.tsx │ │ │ │ └── MUISearchView.tests.tsx │ │ ├── actions │ │ │ └── MUIActionButton.tsx │ │ ├── __fixtures__ │ │ │ └── models.ts │ │ └── searchFields │ │ │ └── MUITextSearchField.tsx │ ├── tsconfig.json │ ├── testsetup.js │ ├── LICENSE │ └── package.json ├── rev-backend-mongodb │ ├── .npmignore │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ ├── testconfig.ts │ │ │ └── backend.tests.ts │ ├── typedoc.js │ ├── tsconfig.json │ ├── LICENSE │ └── package.json └── examples │ ├── .npmrc │ ├── src │ ├── model_manager │ │ ├── creating_a_model_manager.ts │ │ ├── registering_models.ts │ │ └── handling_validation_errors.ts │ ├── searching_and_reading_data │ │ ├── read_with_no_filter.ts │ │ ├── read_with_paging.ts │ │ ├── read_with_sorting.ts │ │ └── read_with_where_clause.ts │ ├── creating_a_ui │ │ ├── custom_list │ │ │ └── demo.html │ │ ├── detailview │ │ │ └── demo.html │ │ ├── simple_list │ │ │ ├── demo.html │ │ │ └── simple_list.tsx │ │ ├── related_data │ │ │ └── demo.html │ │ ├── searchable_list │ │ │ └── demo.html │ │ ├── detailview_allfields │ │ │ └── demo.html │ │ ├── searchview_allfields │ │ │ └── demo.html │ │ ├── utils.ts │ │ ├── webpack.config.js │ │ └── api.ts │ ├── defining_and_using_models │ │ ├── adding_model_data.ts │ │ ├── removing_multiple_records.ts │ │ ├── updating_multiple_records.ts │ │ ├── validating_data.ts │ │ ├── removing_data.ts │ │ ├── updating_data.ts │ │ ├── creating_models.ts │ │ ├── searching_and_paging.ts │ │ └── creating_and_reading_a_model.ts │ ├── using_backends │ │ ├── using_an_inmemory_backend.ts │ │ ├── using_an_api_backend.ts │ │ └── using_a_mongodb_backend.ts │ ├── using_related_models │ │ ├── updating_related_data.ts │ │ ├── reading_related_data.ts │ │ ├── creating_related_data.ts │ │ └── creating_related_models.ts │ └── creating_an_api │ │ ├── serving_an_api.ts │ │ ├── model_data.ts │ │ └── defining_an_api.ts │ ├── tsconfig.json │ ├── LICENSE │ └── package.json ├── .npmrc ├── docs ├── mkdocs │ ├── src │ │ ├── changelog.md │ │ ├── img │ │ │ ├── ui-overview.png │ │ │ ├── graphql-query.png │ │ │ ├── ui-custom-list.png │ │ │ ├── ui-simple-list.png │ │ │ ├── graphql-mutations.png │ │ │ ├── ui-searchable-list.png │ │ │ ├── ui-detailview-create.png │ │ │ ├── ui-detailview-invalid.png │ │ │ ├── ui-relatedmodel-default.png │ │ │ ├── ui-relatedmodel-getter.png │ │ │ └── ui-relatedmodel-tostring.png │ │ ├── css │ │ │ ├── style.css │ │ │ └── highlight.css │ │ ├── components │ │ │ ├── rev-ui.md │ │ │ ├── rev-api-client.md │ │ │ ├── rev-api.md │ │ │ └── rev-models.md │ │ ├── using_models │ │ │ ├── updating_data.md │ │ │ ├── deleting_data.md │ │ │ ├── creating_data.md │ │ │ └── revjs_backends.md │ │ ├── creating_a_ui │ │ │ └── simple_list.md │ │ └── index.md │ ├── Dockerfile │ ├── docker-compose.yml │ └── mkdocs.yml ├── screenshot0.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png └── typedoc │ ├── theme │ ├── assets │ │ ├── images │ │ │ ├── widgets.png │ │ │ └── widgets@2x.png │ │ └── css │ │ │ └── style.css │ ├── partials │ │ ├── navigation.hbs │ │ └── header.hbs │ └── layouts │ │ └── default.hbs │ └── typedoc.js ├── .editorconfig ├── .vscode └── settings.json ├── lerna.json ├── .gitignore ├── revjs.code-workspace ├── LICENSE ├── .circleci └── config.yml ├── tslint.json └── package.json /packages/rev-api/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /packages/rev-ui/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /packages/rev-models/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /packages/rev-api-client/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /packages/rev-ui-materialui/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz -------------------------------------------------------------------------------- /packages/examples/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-api/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-ui/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-api-client/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-models/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /docs/mkdocs/src/changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | {!CHANGELOG.md!} 4 | -------------------------------------------------------------------------------- /packages/rev-api/src/untyped.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module 'graphql-type-json'; -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-ui-materialui/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './backend'; 3 | -------------------------------------------------------------------------------- /packages/rev-models/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './fields'; 3 | -------------------------------------------------------------------------------- /packages/rev-api/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './decorators'; 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.ts] 2 | indent_style = space 3 | indent_size = 4 4 | end_of_line = lf -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /docs/screenshot0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/screenshot0.png -------------------------------------------------------------------------------- /docs/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/screenshot1.png -------------------------------------------------------------------------------- /docs/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/screenshot2.png -------------------------------------------------------------------------------- /docs/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/screenshot3.png -------------------------------------------------------------------------------- /packages/rev-models/src/backends/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { IBackend } from './backend'; 3 | -------------------------------------------------------------------------------- /packages/rev-api-client/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { ModelApiBackend } from './client/backend'; 3 | -------------------------------------------------------------------------------- /packages/rev-ui/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.check.workspaceVersion": false 3 | } -------------------------------------------------------------------------------- /packages/rev-api-client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.check.workspaceVersion": false 3 | } -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-overview.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/graphql-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/graphql-query.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-custom-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-custom-list.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-simple-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-simple-list.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/graphql-mutations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/graphql-mutations.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-searchable-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-searchable-list.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-detailview-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-detailview-create.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-detailview-invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-detailview-invalid.png -------------------------------------------------------------------------------- /docs/typedoc/theme/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/typedoc/theme/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-relatedmodel-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-relatedmodel-default.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-relatedmodel-getter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-relatedmodel-getter.png -------------------------------------------------------------------------------- /docs/mkdocs/src/img/ui-relatedmodel-tostring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/mkdocs/src/img/ui-relatedmodel-tostring.png -------------------------------------------------------------------------------- /docs/typedoc/theme/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RevJS/revjs/HEAD/docs/typedoc/theme/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /packages/rev-ui/typedoc.js: -------------------------------------------------------------------------------- 1 | 2 | const typedocConfig = require('../../docs/typedoc/typedoc'); 3 | module.exports = typedocConfig('rev-ui'); 4 | -------------------------------------------------------------------------------- /packages/rev-models/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IObject { [key: string]: any; } 3 | export interface IKeyMap { [key: string]: T; } 4 | -------------------------------------------------------------------------------- /packages/rev-models/typedoc.js: -------------------------------------------------------------------------------- 1 | 2 | const typedocConfig = require('../../docs/typedoc/typedoc'); 3 | module.exports = typedocConfig('rev-models'); 4 | -------------------------------------------------------------------------------- /packages/rev-api-client/typedoc.js: -------------------------------------------------------------------------------- 1 | 2 | const typedocConfig = require('../../docs/typedoc/typedoc'); 3 | module.exports = typedocConfig('rev-api-client'); 4 | -------------------------------------------------------------------------------- /docs/mkdocs/src/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | li.toctree-l2:first-child { 3 | display: none; 4 | } 5 | 6 | li.toctree-l3:first-child { 7 | display: none; 8 | } 9 | -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/typedoc.js: -------------------------------------------------------------------------------- 1 | 2 | const typedocConfig = require('../../docs/typedoc/typedoc'); 3 | module.exports = typedocConfig('rev-backend-mongodb'); 4 | -------------------------------------------------------------------------------- /docs/mkdocs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-alpine 2 | 3 | RUN pip install mkdocs 4 | RUN pip install markdown-include 5 | 6 | WORKDIR /mkdocs 7 | 8 | CMD [ "mkdocs", "build" ] -------------------------------------------------------------------------------- /docs/mkdocs/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | 5 | mkdocs: 6 | build: . 7 | volumes: 8 | - .:/mkdocs 9 | - ../../packages:/packages 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-beta.38", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "0.21.1", 7 | "concurrency": 1, 8 | "hoist": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/rev-models/src/validation/validators/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './common'; 3 | export * from './datetime'; 4 | export * from './related'; 5 | export * from './selection'; 6 | -------------------------------------------------------------------------------- /packages/rev-models/src/types.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | // tslint:disable-next-line:interface-name 5 | interface Error { 6 | result?: any; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/rev-api/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { IModelApiManager, IApiMeta } from './api/types'; 3 | export { ModelApiManager } from './api/manager'; 4 | export { GraphQLApi } from './graphql/api'; 5 | -------------------------------------------------------------------------------- /packages/rev-ui/src/__test_utils__/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export function sleep(timeout: number) { 3 | return new Promise((resolve) => { 4 | setTimeout(resolve, timeout); 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /packages/rev-models/src/__test_utils__/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export function sleep(timeout: number) { 3 | return new Promise((resolve) => { 4 | setTimeout(resolve, timeout); 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './conjunction'; 3 | export * from './field'; 4 | export * from './query'; 5 | export * from './value'; 6 | export * from './valuelist'; 7 | -------------------------------------------------------------------------------- /packages/rev-api-client/src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export {}; 3 | 4 | declare global { 5 | // tslint:disable-next-line:interface-name 6 | interface Error { 7 | response?: any; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.map 3 | *.tgz 4 | packages/examples/src/creating_a_ui/js 5 | lib 6 | npm-debug.log 7 | lerna-debug.log 8 | package-lock.json 9 | docs/dist 10 | docs/mkdocs/dist 11 | test-results.xml 12 | .DS_Store -------------------------------------------------------------------------------- /packages/rev-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "include": [ 8 | "./src/**/*" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/fields/__tests__/MUIRelatedModelField.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | describe('MUIRelatedModelField', () => { 3 | 4 | it.skip('Write tests, once we decide if this field should be used!', () => { 5 | 6 | }); 7 | 8 | }); -------------------------------------------------------------------------------- /packages/rev-models/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "include": [ 8 | "./src/**/*" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/rev-api-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "include": [ 8 | "./src/**/*" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/src/__tests__/testconfig.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IMongoDBBackendConfig } from '../backend'; 3 | 4 | export const testConfig: IMongoDBBackendConfig = { 5 | url: 'mongodb://localhost:27017', 6 | dbName: 'RevJS' 7 | }; 8 | -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "include": [ 8 | "./src/**/*" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/rev-models/src/fields/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './field'; 3 | export * from './textfields'; 4 | export * from './numberfields'; 5 | export * from './selectionfields'; 6 | export * from './datetimefields'; 7 | export * from './relatedfields'; 8 | -------------------------------------------------------------------------------- /packages/rev-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "jsx": "react" 7 | }, 8 | "include": [ 9 | "./src/**/*" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/rev-ui-materialui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "jsx": "react" 7 | }, 8 | "include": [ 9 | "./src/**/*" 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/examples/src/model_manager/creating_a_model_manager.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ModelManager, InMemoryBackend } from 'rev-models'; 3 | 4 | // Create a ModelManager with an InMemoryBackend 5 | export const modelManager = new ModelManager(); 6 | modelManager.registerBackend('default', new InMemoryBackend()); 7 | -------------------------------------------------------------------------------- /revjs.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "packages/rev-models" 5 | }, 6 | { 7 | "path": "packages/rev-api" 8 | }, 9 | { 10 | "path": "packages/rev-api-client" 11 | }, 12 | { 13 | "path": "packages/rev-forms-materialui" 14 | }, 15 | { 16 | "path": "." 17 | } 18 | ], 19 | "settings": {} 20 | } -------------------------------------------------------------------------------- /packages/examples/src/searching_and_reading_data/read_with_no_filter.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Post, modelManager, createData } from './models'; 3 | 4 | (async () => { 5 | await createData(); 6 | 7 | const res = await modelManager.read(Post); 8 | 9 | for (let post of res.results) { 10 | console.log(post.title); 11 | } 12 | })(); 13 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "jsx": "react", 7 | "declaration": false, 8 | "strict": false, 9 | "strictNullChecks": false 10 | }, 11 | "include": [ 12 | "./src/**/*" 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/rev-api/src/graphql/types/NoModels.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLString } from 'graphql'; 2 | 3 | export const NoModelsObjectType = new GraphQLObjectType({ 4 | name: 'query', 5 | fields: { no_models: { 6 | type: GraphQLString, 7 | resolve() { 8 | return 'No models have been registered for read access'; 9 | }} 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /docs/mkdocs/src/components/rev-ui.md: -------------------------------------------------------------------------------- 1 | 2 | # `rev-ui` 3 | 4 | Check out [Creating a User Interface](../creating_a_ui/overview.md) 5 | 6 | *Jump to the [rev-ui API Documentation](/api/rev-ui)* 7 | 8 | ## Contributing 9 | 10 | We are actively looking to build a team around RevJS. If you are interesting in 11 | contributing, fork us on github or drop us a 12 | [mail](mailto:russ@russellbriggs.co)! 13 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/fields/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IFieldComponentProps } from 'rev-ui/lib/fields/Field'; 3 | import { ISearchFieldComponentProps } from 'rev-ui/lib/fields/SearchField'; 4 | 5 | export function getGridWidthProps(props: IFieldComponentProps | ISearchFieldComponentProps) { 6 | return { 7 | xs: props.colspanNarrow, 8 | md: props.colspan, 9 | lg: props.colspanWide 10 | }; 11 | } -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/custom_list/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/detailview/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/simple_list/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/related_data/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/rev-ui/src/utils/deepCopy.ts: -------------------------------------------------------------------------------- 1 | import { IObject } from 'rev-models/lib/utils/types'; 2 | 3 | /** 4 | * @private 5 | */ 6 | export function deepCopy(obj: IObject) { 7 | const clone: IObject = {}; 8 | for (let key in obj) { 9 | if (obj[key] != null && typeof(obj[key]) == 'object') { 10 | clone[key] = deepCopy(obj[key]); 11 | } 12 | else { 13 | clone[key] = obj[key]; 14 | } 15 | } 16 | return clone; 17 | } 18 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/searchable_list/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/detailview_allfields/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/searchview_allfields/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RevJS Demo 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/utils.ts: -------------------------------------------------------------------------------- 1 | const COLOURS = ['#e6194b', '#3cb44b', '#ffe119', '#0082c8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#d2f53c', '#fabebe', '#008080', '#e6beff', '#aa6e28', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000080', '#808080', '#FFFFFF', '#000000']; 2 | 3 | let colourIndex = -1; 4 | 5 | export function getColour() { 6 | colourIndex++; 7 | if (colourIndex == COLOURS.length) { 8 | colourIndex = 0; 9 | } 10 | return COLOURS[colourIndex]; 11 | } -------------------------------------------------------------------------------- /packages/rev-models/src/validation/validators/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ModelValidationResult } from '../../validationresult'; 3 | 4 | export function expectValidationFailure(validatorName: string, fieldName: string, message: string, vResult: ModelValidationResult) { 5 | expect(vResult.valid).to.equal(false); 6 | expect(vResult.fieldErrors[fieldName].length).to.equal(1); 7 | expect(vResult.fieldErrors[fieldName][0]).to.deep.equal({ 8 | message: message, 9 | code: validatorName 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/adding_model_data.ts: -------------------------------------------------------------------------------- 1 | import { modelManager, City, Customer } from './creating_models'; 2 | 3 | (async () => { 4 | 5 | const city1 = new City({ 6 | name: 'Wellington' 7 | }); 8 | const createResult = await modelManager.create(city1); 9 | 10 | const customer1 = new Customer({ 11 | first_name: 'Jim', 12 | last_name: 'Jones', 13 | age: 35, 14 | gender: 'M', 15 | city: createResult.result 16 | }); 17 | await modelManager.create(customer1); 18 | 19 | })(); 20 | -------------------------------------------------------------------------------- /packages/rev-ui/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "npm", 6 | "isShellCommand": true, 7 | "showOutput": "always", 8 | "suppressTaskName": true, 9 | "tasks": [ 10 | { 11 | "taskName": "build", 12 | "args": ["run", "build"] 13 | }, 14 | { 15 | "taskName": "test", 16 | "args": ["run", "mocha", "${relativeFile}"] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /packages/rev-api-client/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "npm", 6 | "isShellCommand": true, 7 | "showOutput": "always", 8 | "suppressTaskName": true, 9 | "tasks": [ 10 | { 11 | "taskName": "build", 12 | "args": ["run", "build"] 13 | }, 14 | { 15 | "taskName": "test", 16 | "args": ["run", "mocha", "${relativeFile}"] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /docs/mkdocs/src/components/rev-api-client.md: -------------------------------------------------------------------------------- 1 | 2 | # rev-api-client - RevJS GraphQL API Client Backend 3 | 4 | The `rev-api-client` module provides a `ModelApiBackend` RevJS backend, which 5 | allows you to create, read, update and delete model data from the client in 6 | exactly the same way you do on the server. 7 | 8 | *Jump to the [rev-api-client API Documentation](/api/rev-api-client)* 9 | 10 | ## Contributing 11 | 12 | We are actively looking to build a team around RevJS. If you are interesting in 13 | contributing, fork us on github or drop us a 14 | [mail](mailto:russ@russellbriggs.co)! 15 | -------------------------------------------------------------------------------- /packages/rev-ui/src/utils/props.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /** 4 | * @private 5 | */ 6 | export interface IStandardComponentProps { 7 | style?: Partial; 8 | } 9 | 10 | /** 11 | * @private 12 | */ 13 | export function getStandardProps(props: any) { 14 | const sProps: IStandardComponentProps = {}; 15 | function addProp(propName: keyof IStandardComponentProps) { 16 | if (typeof props[propName] != 'undefined') { 17 | sProps[propName] = props[propName]; 18 | } 19 | } 20 | addProp('style'); 21 | return sProps; 22 | } -------------------------------------------------------------------------------- /docs/typedoc/typedoc.js: -------------------------------------------------------------------------------- 1 | 2 | // Base TypeDoc Configuration 3 | module.exports = (moduleName) => ({ 4 | out: '../../docs/dist/api/' + moduleName, 5 | 6 | readme: 'none', 7 | includes: '../', 8 | exclude: [ 9 | '**/__tests__/**/*', 10 | '**/__test_utils__/**/*', 11 | '**/__fixtures__/**/*', 12 | '**/testsuite/**/*' 13 | ], 14 | theme: '../../docs/typedoc/theme/', 15 | 16 | gaID: 'UA-115939002-1', 17 | gaSite: 'revjs.org', 18 | 19 | mode: 'file', 20 | excludeExternals: true, 21 | excludeNotExported: true, 22 | excludePrivate: true 23 | }); 24 | -------------------------------------------------------------------------------- /packages/rev-models/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './decorators'; 3 | import * as fields from './fields'; 4 | export { fields }; 5 | 6 | export { IModel, IModelMeta, IModelManager, IValidationContext, IDefaultsContext } from './models/types'; 7 | export { ModelManager } from './models/manager'; 8 | export { IMethodContext } from './operations/exec'; 9 | export { IModelOperationResult, ModelOperationResult } from './operations/operationresult'; 10 | export { STANDARD_OPERATIONS } from './operations/operation'; 11 | export { InMemoryBackend } from './backends/inmemory/backend'; 12 | export { ValidationError } from './validation/validationerror'; 13 | -------------------------------------------------------------------------------- /packages/rev-api/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "npm", 6 | "isShellCommand": true, 7 | "showOutput": "always", 8 | "suppressTaskName": true, 9 | "tasks": [ 10 | { 11 | "taskName": "build", 12 | "args": ["run", "build"], 13 | "isBuildCommand": true 14 | }, 15 | { 16 | "taskName": "test", 17 | "args": ["run", "mocha", "${relativeFile}"], 18 | "isTestCommand": true 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /packages/rev-models/src/operations/operation.ts: -------------------------------------------------------------------------------- 1 | import { IObject } from '../utils/types'; 2 | 3 | export const STANDARD_OPERATIONS = ['create', 'read', 'update', 'remove']; 4 | 5 | /** 6 | * Metadata about the current operation. Provided as part of a 7 | * [[ModelOperationResult]] and also to [[IFieldValidator]]s amd 8 | * [[IAsyncFieldValidator]]s 9 | */ 10 | export interface IModelOperation { 11 | /** 12 | * The current operation name (e.g. `'create'` or `'update'`) 13 | */ 14 | operationName: string; 15 | /** 16 | * The `where` clause associated with the current operation 17 | */ 18 | where?: IObject; 19 | } 20 | -------------------------------------------------------------------------------- /packages/examples/src/model_manager/registering_models.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TextField, ModelManager, InMemoryBackend } from 'rev-models'; 3 | 4 | // Define models 5 | 6 | export class User { 7 | @TextField() 8 | first_name: string; 9 | @TextField() 10 | last_name: string; 11 | } 12 | 13 | export class Post { 14 | @TextField() 15 | title: string; 16 | @TextField({ multiLine: true }) 17 | body: string; 18 | } 19 | 20 | // Create ModelManager and register the models 21 | 22 | export const modelManager = new ModelManager(); 23 | modelManager.registerBackend('default', new InMemoryBackend()); 24 | 25 | modelManager.register(User); 26 | modelManager.register(Post); 27 | -------------------------------------------------------------------------------- /packages/rev-models/src/operations/utils.ts: -------------------------------------------------------------------------------- 1 | import { IModel, IModelMeta } from '../models/types'; 2 | import { IObject } from '../utils/types'; 3 | 4 | /** 5 | * @private 6 | */ 7 | export function getModelPrimaryKeyQuery(model: T, meta: IModelMeta) { 8 | if (!meta.primaryKey) { 9 | throw new Error('KeyError: no primaryKey defined'); 10 | } 11 | else { 12 | let pkQuery: IObject = {}; 13 | if (typeof model[meta.primaryKey] == 'undefined') { 14 | throw new Error(`KeyError: primary key field '${meta.primaryKey}' is undefined`); 15 | } 16 | pkQuery[meta.primaryKey] = model[meta.primaryKey]; 17 | return pkQuery; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/views/MUISearchView.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import { withStyles, StyledComponentProps } from '@material-ui/core/styles'; 5 | 6 | import Grid from '@material-ui/core/Grid'; 7 | import { ISearchViewProps } from 'rev-ui/lib/views/SearchView'; 8 | 9 | const styles = withStyles({ 10 | root: { 11 | width: '100%' 12 | } 13 | }); 14 | 15 | type IMUISearchViewProps = ISearchViewProps & StyledComponentProps; 16 | 17 | export const MUISearchView = styles((props) => { 18 | 19 | return ( 20 | 21 | {props.children} 22 | 23 | ); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /packages/rev-api/src/graphql/types/ApiReadMeta.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLInt } from 'graphql'; 2 | import { IReadMeta } from 'rev-models/lib/models/types'; 3 | 4 | export const ApiReadMetaObjectType = new GraphQLObjectType({ 5 | name: 'api_read_meta', 6 | fields: { 7 | limit: { 8 | type: GraphQLInt, 9 | resolve: (rootValue: IReadMeta) => rootValue.limit 10 | }, 11 | offset: { 12 | type: GraphQLInt, 13 | resolve: (rootValue: IReadMeta) => rootValue.offset 14 | }, 15 | totalCount: { 16 | type: GraphQLInt, 17 | resolve: (rootValue: IReadMeta) => rootValue.totalCount 18 | }, 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/removing_multiple_records.ts: -------------------------------------------------------------------------------- 1 | import { Post, modelManager, createData } from './post_model'; 2 | 3 | (async () => { 4 | await createData(); 5 | 6 | // Count unpublished posts 7 | const unpublishedPosts = await modelManager.read(Post, { 8 | where: { 9 | published: false 10 | } 11 | }); 12 | console.log('Number of unpublished posts:', unpublishedPosts.meta.totalCount); 13 | 14 | // Delete all unpublished posts 15 | const deleteResult = await modelManager.remove(new Post(), { 16 | where: { 17 | published: false 18 | } 19 | }); 20 | console.log('Deleted records:', deleteResult.meta.totalCount); 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/actions/MUIActionButton.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import { IActionComponentProps } from 'rev-ui/lib/actions/types'; 5 | import Button from '@material-ui/core/Button'; 6 | 7 | export const MUIActionButton: React.SFC = (props) => { 8 | 9 | const childContent = props.children || props.label; 10 | const buttonType = props.defaultAction ? 'submit' : 'button'; 11 | 12 | return ( 13 | 21 | ); 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /packages/examples/src/using_backends/using_an_inmemory_backend.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, IntegerField, 4 | ModelManager, InMemoryBackend 5 | } from 'rev-models'; 6 | 7 | class TestModel { 8 | @AutoNumberField({ primaryKey: true }) 9 | item_number: number; 10 | @TextField() 11 | name: string; 12 | @TextField({ required: false }) 13 | description: string; 14 | @IntegerField() 15 | score: number; 16 | 17 | constructor(data: Partial) { 18 | Object.assign(this, data); 19 | } 20 | } 21 | 22 | // Create a ModelManager with an InMemoryBackend 23 | const modelManager = new ModelManager(); 24 | modelManager.registerBackend('default', new InMemoryBackend()); 25 | modelManager.register(TestModel); 26 | -------------------------------------------------------------------------------- /packages/rev-api/src/graphql/types.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModelManager, IModel } from 'rev-models/lib/models/types'; 3 | import { IModelApiManager } from '../api/types'; 4 | import { fields } from 'rev-models'; 5 | import { GraphQLObjectType, GraphQLType, GraphQLInputObjectType } from 'graphql'; 6 | 7 | export interface IGraphQLApi { 8 | getModelManager(): IModelManager; 9 | getApiManager(): IModelApiManager; 10 | getReadableModels(): string[]; 11 | getGraphQLFieldConverter(field: fields.Field): IGraphQLFieldConverter; 12 | getModelObject(modelName: string): GraphQLObjectType; 13 | getModelInputObject(modelName: string): GraphQLInputObjectType; 14 | } 15 | 16 | export interface IGraphQLFieldConverter { 17 | type: GraphQLType; 18 | converter(model: IModel, fieldName: string): any; 19 | } 20 | -------------------------------------------------------------------------------- /docs/typedoc/theme/partials/navigation.hbs: -------------------------------------------------------------------------------- 1 | {{#if isVisible}} 2 | {{#if isLabel}} 3 |
  • 4 | {{{wbr title}}} 5 |
  • 6 | {{else}} 7 | {{#if isGlobals}} 8 | {{else}} 9 |
  • 10 | {{{wbr title}}} 11 | {{#if isInPath}} 12 | {{#if children}} 13 |
      14 | {{#each children}} 15 | {{> navigation}} 16 | {{/each}} 17 |
    18 | {{/if}} 19 | {{/if}} 20 |
  • 21 | {{/if}} 22 | {{/if}} 23 | {{/if}} 24 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/updating_multiple_records.ts: -------------------------------------------------------------------------------- 1 | import { Post, modelManager, createData } from './post_model'; 2 | 3 | (async () => { 4 | await createData(); 5 | 6 | // Count unpublished posts 7 | const unpublishedPosts = await modelManager.read(Post, { 8 | where: { 9 | published: false 10 | } 11 | }); 12 | console.log('Number of unpublished posts:', unpublishedPosts.meta.totalCount); 13 | 14 | // Publish all unpublished posts! 15 | const publishResult = await modelManager.update( 16 | new Post({ 17 | published: true 18 | }), 19 | { 20 | where: { 21 | published: false 22 | } 23 | } 24 | ); 25 | console.log('Updated records:', publishResult.meta.totalCount); 26 | 27 | })(); 28 | -------------------------------------------------------------------------------- /docs/typedoc/theme/assets/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #efefef; 3 | } 4 | .tsd-page-toolbar { 5 | background-color: #2980B9; 6 | border: 0; 7 | } 8 | .tsd-page-toolbar, .tsd-page-toolbar a { 9 | color: #fcfcfc; 10 | } 11 | .tsd-page-title { 12 | background: #343131; 13 | color: #e3e3e3; 14 | } 15 | .tsd-breadcrumb li::after { 16 | content: none; 17 | } 18 | .tsd-breadcrumb a { 19 | color: #e3e3e3; 20 | } 21 | .tsd-navigation.primary { 22 | padding-bottom: 0px; 23 | } 24 | .tsd-signature, .tsd-signature-symbol, .tsd-signature-type { 25 | font-weight: bold; 26 | } 27 | .home-link { 28 | margin-left: 30px; 29 | font-style: italic; 30 | position: relative; 31 | z-index: 2; 32 | } 33 | a.home-link:hover { 34 | text-decoration: underline; 35 | } 36 | .tsd-is-private { 37 | display: none; 38 | } -------------------------------------------------------------------------------- /packages/rev-ui/src/actions/types.ts: -------------------------------------------------------------------------------- 1 | import { IStandardComponentProps } from '../utils/props'; 2 | 3 | /** 4 | * The props passed to ActionComponents, when they are set up via e.g. 5 | * ``, or via the 6 | * [[UI_COMPONENTS]] configuration. 7 | * @private 8 | */ 9 | export interface IActionComponentProps extends IStandardComponentProps { 10 | 11 | /** The label to be displayed */ 12 | label: string; 13 | 14 | /** Whether the component should be disabled */ 15 | disabled: boolean; 16 | 17 | /** Whether the component is the default action */ 18 | defaultAction: boolean; 19 | 20 | /** The method to call to trigger the action */ 21 | doAction(): Promise; 22 | 23 | /** Any children passed to the parent ActionComponent */ 24 | children?: React.ReactNode; 25 | } 26 | -------------------------------------------------------------------------------- /packages/rev-models/src/operations/hydrate.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModel, IModelManager } from '../models/types'; 3 | import { RelatedModelFieldBase } from '../fields'; 4 | 5 | /** 6 | * @private 7 | * Documentation in ModelManager class 8 | */ 9 | export function hydrate(manager: IModelManager, model: new() => T, data: any): T { 10 | 11 | let meta = manager.getModelMeta(model); 12 | let instance = new model(); 13 | 14 | if (data && typeof data == 'object') { 15 | for (let field of meta.fields) { 16 | if (field.options.stored 17 | && typeof data[field.name] != 'undefined' 18 | && !(field instanceof RelatedModelFieldBase)) { 19 | instance[field.name] = field.fromBackendValue(manager, data[field.name]); 20 | } 21 | } 22 | } 23 | 24 | return instance; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /packages/rev-api/src/graphql/mutation/mutation.ts: -------------------------------------------------------------------------------- 1 | import { getModelOperationMutations } from './operations'; 2 | import { getModelMethodMutations } from './methods'; 3 | import { IGraphQLApi } from '../types'; 4 | 5 | export function getMutationConfig(api: IGraphQLApi) { 6 | 7 | let mutations = { 8 | name: 'mutation', 9 | fields: {} 10 | }; 11 | const manager = api.getApiManager(); 12 | 13 | for (let modelName of manager.getModelNames()) { 14 | 15 | let meta = manager.getApiMeta(modelName); 16 | 17 | let operationMutations = getModelOperationMutations(api, meta); 18 | let methodMutations = getModelMethodMutations(api, meta); 19 | 20 | Object.assign(mutations.fields, operationMutations, methodMutations); 21 | 22 | } 23 | 24 | if (Object.keys(mutations.fields).length > 0) { 25 | return mutations; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/__fixtures__/models.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntegerField, TextField, BooleanField, DateTimeField, 3 | ModelManager, InMemoryBackend, 4 | } from 'rev-models'; 5 | 6 | export class Post { 7 | @IntegerField({ primaryKey: true }) 8 | id: number; 9 | @TextField() 10 | title: string; 11 | @TextField() 12 | body: string; 13 | @TextField({ required: false }) 14 | keywords: string; 15 | @BooleanField() 16 | published: boolean; 17 | @DateTimeField() 18 | post_date: string; 19 | 20 | constructor(data?: Partial) { 21 | Object.assign(this, data); 22 | } 23 | } 24 | 25 | export function getModelManager() { 26 | const modelManager = new ModelManager(); 27 | modelManager.registerBackend('default', new InMemoryBackend()); 28 | modelManager.register(Post); 29 | return modelManager; 30 | } -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/value.ts: -------------------------------------------------------------------------------- 1 | import { QueryNode } from './query'; 2 | import { IQueryParser, IQueryNode } from '../types'; 3 | import { isFieldValue } from '../utils'; 4 | import { IModel } from '../../models/types'; 5 | 6 | /** 7 | * @private 8 | */ 9 | export class ValueOperator extends QueryNode { 10 | 11 | constructor( 12 | parser: IQueryParser, 13 | model: new() => T, 14 | operator: string, 15 | public value: any, 16 | parent?: IQueryNode) { 17 | 18 | super(parser, model, operator, parent); 19 | if (!(this.operator in parser.FIELD_OPERATORS)) { 20 | throw new Error(`unrecognised field operator '${this.operator}'`); 21 | } 22 | else if (!isFieldValue(this.value)) { 23 | throw new Error(`invalid field value '${this.value}'`); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/rev-ui/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { ModelProvider } from './provider/ModelProvider'; 3 | export { withModelManager, IModelManagerProp } from './provider/withModelManager'; 4 | 5 | export { DetailView, IDetailViewContextProp } from './views/DetailView'; 6 | export { ListView, IListViewComponentProps } from './views/ListView'; 7 | export { SearchView, ISearchViewContextProp } from './views/SearchView'; 8 | export { withDetailViewContext } from './views/withDetailViewContext'; 9 | 10 | export { Field, IFieldComponentProps } from './fields/Field'; 11 | export { SearchField, ISearchFieldComponentProps } from './fields/SearchField'; 12 | 13 | export { PostAction } from './actions/PostAction'; 14 | export { SaveAction } from './actions/SaveAction'; 15 | export { RemoveAction } from './actions/RemoveAction'; 16 | export { SearchAction } from './actions/SearchAction'; 17 | export { IActionComponentProps } from './actions/types'; 18 | -------------------------------------------------------------------------------- /packages/rev-models/src/backends/inmemory/sort.ts: -------------------------------------------------------------------------------- 1 | import { IObject } from '../../utils/types'; 2 | 3 | /** 4 | * @private 5 | */ 6 | export function sortRecords(records: IObject[], orderBy: string[]) { 7 | return records.sort((r1, r2) => { 8 | for (let order_spec of orderBy) { 9 | let tokens = order_spec.split(' '); 10 | let val1 = (typeof r1[tokens[0]] == 'string') ? 11 | r1[tokens[0]].toLowerCase() : r1[tokens[0]]; 12 | let val2 = (typeof r2[tokens[0]] == 'string') ? 13 | r2[tokens[0]].toLowerCase() : r2[tokens[0]]; 14 | if (val1 != val2) { 15 | if (tokens[1] == 'desc') { 16 | return (val1 > val2) ? -1 : 1; 17 | } 18 | else { 19 | return (val1 < val2) ? -1 : 1; 20 | } 21 | } 22 | } 23 | return 0; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/validating_data.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Customer, modelManager } from './creating_models'; 3 | 4 | (async () => { 5 | 6 | let person = new Customer({ 7 | first_name: 'Bill', 8 | last_name: 'Bloggs', 9 | age: 31, 10 | gender: 'M' 11 | }); 12 | 13 | // models.create() will throw if model is invalid 14 | 15 | let res = await modelManager.create(person); 16 | console.log('Created Customer ID', res.result.id); 17 | 18 | // You can also manually trigger validation: 19 | 20 | let invalid_person = new Customer(); 21 | invalid_person.gender = 'Z'; 22 | 23 | let validationResult = await modelManager.validate(invalid_person); 24 | 25 | if (!validationResult.valid) { 26 | console.log('Field errors:', validationResult.fieldErrors); 27 | console.log('Model errors:', validationResult.modelErrors); 28 | } 29 | 30 | })(); 31 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/removing_data.ts: -------------------------------------------------------------------------------- 1 | 2 | import { City, modelManager } from './creating_models'; 3 | 4 | (async () => { 5 | 6 | await modelManager.create(new City({ name: 'Wellington' })); 7 | await modelManager.create(new City({ name: 'Auckland' })); 8 | await modelManager.create(new City({ name: 'Hamilton' })); 9 | await modelManager.create(new City({ name: 'Christchurch' })); 10 | 11 | const origRecords = await modelManager.read(City); 12 | console.log('Original Records:', origRecords.results); 13 | 14 | // Retrieve Auckland 15 | const cities = await modelManager.read(City, { where: { 16 | name: 'Auckland' 17 | }}); 18 | 19 | // Remove it! 20 | await modelManager.remove(cities.results[0]); 21 | 22 | // Retrieve remaining records 23 | const newRecords = await modelManager.read(City); 24 | console.log('Remaining Records:', newRecords.results); 25 | 26 | })(); 27 | -------------------------------------------------------------------------------- /packages/rev-api/src/api/types.ts: -------------------------------------------------------------------------------- 1 | import { IModelManager } from 'rev-models/lib/models/types'; 2 | import { IModel, fields } from 'rev-models'; 3 | import { GraphQLSchema } from 'graphql'; 4 | 5 | export interface IApiMethodMeta { 6 | args?: fields.Field[]; 7 | modelData?: boolean; 8 | } 9 | 10 | export interface IApiMeta { 11 | model: string; 12 | operations: string[]; 13 | methods: { 14 | [methodName: string]: IApiMethodMeta; 15 | }; 16 | } 17 | 18 | export interface IModelApiManager { 19 | 20 | getModelManager(): IModelManager; 21 | isRegistered(modelName: string): boolean; 22 | register(model: new(...args: any[]) => T, apiMeta?: IApiMeta): void; 23 | 24 | getModelNames(): string[]; 25 | getModelNamesByOperation(operationName: string): string[]; 26 | getGraphQLSchema(): GraphQLSchema; 27 | 28 | getApiMeta(modelName: string): IApiMeta; 29 | clearManager(): void; 30 | } 31 | -------------------------------------------------------------------------------- /packages/examples/src/using_backends/using_an_api_backend.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, IntegerField, 4 | ModelManager 5 | } from 'rev-models'; 6 | 7 | import { ModelApiBackend } from 'rev-api-client'; 8 | 9 | class TestModel { 10 | @AutoNumberField({ primaryKey: true }) 11 | item_number: number; 12 | @TextField() 13 | name: string; 14 | @TextField({ required: false }) 15 | description: string; 16 | @IntegerField() 17 | score: number; 18 | 19 | constructor(data: Partial) { 20 | Object.assign(this, data); 21 | } 22 | } 23 | 24 | // Create a ModelApiBackend, which consumes a rev-api GraphQL API at /api 25 | const apiBackend = new ModelApiBackend('/api'); 26 | 27 | // Create the ModelManager and register the backnd and a model 28 | const modelManager = new ModelManager(); 29 | modelManager.registerBackend('default', apiBackend); 30 | modelManager.register(TestModel); 31 | -------------------------------------------------------------------------------- /packages/rev-ui/testsetup.js: -------------------------------------------------------------------------------- 1 | 2 | // Configure JSDOM 3 | // from https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md 4 | 5 | const { JSDOM } = require('jsdom'); 6 | const jsdom = new JSDOM(''); 7 | const { window } = jsdom; 8 | 9 | function copyProps(src, target) { 10 | const props = Object.getOwnPropertyNames(src) 11 | .filter(prop => typeof target[prop] === 'undefined') 12 | .reduce((result, prop) => ({ 13 | ...result, 14 | [prop]: Object.getOwnPropertyDescriptor(src, prop), 15 | }), {}); 16 | Object.defineProperties(target, props); 17 | } 18 | 19 | global.window = window; 20 | global.document = window.document; 21 | global.navigator = { 22 | userAgent: 'node.js', 23 | }; 24 | copyProps(window, global); 25 | 26 | // Configure Enzyme 27 | const Enzyme = require('enzyme'); 28 | const Adapter = require('enzyme-adapter-react-16'); 29 | 30 | Enzyme.configure({ adapter: new Adapter() }); 31 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/testsetup.js: -------------------------------------------------------------------------------- 1 | 2 | // Configure JSDOM 3 | // from https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md 4 | 5 | const { JSDOM } = require('jsdom'); 6 | const jsdom = new JSDOM(''); 7 | const { window } = jsdom; 8 | 9 | function copyProps(src, target) { 10 | const props = Object.getOwnPropertyNames(src) 11 | .filter(prop => typeof target[prop] === 'undefined') 12 | .reduce((result, prop) => ({ 13 | ...result, 14 | [prop]: Object.getOwnPropertyDescriptor(src, prop), 15 | }), {}); 16 | Object.defineProperties(target, props); 17 | } 18 | 19 | global.window = window; 20 | global.document = window.document; 21 | global.navigator = { 22 | userAgent: 'node.js', 23 | }; 24 | copyProps(window, global); 25 | 26 | // Configure Enzyme 27 | const Enzyme = require('enzyme'); 28 | const Adapter = require('enzyme-adapter-react-16'); 29 | 30 | Enzyme.configure({ adapter: new Adapter() }); 31 | -------------------------------------------------------------------------------- /packages/rev-ui/src/views/withDetailViewContext.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import * as PropTypes from 'prop-types'; 4 | import { IDetailViewContextProp } from './DetailView'; 5 | 6 | /** 7 | * Use this higher-order component function to provide the passed component 8 | * with access to [[IDetailViewContext]] via a `detailViewContext` prop. 9 | * @param component The component to wrap 10 | */ 11 | export function withDetailViewContext

    (component: React.ComponentType

    ): React.ComponentType

    { 12 | 13 | return class extends React.Component

    { 14 | 15 | context: IDetailViewContextProp; 16 | static contextTypes = { 17 | detailViewContext: PropTypes.object 18 | }; 19 | 20 | render() { 21 | const WrappedComponent = component; 22 | return ; 23 | } 24 | 25 | }; 26 | 27 | } -------------------------------------------------------------------------------- /packages/rev-ui/src/views/withSearchViewContext.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import * as PropTypes from 'prop-types'; 4 | import { ISearchViewContextProp } from './SearchView'; 5 | 6 | /** 7 | * Use this higher-order component function to provide the passed component 8 | * with access to [[ISearchViewContext]] via a `searchViewContext` prop. 9 | * @param component The component to wrap 10 | */ 11 | export function withSearchViewContext

    (component: React.ComponentType

    ): React.ComponentType

    { 12 | 13 | return class extends React.Component

    { 14 | 15 | context: ISearchViewContextProp; 16 | static contextTypes = { 17 | searchViewContext: PropTypes.object 18 | }; 19 | 20 | render() { 21 | const WrappedComponent = component; 22 | return ; 23 | } 24 | 25 | }; 26 | 27 | } -------------------------------------------------------------------------------- /docs/mkdocs/src/using_models/updating_data.md: -------------------------------------------------------------------------------- 1 | # Updating Model Data 2 | 3 | You can update records using the 4 | [ModelManager.update()](/api/rev-models/classes/modelmanager.html#update) 5 | method. There are a couple of ways to use it: 6 | 7 | ## Updating Data from Reads 8 | 9 | If you have retrieved some data via a 10 | [read()](/api/rev-models/classes/modelmanager.html#read) operation, then you 11 | can update the retrieved records directly, as shown in the example below: 12 | 13 | ```ts 14 | {!examples/src/defining_and_using_models/updating_data.ts!} 15 | ``` 16 | 17 | **NOTE:** In order to update records without specifying a *where* clause, your 18 | model must have a field with `primaryKey: true` set. 19 | 20 | ## Updating Data with a Where clause 21 | 22 | If you wish to modify one or more fields across *multiple records*, you can 23 | use the **where** clause, as shown in the example below: 24 | 25 | ```ts 26 | {!examples/src/defining_and_using_models/updating_multiple_records.ts!} 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/mkdocs/src/using_models/deleting_data.md: -------------------------------------------------------------------------------- 1 | # Deleting Model Data 2 | 3 | You can delete records using the 4 | [ModelManager.remove()](/api/rev-models/classes/modelmanager.html#remove) 5 | method ('delete' is a reserved word in JavaScript). There are a couple of ways to use the remove() method: 6 | 7 | ## Deleting Data from Reads 8 | 9 | If you have retrieved some data via a 10 | [read()](/api/rev-models/classes/modelmanager.html#read) operation, then you 11 | can remove the retrieved records directly, as shown in the example below: 12 | 13 | ```ts 14 | {!examples/src/defining_and_using_models/removing_data.ts!} 15 | ``` 16 | 17 | **NOTE:** In order to remove records without specifying a *where* clause, your 18 | model must have a field with `primaryKey: true` set. 19 | 20 | ## Deleting Data with a Where clause 21 | 22 | If you wish to delete multiple records, you can 23 | use the **where** clause, as shown in the example below: 24 | 25 | ```ts 26 | {!examples/src/defining_and_using_models/removing_multiple_records.ts!} 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/mkdocs/src/components/rev-api.md: -------------------------------------------------------------------------------- 1 | 2 | # rev-api - RevJS GraphQL API Generator 3 | 4 | The `rev-api` module provides the following: 5 | 6 | * A **ModelApiManager** object, for registering your models to be exposed via 7 | the GraphQL API 8 | * An **@ApiOperations** decorator, for annotating your models with the operations 9 | that are allowed on them (`read`, `create`, etc.) 10 | * An **@ApiMethod** decorator, for annotating any model methods that you 11 | want to expose as GraphQL mutations. 12 | 13 | For more information, check out [Creating an API](../creating_an_api/overview.md) 14 | 15 | *Jump to the [rev-api API Documentation](/api/rev-api)* 16 | 17 | ## Example 18 | 19 | The example below creates a few models and assigns them to a ModelApiManager. 20 | 21 | ```ts 22 | {!examples/src/creating_an_api/defining_an_api.ts!} 23 | ``` 24 | 25 | ## Contributing 26 | 27 | We are actively looking to build a team around RevJS. If you are interesting in 28 | contributing, fork us on github or drop us a 29 | [mail](mailto:russ@russellbriggs.co)! 30 | -------------------------------------------------------------------------------- /packages/examples/src/using_related_models/updating_related_data.ts: -------------------------------------------------------------------------------- 1 | 2 | import { User, Post, Comment, modelManager } from './creating_related_models'; 3 | import { createData } from './creating_related_data'; 4 | 5 | (async () => { 6 | await createData(); 7 | 8 | // Get the first post 9 | const post1 = (await modelManager.read(Post, { limit: 1 })).results[0]; 10 | 11 | // Get the first user 12 | const user1 = (await modelManager.read(User, { limit: 1 })).results[0]; 13 | 14 | // Make user1 the author of post1 15 | post1.user = user1; 16 | await modelManager.update(post1); 17 | 18 | // If we know the ID of the record we want to link, we can pass a new 19 | // model instance containing the related ID: 20 | post1.user = new User({ id: 2 }); 21 | await modelManager.update(post1); 22 | 23 | // To un-link a model, set the RelatedModel field to null 24 | const comment1 = (await modelManager.read(Comment, { limit: 1 })).results[0]; 25 | comment1.user = null; 26 | await modelManager.update(comment1); 27 | 28 | })(); -------------------------------------------------------------------------------- /packages/examples/src/searching_and_reading_data/read_with_paging.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Post, modelManager, createData } from './models'; 3 | 4 | (async () => { 5 | await createData(); 6 | 7 | // Get the first 3 published posts 8 | const first3Posts = await modelManager.read(Post, { 9 | where: { 10 | published: true 11 | }, 12 | offset: 0, 13 | limit: 3 14 | }); 15 | console.log('First 3 Published Posts:', first3Posts.results); 16 | 17 | // Get the next 3 published posts 18 | const next3Posts = await modelManager.read(Post, { 19 | where: { 20 | published: true 21 | }, 22 | offset: 3, 23 | limit: 3 24 | }); 25 | console.log('Next 3 Posts:', next3Posts.results); 26 | 27 | // Just get all the published posts (max 100) 28 | const allPosts = await modelManager.read(Post, { 29 | where: { 30 | published: true 31 | }, 32 | limit: 100 33 | }); 34 | console.log('All Teh Posts:', allPosts.results); 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /packages/examples/src/searching_and_reading_data/read_with_sorting.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Post, modelManager, createData } from './models'; 3 | 4 | (async () => { 5 | await createData(); 6 | 7 | // Read all published posts, sorted by Title 8 | const postsByTitle = await modelManager.read(Post, { 9 | where: { 10 | published: true 11 | }, 12 | limit: 100, 13 | orderBy: ['title'] 14 | }); 15 | console.log('Posts by Title:', postsByTitle.results); 16 | 17 | // Read all posts, sorted by Title in descending order 18 | const postsByTitleDesc = await modelManager.read(Post, { 19 | limit: 100, 20 | orderBy: ['title desc'] 21 | }); 22 | console.log('Posts by Titie in descending order:', postsByTitleDesc.results); 23 | 24 | // Read the first 10 posts sorted by Rating, then by Title 25 | const top10Posts = await modelManager.read(Post, { 26 | limit: 10, 27 | orderBy: ['rating desc', 'title'] 28 | }); 29 | console.log('Top Posts:', top10Posts.results); 30 | 31 | })(); 32 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/updating_data.ts: -------------------------------------------------------------------------------- 1 | import { City, modelManager } from './creating_models'; 2 | 3 | (async () => { 4 | 5 | await modelManager.create(new City({ name: 'Wellington' })); 6 | await modelManager.create(new City({ name: 'Auckland' })); 7 | await modelManager.create(new City({ name: 'Hamilton' })); 8 | await modelManager.create(new City({ name: 'Christchurch' })); 9 | 10 | // Retrieve Auckland's record 11 | const cities = await modelManager.read(City, { where: { 12 | name: 'Auckland' 13 | }}); 14 | console.log('Original Record:', cities.results[0]); 15 | 16 | // Change it's name and trigger an update 17 | const auckland = cities.results[0]; 18 | auckland.name = 'City of Sails'; 19 | 20 | await modelManager.update(auckland); 21 | 22 | // Check that Auckland's record has been updated 23 | const cityRetrieved = await modelManager.read(City, { where: { 24 | name: 'City of Sails' 25 | }}); 26 | console.log('Updated Record:', cityRetrieved.results[0]); 27 | 28 | })(); 29 | -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/query.ts: -------------------------------------------------------------------------------- 1 | import { IQueryParser, IQueryNode } from '../types'; 2 | 3 | import { printObj } from '../../utils/index'; 4 | import { IModel } from '../../models/types'; 5 | 6 | /** 7 | * @private 8 | */ 9 | export class QueryNode implements IQueryNode { 10 | public children: Array>; 11 | 12 | constructor( 13 | public parser: IQueryParser, 14 | public model: new() => T, 15 | public operator: string, 16 | public parent?: IQueryNode) { 17 | 18 | // Nodes should store the operator name without its prefix 19 | const opName = parser.getUnprefixedOperatorName(operator); 20 | if (opName) { 21 | this.operator = opName; 22 | } 23 | 24 | this.children = []; 25 | } 26 | 27 | assertIsNonEmptyObject(value: any) { 28 | if (!value || typeof value != 'object' || Object.keys(value).length == 0) { 29 | throw new Error(`element ${printObj(value)} is not valid for '${this.operator}'`); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/conjunction.ts: -------------------------------------------------------------------------------- 1 | 2 | import { QueryNode } from './query'; 3 | import { IQueryParser, IQueryNode } from '../types'; 4 | import { IModel } from '../../models/types'; 5 | 6 | /** 7 | * @private 8 | */ 9 | export class ConjunctionNode extends QueryNode { 10 | 11 | constructor( 12 | parser: IQueryParser, 13 | model: new() => T, 14 | operator: string, 15 | value: any, 16 | parent?: IQueryNode) { 17 | 18 | super(parser, model, operator, parent); 19 | 20 | if (!(this.operator in parser.CONJUNCTION_OPERATORS)) { 21 | throw new Error(`unrecognised conjunction operator '${this.operator}'`); 22 | } 23 | if (!value || !(value instanceof Array)) { 24 | throw new Error(`value for '${this.operator}' must be an array`); 25 | } 26 | for (let elem of value) { 27 | this.assertIsNonEmptyObject(elem); 28 | this.children.push(parser.getQueryNodeForQuery(model, elem, this)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-ui/src/provider/withModelManager.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import * as PropTypes from 'prop-types'; 4 | import { IModelProviderContext } from './ModelProvider'; 5 | import { IModelManager } from 'rev-models'; 6 | 7 | /** 8 | * @private 9 | */ 10 | export interface IModelManagerProp { 11 | modelManager: IModelManager; 12 | } 13 | 14 | /** 15 | * Use this higher-order component function to provide the passed component 16 | * with access to the current ModelManager via a `modelManager` prop. 17 | * @param component The component to wrap 18 | */ 19 | export function withModelManager

    (component: React.ComponentType

    ): React.ComponentType

    { 20 | 21 | return class extends React.Component

    { 22 | 23 | context: IModelProviderContext; 24 | static contextTypes = { 25 | modelManager: PropTypes.object 26 | }; 27 | 28 | render() { 29 | const WrappedComponent = component; 30 | return ; 31 | } 32 | 33 | }; 34 | 35 | } -------------------------------------------------------------------------------- /packages/examples/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-api/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-models/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-ui/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-api-client/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/valuelist.ts: -------------------------------------------------------------------------------- 1 | import { QueryNode } from './query'; 2 | import { IQueryParser, IQueryNode } from '../types'; 3 | import { isFieldValue } from '../utils'; 4 | import { IModel } from '../../models/types'; 5 | 6 | /** 7 | * @private 8 | */ 9 | export class ValueListOperator extends QueryNode { 10 | 11 | constructor( 12 | parser: IQueryParser, 13 | model: new() => T, 14 | operator: string, 15 | public values: any[], 16 | parent?: IQueryNode) { 17 | 18 | super(parser, model, operator, parent); 19 | if (!(this.operator in parser.FIELD_OPERATORS)) { 20 | throw new Error(`unrecognised field operator '${this.operator}'`); 21 | } 22 | if (!this.values || !(this.values instanceof Array)) { 23 | throw new Error(`value for '${this.operator}' must be an array`); 24 | } 25 | for (let elem of this.values) { 26 | if (!isFieldValue(elem)) { 27 | throw new Error(`invalid field value '${elem}'`); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2017 Russell Briggs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/examples/src/using_related_models/reading_related_data.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Post, modelManager } from './creating_related_models'; 3 | import { createData } from './creating_related_data'; 4 | 5 | (async () => { 6 | await createData(); 7 | 8 | // Read all posts (without related data) 9 | const plainPosts = await modelManager.read(Post); 10 | console.log('Plain posts:', plainPosts.results); 11 | 12 | // Read all posts, including the 'user' field 13 | const posts2 = await modelManager.read(Post, { 14 | related: ['user'] 15 | }); 16 | console.log('Posts with User:', posts2.results); 17 | 18 | // Read all posts, including the 'user' and 'comments' 19 | const posts3 = await modelManager.read(Post, { 20 | related: ['user', 'comments'] 21 | }); 22 | console.log('Posts with User and Comments:', posts3.results); 23 | 24 | // Read all posts, including the 'user', 'comments' and 'comments.user' 25 | const posts4 = await modelManager.read(Post, { 26 | related: ['user', 'comments.user'] 27 | }); 28 | console.log('Posts with User, Comments and Comment Author:', posts4.results); 29 | 30 | })(); -------------------------------------------------------------------------------- /packages/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "0.21.1", 4 | "description": "RevJS Examples", 5 | "private": true, 6 | "scripts": { 7 | "build": "run-bin tslint -p . && run-bin tsc --noEmit && npm run build-ui", 8 | "build-ui": "run-bin webpack --mode development --config src/creating_a_ui/webpack.config.js", 9 | "build-ui-watch": "run-bin webpack --watch --mode development --config src/creating_a_ui/webpack.config.js", 10 | "start-ui-api": "run-bin ts-node src/creating_a_ui/api.ts", 11 | "ts-node": "run-bin ts-node" 12 | }, 13 | "author": "Russell Briggs ", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/RevJS/revjs.git" 18 | }, 19 | "dependencies": { 20 | "graphql-server-koa": "1.x", 21 | "koa": "2.x", 22 | "koa-bodyparser": "4.x", 23 | "koa-router": "7.x", 24 | "rev-api": "^0.21.1", 25 | "rev-api-client": "^0.21.1", 26 | "rev-backend-mongodb": "^0.21.1", 27 | "rev-models": "^0.21.1", 28 | "rev-ui": "^0.21.1", 29 | "rev-ui-materialui": "^0.21.1" 30 | }, 31 | "devDependencies": { 32 | "run-bin": "1.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/examples/src/searching_and_reading_data/read_with_where_clause.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Post, modelManager, createData } from './models'; 3 | 4 | (async () => { 5 | await createData(); 6 | 7 | // Find all posts in the 'technology' category that are published 8 | const techPosts = await modelManager.read(Post, { 9 | where: { 10 | category: 'technology', 11 | published: true 12 | } 13 | }); 14 | console.log('Tech Posts:', techPosts.results); 15 | 16 | // Find all published posts in the 'science' OR 'technology' categories 17 | const sciTechPosts = await modelManager.read(Post, { 18 | where: { 19 | _or: [ 20 | { category: 'science' }, 21 | { category: 'technology' } 22 | ], 23 | published: true 24 | } 25 | }); 26 | console.log('Science & Technology Posts:', sciTechPosts.results); 27 | 28 | // Find all posts with a rating greater than or equal to 3 29 | const goodPosts = await modelManager.read(Post, { 30 | where: { 31 | rating: { _gte: 3 } 32 | } 33 | }); 34 | console.log('Good Posts:', goodPosts.results); 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/views/MUIDetailView.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import { withStyles, WithStyles } from '@material-ui/core/styles'; 5 | 6 | import Grid from '@material-ui/core/Grid'; 7 | import { IDetailViewProps } from 'rev-ui/lib/views/DetailView'; 8 | 9 | const styles = { 10 | root: { 11 | width: '100%' 12 | } 13 | }; 14 | 15 | type IMUIDetailViewProps = IDetailViewProps & WithStyles; 16 | 17 | export const MUIDetailView: React.ComponentType = withStyles(styles)( 18 | class extends React.Component { 19 | 20 | constructor(props: IMUIDetailViewProps) { 21 | super(props); 22 | this.handleSubmit.bind(this); 23 | } 24 | 25 | handleSubmit(e: React.FormEvent) { 26 | e.preventDefault(); 27 | } 28 | 29 | render() { 30 | return ( 31 |

    32 | 33 | {this.props.children} 34 | 35 |
    36 | ); 37 | } 38 | 39 | } 40 | ); -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:8 11 | - image: circleci/mongo:3.6 12 | 13 | working_directory: ~/repo 14 | 15 | steps: 16 | - checkout 17 | 18 | # Download and cache dependencies 19 | - restore_cache: 20 | keys: 21 | - v1-dependencies-{{ checksum "package.json" }} 22 | # fallback to using the latest cache if no exact match is found 23 | - v1-dependencies- 24 | 25 | - run: npm install 26 | - run: npm run build 27 | 28 | - save_cache: 29 | paths: 30 | - node_modules 31 | key: v1-dependencies-{{ checksum "package.json" }} 32 | 33 | # run tests! 34 | - run: 35 | command: npm run test-ci 36 | environment: 37 | MOCHA_FILE: "/tmp/test-results/junit/result-[hash].xml" 38 | 39 | - store_test_results: 40 | path: /tmp/test-results 41 | 42 | - store_artifacts: 43 | path: /tmp/test-results 44 | -------------------------------------------------------------------------------- /packages/rev-models/src/models/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModelMeta } from './types'; 3 | 4 | /** 5 | * @private 6 | */ 7 | export function checkIsValidModelConstructor(model: any) { 8 | if (!model || typeof model != 'function' || !model.name) { 9 | throw new Error('ModelError: Supplied model is not a model constructor.'); 10 | } 11 | let instance: any; 12 | try { 13 | instance = new model(); 14 | } 15 | catch (e) { 16 | throw new Error(`ModelError: constructor threw an error when called with no arguments: ${e.message}`); 17 | } 18 | if (Object.keys(instance).length) { 19 | throw new Error('ModelError: constructor must not set default field values. You should use a defaults() method instead.'); 20 | } 21 | } 22 | 23 | /** 24 | * @private 25 | */ 26 | export function checkFieldsList(meta: IModelMeta, fields: string[]) { 27 | if (!(fields instanceof Array)) { 28 | throw new Error('"fields" must be an array of field names'); 29 | } 30 | for (let field of fields) { 31 | if (!(field in meta.fieldsByName)) { 32 | throw new Error(`"fields" error: Field '${field}' does not exist in model ${meta.name}`); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/examples/src/creating_an_api/serving_an_api.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as koa from 'koa'; 3 | import * as koaRouter from 'koa-router'; 4 | import * as koaBody from 'koa-bodyparser'; 5 | import { graphqlKoa, graphiqlKoa } from 'graphql-server-koa'; 6 | 7 | // Load RevJS API and generate GrapQL Schema 8 | 9 | import { api } from './defining_an_api'; 10 | import { createData } from './model_data'; 11 | 12 | const schema = api.getGraphQLSchema(); 13 | 14 | // Create Koa & Apollo GraphQL Server 15 | 16 | const app = new koa(); 17 | const port = 3000; 18 | 19 | const router = new koaRouter(); 20 | router.post('/graphql', graphqlKoa({ schema: schema })); 21 | router.get('/graphql', graphqlKoa({ schema: schema })); 22 | router.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' })); 23 | 24 | app.use(koaBody()); 25 | app.use(router.routes()); 26 | app.use(router.allowedMethods()); 27 | 28 | app.listen(port); 29 | 30 | console.log(`GraphQL Server is running on port ${port}.`); 31 | console.log(`GraphiQL UI is running at http://localhost:${port}/graphiql`); 32 | 33 | // Load sample data 34 | 35 | createData() 36 | .then(() => { 37 | console.log('Data Loaded.'); 38 | }) 39 | .catch((e) => { 40 | console.error('Error loading data', e); 41 | }); -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rev-backend-mongodb", 3 | "version": "0.21.1", 4 | "description": "RevJS Isomorphic Data Library - Mongo DB Backend", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "run-bin tslint -p . && rm -rf lib/ && run-bin tsc", 8 | "build-watch": "run-bin tsc --watch", 9 | "build-docs": "run-bin typedoc --options ./typedoc.js ./src", 10 | "test": "run-bin mocha -r ts-node/register --recursive \"./src/**/__tests__/*\"", 11 | "test-ci": "run-bin mocha -r ts-node/register --recursive \"./src/**/__tests__/*\" --reporter mocha-circleci-reporter", 12 | "test-watch": "run-bin mocha -r ts-node/register --recursive \"./src/**/__tests__/*\" --watch --watch-extensions ts,tsx", 13 | "mocha": "run-bin mocha -r ts-node/register", 14 | "pack": "npm pack" 15 | }, 16 | "author": "Russell Briggs ", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/RevJS/revjs.git" 21 | }, 22 | "typings": "lib/index.d.ts", 23 | "dependencies": { 24 | "@types/mongodb": "3.x", 25 | "mongodb": "3.x", 26 | "rev-models": "^0.21.1" 27 | }, 28 | "devDependencies": { 29 | "run-bin": "1.x" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/rev-models/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rev-models", 3 | "version": "0.21.1", 4 | "description": "RevJS Isomorphic Data Library - Core Data Model Components", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "run-bin tslint -p . && rm -rf lib/ && run-bin tsc", 8 | "build-watch": "run-bin tsc --watch", 9 | "build-docs": "run-bin typedoc --options ./typedoc.js ./src", 10 | "test": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/*\"", 11 | "test-ci": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/*\" --reporter mocha-circleci-reporter", 12 | "test-watch": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/*\" --watch --watch-extensions ts,tsx", 13 | "ts-node": "run-bin TS_NODE_FILES=true ts-node", 14 | "mocha": "run-bin mocha -r ts-node/register", 15 | "pack": "npm pack" 16 | }, 17 | "author": "Russell Briggs ", 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/RevJS/revjs.git" 22 | }, 23 | "typings": "lib/index.d.ts", 24 | "dependencies": { 25 | "tslib": "1.x" 26 | }, 27 | "devDependencies": { 28 | "run-bin": "1.x" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/rev-models/src/backends/testsuite/index.ts: -------------------------------------------------------------------------------- 1 | import { IBackend } from '../backend'; 2 | import { createTests } from './create.tests'; 3 | import { autoNumberTests } from './autonumber.tests'; 4 | import { readTests } from './read.tests'; 5 | import { queryTests } from './query.tests'; 6 | import { updateTests } from './update.tests'; 7 | import { removeTests } from './remove.tests'; 8 | import { createWithRelatedModelTests } from './create.related.tests'; 9 | import { readWithRelatedModelTests } from './read.related.tests'; 10 | import { updateWithRelatedModelTests } from './update.related.tests'; 11 | 12 | export interface IBackendTestConfig { 13 | backend: IBackend; 14 | skipRawValueTests?: boolean; 15 | skipRelatedModelListStoreTest?: boolean; 16 | } 17 | 18 | export function standardBackendTests(backendName: string, config: IBackendTestConfig) { 19 | readTests(backendName, config); 20 | queryTests(backendName, config); 21 | createTests(backendName, config); 22 | updateTests(backendName, config); 23 | removeTests(backendName, config); 24 | autoNumberTests(backendName, config); 25 | readWithRelatedModelTests(backendName, config); 26 | createWithRelatedModelTests(backendName, config); 27 | updateWithRelatedModelTests(backendName, config); 28 | } 29 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/creating_models.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, SelectField, IntegerField, 4 | RelatedModel, ModelManager, InMemoryBackend 5 | } from 'rev-models'; 6 | 7 | // Define models 8 | 9 | export class City { 10 | @AutoNumberField({ primaryKey: true }) 11 | id: number; 12 | @TextField() 13 | name: string; 14 | 15 | constructor(data?: Partial) { 16 | Object.assign(this, data); 17 | } 18 | } 19 | 20 | export class Customer { 21 | @AutoNumberField({ primaryKey: true }) 22 | id: number; 23 | @TextField() 24 | first_name: string; 25 | @TextField() 26 | last_name: string; 27 | @IntegerField() 28 | age: number; 29 | @SelectField({ selection: [['M', 'Male'], ['F', 'Female']] }) 30 | gender: string; 31 | @RelatedModel({ model: 'City', required: false }) 32 | city: City; 33 | 34 | constructor(data?: Partial) { 35 | Object.assign(this, data); 36 | } 37 | } 38 | 39 | // Create ModelManager and register the models 40 | 41 | export const modelManager = new ModelManager(); 42 | modelManager.registerBackend('default', new InMemoryBackend()); 43 | 44 | modelManager.register(City); 45 | modelManager.register(Customer); 46 | -------------------------------------------------------------------------------- /packages/examples/src/model_manager/handling_validation_errors.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | TextField, SelectField, ModelManager, InMemoryBackend, 4 | ValidationError 5 | } from 'rev-models'; 6 | 7 | // Define model 8 | 9 | export class Comment { 10 | @TextField() 11 | comment: string; 12 | @SelectField({ selection: [['draft', 'Draft'], ['posted', 'Posted']]}) 13 | status: string; 14 | 15 | constructor(data?: Partial) { 16 | Object.assign(this, data); 17 | } 18 | } 19 | 20 | const modelManager = new ModelManager(); 21 | modelManager.registerBackend('default', new InMemoryBackend()); 22 | modelManager.register(Comment); 23 | 24 | // Try to create an invalid Comment 25 | 26 | (async () => { 27 | 28 | try { 29 | await modelManager.create(new Comment({ 30 | comment: 'This comment has an invalid status', 31 | status: 'Awesome!' 32 | })); 33 | } 34 | catch (e) { 35 | if (e instanceof ValidationError) { 36 | console.log('Model Failed Validation :('); 37 | console.log('Field Errors:', e.validation.fieldErrors); 38 | console.log('Model Errors:', e.validation.modelErrors); 39 | } 40 | else { 41 | throw e; 42 | } 43 | } 44 | 45 | })(); 46 | -------------------------------------------------------------------------------- /packages/rev-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rev-api", 3 | "version": "0.21.1", 4 | "description": "RevJS Isomorphic Data Library - API Library", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "run-bin tslint -p . && rm -rf lib/ && run-bin tsc", 8 | "build-docs": "echo DOCS_TODO", 9 | "build-watch": "run-bin tsc --watch", 10 | "test": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/**/*\"", 11 | "test-ci": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/**/*\" --reporter mocha-circleci-reporter", 12 | "test-watch": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/**/*\" --watch --watch-extensions ts,tsx", 13 | "mocha": "run-bin TS_NODE_FILES=true mocha -r ts-node/register", 14 | "pack": "npm pack" 15 | }, 16 | "author": "Russell Briggs ", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/RevJS/revjs.git" 21 | }, 22 | "typings": "lib/index.d.ts", 23 | "dependencies": { 24 | "graphql": "0.x", 25 | "graphql-type-json": "0.x", 26 | "rev-models": "^0.21.1", 27 | "tslib": "1.x" 28 | }, 29 | "devDependencies": { 30 | "run-bin": "1.x" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/examples/src/using_related_models/creating_related_data.ts: -------------------------------------------------------------------------------- 1 | 2 | import { User, Post, Comment, modelManager } from './creating_related_models'; 3 | 4 | export async function createData() { 5 | 6 | // Create Users 7 | 8 | const joe = (await modelManager.create(new User({ 9 | username: 'joe123' 10 | }))).result; 11 | 12 | const bill = (await modelManager.create(new User({ 13 | username: 'bill27' 14 | }))).result; 15 | 16 | const jane = (await modelManager.create(new User({ 17 | username: 'jane12' 18 | }))).result; 19 | 20 | // Create Posts 21 | 22 | const post1 = (await modelManager.create(new Post({ 23 | title: 'Related Data in RevJS', 24 | body: 'Pretty easy to do eh?', 25 | user: jane 26 | }))).result; 27 | 28 | await modelManager.create(new Post({ 29 | title: 'The Rain in Spain', 30 | body: 'Really is a pain?', 31 | user: jane 32 | })); 33 | 34 | // Create Comments 35 | 36 | await modelManager.create(new Comment({ 37 | comment: 'True!', 38 | post: post1, 39 | user: bill 40 | })); 41 | 42 | await modelManager.create(new Comment({ 43 | comment: 'Hmmm, but I reeeeaally like writing SQL...', 44 | post: post1, 45 | user: joe 46 | })); 47 | 48 | } -------------------------------------------------------------------------------- /packages/rev-ui/src/provider/ModelProvider.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import * as PropTypes from 'prop-types'; 4 | 5 | import { IModelManager } from 'rev-models'; 6 | 7 | /** 8 | * The `` component provides access to a shared RevJS 9 | * **ModelManager** for child components. Components such as 10 | * `` and `` must be nested inside a 11 | * `` component. 12 | * 13 | * You can give your own components access to the specified modelManager 14 | * using the [[withModelManager]] higher order component, which will 15 | * provide the component with a `modelManager` prop. 16 | */ 17 | export interface IModelProviderProps { 18 | modelManager: IModelManager; 19 | } 20 | 21 | /** 22 | * @private 23 | */ 24 | export interface IModelProviderContext { 25 | modelManager: IModelManager; 26 | } 27 | 28 | /** 29 | * @private 30 | */ 31 | export class ModelProvider extends React.Component { 32 | 33 | static childContextTypes = { 34 | modelManager: PropTypes.object 35 | }; 36 | 37 | getChildContext(): IModelProviderContext { 38 | return { 39 | modelManager: this.props.modelManager 40 | }; 41 | } 42 | 43 | render() { 44 | return this.props.children; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /packages/rev-models/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Object.assign */ 3 | if (typeof Object.assign != 'function') { 4 | (function() { 5 | Object.assign = function(target: any) { 6 | 'use strict'; 7 | if (target === undefined || target === null) { 8 | throw new TypeError('Cannot convert undefined or null to object'); 9 | } 10 | 11 | let output = Object(target); 12 | for (let index = 1; index < arguments.length; index++) { 13 | let source = arguments[index]; 14 | if (source !== undefined && source !== null) { 15 | for (let nextKey in source) { 16 | if (source.hasOwnProperty(nextKey)) { 17 | output[nextKey] = source[nextKey]; 18 | } 19 | } 20 | } 21 | } 22 | return output; 23 | }; 24 | })(); 25 | } 26 | 27 | /* Function.name */ 28 | if (!(function f() {}).name) { 29 | Object.defineProperty(Function.prototype, 'name', { 30 | get: function() { 31 | let name = this.toString().match(/^function\s*([^\s(]+)/)![1]; 32 | // For better performance only parse once, and then cache the 33 | // result through a new accessor for repeated access. 34 | Object.defineProperty(this, 'name', { value: name }); 35 | return name; 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /docs/mkdocs/src/components/rev-models.md: -------------------------------------------------------------------------------- 1 | 2 | # rev-models - RevJS Data Models 3 | 4 | The `rev-models` module provides the following: 5 | 6 | * A set of **Built-in Field Types** for defining your 7 | data models 8 | * A **ModelManager** object, which holds the list of 9 | your registered models, and provides functions for **create**, **read**, 10 | **update** and **delete**. 11 | * An **in-memory** storage backend, so you can play with RevJS functions without 12 | needing to set up a database. 13 | 14 | *Jump to the [rev-models API Documentation](/api/rev-models)* 15 | 16 | ## Storage Backends 17 | 18 | In addition to in-memory storage, the following backends are available: 19 | 20 | The `rev-backend-mongodb` module provides a **MongoDBBackend** class which stores 21 | and retrieves data from a MongoDB No-SQL database. Jump to the 22 | [rev-backend-mongodb API Documentation](/api/rev-backend-mongodb). 23 | 24 | The `rev-api-client` module provides a **ModelApiBackend** class which stores 25 | and retrieves data from the GraphQL API generated by `rev-api`. 26 | Jump to the [rev-api-client API Documentation](/api/rev-api-client). 27 | 28 | ## Contributing 29 | 30 | We are actively looking to build a team around RevJS. If you are interesting in 31 | contributing, fork us on github or drop us a 32 | [mail](mailto:russ@russellbriggs.co)! 33 | -------------------------------------------------------------------------------- /packages/rev-models/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { IObject } from './types'; 2 | 3 | /** 4 | * @private 5 | */ 6 | export function isSet(value: any): boolean { 7 | return (typeof value != 'undefined' && value !== null); 8 | } 9 | 10 | /** 11 | * @private 12 | */ 13 | export function printObj(value: any, multiline = false): string { 14 | return JSON.stringify(value, null, multiline ? 2 : undefined); 15 | } 16 | 17 | /** 18 | * @private 19 | */ 20 | export function escapeForRegex(str: string) { 21 | if (typeof str != 'string') { 22 | throw new TypeError('Supplied value is not a string!'); 23 | } 24 | return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); 25 | } 26 | 27 | /** 28 | * @private 29 | */ 30 | export function withTimeout(promise: Promise, timeout: number, name: string) { 31 | return Promise.race([ 32 | promise, 33 | new Promise((resolve, reject) => { 34 | setTimeout(() => { 35 | reject(new Error(`${name} - timed out after ${timeout} milliseconds`)); 36 | }, timeout); 37 | }) 38 | ]); 39 | } 40 | 41 | /** 42 | * @private 43 | */ 44 | export function dedupeStringArray(array: string[]) { 45 | const items: IObject = {}; 46 | array.forEach((item) => { 47 | items[item] = true; 48 | }); 49 | return Object.keys(items); 50 | } -------------------------------------------------------------------------------- /packages/rev-api-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rev-api-client", 3 | "version": "0.21.1", 4 | "description": "RevJS Isomorphic Data Library - API Client Library", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "run-bin tslint -p . && rm -rf lib/ && run-bin tsc", 8 | "build-docs": "run-bin typedoc --options ./typedoc.js ./src", 9 | "build-watch": "run-bin tsc --watch", 10 | "test": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/*\"", 11 | "test-ci": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/*\" --reporter mocha-circleci-reporter", 12 | "test-watch": "run-bin TS_NODE_FILES=true mocha -r ts-node/register --recursive \"./src/**/__tests__/*\" --watch --watch-extensions ts,tsx", 13 | "mocha": "run-bin TS_NODE_FILES=true mocha -r ts-node/register", 14 | "pack": "npm pack" 15 | }, 16 | "author": "Russell Briggs ", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/RevJS/revjs.git" 21 | }, 22 | "typings": "lib/index.d.ts", 23 | "dependencies": { 24 | "axios": "0.x", 25 | "graphql": "0.x", 26 | "json-to-graphql-query": "1.x", 27 | "rev-models": "^0.21.1", 28 | "tslib": "1.x" 29 | }, 30 | "devDependencies": { 31 | "rev-api": "^0.21.1", 32 | "run-bin": "1.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/rev-models/src/queries/types.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModel, IModelManager } from '../models/types'; 3 | 4 | /** 5 | * @private 6 | */ 7 | export interface IQueryNode { 8 | parser: IQueryParser; 9 | model: new() => T; 10 | operator: string; 11 | parent?: IQueryNode; 12 | children: Array>; 13 | } 14 | 15 | /** 16 | * @private 17 | */ 18 | export interface IOperatorRegister { 19 | [operator: string]: new( 20 | parser: IQueryParser, 21 | model: new() => IModel, 22 | operator: string, 23 | value: any, 24 | parent?: IQueryNode) => IQueryNode; 25 | } 26 | 27 | /** 28 | * @private 29 | */ 30 | export interface IQueryParser { 31 | manager: IModelManager; 32 | OPERATOR_PREFIX: string; 33 | CONJUNCTION_OPERATORS: IOperatorRegister; 34 | FIELD_OPERATORS: IOperatorRegister; 35 | 36 | registerFieldOperators(operators: IOperatorRegister): void; 37 | registerConjunctionOperators(operators: IOperatorRegister): void; 38 | 39 | isFieldOperator(prefixedOperatorName: string): boolean; 40 | isConjunctionOperator(prefixedOperatorName: string): boolean; 41 | getUnprefixedOperatorName(prefixedOperatorName: string): string; 42 | 43 | getQueryNodeForQuery( 44 | model: new() => T, 45 | value: any, 46 | parent?: IQueryNode): IQueryNode; 47 | } 48 | -------------------------------------------------------------------------------- /packages/rev-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rev-ui", 3 | "version": "0.21.1", 4 | "description": "React UI base components for RevJS", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "run-bin tslint -p . && rm -rf lib/ && run-bin tsc", 8 | "build-docs": "run-bin typedoc --options ./typedoc.js ./src", 9 | "build-watch": "run-bin tsc --watch", 10 | "test": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register --recursive \"./src/**/__tests__/**/*.ts{,x}\"", 11 | "test-ci": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register --recursive \"./src/**/__tests__/**/*.ts{,x}\" --reporter mocha-circleci-reporter", 12 | "test-watch": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register --recursive \"./src/**/__tests__/**/*.ts{,x}\" --watch --watch-extensions ts,tsx", 13 | "mocha": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register", 14 | "pack": "npm pack" 15 | }, 16 | "author": "Russell Briggs ", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/RevJS/revjs.git" 21 | }, 22 | "typings": "lib/index.d.ts", 23 | "dependencies": { 24 | "@types/prop-types": "15.x", 25 | "@types/react": "16.x", 26 | "prop-types": "15.x", 27 | "react": "16.x", 28 | "rev-models": "^0.21.1", 29 | "tslib": "1.x" 30 | }, 31 | "devDependencies": { 32 | "run-bin": "1.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/rev-ui/src/provider/__tests__/ModelProvider.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import * as PropTypes from 'prop-types'; 4 | import { expect } from 'chai'; 5 | import { ModelManager } from 'rev-models'; 6 | import { mount, ReactWrapper } from 'enzyme'; 7 | import { ModelProvider } from '../ModelProvider'; 8 | 9 | describe('ModelProvider', () => { 10 | 11 | let modelManager: ModelManager; 12 | let passedModelManager: ModelManager; 13 | let wrapper: ReactWrapper; 14 | 15 | class SpyComponent extends React.Component { 16 | static contextTypes = { 17 | modelManager: PropTypes.object 18 | }; 19 | constructor(props: any, context: any) { 20 | super(props, context); 21 | passedModelManager = this.context.modelManager; 22 | } 23 | render() { 24 | return

    SpyComponent

    ; 25 | } 26 | } 27 | 28 | before(() => { 29 | modelManager = new ModelManager(); 30 | wrapper = mount( 31 | 32 | 33 | 34 | ); 35 | }); 36 | 37 | it('passes down the specified ModelManager via React Context', () => { 38 | expect(passedModelManager).to.equal(modelManager); 39 | }); 40 | 41 | it('renders children directly', () => { 42 | expect(wrapper.at(0).text()).to.equal('SpyComponent'); 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /docs/mkdocs/src/creating_a_ui/simple_list.md: -------------------------------------------------------------------------------- 1 | 2 | # Rendering a Simple ListView 3 | 4 | By default, a [<ListView />](/api/rev-ui/interfaces/ilistviewprops.html) 5 | will render a table, as shown in the screenshot below: 6 | 7 | ![RevJS Simple ListView](../img/ui-simple-list.png) 8 | 9 | The data starts loading as soon as the ListView is rendered. A Loading 10 | indicator is shown until the data has finished loading. Once the data is loaded, 11 | the user can use the forward and back buttons to page through the data. 12 | 13 | ## JSX for a Simple List 14 | 15 | The following JSX code will render the list shown above: 16 | 17 | ```jsx 18 | 19 | 33 | 34 | ``` 35 | 36 | *(Complete working example 37 | [here](https://github.com/RevJS/revjs/blob/master/packages/examples/src/creating_a_ui/simple_list/simple_list.tsx)).* 38 | 39 | You can use the `fields` prop of the ListView to select which columns to 40 | show, and the `where`, `orderBy` and `limit` props to control the data shown. 41 | 42 | In the next example, we'll look at how to render your own list view components. -------------------------------------------------------------------------------- /packages/examples/src/using_backends/using_a_mongodb_backend.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, IntegerField, 4 | ModelManager 5 | } from 'rev-models'; 6 | 7 | import { MongoDBBackend } from 'rev-backend-mongodb'; 8 | 9 | class TestModel { 10 | @AutoNumberField({ primaryKey: true }) 11 | item_number: number; 12 | @TextField() 13 | name: string; 14 | @TextField({ required: false }) 15 | description: string; 16 | @IntegerField() 17 | score: number; 18 | 19 | constructor(data: Partial) { 20 | Object.assign(this, data); 21 | } 22 | } 23 | 24 | (async () => { 25 | 26 | // Create a MongoDBBackend and connect to MongoDB 27 | 28 | const mongo = new MongoDBBackend({ 29 | url: 'mongodb://localhost:27017', 30 | dbName: 'example_db' 31 | }); 32 | await mongo.connect(); 33 | 34 | // Create a ModelManager, and assign MongoDB as the default backend 35 | 36 | const modelManager = new ModelManager(); 37 | modelManager.registerBackend('default', mongo); 38 | modelManager.register(TestModel); 39 | 40 | // Create some data, then disconnect afterwards 41 | 42 | await modelManager.create(new TestModel({ 43 | name: 'data from RevJS!', 44 | description: 'This beautiful record was created by RevJS', 45 | score: 110 46 | })); 47 | console.log('Data successfully created in MongoDB!'); 48 | 49 | await mongo.disconnect(); 50 | 51 | })(); -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/searching_and_paging.ts: -------------------------------------------------------------------------------- 1 | import { City, modelManager } from './creating_models'; 2 | 3 | (async () => { 4 | 5 | await modelManager.create(new City({ name: 'Wellington' })); 6 | await modelManager.create(new City({ name: 'Auckland' })); 7 | await modelManager.create(new City({ name: 'Hamilton' })); 8 | await modelManager.create(new City({ name: 'Christchurch' })); 9 | 10 | // No filter 11 | const allRecords = await modelManager.read(City); 12 | console.log('All cities:', allRecords.results); 13 | 14 | // Sorting 15 | const sortedRecords = await modelManager.read(City, { orderBy: ['name'] }); 16 | console.log('All cities, sorted by name:', sortedRecords.results); 17 | 18 | // Searching 19 | 20 | const subscribers = await modelManager.read(City, { 21 | where: { 22 | name: { _like: '%ton' } 23 | } 24 | }); 25 | console.log('Cities that end in "ton":', subscribers.results); 26 | 27 | const agedPeople = await modelManager.read(City, { where: { 28 | _or: [ 29 | { name: { _like: '%e%' }}, 30 | { name: { _like: '%a%' }} 31 | ] 32 | }}); 33 | console.log('Cities that have an E or an A in the name:', agedPeople.results); 34 | 35 | // Paging 36 | 37 | const last3Records = await modelManager.read(City, { orderBy: ['name'], offset: 2, limit: 2 }); 38 | console.log('Last 2 records:', last3Records.results); 39 | 40 | })(); 41 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rev-ui-materialui", 3 | "version": "0.21.1", 4 | "description": "React MaterialUI components for RevJS", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "run-bin tslint -p . && rm -rf lib/ && run-bin tsc", 8 | "build-docs": "echo DOCS_TODO", 9 | "build-watch": "run-bin tsc --watch", 10 | "test": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register --recursive \"./src/**/__tests__/*\"", 11 | "test-ci": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register --recursive \"./src/**/__tests__/*\" --reporter mocha-circleci-reporter", 12 | "test-watch": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register --recursive \"./src/**/__tests__/*\" --watch --watch-extensions ts,tsx", 13 | "mocha": "run-bin TS_NODE_FILES=true mocha -r ./testsetup -r ts-node/register", 14 | "pack": "npm pack" 15 | }, 16 | "author": "Russell Briggs ", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/RevJS/revjs.git" 21 | }, 22 | "typings": "lib/index.d.ts", 23 | "dependencies": { 24 | "@material-ui/core": "1.x", 25 | "@material-ui/icons": "1.x", 26 | "prop-types": "15.x", 27 | "react": "16.x", 28 | "react-dom": "16.x", 29 | "rev-models": "^0.21.1", 30 | "rev-ui": "^0.21.1", 31 | "tslib": "1.x" 32 | }, 33 | "devDependencies": { 34 | "run-bin": "1.x" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/examples/src/creating_an_api/model_data.ts: -------------------------------------------------------------------------------- 1 | 2 | import { User, Post, Comment, modelManager } from './defining_an_api'; 3 | 4 | export async function createData() { 5 | 6 | // Create Users 7 | 8 | const joe = (await modelManager.create(new User({ 9 | username: 'joe123' 10 | }))).result; 11 | 12 | const bill = (await modelManager.create(new User({ 13 | username: 'bill27' 14 | }))).result; 15 | 16 | const jane = (await modelManager.create(new User({ 17 | username: 'jane12' 18 | }))).result; 19 | 20 | // Create Posts 21 | 22 | const post1 = (await modelManager.create(new Post({ 23 | title: 'Cool post, served by rev-api', 24 | body: 'Heres some body text for the post', 25 | user: jane 26 | }))).result; 27 | 28 | const post2 = (await modelManager.create(new Post({ 29 | title: 'The Rain in Spain', 30 | body: 'Really is a pain?', 31 | user: jane 32 | }))).result; 33 | 34 | // Create Comments 35 | 36 | await modelManager.create(new Comment({ 37 | comment: 'True!', 38 | post: post1, 39 | user: bill 40 | })); 41 | 42 | await modelManager.create(new Comment({ 43 | comment: 'Hmmm, but I reeeeaally like writing SQL...', 44 | post: post1, 45 | user: joe 46 | })); 47 | 48 | await modelManager.create(new Comment({ 49 | comment: 'no comment', 50 | post: post2, 51 | user: bill 52 | })); 53 | 54 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | * Possible values: 4 | * - the name of a built-in config 5 | * - the name of an NPM module which has a "main" file that exports a config object 6 | * - a relative path to a JSON file 7 | */ 8 | "extends": [ 9 | "tslint:latest" 10 | ], 11 | "rules": { 12 | /* 13 | * Any rules specified here will override those from the base config we are extending 14 | */ 15 | "max-line-length": [false], 16 | "max-classes-per-file": false, 17 | "no-var-requires": false, 18 | "trailing-comma": [false], 19 | "object-literal-sort-keys": false, 20 | "ordered-imports": false, 21 | "no-console": [false], 22 | "no-unused-new": false, 23 | "triple-equals": false, 24 | "one-line": [false, "check-catch", "check-finally", "check-else"], 25 | "only-arrow-functions": [false], 26 | "quotemark": [true, "single", "jsx-double"], 27 | "forin": false, 28 | "variable-name": [false], 29 | "no-empty": false, 30 | "no-empty-interface": false, 31 | "no-implicit-dependencies": false, 32 | "no-string-literal": false, 33 | "no-submodule-imports": false, 34 | "object-literal-shorthand": false, 35 | "member-access": [false], 36 | "prefer-conditional-expression": false, 37 | "prefer-const": false, 38 | "prefer-for-of": false, 39 | "no-unused-expression": [false], 40 | "member-ordering": [false], 41 | "prefer-object-spread": false, 42 | "eofline": false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/rev-api/src/graphql/mutation/methods.ts: -------------------------------------------------------------------------------- 1 | import { IApiMeta } from '../../api/types'; 2 | import { GraphQLFieldConfigMap, GraphQLNonNull, GraphQLString, GraphQLFieldConfigArgumentMap } from 'graphql'; 3 | import * as GraphQLJSON from 'graphql-type-json'; 4 | import { getMethodResolver } from './resolve_method'; 5 | import { IGraphQLApi } from '../types'; 6 | 7 | export function getModelMethodMutations(api: IGraphQLApi, meta: IApiMeta): GraphQLFieldConfigMap { 8 | let fields: GraphQLFieldConfigMap = {}; 9 | 10 | for (let methodName in meta.methods) { 11 | let mutationName = meta.model + '_' + methodName; 12 | let methodMeta = meta.methods[methodName]; 13 | 14 | let argsConfig: GraphQLFieldConfigArgumentMap = {}; 15 | 16 | if (methodMeta.modelData) { 17 | let modelInputType = api.getModelInputObject(meta.model); 18 | argsConfig['model'] = { 19 | type: new GraphQLNonNull(modelInputType) 20 | }; 21 | } 22 | 23 | if (methodMeta.args) { 24 | for (let arg of methodMeta.args) { 25 | argsConfig[arg.name] = { 26 | type: new GraphQLNonNull(GraphQLString) 27 | }; 28 | } 29 | } 30 | 31 | fields[mutationName] = { 32 | type: GraphQLJSON, 33 | args: argsConfig, 34 | resolve: getMethodResolver(api.getApiManager(), meta.model, methodName) 35 | }; 36 | } 37 | 38 | return fields; 39 | } -------------------------------------------------------------------------------- /packages/rev-ui/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for node debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Unit Tests", 9 | "type": "node2", 10 | "request": "launch", 11 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 12 | "args": ["--require", "./tests-setup.js", "--recursive", "src/**/__tests__/*.ts"], 13 | "cwd": "${workspaceRoot}", 14 | "outFiles": [], 15 | "sourceMaps": true 16 | }, 17 | { 18 | "name": "Debug Current Unit Test", 19 | "type": "node2", 20 | "request": "launch", 21 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 22 | "args": ["--require", "./tests-setup.js", "${relativeFile}"], 23 | "cwd": "${workspaceRoot}", 24 | "outFiles": [], 25 | "sourceMaps": true 26 | }, 27 | { 28 | "name": "Run Current File", 29 | "type": "node2", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/node_modules/.bin/ts-node", 32 | "args": ["${relativeFile}"], 33 | "cwd": "${workspaceRoot}", 34 | "outFiles": [], 35 | "sourceMaps": true 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /packages/rev-api/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for node debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Unit Tests", 9 | "type": "node2", 10 | "request": "launch", 11 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 12 | "args": ["--require", "./tests-setup.js", "--recursive", "src/**/__tests__/*.ts"], 13 | "cwd": "${workspaceRoot}", 14 | "outFiles": [], 15 | "sourceMaps": true 16 | }, 17 | { 18 | "name": "Debug Current Unit Test", 19 | "type": "node2", 20 | "request": "launch", 21 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 22 | "args": ["--require", "./tests-setup.js", "${relativeFile}"], 23 | "cwd": "${workspaceRoot}", 24 | "outFiles": [], 25 | "sourceMaps": true 26 | }, 27 | { 28 | "name": "Run Current File", 29 | "type": "node2", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/node_modules/.bin/ts-node", 32 | "args": ["${relativeFile}"], 33 | "cwd": "${workspaceRoot}", 34 | "outFiles": [], 35 | "sourceMaps": true 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /packages/rev-api-client/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for node debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Unit Tests", 9 | "type": "node2", 10 | "request": "launch", 11 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 12 | "args": ["--require", "./tests-setup.js", "--recursive", "src/**/__tests__/*.ts"], 13 | "cwd": "${workspaceRoot}", 14 | "outFiles": [], 15 | "sourceMaps": true 16 | }, 17 | { 18 | "name": "Debug Current Unit Test", 19 | "type": "node2", 20 | "request": "launch", 21 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 22 | "args": ["--require", "./tests-setup.js", "${relativeFile}"], 23 | "cwd": "${workspaceRoot}", 24 | "outFiles": [], 25 | "sourceMaps": true 26 | }, 27 | { 28 | "name": "Run Current File", 29 | "type": "node2", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/node_modules/.bin/ts-node", 32 | "args": ["${relativeFile}"], 33 | "cwd": "${workspaceRoot}", 34 | "outFiles": [], 35 | "sourceMaps": true 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /packages/rev-models/src/decorators/__tests__/extends.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import * as d from '../fields'; 4 | 5 | describe('rev.decorators.fields - extended model tests', () => { 6 | 7 | function expectToHaveFields(target: any, fieldNames: string[]) { 8 | expect(target.prototype.__fields).to.be.an('Array'); 9 | expect(target.prototype.__fields).to.have.length(fieldNames.length); 10 | fieldNames.forEach((name, idx) => { 11 | expect(target.prototype.__fields[idx].name).to.equal(name); 12 | }); 13 | } 14 | 15 | class BaseModel { 16 | @d.AutoNumberField() 17 | baseId: number; 18 | @d.TextField() 19 | baseName: string; 20 | } 21 | 22 | class ExtendedModel1 extends BaseModel { 23 | @d.TextField() 24 | firstExtendedField: string; 25 | } 26 | 27 | class ExtendedModel2 extends BaseModel { 28 | @d.TextField() 29 | secondExtendedField: string; 30 | } 31 | 32 | it('BaseModel has correct fields', () => { 33 | expectToHaveFields(BaseModel, [ 34 | 'baseId', 'baseName' 35 | ]); 36 | }); 37 | 38 | it('ExtendedModel1 has correct fields', () => { 39 | expectToHaveFields(ExtendedModel1, [ 40 | 'baseId', 'baseName', 'firstExtendedField' 41 | ]); 42 | }); 43 | 44 | it('ExtendedModel2 has correct fields', () => { 45 | expectToHaveFields(ExtendedModel2, [ 46 | 'baseId', 'baseName', 'secondExtendedField' 47 | ]); 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /packages/rev-api/src/decorators/decorators.ts: -------------------------------------------------------------------------------- 1 | import { IApiMethodMeta } from '../api/types'; 2 | import { STANDARD_OPERATIONS } from 'rev-models'; 3 | 4 | // ApiOperations class decorator 5 | 6 | export function ApiOperations(operations: string[]) { 7 | 8 | if (!(operations instanceof Array)) { 9 | throw new Error('ApiOperations decorator must be passed an array of allowed operations.'); 10 | } 11 | else { 12 | operations.forEach((op) => { 13 | if (STANDARD_OPERATIONS.indexOf(op) == -1) { 14 | throw new Error(`ApiOperations decorator error: '${op}' is not a supported operation.`); 15 | } 16 | }); 17 | } 18 | 19 | return function(target: any) { 20 | Object.defineProperty(target.prototype, '__apiOperations', { 21 | enumerable: false, value: operations 22 | }); 23 | }; 24 | } 25 | 26 | // ApiMethod method decorator 27 | 28 | function addApiMethod(target: any, methodName: string, meta?: IApiMethodMeta) { 29 | 30 | if (typeof target[methodName] != 'function') { 31 | throw new Error(`ApiMethod decorator error: '${methodName}' is not a function.`); 32 | } 33 | 34 | if (!target.__apiMethods) { 35 | Object.defineProperty(target, '__apiMethods', { 36 | enumerable: false, value: {} 37 | }); 38 | } 39 | target.__apiMethods[methodName] = meta || {}; 40 | } 41 | 42 | export function ApiMethod(meta?: IApiMethodMeta) 43 | { 44 | return function(target: any, propName: string) { 45 | addApiMethod(target, propName, meta); 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/views/__tests__/MUIDetailView.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import { expect } from 'chai'; 4 | import { mount, ReactWrapper } from 'enzyme'; 5 | 6 | import { MUIDetailView } from '../MUIDetailView'; 7 | import { IDetailViewProps } from 'rev-ui/lib/views/DetailView'; 8 | import Grid from '@material-ui/core/Grid'; 9 | 10 | describe('MUIDetailView', () => { 11 | 12 | let props: IDetailViewProps; 13 | let wrapper: ReactWrapper; 14 | 15 | function getComponentProps(): IDetailViewProps { 16 | return { 17 | model: 'Post', 18 | style: {marginTop: 10} 19 | }; 20 | } 21 | 22 | function MockComponent() { 23 | return MOCK!; 24 | } 25 | 26 | before(() => { 27 | props = getComponentProps(); 28 | wrapper = mount( 29 | 30 | 31 | 32 | ); 33 | }); 34 | 35 | it('renders a grid with properties as expected', () => { 36 | const grid = wrapper.find(Grid); 37 | expect(grid).to.have.length(1); 38 | expect(grid.prop('container')).to.be.true; 39 | }); 40 | 41 | it('grid contains the wrapped component', () => { 42 | const grid = wrapper.find(Grid); 43 | expect(grid.find(MockComponent)).to.have.length(1); 44 | }); 45 | 46 | it('style is applied to outer form', () => { 47 | const outerDiv = wrapper.find('form'); 48 | expect(outerDiv.prop('style')).to.deep.equal({marginTop: 10}); 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/views/__tests__/MUISearchView.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import { expect } from 'chai'; 4 | import { mount, ReactWrapper } from 'enzyme'; 5 | 6 | import { MUISearchView } from '../MUISearchView'; 7 | import { ISearchViewProps } from 'rev-ui/lib/views/SearchView'; 8 | import Grid from '@material-ui/core/Grid'; 9 | 10 | describe('MUISearchView', () => { 11 | 12 | let props: ISearchViewProps; 13 | let wrapper: ReactWrapper; 14 | 15 | function getComponentProps(): ISearchViewProps { 16 | return { 17 | model: 'Post', 18 | onSearch: () => null, 19 | style: {marginTop: 10} 20 | }; 21 | } 22 | 23 | function MockComponent() { 24 | return MOCK!; 25 | } 26 | 27 | before(() => { 28 | props = getComponentProps(); 29 | wrapper = mount( 30 | 31 | 32 | 33 | ); 34 | }); 35 | 36 | it('renders a grid with properties as expected', () => { 37 | const grid = wrapper.find(Grid); 38 | expect(grid).to.have.length(1); 39 | expect(grid.prop('container')).to.be.true; 40 | }); 41 | 42 | it('grid contains the wrapped component', () => { 43 | const grid = wrapper.find(Grid); 44 | expect(grid.find(MockComponent)).to.have.length(1); 45 | }); 46 | 47 | it('style is applied to outer div', () => { 48 | const outerDiv = wrapper.find('div').at(0); 49 | expect(outerDiv.prop('style')).to.deep.equal({marginTop: 10}); 50 | }); 51 | 52 | }); -------------------------------------------------------------------------------- /packages/rev-models/src/operations/remove.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModel, IModelManager, IRemoveOptions, IRemoveMeta } from '../models/types'; 3 | import { ModelOperationResult } from './operationresult'; 4 | import { IModelOperation } from './operation'; 5 | import { getModelPrimaryKeyQuery } from './utils'; 6 | import { IRemoveParams } from '../backends/backend'; 7 | 8 | /** 9 | * @private 10 | */ 11 | export const DEFAULT_REMOVE_OPTIONS: IRemoveOptions = {}; 12 | 13 | /** 14 | * @private 15 | * Documentation in ModelManager class 16 | */ 17 | export async function remove(manager: IModelManager, model: T, options?: IRemoveOptions): Promise> { 18 | 19 | let meta = manager.getModelMeta(model); 20 | if (!meta.stored) { 21 | throw new Error('Cannot call remove() on models with stored: false'); 22 | } 23 | 24 | let backend = manager.getBackend(meta.backend); 25 | let opts = Object.assign({}, DEFAULT_REMOVE_OPTIONS, options) as IRemoveParams; 26 | 27 | if (!opts.where || typeof opts.where != 'object') { 28 | if (!meta.primaryKey || meta.primaryKey.length == 0) { 29 | throw new Error('remove() must be called with a where clause for models with no primaryKey'); 30 | } 31 | else { 32 | opts.where = getModelPrimaryKeyQuery(model, meta); 33 | } 34 | } 35 | 36 | let operation: IModelOperation = { 37 | operationName: 'remove', 38 | where: opts.where 39 | }; 40 | let operationResult = new ModelOperationResult(operation); 41 | return backend.remove(manager, model, opts, operationResult); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revjs", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "postinstall": "lerna bootstrap", 6 | "build": "lerna run build", 7 | "build-docs": "npm run build-mkdocs && npm run build-typedocs", 8 | "build-mkdocs": "cd docs/mkdocs && docker-compose up && cp -R dist ../", 9 | "build-typedocs": "lerna run build-docs", 10 | "test": "lerna run test", 11 | "test-ci": "lerna run test-ci", 12 | "pack": "lerna run pack", 13 | "updated": "lerna updated", 14 | "publish": "npm run build && npm run test && lerna publish", 15 | "publish-nobuild": "lerna publish", 16 | "clean-modules": "lerna clean" 17 | }, 18 | "devDependencies": { 19 | "@babel/runtime": "7.0.0-beta.55", 20 | "@types/chai": "4.1.4", 21 | "@types/enzyme": "3.1.12", 22 | "@types/graphql": "^0.13.4", 23 | "@types/koa": "2.0.44", 24 | "@types/koa-bodyparser": "4.2.0", 25 | "@types/koa-router": "7.0.27", 26 | "@types/mocha": "5.2.5", 27 | "@types/prop-types": "15.x", 28 | "@types/react": "16.x", 29 | "@types/react-dom": "16.x", 30 | "@types/rewire": "2.5.28", 31 | "@types/sinon": "5.0.1", 32 | "chai": "4.1.2", 33 | "enzyme": "3.3.0", 34 | "enzyme-adapter-react-16": "1.1.1", 35 | "jsdom": "11.12.0", 36 | "lerna": "3.0.0-rc.0", 37 | "mocha": "5.2.0", 38 | "mocha-circleci-reporter": "0.0.3", 39 | "react": "16.x", 40 | "react-dom": "16.x", 41 | "rewire": "4.0.1", 42 | "sinon": "6.1.4", 43 | "ts-loader": "4.4.2", 44 | "ts-node": "7.0.0", 45 | "tslint": "5.11.0", 46 | "typedoc": "0.11.1", 47 | "typescript": "3.0.1", 48 | "webpack": "4.16.4", 49 | "webpack-cli": "3.1.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/rev-api-client/src/client/__test_utils__/mockHttpClient.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ModelApiManager, GraphQLApi } from 'rev-api'; 3 | import { graphql } from 'graphql'; 4 | import { ModelManager } from 'rev-models'; 5 | import { AxiosRequestConfig, AxiosResponse } from 'axios'; 6 | import { ExecutionResult } from 'graphql/execution/execute'; 7 | 8 | /** 9 | * Returns a mock axios function with a fixed http response 10 | * @param mockResponse 11 | */ 12 | export function getMockHttpClient(mockResponse: AxiosResponse) { 13 | return async (config: AxiosRequestConfig): Promise> => { 14 | return mockResponse; 15 | }; 16 | } 17 | 18 | /** 19 | * Returns a mock axios function which makes actual graphql queries against 20 | * any models defined in the passed modelManager 21 | * @param modelManager 22 | */ 23 | export function getMockApiHttpClient(modelManager: ModelManager) { 24 | 25 | const apiManager = new ModelApiManager(modelManager); 26 | 27 | const modelNames = modelManager.getModelNames(); 28 | modelNames.forEach((modelName) => { 29 | const meta = modelManager.getModelMeta(modelName); 30 | apiManager.register(meta.ctor, { operations: ['create', 'read', 'update', 'remove']}); 31 | }); 32 | 33 | const api = new GraphQLApi(apiManager); 34 | const schema = api.getSchema(); 35 | 36 | return async (config: AxiosRequestConfig): Promise> => { 37 | const queryResult = await graphql(schema, config.data.query); 38 | return { 39 | data: queryResult, 40 | status: 200, 41 | statusText: '', 42 | headers: {}, 43 | config: {} 44 | }; 45 | }; 46 | } -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/__tests__/value.tests.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import * as d from '../../../decorators'; 4 | import { QueryParser } from '../../queryparser'; 5 | import { ValueOperator } from '../value'; 6 | import { ModelManager } from '../../../models/manager'; 7 | import { InMemoryBackend } from '../../../backends/inmemory/backend'; 8 | 9 | class TestModel { 10 | @d.IntegerField() 11 | id: number; 12 | @d.TextField() 13 | name: string; 14 | @d.BooleanField() 15 | active: boolean; 16 | } 17 | 18 | let manager = new ModelManager(); 19 | manager.registerBackend('default', new InMemoryBackend()); 20 | manager.register(TestModel); 21 | let parser = new QueryParser(manager); 22 | 23 | describe('class ValueOperator - constructor', () => { 24 | 25 | it('throws if operator is not a field operator', () => { 26 | expect(() => { 27 | new ValueOperator(parser, TestModel, '_and', [], undefined); 28 | }).to.throw('unrecognised field operator'); 29 | }); 30 | 31 | it('throws if value is not a valid field value', () => { 32 | expect(() => { 33 | new ValueOperator(parser, TestModel, '_eq', undefined, undefined); 34 | }).to.throw('invalid field value'); 35 | expect(() => { 36 | new ValueOperator(parser, TestModel, '_eq', {}, undefined); 37 | }).to.throw('invalid field value'); 38 | }); 39 | 40 | it('stores the operator and value as public properties', () => { 41 | let node = new ValueOperator(parser, TestModel, '_eq', 12, undefined); 42 | expect(node.operator).to.equal('eq'); 43 | expect(node.value).to.equal(12); 44 | }); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /packages/examples/src/defining_and_using_models/creating_and_reading_a_model.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, SelectField, 4 | ModelManager, InMemoryBackend 5 | } from 'rev-models'; 6 | 7 | // Define model 8 | 9 | const POST_STATUS = [ 10 | ['draft', 'Draft'], 11 | ['published', 'Published'] 12 | ]; 13 | 14 | export class Post { 15 | @AutoNumberField({ primaryKey: true }) 16 | id: number; 17 | @TextField({ minLength: 5, maxLength: 100 }) 18 | title: string; 19 | @TextField({ multiLine: true }) 20 | body: string; 21 | @SelectField({ selection: POST_STATUS }) 22 | status: string; 23 | 24 | constructor(data?: Partial) { 25 | Object.assign(this, data); 26 | } 27 | } 28 | 29 | // Create ModelManager 30 | 31 | export const modelManager = new ModelManager(); 32 | modelManager.registerBackend('default', new InMemoryBackend()); 33 | modelManager.register(Post); 34 | 35 | (async () => { 36 | 37 | // Create some data 38 | 39 | await modelManager.create(new Post({ 40 | title: 'My First Post', 41 | body: 'This is a really cool post made in RevJS', 42 | status: 'draft' 43 | })); 44 | 45 | await modelManager.create(new Post({ 46 | title: 'RevJS is awesome!', 47 | body: 'I should use it for ALL TEH THINGZZZ!', 48 | status: 'published' 49 | })); 50 | 51 | // Read it back 52 | 53 | const res = await modelManager.read(Post, { 54 | where: { 55 | _or: [ 56 | { title: { _like: '%RevJS%' }}, 57 | { body: { _like: '%RevJS%' }} 58 | ] 59 | } 60 | }); 61 | 62 | console.log(res.results); 63 | 64 | })(); 65 | -------------------------------------------------------------------------------- /packages/rev-models/src/fields/datetimefields.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Field, IFieldOptions } from './field'; 3 | import * as validators from '../validation/validators'; 4 | 5 | /** 6 | * A **DateField**, as it's name suggests, holds just a Date value (no time 7 | * value is stored). 8 | * 9 | * **Accepted Values:** a *string* in the format `YYYY-MM-DD` 10 | */ 11 | export class DateField extends Field { 12 | constructor(name: string, options?: IFieldOptions) { 13 | super(name, options); 14 | this.validators.push(validators.dateOnlyValidator); 15 | } 16 | } 17 | 18 | /** 19 | * A **TimeField**, as it's name suggests, holds just a Time value (no date 20 | * value is stored). 21 | * 22 | * **Accepted Values:** a *string* in the format `HH:MM:SS` 23 | * 24 | * **Note:** You will currently need to store any timezone information in a 25 | * seperate field. Timezone support is a candidate for a future release! 26 | */ 27 | export class TimeField extends Field { 28 | constructor(name: string, options?: IFieldOptions) { 29 | super(name, options); 30 | this.validators.push(validators.timeOnlyValidator); 31 | } 32 | } 33 | 34 | /** 35 | * A **DateTimeField** stores a full Date and Time value 36 | * 37 | * **Accepted Values:** a *string* in the format `YYYY-MM-DDTHH:MM:SS` (e.g. 38 | * `2018-02-26T08:44:12`) 39 | * 40 | * **Note:** You will currently need to store any timezone information in a 41 | * seperate field. Timezone support is a candidate for a future release! 42 | */ 43 | export class DateTimeField extends Field { 44 | constructor(name: string, options?: IFieldOptions) { 45 | super(name, options); 46 | this.validators.push(validators.dateTimeValidator); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/examples/src/using_related_models/creating_related_models.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, RelatedModel, RelatedModelList, 4 | ModelManager, InMemoryBackend 5 | } from 'rev-models'; 6 | 7 | export class User { 8 | @AutoNumberField({ primaryKey: true }) 9 | id: number; 10 | @TextField() 11 | username: string; 12 | @RelatedModelList({ model: 'Post', field: 'user' }) 13 | posts: Post[]; 14 | @RelatedModelList({ model: 'Comment', field: 'user' }) 15 | comments: Comment[]; 16 | 17 | constructor(data?: Partial) { 18 | Object.assign(this, data); 19 | } 20 | } 21 | 22 | export class Post { 23 | @AutoNumberField({ primaryKey: true }) 24 | id: number; 25 | @RelatedModel({ model: 'User' }) 26 | user: User; 27 | @TextField() 28 | title: string; 29 | @TextField({ multiLine: true }) 30 | body: string; 31 | @RelatedModelList({ model: 'Comment', field: 'post' }) 32 | comments: Comment[]; 33 | 34 | constructor(data?: Partial) { 35 | Object.assign(this, data); 36 | } 37 | } 38 | 39 | export class Comment { 40 | @AutoNumberField({ primaryKey: true }) 41 | id: number; 42 | @RelatedModel({ model: 'Post' }) 43 | post: Post; 44 | @RelatedModel({ model: 'User', required: false }) 45 | user: User; 46 | @TextField() 47 | comment: string; 48 | 49 | constructor(data?: Partial) { 50 | Object.assign(this, data); 51 | } 52 | } 53 | 54 | export const modelManager = new ModelManager(); 55 | modelManager.registerBackend('default', new InMemoryBackend()); 56 | modelManager.register(User); 57 | modelManager.register(Post); 58 | modelManager.register(Comment); 59 | -------------------------------------------------------------------------------- /packages/rev-backend-mongodb/src/__tests__/backend.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { MongoDBBackend } from '../backend'; 4 | import { testConfig } from './testconfig'; 5 | 6 | import { standardBackendTests, IBackendTestConfig } from 'rev-models/lib/backends/testsuite'; 7 | 8 | describe('MongoDBBackend', () => { 9 | let backend: MongoDBBackend; 10 | 11 | describe('Connecting to MongoDB', () => { 12 | 13 | afterEach(() => { 14 | backend.disconnect(); 15 | }); 16 | 17 | it('can connect to existing mongodb server', async () => { 18 | backend = new MongoDBBackend(testConfig); 19 | await backend.connect(); 20 | }); 21 | 22 | it('throws an error when connection fails', async () => { 23 | backend = new MongoDBBackend({ 24 | url: 'mongodb://localhost:12345', 25 | dbName: 'RevJS' 26 | }); 27 | await backend.connect() 28 | .then(() => { 29 | throw new Error('expected to throw'); 30 | }) 31 | .catch((e) => { 32 | expect(e.message).to.contain('failed to connect to server'); 33 | }); 34 | }); 35 | 36 | }); 37 | 38 | }); 39 | 40 | describe('MongoDBBackend - RevJS Backend Tests', () => { 41 | const config: IBackendTestConfig = {} as any; 42 | let backend: MongoDBBackend; 43 | 44 | before(async () => { 45 | backend = new MongoDBBackend(testConfig); 46 | await backend.connect(); 47 | config.backend = backend; 48 | }); 49 | 50 | standardBackendTests('MongoDBBackend', config); 51 | 52 | after(() => { 53 | backend.disconnect(); 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/field.ts: -------------------------------------------------------------------------------- 1 | 2 | import { QueryNode } from './query'; 3 | import { IQueryParser, IQueryNode } from '../types'; 4 | import { isFieldValue } from '../utils'; 5 | import { ValueOperator } from './value'; 6 | import { IModel } from '../../models/types'; 7 | 8 | /** 9 | * @private 10 | */ 11 | export class FieldNode extends QueryNode { 12 | 13 | constructor( 14 | parser: IQueryParser, 15 | model: new() => T, 16 | public fieldName: string, 17 | value: any, 18 | parent?: IQueryNode) { 19 | 20 | super(parser, model, fieldName, parent); 21 | 22 | const meta = parser.manager.getModelMeta(model); 23 | 24 | if (!(fieldName in meta.fieldsByName)) { 25 | throw new Error(`'${fieldName}' is not a recognised field`); 26 | } 27 | else if (isFieldValue(value)) { 28 | this.children.push(new ValueOperator(parser, model, '_eq', value, this)); 29 | } 30 | else if (!value || typeof value != 'object' || Object.keys(value).length == 0) { 31 | throw new Error(`invalid field query value for field '${fieldName}'`); 32 | } 33 | else { 34 | let keys = Object.keys(value); 35 | for (let key of keys) { 36 | if (!parser.isFieldOperator(key)) { 37 | throw new Error(`unrecognised field operator '${key}'`); 38 | } 39 | else { 40 | const opName = parser.getUnprefixedOperatorName(key); 41 | this.children.push(new parser.FIELD_OPERATORS[opName](parser, model, key, value[key], this)); 42 | } 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /packages/rev-models/src/operations/__tests__/utils.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { getModelPrimaryKeyQuery } from '../utils'; 4 | 5 | class TestModel { 6 | id: number; 7 | id2: number; 8 | name: string; 9 | } 10 | 11 | describe('getModelPrimaryKeyQuery()', () => { 12 | 13 | it('returns a query for a model with one primary key', () => { 14 | let meta: any = { primaryKey: 'id' }; 15 | let model = new TestModel(); 16 | Object.assign(model, { id: 12, name: 'fred' }); 17 | expect(getModelPrimaryKeyQuery(model, meta)) 18 | .to.deep.equal({ id: 12 }); 19 | }); 20 | 21 | it('throws an error if meta.primaryKey is not defined', () => { 22 | let meta: any = {}; 23 | let model = new TestModel(); 24 | Object.assign(model, { id: 12, name: 'fred' }); 25 | expect(() => { 26 | getModelPrimaryKeyQuery(model, meta); 27 | }).to.throw('no primaryKey defined'); 28 | }); 29 | 30 | it('throws an error if meta.primaryKey is an empty string', () => { 31 | let meta: any = { primaryKey: '' }; 32 | let model = new TestModel(); 33 | Object.assign(model, { id: 12, name: 'fred' }); 34 | expect(() => { 35 | getModelPrimaryKeyQuery(model, meta); 36 | }).to.throw('no primaryKey defined'); 37 | }); 38 | 39 | it('throws an error if a primary key field does not have a value', () => { 40 | let meta: any = { primaryKey: 'id2' }; 41 | let model = new TestModel(); 42 | Object.assign(model, { id: 12, name: 'fred' }); 43 | expect(() => { 44 | getModelPrimaryKeyQuery(model, meta); 45 | }).to.throw('primary key field \'id2\' is undefined'); 46 | }); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /packages/rev-ui/src/provider/__tests__/withModelManager.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import { expect } from 'chai'; 4 | import { mount } from 'enzyme'; 5 | import * as models from '../../__fixtures__/models'; 6 | import { ModelManager } from 'rev-models'; 7 | import { ModelProvider } from '../ModelProvider'; 8 | import { withModelManager, IModelManagerProp } from '../withModelManager'; 9 | 10 | interface ISpyComponentProps { 11 | prop1: string; 12 | prop2: string; 13 | } 14 | 15 | describe('withModelManager()', () => { 16 | 17 | let modelManager: ModelManager; 18 | 19 | let receivedProps: ISpyComponentProps & IModelManagerProp; 20 | 21 | class SpyComponentC extends React.Component { 22 | constructor(props: any) { 23 | super(props); 24 | receivedProps = props; 25 | } 26 | render() { 27 | return
    Spy Component
    ; 28 | } 29 | } 30 | 31 | const SpyComponent = withModelManager(SpyComponentC); 32 | 33 | before(() => { 34 | receivedProps = null as any; 35 | modelManager = models.getModelManager(); 36 | mount( 37 | 38 | 39 | 40 | ); 41 | }); 42 | 43 | it('passes down modelManager from context', () => { 44 | expect(receivedProps.modelManager).not.to.be.undefined; 45 | expect(receivedProps.modelManager).to.equal(modelManager); 46 | }); 47 | 48 | it('passes through other props', () => { 49 | expect(receivedProps.prop1).to.equal('prop1'); 50 | expect(receivedProps.prop2).to.equal('prop2'); 51 | }); 52 | 53 | }); -------------------------------------------------------------------------------- /packages/rev-models/src/backends/inmemory/__tests__/testdata.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as d from '../../../decorators'; 3 | 4 | let GENDERS = [ 5 | ['male', 'Male'], 6 | ['female', 'Female'] 7 | ]; 8 | 9 | export class TestModel { 10 | @d.IntegerField({ primaryKey: true }) 11 | id: number; 12 | @d.TextField() 13 | name: string; 14 | @d.IntegerField({ required: false }) 15 | age: number; 16 | @d.SelectField({ required: false, selection: GENDERS }) 17 | gender: string; 18 | @d.BooleanField({ required: false }) 19 | newsletter: boolean; 20 | @d.DateField({ required: false }) 21 | date_registered: Date; 22 | 23 | getDescription?() { 24 | return `${this.name}, age ${this.age}`; 25 | } 26 | } 27 | 28 | export const testData = [ 29 | { 30 | id: 0, 31 | name: 'John Doe', 32 | age: 20, 33 | gender: 'male', 34 | newsletter: true, 35 | date_registered: '2016-05-26' 36 | }, 37 | { 38 | id: 1, 39 | name: 'Jane Doe', 40 | age: 23, 41 | gender: 'female', 42 | newsletter: true, 43 | date_registered: '2017-01-01' 44 | }, 45 | { 46 | id: 2, 47 | name: 'Felix The Cat', 48 | age: 3, 49 | gender: 'male', 50 | newsletter: false, 51 | date_registered: '2016-12-03' 52 | }, 53 | { 54 | id: 3, 55 | name: 'Rambo', 56 | age: 45, 57 | gender: 'male', 58 | newsletter: true, 59 | date_registered: '2015-06-11' 60 | }, 61 | { 62 | id: 4, 63 | name: 'Frostella the Snowlady', 64 | age: 28, 65 | gender: 'female', 66 | newsletter: false, 67 | date_registered: '2016-12-25' 68 | } 69 | ]; 70 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/fields/MUIBooleanField.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import FormControl from '@material-ui/core/FormControl'; 6 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 7 | import FormHelperText from '@material-ui/core/FormHelperText'; 8 | import Checkbox from '@material-ui/core/Checkbox'; 9 | 10 | import { IFieldComponentProps } from 'rev-ui/lib/fields/Field'; 11 | import { getGridWidthProps } from './utils'; 12 | 13 | export const MUIBooleanField: React.StatelessComponent = (props) => { 14 | 15 | const gridWidthProps = getGridWidthProps(props); 16 | const fieldId = props.field.name; 17 | 18 | let errorText = ''; 19 | props.errors.forEach((err) => { 20 | errorText += err.message + '. '; 21 | }); 22 | 23 | const value = props.value ? true : false; 24 | 25 | return ( 26 | 27 | 28 | 29 | props.onChange(event.target.checked)} 35 | color="primary" 36 | /> 37 | } 38 | disabled={props.disabled} 39 | label={props.label} 40 | /> 41 | {errorText && 42 | 43 | {errorText} 44 | } 45 | 46 | 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/fields/MUIDateField.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import FormControl from '@material-ui/core/FormControl'; 6 | import FormHelperText from '@material-ui/core/FormHelperText'; 7 | import Input from '@material-ui/core/Input'; 8 | import InputLabel from '@material-ui/core/InputLabel'; 9 | 10 | import { IFieldComponentProps } from 'rev-ui/lib/fields/Field'; 11 | import { getGridWidthProps } from './utils'; 12 | 13 | export const MUIDateField: React.StatelessComponent = (props) => { 14 | 15 | const gridWidthProps = getGridWidthProps(props); 16 | const fieldId = props.field.name; 17 | 18 | let error = props.errors.length > 0; 19 | let errorText = ''; 20 | props.errors.forEach((err) => { 21 | errorText += err.message + '. '; 22 | }); 23 | 24 | return ( 25 | 26 | 27 | 28 | 33 | {props.label} 34 | 35 | props.onChange(event.target.value)} 40 | error={error} 41 | disabled={props.disabled} 42 | /> 43 | {errorText && 44 | 45 | {errorText} 46 | } 47 | 48 | 49 | 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /packages/rev-api-client/src/client/__tests__/backend.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { ModelApiBackend } from '../backend'; 4 | import { ModelManager, InMemoryBackend } from 'rev-models'; 5 | import { standardBackendTests, IBackendTestConfig } from 'rev-models/lib/backends/testsuite'; 6 | import { registerModels } from 'rev-models/lib/backends/testsuite/models'; 7 | import { getMockApiHttpClient } from '../__test_utils__/mockHttpClient'; 8 | 9 | describe('ModelApiBackend', () => { 10 | 11 | it('can be constructed with an apiUrl', () => { 12 | expect(() => { 13 | new ModelApiBackend('/api'); 14 | }).not.to.throw(); 15 | }); 16 | 17 | it('throws if apiUrl is not provided', () => { 18 | expect(() => { 19 | new (ModelApiBackend as any)(); 20 | }).to.throw('You must provide an apiUrl'); 21 | }); 22 | 23 | }); 24 | 25 | describe('ModelApiBackend - RevJS Backend Tests', () => { 26 | const config: IBackendTestConfig = {} as any; 27 | let backend: ModelApiBackend; 28 | 29 | before(async () => { 30 | // Create an in-memory model manager for API data 31 | const apiModelManager = new ModelManager(); 32 | apiModelManager.registerBackend('default', new InMemoryBackend()); 33 | registerModels(apiModelManager); 34 | 35 | // Create a mock Axios client for querying the API 36 | const mockHttpClient = getMockApiHttpClient(apiModelManager); 37 | 38 | // Create the backend ready for testing 39 | backend = new ModelApiBackend('/api', mockHttpClient); 40 | config.backend = backend; 41 | 42 | }); 43 | 44 | // Configure capabilities 45 | config.skipRawValueTests = true; 46 | config.skipRelatedModelListStoreTest = true; 47 | 48 | standardBackendTests('ModelApiBackend', config); 49 | 50 | }); -------------------------------------------------------------------------------- /docs/mkdocs/src/using_models/creating_data.md: -------------------------------------------------------------------------------- 1 | # Creating Model Data 2 | 3 | Since RevJS models are just JavaScript classes, you create new records by 4 | creating new instances of your model class, and passing them to the 5 | [ModelManager.create()](/api/rev-models/classes/modelmanager.html#create) 6 | method. 7 | 8 | ## Defining Model Constructors 9 | 10 | To make it easier to populate new records, we recommend adding a constructor 11 | function to your class, as shown in the example below: 12 | 13 | ```ts 14 | class MyCoolModel { 15 | @TextField() 16 | title: string; 17 | @TextField({ multiLine: true }) 18 | body: string; 19 | 20 | constructor(data?: Partial) { 21 | Object.assign(this, data); 22 | } 23 | } 24 | ``` 25 | 26 | Doing this means you can then construct new records with new data in a single 27 | statement, as shown below: 28 | 29 | ```ts 30 | const new_record = new MyCoolModel({ 31 | title: 'New Record', 32 | body: 'This is a cool new record with some data!' 33 | }); 34 | ``` 35 | 36 | **IMPORTANT NOTE:** It should also be possible to construct instances 37 | of your model **without** passing any data. RevJS needs to do this when 38 | hydrating models from data retrieved from a backend. 39 | 40 | ## Storing Model Data 41 | 42 | To store data for your model in a backend, simply pass a populated instance 43 | of your model to the 44 | [ModelManager.create()](/api/rev-models/classes/modelmanager.html#create) 45 | method. 46 | 47 | The model data will be validated before it is passed to the backend. If any 48 | validation errors occur, a 49 | [ValidationError](/api/rev-models/classes/validationerror.html) will be 50 | thrown. 51 | 52 | The example below shows how to define a model and create some data for it: 53 | 54 | ```ts 55 | {!examples/src/defining_and_using_models/creating_and_reading_a_model.ts!} 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/typedoc/theme/layouts/default.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {{> header}} 17 | 18 |
    19 |
    20 |
    21 | {{{contents}}} 22 |
    23 | 40 |
    41 |
    42 | 43 | {{> footer}} 44 | 45 |
    46 | 47 | 48 | 49 | {{> analytics}} 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | const outputPath = path.join(__dirname, 'js'); 6 | 7 | module.exports = function() { 8 | return { 9 | entry: { 10 | simple_list: './src/creating_a_ui/simple_list/simple_list.tsx', 11 | custom_list: './src/creating_a_ui/custom_list/custom_list.tsx', 12 | searchable_list: './src/creating_a_ui/searchable_list/searchable_list.tsx', 13 | related_data: './src/creating_a_ui/related_data/related_data.tsx', 14 | detailview: './src/creating_a_ui/detailview/detailview.tsx', 15 | detailview_allfields: './src/creating_a_ui/detailview_allfields/detailview_allfields.tsx', 16 | searchview_allfields: './src/creating_a_ui/searchview_allfields/searchview_allfields.tsx', 17 | }, 18 | output: { 19 | path: outputPath, 20 | filename: '[name].js' 21 | }, 22 | module: { 23 | rules: [ 24 | { test: /\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/ } 25 | ] 26 | }, 27 | resolve: { 28 | extensions: ['.ts', '.tsx', '.js'] 29 | }, 30 | performance: { 31 | hints: false 32 | }, 33 | optimization: { 34 | minimizer: [ 35 | new UglifyJSPlugin({ 36 | sourceMap: true, 37 | uglifyOptions: { 38 | compress: { 39 | inline: false 40 | }, 41 | mangle: { 42 | keep_fnames: true 43 | } 44 | } 45 | }), 46 | ] 47 | }, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/rev-ui/src/__fixtures__/models.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModelManager, InMemoryBackend, 3 | IntegerField, TextField, BooleanField, DateTimeField, 4 | RelatedModel, RelatedModelList, AutoNumberField 5 | } from 'rev-models'; 6 | 7 | export class User { 8 | @AutoNumberField({ primaryKey: true }) 9 | id: number; 10 | @TextField() 11 | name: string; 12 | @RelatedModelList({ model: 'Post', field: 'user' }) 13 | posts: Post[]; 14 | 15 | constructor(data?: Partial) { 16 | Object.assign(this, data); 17 | } 18 | } 19 | 20 | export class Post { 21 | @AutoNumberField({ primaryKey: true }) 22 | id: number; 23 | @TextField() 24 | title: string; 25 | @TextField() 26 | body: string; 27 | @BooleanField({ required: false }) 28 | published: boolean; 29 | @DateTimeField({ required: false }) 30 | post_date: string; 31 | @RelatedModel({ model: 'User', required: false }) 32 | user: User; 33 | @RelatedModelList({ model: 'Comment', field: 'post' }) 34 | comments: Comment[]; 35 | 36 | constructor(data?: Partial) { 37 | Object.assign(this, data); 38 | } 39 | } 40 | 41 | export class Comment { 42 | @IntegerField({ primaryKey: true }) 43 | id: number; 44 | @RelatedModel({ model: 'Post' }) 45 | post: Post; 46 | @TextField() 47 | comment: string; 48 | @RelatedModel({ model: 'User' }) 49 | user: User; 50 | 51 | constructor(data?: Partial) { 52 | Object.assign(this, data); 53 | } 54 | } 55 | 56 | export function getModelManager() { 57 | const modelManager = new ModelManager(); 58 | modelManager.registerBackend('default', new InMemoryBackend()); 59 | modelManager.register(User); 60 | modelManager.register(Post); 61 | modelManager.register(Comment); 62 | return modelManager; 63 | } 64 | -------------------------------------------------------------------------------- /docs/mkdocs/src/using_models/revjs_backends.md: -------------------------------------------------------------------------------- 1 | # RevJS Backends 2 | 3 | RevJS has a **pluggable backend architecture**, to allow you to store your 4 | models in a variety of databases, and send them across the network using APIs. 5 | 6 | ## Using a Backend 7 | 8 | You specify a backend when you create an instance of a 9 | [ModelManager](/api/rev-models/classes/modelmanager.html), for example: 10 | 11 | ```ts 12 | const manager = new ModelManager(); 13 | manager.registerBackend('default', new InMemoryBackend()); 14 | ``` 15 | 16 | If you have data in multiple databases or APIs, you can register 17 | more than one backend for a model manager. 18 | 19 | When registering your models, you can tell the ModelManager which backend to 20 | source them from. For example: 21 | 22 | ```ts 23 | manager.register(TestModel, { backend: 'customer_db' }); 24 | ``` 25 | 26 | If you do not specify a backend when registering your model, then the 27 | `default` backend is used. 28 | 29 | ## Persistant Storage Backends 30 | 31 | * **[MongoDBBackend](/api/rev-backend-mongodb/classes/mongodbbackend.html)** - 32 | stores and retrieves your data from MongoDB 33 | 34 | ## API Backends 35 | 36 | * **[ModelApiBackend](/api/rev-api-client/classes/modelapibackend.html)** - 37 | store and retrieve data from a GraphQL API created by the `rev-api` module 38 | 39 | ## Ephemeral Backends 40 | 41 | * **[InMemoryBackend](/api/rev-models/classes/inmemorybackend.html)** - stores your 42 | model data temporarily in-memory. Ideal for initial development and automated 43 | testing. 44 | 45 | ## Contributing 46 | 47 | We have defined a standard [IBackend](/api/rev-models/interfaces/ibackend.html) 48 | interface, as well as a 49 | [Standard Test Suite](https://github.com/RevJS/revjs/blob/master/packages/rev-models/src/backends/testsuite/index.ts) 50 | to help with new backend development. We're keen to accept any contributions 51 | from the community! -------------------------------------------------------------------------------- /packages/rev-models/src/operations/create.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModel, IModelManager, ICreateOptions, ICreateMeta } from '../models/types'; 3 | import { validate } from './validate'; 4 | import { ModelOperationResult } from './operationresult'; 5 | import { IModelOperation } from './operation'; 6 | import { ValidationError } from '../validation/validationerror'; 7 | 8 | /** 9 | * @private 10 | */ 11 | export const DEFAULT_CREATE_OPTIONS: ICreateOptions = {}; 12 | 13 | /** 14 | * @private 15 | * Documentation in ModelManager class 16 | */ 17 | export async function create(manager: IModelManager, model: T, options?: ICreateOptions): Promise> { 18 | 19 | if (typeof model != 'object') { 20 | throw new Error('Specified model is not a Model instance'); 21 | } 22 | 23 | let meta = manager.getModelMeta(model); 24 | if (!meta.stored) { 25 | throw new Error('Cannot call create() on models with stored: false'); 26 | } 27 | 28 | let backend = manager.getBackend(meta.backend); 29 | 30 | let operation: IModelOperation = { 31 | operationName: 'create' 32 | }; 33 | let operationResult = new ModelOperationResult(operation); 34 | let opts = Object.assign({}, DEFAULT_CREATE_OPTIONS, options); 35 | 36 | let validationResult = await validate(manager, model, operation, opts.validation ? opts.validation : undefined); 37 | 38 | if (!validationResult.valid) { 39 | operationResult.addError('Model failed validation', 'validation_error'); 40 | operationResult.validation = validationResult; 41 | const error = new ValidationError(validationResult); 42 | error.result = operationResult; 43 | throw error; 44 | } 45 | else { 46 | operationResult.validation = validationResult; 47 | } 48 | 49 | return backend.create(manager, model, opts, operationResult); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /packages/rev-models/src/validation/__tests__/validationerror.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { ValidationError } from '../validationerror'; 4 | import { ModelValidationResult } from '../validationresult'; 5 | 6 | describe('ValidationError', () => { 7 | 8 | it('is a subclass of Error', () => { 9 | const err = new ValidationError(); 10 | expect(err).to.be.instanceof(Error); 11 | }); 12 | 13 | it('throws with a default "ValidationError" message', () => { 14 | expect(() => { 15 | throw new ValidationError(); 16 | }).to.throw('ValidationError'); 17 | }); 18 | 19 | describe('when a validation result is provided', () => { 20 | const validationResult = new ModelValidationResult(); 21 | validationResult.addFieldError('title', 'Title must not include fake news'); 22 | validationResult.addFieldError('field2', 'This field is crook, bro', 'ABC123', { test: 1 }); 23 | validationResult.addModelError('The model is not valid', 'code123', { hmm: true }); 24 | 25 | it('includes field errors then model errors in the error message', () => { 26 | const err = new ValidationError(validationResult); 27 | 28 | expect(err.message).to.equal(`ValidationError 29 | * title: Title must not include fake news 30 | * field2: This field is crook, bro 31 | * The model is not valid`); 32 | }); 33 | 34 | it('exposes a "validation" property with the validation result', () => { 35 | const err = new ValidationError(validationResult); 36 | 37 | expect(err).to.have.property('validation', validationResult); 38 | }); 39 | 40 | it('does not include errors in the message when detailsInMessage = false', () => { 41 | const err = new ValidationError(validationResult, false); 42 | 43 | expect(err.message).to.equal('ValidationError'); 44 | }); 45 | 46 | }); 47 | 48 | }); -------------------------------------------------------------------------------- /packages/rev-models/src/backends/backend.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ModelOperationResult } from '../operations/operationresult'; 3 | import { IModel, IModelManager, ICreateMeta, IUpdateMeta, IRemoveMeta, IReadMeta, IExecMeta, IExecArgs } from '../models/types'; 4 | import { IObject } from '../utils/types'; 5 | 6 | /** 7 | * Interface that all RevJS backends are required to implement 8 | */ 9 | 10 | /** @private */ 11 | export interface ICreateParams { 12 | // Reserved for future use! 13 | } 14 | 15 | /** @private */ 16 | export interface IUpdateParams { 17 | where: IObject; 18 | fields: string[]; 19 | } 20 | 21 | /** @private */ 22 | export interface IRemoveParams { 23 | where: IObject; 24 | } 25 | 26 | /** @private */ 27 | export interface IReadParams { 28 | where: IObject; 29 | orderBy: string[]; 30 | limit: number; 31 | offset: number; 32 | related: string[]; 33 | rawValues: string[]; 34 | } 35 | 36 | /** @private */ 37 | export interface IExecParams { 38 | method: string; 39 | args: IExecArgs; 40 | } 41 | 42 | export interface IBackend { 43 | create(manager: IModelManager, model: T, params: ICreateParams, result: ModelOperationResult): Promise>; 44 | update(manager: IModelManager, model: T, params: IUpdateParams, result: ModelOperationResult): Promise>; 45 | remove(manager: IModelManager, model: T, params: IRemoveParams, result: ModelOperationResult): Promise>; 46 | read(manager: IModelManager, model: new() => T, params: IReadParams, result: ModelOperationResult): Promise>; 47 | exec(manager: IModelManager, model: IModel, params: IExecParams, result: ModelOperationResult): Promise>; 48 | } 49 | -------------------------------------------------------------------------------- /docs/mkdocs/src/index.md: -------------------------------------------------------------------------------- 1 | # RevJS - Rev up your data-driven JS app development! 2 | 3 | ## What is RevJS? 4 | 5 | RevJS is a suite of JavaScript modules designed to speed up development of 6 | data-driven JS applications. 7 | 8 | RevJS allows you to 9 | 10 | * Define a relational **data model** using plain JS classes, and built-in or custom field types 11 | * Define custom **validation logic** directly in your models 12 | * Easily create a **GraphQL API** to make your models available over the network 13 | * Quickly build a **user interface** for the web or mobile, using our React higher-order components 14 | 15 | ## Example 16 | 17 | The below example shows how to create a simple data model with RevJS's `rev-models` module: 18 | 19 | ```ts 20 | {!examples/src/defining_and_using_models/creating_models.ts!} 21 | ``` 22 | 23 | ResJS is designed for use with **TypeScript**, to give you all the 24 | benefits of strong typing and intellisense, however it should work with 25 | standard ES6+ too. (we're looking for someone interested in creating and 26 | maintaining a revjs ES6+ guide!...) 27 | 28 | ## Components 29 | 30 | * **[rev-models](components/rev-models.md)** - Define your Data Models and Validation, 31 | and store and retrieve them from one of several [backends](using_models/revjs_backends.md). 32 | * **[rev-api](components/rev-api.md)** - Expose your data model via an automatically-generated GraphQL API 33 | * **[rev-api-client](components/rev-api-client.md)** - Client-side wrapper for your API. Use your models 34 | client-side in the same way you do on the server. 35 | * **[rev-ui](components/rev-ui.md)** - Quickly build user interfaces with data from your RevJS backend using our 36 | React higher-order components. 37 | 38 | ## Contributing 39 | 40 | We are actively looking to build a team around RevJS. If you are interesting in 41 | contributing, fork us on github or drop us a 42 | [mail](mailto:russ@russellbriggs.co)! 43 | 44 | ## License 45 | 46 | MIT 47 | -------------------------------------------------------------------------------- /packages/rev-ui/src/views/__tests__/DetailView/withDetailViewContext.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import { expect } from 'chai'; 4 | import { mount } from 'enzyme'; 5 | import * as models from '../../../__fixtures__/models'; 6 | import { ModelManager } from 'rev-models'; 7 | import { ModelProvider } from '../../../provider/ModelProvider'; 8 | import { DetailView, IDetailViewContextProp } from '../../DetailView'; 9 | import { withDetailViewContext } from '../../withDetailViewContext'; 10 | 11 | interface ISpyComponentProps { 12 | prop1: string; 13 | prop2: string; 14 | } 15 | 16 | describe('withDetailViewContext()', () => { 17 | 18 | let modelManager: ModelManager; 19 | 20 | let receivedProps: ISpyComponentProps & IDetailViewContextProp; 21 | 22 | class SpyComponentC extends React.Component { 23 | constructor(props: any) { 24 | super(props); 25 | receivedProps = props; 26 | } 27 | render() { 28 | return
    Spy Component
    ; 29 | } 30 | } 31 | 32 | const SpyComponent = withDetailViewContext(SpyComponentC); 33 | 34 | before(() => { 35 | receivedProps = null as any; 36 | modelManager = models.getModelManager(); 37 | mount( 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }); 45 | 46 | it('passes down detailViewContext from context', () => { 47 | expect(receivedProps.detailViewContext).not.to.be.undefined; 48 | expect(receivedProps.detailViewContext.modelMeta.name).to.equal('Post'); 49 | }); 50 | 51 | it('passes through other props', () => { 52 | expect(receivedProps.prop1).to.equal('prop1'); 53 | expect(receivedProps.prop2).to.equal('prop2'); 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /packages/rev-models/src/validation/validationerror.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModelOperationResult } from '../operations/operationresult'; 3 | import { IModelValidationResult } from './validationresult'; 4 | 5 | /** 6 | * A ValidationError is thrown by a [[ModelManager]] when an operation fails 7 | * due to model validation. 8 | * 9 | * The `validation` property contains the failure details in a 10 | * [[IModelValidationResult]] object. 11 | * 12 | * The below example shows an example of handling a ValidationError returned 13 | * from a ModelManager: 14 | * 15 | * ```ts 16 | * [[include:examples/src/model_manager/handling_validation_errors.ts]] 17 | * ``` 18 | */ 19 | export class ValidationError extends Error { 20 | /** 21 | * The result of the model operation (normally failed due to model validation) 22 | */ 23 | result: IModelOperationResult; 24 | /** 25 | * The details of the validation problem 26 | */ 27 | validation: IModelValidationResult | null; 28 | 29 | constructor( 30 | validationResult: IModelValidationResult | null = null, 31 | detailsInMessage = true 32 | ) { 33 | let message = 'ValidationError'; 34 | 35 | if (validationResult && detailsInMessage) { 36 | for (let fieldName in validationResult.fieldErrors) { 37 | for (let fieldErr of validationResult.fieldErrors[fieldName]) { 38 | message += `\n * ${fieldName}: ${fieldErr.message}`; 39 | } 40 | } 41 | for (let modelErr of validationResult.modelErrors) { 42 | message += `\n * ${modelErr.message}`; 43 | } 44 | } 45 | 46 | super(message); 47 | 48 | // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work 49 | Object.setPrototypeOf(this, ValidationError.prototype); 50 | 51 | this.validation = validationResult; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /packages/rev-models/src/validation/validationmsg.ts: -------------------------------------------------------------------------------- 1 | // TODO: Do something more clevererer than this... 2 | 3 | export let VALIDATION_MESSAGES = { 4 | required: (label: string) => `${label} is a required field`, 5 | string_empty: (label: string) => `${label} is a required field`, 6 | not_a_string: (label: string) => `${label} should be a string`, 7 | not_a_number: (label: string) => `${label} should be a number`, 8 | not_an_integer: (label: string) => `${label} should be an integer`, 9 | not_a_boolean: (label: string) => `${label} should be either true or false`, 10 | not_a_date: (label: string) => `${label} should be a date`, 11 | not_a_time: (label: string) => `${label} should be a time`, 12 | not_a_datetime: (label: string) => `${label} should be a date and time`, 13 | min_string_length: (label: string, val: number) => `${label} should have at least ${val} characters`, 14 | max_string_length: (label: string, val: number) => `${label} should have at most ${val} characters`, 15 | min_value: (label: string, val: any) => `${label} should be at least ${val}`, 16 | max_value: (label: string, val: any) => `${label} should be at most ${val}`, 17 | no_regex_match: (label: string) => `${label} is not in the correct format`, 18 | no_selection_match: (label: string) => `Invalid selection for ${label}`, 19 | list_empty: (label: string) => `${label} is a required field`, 20 | selection_not_an_array: (label: string) => `${label} should be a list of selections`, 21 | extra_field: (name: string) => `Field '${name}' does not exist in model metadata`, 22 | invalid_model_class: (label: string) => `Invalid object class for field '${label}'`, 23 | missing_model_primary_key: (label: string) => `The object set for field '${label}' does not have a primary key value`, 24 | invalid_model_list_data: (label: string) => `${label} must be a list of objects`, 25 | invalid_model_list_class: (label: string) => `One or more objects in ${label} has the wrong class.` 26 | }; 27 | -------------------------------------------------------------------------------- /packages/rev-api/src/graphql/__tests__/api.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as models from '../__fixtures__/models'; 3 | import { ModelApiManager } from '../../api/manager'; 4 | import { GraphQLApi } from '../api'; 5 | import { GraphQLFloat } from 'graphql/type/scalars'; 6 | import { fields } from 'rev-models'; 7 | 8 | import { expect } from 'chai'; 9 | 10 | describe('GraphQLApi class', () => { 11 | let manager: ModelApiManager; 12 | let api: GraphQLApi; 13 | 14 | beforeEach(() => { 15 | manager = new ModelApiManager(models.getModelManager()); 16 | }); 17 | 18 | describe('constructor()', () => { 19 | 20 | it('successfully creates a manager', () => { 21 | expect(() => { 22 | api = new GraphQLApi(manager); 23 | }).to.not.throw(); 24 | expect(api.getApiManager()).to.equal(manager); 25 | }); 26 | 27 | it('throws if not passed a ModelManager', () => { 28 | expect(() => { 29 | api = new GraphQLApi(undefined as any); 30 | }).to.throw('Invalid ModelApiManager passed in constructor'); 31 | }); 32 | 33 | }); 34 | 35 | describe('getGraphQLFieldMapping()', () => { 36 | class UnknownField extends fields.Field {} 37 | 38 | before(() => { 39 | api = new GraphQLApi(manager); 40 | }); 41 | 42 | it('returns mapping for a known field type', () => { 43 | const mapping = api.getGraphQLFieldMapping(new fields.NumberField('test')); 44 | expect(mapping).to.be.an('object'); 45 | expect(mapping.type).to.equal(GraphQLFloat); 46 | expect(mapping.converter).to.be.a('function'); 47 | }); 48 | 49 | it('throws an error when called with an unknown field type', () => { 50 | expect(() => { 51 | api.getGraphQLFieldMapping(new UnknownField('test')); 52 | }).to.throw(`No fieldMapping found for field type 'UnknownField'`); 53 | }); 54 | 55 | }); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /packages/rev-ui/src/views/__tests__/SearchView/withSearchViewContext.tests.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | import { expect } from 'chai'; 4 | import { mount } from 'enzyme'; 5 | import * as models from '../../../__fixtures__/models'; 6 | import { ModelManager } from 'rev-models'; 7 | import { ModelProvider } from '../../../provider/ModelProvider'; 8 | import { SearchView, ISearchViewContextProp } from '../../SearchView'; 9 | import { withSearchViewContext } from '../../withSearchViewContext'; 10 | 11 | interface ISpyComponentProps { 12 | prop1: string; 13 | prop2: string; 14 | } 15 | 16 | describe('withSearchViewContext()', () => { 17 | 18 | let modelManager: ModelManager; 19 | 20 | let receivedProps: ISpyComponentProps & ISearchViewContextProp; 21 | 22 | class SpyComponentC extends React.Component { 23 | constructor(props: any) { 24 | super(props); 25 | receivedProps = props; 26 | } 27 | render() { 28 | return
    Spy Component
    ; 29 | } 30 | } 31 | 32 | const SpyComponent = withSearchViewContext(SpyComponentC); 33 | 34 | before(() => { 35 | receivedProps = null as any; 36 | modelManager = models.getModelManager(); 37 | mount( 38 | 39 | null}> 40 | 41 | 42 | 43 | ); 44 | }); 45 | 46 | it('passes down searchViewContext from context', () => { 47 | expect(receivedProps.searchViewContext).not.to.be.undefined; 48 | expect(receivedProps.searchViewContext.modelMeta.name).to.equal('Post'); 49 | }); 50 | 51 | it('passes through other props', () => { 52 | expect(receivedProps.prop1).to.equal('prop1'); 53 | expect(receivedProps.prop2).to.equal('prop2'); 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /packages/rev-api-client/src/client/__fixtures__/models.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModelManager, InMemoryBackend, 3 | TextField, DateField, BooleanField, DateTimeField, 4 | AutoNumberField, RelatedModel, RelatedModelList 5 | } from 'rev-models'; 6 | 7 | export class User { 8 | @AutoNumberField({ primaryKey: true }) 9 | id: number; 10 | @TextField() 11 | name: string; 12 | @DateField() 13 | date_registered: string; 14 | @RelatedModelList({ model: 'Post', field: 'user' }) 15 | posts: Post[]; 16 | 17 | constructor(data?: Partial) { 18 | Object.assign(this, data); 19 | } 20 | 21 | userMethod1() {} 22 | } 23 | 24 | export class Post { 25 | @AutoNumberField({ primaryKey: true }) 26 | id: number; 27 | @TextField() 28 | title: string; 29 | @TextField() 30 | body: string; 31 | @BooleanField() 32 | published: boolean; 33 | @DateTimeField() 34 | post_date: string; 35 | @RelatedModel({ model: 'User' }) 36 | user: User; 37 | @RelatedModelList({ model: 'Comment', field: 'post' }) 38 | comments: Comment[]; 39 | 40 | constructor(data?: Partial) { 41 | Object.assign(this, data); 42 | } 43 | 44 | postMethod1() {} 45 | postMethod2() {} 46 | postMethod3() {} 47 | } 48 | 49 | export class Comment { 50 | @AutoNumberField({ primaryKey: true }) 51 | id: number; 52 | @RelatedModel({ model: 'Post' }) 53 | post: Post; 54 | @TextField() 55 | comment: string; 56 | @RelatedModel({ model: 'User' }) 57 | user: User; 58 | 59 | constructor(data?: Partial) { 60 | Object.assign(this, data); 61 | } 62 | } 63 | 64 | export function getModelManager() { 65 | const modelManager = new ModelManager(); 66 | modelManager.registerBackend('default', new InMemoryBackend()); 67 | modelManager.register(User); 68 | modelManager.register(Post); 69 | modelManager.register(Comment); 70 | return modelManager; 71 | } 72 | -------------------------------------------------------------------------------- /packages/rev-ui/src/config.ts: -------------------------------------------------------------------------------- 1 | 2 | const PassthroughComponent: any = (props: any) => { 3 | return props.children; 4 | }; 5 | 6 | export const UI_COMPONENTS = { 7 | views: { 8 | DetailView: PassthroughComponent, 9 | ListView: PassthroughComponent, 10 | SearchView: PassthroughComponent 11 | }, 12 | actions: { 13 | PostAction: PassthroughComponent, 14 | SaveAction: PassthroughComponent, 15 | RemoveAction: PassthroughComponent, 16 | SearchAction: PassthroughComponent 17 | }, 18 | fields: { 19 | TextField: PassthroughComponent, 20 | EmailField: PassthroughComponent, 21 | URLField: PassthroughComponent, 22 | PasswordField: PassthroughComponent, 23 | NumberField: PassthroughComponent, 24 | IntegerField: PassthroughComponent, 25 | AutoNumberField: PassthroughComponent, 26 | BooleanField: PassthroughComponent, 27 | SelectField: PassthroughComponent, 28 | MultiSelectField: PassthroughComponent, 29 | DateField: PassthroughComponent, 30 | TimeField: PassthroughComponent, 31 | DateTimeField: PassthroughComponent, 32 | RelatedModelField: PassthroughComponent, 33 | RelatedModelListField: PassthroughComponent, 34 | }, 35 | searchFields: { 36 | TextField: PassthroughComponent, 37 | EmailField: PassthroughComponent, 38 | URLField: PassthroughComponent, 39 | PasswordField: PassthroughComponent, 40 | NumberField: PassthroughComponent, 41 | IntegerField: PassthroughComponent, 42 | AutoNumberField: PassthroughComponent, 43 | BooleanField: PassthroughComponent, 44 | SelectField: PassthroughComponent, 45 | MultiSelectField: PassthroughComponent, 46 | DateField: PassthroughComponent, 47 | TimeField: PassthroughComponent, 48 | DateTimeField: PassthroughComponent, 49 | RelatedModelField: PassthroughComponent, 50 | RelatedModelListField: PassthroughComponent, 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /docs/mkdocs/src/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Visual Studio 2015 dark style 3 | * Author: Nicolas LLOBERA 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | overflow-x: auto; 9 | padding: 0.5em; 10 | background: #1E1E1E; 11 | color: #DCDCDC; 12 | } 13 | 14 | .hljs-keyword, 15 | .hljs-literal, 16 | .hljs-symbol, 17 | .hljs-name { 18 | color: #569CD6; 19 | } 20 | .hljs-link { 21 | color: #569CD6; 22 | text-decoration: underline; 23 | } 24 | 25 | .hljs-built_in, 26 | .hljs-type { 27 | color: #4EC9B0; 28 | } 29 | 30 | .hljs-number, 31 | .hljs-class { 32 | color: #B8D7A3; 33 | } 34 | 35 | .hljs-string, 36 | .hljs-meta-string { 37 | color: #D69D85; 38 | } 39 | 40 | .hljs-regexp, 41 | .hljs-template-tag { 42 | color: #9A5334; 43 | } 44 | 45 | .hljs-subst, 46 | .hljs-function, 47 | .hljs-title, 48 | .hljs-params, 49 | .hljs-formula { 50 | color: #DCDCDC; 51 | } 52 | 53 | .hljs-comment, 54 | .hljs-quote { 55 | color: #57A64A; 56 | font-style: italic; 57 | } 58 | 59 | .hljs-doctag { 60 | color: #608B4E; 61 | } 62 | 63 | .hljs-meta, 64 | .hljs-meta-keyword, 65 | .hljs-tag { 66 | color: #9B9B9B; 67 | } 68 | 69 | .hljs-variable, 70 | .hljs-template-variable { 71 | color: #BD63C5; 72 | } 73 | 74 | .hljs-attr, 75 | .hljs-attribute, 76 | .hljs-builtin-name { 77 | color: #9CDCFE; 78 | } 79 | 80 | .hljs-section { 81 | color: gold; 82 | } 83 | 84 | .hljs-emphasis { 85 | font-style: italic; 86 | } 87 | 88 | .hljs-strong { 89 | font-weight: bold; 90 | } 91 | 92 | /*.hljs-code { 93 | font-family:'Monospace'; 94 | }*/ 95 | 96 | .hljs-bullet, 97 | .hljs-selector-tag, 98 | .hljs-selector-id, 99 | .hljs-selector-class, 100 | .hljs-selector-attr, 101 | .hljs-selector-pseudo { 102 | color: #D7BA7D; 103 | } 104 | 105 | .hljs-addition { 106 | background-color: #144212; 107 | display: inline-block; 108 | width: 100%; 109 | } 110 | 111 | .hljs-deletion { 112 | background-color: #600; 113 | display: inline-block; 114 | width: 100%; 115 | } 116 | -------------------------------------------------------------------------------- /docs/mkdocs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: RevJS Guide 2 | site_description: Rev up your data-driven JS app development! RevJS is a suite of JavaScript modules designed to speed up development of data-driven applications. It allows you to easily define a relational data model with field types and validation, and automatically generate a GraphQL API and React user interface. 3 | pages: 4 | - Welcome to RevJS!: index.md 5 | - Working with Models: 6 | - Defining Models: using_models/creating_models.md 7 | - Adding Validation: using_models/model_validation.md 8 | - Storage Backends: using_models/revjs_backends.md 9 | - Creating Data: using_models/creating_data.md 10 | - Reading Data: using_models/reading_data.md 11 | - Updating Data: using_models/updating_data.md 12 | - Deleting Data: using_models/deleting_data.md 13 | - Working with Related Models: using_models/related_data.md 14 | - Creating a GraphQL API: 15 | - Creating an API: creating_an_api/overview.md 16 | - Creating a User Interface: 17 | - RevJS UI Overview: creating_a_ui/overview.md 18 | - Simple ListView: creating_a_ui/simple_list.md 19 | - Custom ListView: creating_a_ui/custom_list.md 20 | - Listing Related Model Data: creating_a_ui/related_data.md 21 | - Searchable ListView: creating_a_ui/searchable_list.md 22 | - DetailViews: creating_a_ui/detail_view.md 23 | - RevJS Module Reference: 24 | - rev-models: components/rev-models.md 25 | - rev-api: components/rev-api.md 26 | - rev-api-client: components/rev-api-client.md 27 | - rev-ui: components/rev-ui.md 28 | - Change Log: changelog.md 29 | strict: true 30 | theme: readthedocs 31 | docs_dir: /mkdocs/src 32 | site_dir: /mkdocs/dist 33 | repo_url: https://github.com/RevJS/revjs 34 | edit_uri: /RevJS/revjs/edit/master/docs/mkdocs/src/ 35 | google_analytics: ['UA-115939002-1','revjs.org'] 36 | markdown_extensions: 37 | - toc: 38 | permalink: True 39 | - markdown_include.include: 40 | base_path: /packages 41 | extra_javascript: 42 | - js/highlight.pack.js 43 | extra_css: 44 | - css/highlight.css 45 | - css/style.css -------------------------------------------------------------------------------- /packages/rev-models/src/queries/utils.ts: -------------------------------------------------------------------------------- 1 | import { escapeForRegex } from '../utils/index'; 2 | 3 | /** 4 | * @private 5 | */ 6 | export function isFieldValue(value: any) { 7 | return (typeof value == 'string' 8 | || typeof value == 'boolean' 9 | || (typeof value == 'number' && !isNaN(value)) 10 | || (typeof value == 'object' && value instanceof Date) 11 | || value === null); 12 | } 13 | 14 | /** 15 | * @private 16 | */ 17 | export function getLikeOperatorRegExp(likeStr: string) { 18 | // Build a RegExp from string with % wildcards (i.e. LIKE in SQL) 19 | // Can't use a simple RegExp because JS doesn't support lookbehinds :( 20 | likeStr = escapeForRegex(likeStr); 21 | if (likeStr == '') { 22 | return /^.{0}$/m; // match only empty strings 23 | } 24 | let m: RegExpExecArray | null; 25 | // First, find instances of '%%' (which should match '%' in the data) 26 | let doubleMatcher = /%%/g; 27 | let doubleLocs: number[] = []; 28 | while(m = doubleMatcher.exec(likeStr)) { // tslint:disable-line 29 | doubleLocs.push(m.index); 30 | } 31 | // Now find instances of single '%'s 32 | let singleMatcher = /%/g; 33 | let singleLocs: number[] = []; 34 | while(m = singleMatcher.exec(likeStr)) { // tslint:disable-line 35 | if (doubleLocs.indexOf(m.index) == -1 36 | && doubleLocs.indexOf(m.index + 1) == -1 37 | && doubleLocs.indexOf(m.index - 1) == -1) { 38 | singleLocs.push(m.index); 39 | } 40 | } 41 | // Replace single '%'s with '.*' 42 | let wildcardedStr = ''; 43 | let startLoc = 0; 44 | if (singleLocs.length > 0) { 45 | for (let singleLoc of singleLocs) { 46 | wildcardedStr += likeStr.slice(startLoc, singleLoc) + '.*'; 47 | startLoc = singleLoc + 1; 48 | } 49 | wildcardedStr += likeStr.substr(startLoc); 50 | } 51 | else { 52 | wildcardedStr = likeStr; 53 | } 54 | wildcardedStr = wildcardedStr.replace(/%%/g, '%'); 55 | return new RegExp('^' + wildcardedStr + '$', 'im'); 56 | } 57 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/searchFields/MUITextSearchField.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import FormControl from '@material-ui/core/FormControl'; 6 | import Input from '@material-ui/core/Input'; 7 | import InputLabel from '@material-ui/core/InputLabel'; 8 | 9 | import { ISearchFieldComponentProps } from 'rev-ui/lib/fields/SearchField'; 10 | import { getGridWidthProps } from '../fields/utils'; 11 | import { IObject } from 'rev-models/lib/utils/types'; 12 | 13 | // Text Search Field - does a basic 'contains' search 14 | export const MUITextSearchField: React.StatelessComponent = (props) => { 15 | 16 | const gridWidthProps = getGridWidthProps(props); 17 | const fieldId = props.field.name; 18 | 19 | let type = 'text'; 20 | 21 | let value = ''; 22 | const criteria: IObject = props.criteria; 23 | if (criteria && typeof criteria['_like'] == 'string') { 24 | // We assume the current criteria starts and ends with a '%' 25 | value = criteria['_like'].substr(1, criteria['_like'].length - 2); 26 | } 27 | 28 | return ( 29 | 30 | 31 | 32 | 36 | {props.label} 37 | 38 | { 43 | const val = event.target.value; 44 | if (val) { 45 | props.onCriteriaChange({ _like: '%' + event.target.value + '%'}); 46 | } 47 | else { 48 | props.onCriteriaChange(null); 49 | } 50 | }} 51 | /> 52 | 53 | 54 | 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/__tests__/conjunction.tests.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import * as d from '../../../decorators'; 4 | import { ConjunctionNode } from '../conjunction'; 5 | import { QueryParser } from '../../queryparser'; 6 | import { ModelManager } from '../../../models/manager'; 7 | import { InMemoryBackend } from '../../../backends/inmemory/backend'; 8 | 9 | class TestModel { 10 | @d.IntegerField() 11 | id: number; 12 | @d.TextField() 13 | name: string; 14 | @d.BooleanField() 15 | active: boolean; 16 | } 17 | 18 | let manager = new ModelManager(); 19 | manager.registerBackend('default', new InMemoryBackend()); 20 | manager.register(TestModel); 21 | let parser = new QueryParser(manager); 22 | 23 | describe('class ConjunctionNode - constructor', () => { 24 | 25 | it('throws if operator is not a conjunction operator', () => { 26 | expect(() => { 27 | new ConjunctionNode(parser, TestModel, '_gt', [], undefined); 28 | }).to.throw('unrecognised conjunction operator'); 29 | }); 30 | 31 | it('throws if value is not an array', () => { 32 | expect(() => { 33 | new ConjunctionNode(parser, TestModel, '_and', {}, undefined); 34 | }).to.throw('must be an array'); 35 | }); 36 | 37 | it('creates a conjunction node with the correct operator', () => { 38 | let node = new ConjunctionNode(parser, TestModel, '_and', [], undefined); 39 | expect(node.operator).to.equal('and'); 40 | }); 41 | 42 | it('creates a conjunction node with no children if value array is empty', () => { 43 | let node = new ConjunctionNode(parser, TestModel, '_and', [], undefined); 44 | expect(node.children).to.have.length(0); 45 | }); 46 | 47 | it('creates a child node for each element in the value array', () => { 48 | let node = new ConjunctionNode(parser, TestModel, '_and', [ 49 | { id: 1 }, 50 | { name: 'bob' }, 51 | { active: true, name: 'bob' } 52 | ], undefined); 53 | expect(node.children).to.have.length(3); 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /docs/typedoc/theme/partials/header.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | 19 | 20 |
    21 | Menu 22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 | 35 |

    {{#compact}} 36 | {{#if model.kindString}}{{model.kindString}}: {{/if}} 37 | {{model.name}} 38 | {{#if model.typeParameters}} 39 | < 40 | {{#each model.typeParameters}} 41 | {{#if @index}}, {{/if}} 42 | {{name}} 43 | {{/each}} 44 | > 45 | {{/if}} 46 | {{/compact}}

    47 |
    48 |
    49 |
    -------------------------------------------------------------------------------- /packages/rev-models/src/operations/validate.ts: -------------------------------------------------------------------------------- 1 | import { IModel, IModelManager, IValidationOptions } from '../models/types'; 2 | import { IModelOperation } from './operation'; 3 | import { ModelValidationResult } from '../validation/validationresult'; 4 | import { VALIDATION_MESSAGES as msg } from '../validation/validationmsg'; 5 | import { withTimeout } from '../utils'; 6 | import { checkFieldsList } from '../models/utils'; 7 | 8 | /** 9 | * @private 10 | * Documentation in ModelManager class 11 | */ 12 | export async function validate(manager: IModelManager, model: T, operation: IModelOperation, options?: IValidationOptions): Promise { 13 | 14 | let meta = manager.getModelMeta(model); 15 | 16 | let timeout = options && options.timeout ? options.timeout : 5000; 17 | let result = new ModelValidationResult(); 18 | 19 | // Work out fields to be validated 20 | let fields: string[]; 21 | if (options && options.fields) { 22 | checkFieldsList(meta, options.fields); 23 | fields = options.fields; 24 | } 25 | else { 26 | fields = Object.keys(meta.fieldsByName); 27 | } 28 | 29 | // Check for any unknown fields 30 | for (let field of Object.keys(model)) { 31 | if (!(field in meta.fieldsByName)) { 32 | result.addModelError(msg.extra_field(field), 'extra_field'); 33 | } 34 | } 35 | 36 | // Trigger field validation 37 | let promises: Array> = []; 38 | for (let fieldName of fields) { 39 | let fieldObj = meta.fieldsByName[fieldName]; 40 | promises.push(fieldObj.validate(manager, model, operation, result, options)); 41 | } 42 | await withTimeout(Promise.all(promises), timeout, 'validate()'); 43 | 44 | // Trigger model validation 45 | if (typeof model.validate == 'function') { 46 | model.validate({ manager, operation, result, options }); 47 | } 48 | if (typeof model.validateAsync == 'function') { 49 | await withTimeout( 50 | model.validateAsync({ manager, operation, result, options }), 51 | timeout, 'validate()'); 52 | } 53 | 54 | return result; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /packages/rev-models/src/utils/__tests__/utils.tests.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { isSet, printObj, escapeForRegex } from '../index'; 4 | 5 | describe('isSet()', () => { 6 | 7 | it('returns false if value is not set', () => { 8 | expect(isSet(undefined)).to.be.false; 9 | expect(isSet(null)).to.be.false; 10 | }); 11 | 12 | it('returns true if value is set', () => { 13 | expect(isSet('')).to.be.true; 14 | expect(isSet('woo!')).to.be.true; 15 | expect(isSet(0)).to.be.true; 16 | expect(isSet(-200)).to.be.true; 17 | expect(isSet({})).to.be.true; 18 | expect(isSet(new Date())).to.be.true; 19 | }); 20 | 21 | }); 22 | 23 | describe('printObj()', () => { 24 | let testObj = { 25 | flibble: 2, 26 | jibble: { 27 | wibble: 7 28 | } 29 | }; 30 | 31 | let singleLine = '{"flibble":2,"jibble":{"wibble":7}}'; 32 | let multiLine = `{ 33 | "flibble": 2, 34 | "jibble": { 35 | "wibble": 7 36 | } 37 | }`; 38 | 39 | it('prints object contents on a single line by default', () => { 40 | expect(printObj(testObj)).to.equal(singleLine); 41 | }); 42 | 43 | it('multiline option gives multiline output', () => { 44 | expect(printObj(testObj, true)).to.equal(multiLine); 45 | }); 46 | 47 | }); 48 | 49 | describe('escapeForRegex()', () => { 50 | let part1 = 'Hmmm'; 51 | let part2 = 'Ah'; 52 | let regExSpecialChars = [ 53 | '\\', '^', '$', '*', '+', '?', '.', 54 | '(', ')', '|', '{', '}', '[', ']' 55 | ]; 56 | 57 | it('throws an error if value is not a string', () => { 58 | expect(() => { 59 | escapeForRegex(null as any); 60 | }).to.throw('Supplied value is not a string'); 61 | expect(() => { 62 | escapeForRegex({} as any); 63 | }).to.throw('Supplied value is not a string'); 64 | }); 65 | 66 | for (let char of regExSpecialChars) { 67 | it(`should escape ${char}`, () => { 68 | expect(escapeForRegex(part1 + char + part2)) 69 | .to.equal(part1 + '\\' + char + part2); 70 | }); 71 | } 72 | 73 | }); 74 | -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/api.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as models from './models'; 3 | import { createData } from './model_data'; 4 | import { ModelManager, InMemoryBackend } from 'rev-models'; 5 | import { ModelApiManager } from 'rev-api'; 6 | 7 | // Register server models 8 | 9 | export const modelManager = new ModelManager(); 10 | const backend = new InMemoryBackend(); 11 | modelManager.registerBackend('default', backend); 12 | modelManager.register(models.User); 13 | modelManager.register(models.Post); 14 | modelManager.register(models.Comment); 15 | modelManager.register(models.ModelWithAllFields); 16 | 17 | export const api = new ModelApiManager(modelManager); 18 | api.register(models.User); 19 | api.register(models.Post); 20 | api.register(models.Comment); 21 | api.register(models.ModelWithAllFields); 22 | 23 | // Create Koa & Apollo GraphQL Server 24 | 25 | import * as koa from 'koa'; 26 | import * as koaRouter from 'koa-router'; 27 | import * as koaBody from 'koa-bodyparser'; 28 | import { graphqlKoa, graphiqlKoa } from 'graphql-server-koa'; 29 | 30 | const schema = api.getGraphQLSchema(); 31 | 32 | const app = new koa(); 33 | const port = 3000; 34 | 35 | const router = new koaRouter(); 36 | router.post('/graphql', graphqlKoa({ schema: schema })); 37 | router.get('/graphql', graphqlKoa({ schema: schema })); 38 | router.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' })); 39 | 40 | app.use(async (ctx, next) => { 41 | const allowedHeaders = ctx.get('Access-Control-Request-Headers'); 42 | ctx.set('Access-Control-Allow-Origin', '*'); 43 | ctx.set('Access-Control-Allow-Headers', allowedHeaders); 44 | return next(); 45 | }); 46 | app.use(koaBody()); 47 | app.use(router.routes()); 48 | app.use(router.allowedMethods()); 49 | 50 | app.listen(port); 51 | 52 | console.log(`GraphQL Server for UI Demos is running on port ${port}.`); 53 | console.log(`GraphiQL UI is running at http://localhost:${port}/graphiql`); 54 | 55 | // Load demo data 56 | 57 | createData(modelManager) 58 | .then(() => { 59 | console.log('Data Loaded. Simulating API Delay of 500ms.'); 60 | backend.OPERATION_DELAY = 500; 61 | }) 62 | .catch((e) => { 63 | console.error('Error loading data', e); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/rev-models/src/backends/testsuite/modeldata.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IModelManager } from '../../models/types'; 3 | import { TestModel, TestModelNoPK } from './models'; 4 | 5 | export const testData = [ 6 | new TestModel({ 7 | id: 0, 8 | name: 'John Doe', 9 | age: 20, 10 | gender: 'male', 11 | hobbies: ['music', 'karate'], 12 | newsletter: true, 13 | date_registered: '2016-05-26' 14 | }), 15 | new TestModel({ 16 | id: 1, 17 | name: 'Jane Doe', 18 | age: 23, 19 | gender: 'female', 20 | hobbies: ['music'], 21 | newsletter: true, 22 | date_registered: '2017-01-01' 23 | }), 24 | new TestModel({ 25 | id: 2, 26 | name: 'Felix The Cat', 27 | age: 3, 28 | gender: 'male', 29 | hobbies: ['gardening'], 30 | newsletter: false, 31 | date_registered: '2016-12-03' 32 | }), 33 | new TestModel({ 34 | id: 3, 35 | name: 'Rambo', 36 | age: 45, 37 | gender: 'male', 38 | newsletter: true, 39 | date_registered: '2015-06-11' 40 | }), 41 | new TestModel({ 42 | id: 4, 43 | name: 'Frostella the Snowlady', 44 | age: 28, 45 | gender: 'female', 46 | newsletter: false, 47 | date_registered: '2016-12-25' 48 | }) 49 | ]; 50 | 51 | export const testDataNoPK = [ 52 | new TestModelNoPK({ 53 | name: 'record1', 54 | description: 'This is the first record' 55 | }), 56 | new TestModelNoPK({ 57 | name: 'record2', 58 | description: 'This is the second record' 59 | }), 60 | new TestModelNoPK({ 61 | name: 'record3', 62 | description: 'And here is a third!' 63 | }), 64 | ]; 65 | 66 | export async function createTestData(manager: IModelManager) { 67 | for (let model of testData) { 68 | await manager.create(model); 69 | } 70 | for (let model of testDataNoPK) { 71 | await manager.create(model); 72 | } 73 | } 74 | 75 | export async function removeTestData(manager: IModelManager) { 76 | await manager.remove(TestModel, { where: {}}); 77 | await manager.remove(TestModelNoPK, { where: {}}); 78 | } -------------------------------------------------------------------------------- /packages/examples/src/creating_a_ui/simple_list/simple_list.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import { ModelManager } from 'rev-models'; 5 | import { ModelApiBackend } from 'rev-api-client'; 6 | import { ModelProvider, ListView } from 'rev-ui'; 7 | import * as models from '../models'; 8 | 9 | import CssBaseline from '@material-ui/core/CssBaseline'; 10 | import AppBar from '@material-ui/core/AppBar'; 11 | import Toolbar from '@material-ui/core/Toolbar'; 12 | import Typography from '@material-ui/core/Typography'; 13 | import Card from '@material-ui/core/Card'; 14 | 15 | import { registerComponents } from 'rev-ui-materialui'; 16 | registerComponents(); 17 | 18 | export const modelManager = new ModelManager(); 19 | modelManager.registerBackend('default', new ModelApiBackend('http://localhost:3000/graphql')); 20 | modelManager.register(models.User); 21 | modelManager.register(models.Post); 22 | modelManager.register(models.Comment); 23 | 24 | ReactDOM.render(( 25 | 26 | 27 | 28 | 29 | 30 | RevJS - Simple List Demo 31 | 32 | 33 | 34 | 35 | 36 | { 50 | alert('Selected a post:\n' + JSON.stringify(post, null, 2)); 51 | }} 52 | /> 53 | 54 | 55 | 56 | ), 57 | document.getElementById('app') 58 | ); 59 | -------------------------------------------------------------------------------- /packages/examples/src/creating_an_api/defining_an_api.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AutoNumberField, TextField, RelatedModel, RelatedModelList, 4 | ModelManager, InMemoryBackend 5 | } from 'rev-models'; 6 | import { ApiOperations } from 'rev-api/lib/decorators'; 7 | import { ModelApiManager } from 'rev-api'; 8 | 9 | // Use the @ApiOperations decorator to specify what operations are allowed for each model 10 | 11 | @ApiOperations( 12 | ['read'] 13 | ) 14 | export class User { 15 | @AutoNumberField({ primaryKey: true }) 16 | id: number; 17 | @TextField() 18 | username: string; 19 | @RelatedModelList({ model: 'Post', field: 'user' }) 20 | posts: Post[]; 21 | @RelatedModelList({ model: 'Comment', field: 'user' }) 22 | comments: Comment[]; 23 | 24 | constructor(data?: Partial) { 25 | Object.assign(this, data); 26 | } 27 | } 28 | 29 | @ApiOperations( 30 | ['create', 'read', 'update', 'remove'] 31 | ) 32 | export class Post { 33 | @AutoNumberField({ primaryKey: true }) 34 | id: number; 35 | @RelatedModel({ model: 'User' }) 36 | user: User; 37 | @TextField() 38 | title: string; 39 | @TextField({ multiLine: true }) 40 | body: string; 41 | @RelatedModelList({ model: 'Comment', field: 'post' }) 42 | comments: Comment[]; 43 | 44 | constructor(data?: Partial) { 45 | Object.assign(this, data); 46 | } 47 | } 48 | 49 | @ApiOperations( 50 | ['create', 'read', 'remove'] 51 | ) 52 | export class Comment { 53 | @AutoNumberField({ primaryKey: true }) 54 | id: number; 55 | @RelatedModel({ model: 'Post' }) 56 | post: Post; 57 | @RelatedModel({ model: 'User', required: false }) 58 | user: User; 59 | @TextField() 60 | comment: string; 61 | 62 | constructor(data?: Partial) { 63 | Object.assign(this, data); 64 | } 65 | } 66 | 67 | export const modelManager = new ModelManager(); 68 | modelManager.registerBackend('default', new InMemoryBackend()); 69 | modelManager.register(User); 70 | modelManager.register(Post); 71 | modelManager.register(Comment); 72 | 73 | export const api = new ModelApiManager(modelManager); 74 | api.register(User); 75 | api.register(Post); 76 | api.register(Comment); -------------------------------------------------------------------------------- /packages/rev-models/src/queries/nodes/__tests__/valuelist.tests.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import * as d from '../../../decorators'; 4 | import { QueryParser } from '../../queryparser'; 5 | import { ValueListOperator } from '../valuelist'; 6 | import { ModelManager } from '../../../models/manager'; 7 | import { InMemoryBackend } from '../../../backends/inmemory/backend'; 8 | 9 | class TestModel { 10 | @d.IntegerField() 11 | id: number; 12 | @d.TextField() 13 | name: string; 14 | @d.BooleanField() 15 | active: boolean; 16 | } 17 | 18 | let manager = new ModelManager(); 19 | manager.registerBackend('default', new InMemoryBackend()); 20 | manager.register(TestModel); 21 | let parser = new QueryParser(manager); 22 | 23 | describe('class ValueListOperator - constructor', () => { 24 | 25 | it('throws if operator is not a field operator', () => { 26 | expect(() => { 27 | new ValueListOperator(parser, TestModel, '_and', [], undefined); 28 | }).to.throw('unrecognised field operator'); 29 | }); 30 | 31 | it('throws if value is not an array', () => { 32 | expect(() => { 33 | new ValueListOperator(parser, TestModel, '_in', null as any, undefined); 34 | }).to.throw('must be an array'); 35 | expect(() => { 36 | new ValueListOperator(parser, TestModel, '_in', {} as any, undefined); 37 | }).to.throw('must be an array'); 38 | }); 39 | 40 | it('throws if an array element is not a field value', () => { 41 | expect(() => { 42 | new ValueListOperator(parser, TestModel, '_in', ['a', new RegExp('a')], undefined); 43 | }).to.throw('invalid field value'); 44 | }); 45 | 46 | it('does not throw if value array is empty', () => { 47 | expect(() => { 48 | new ValueListOperator(parser, TestModel, '_in', [], undefined); 49 | }).to.not.throw(); 50 | }); 51 | 52 | it('stores the operator and values as public properties', () => { 53 | let valueList = ['a', 'b']; 54 | let node = new ValueListOperator(parser, TestModel, '_in', valueList, undefined); 55 | expect(node.operator).to.equal('in'); 56 | expect(node.values).to.deep.equal(valueList); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /packages/rev-ui-materialui/src/fields/MUITextField.tsx: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import FormControl from '@material-ui/core/FormControl'; 6 | import FormHelperText from '@material-ui/core/FormHelperText'; 7 | import Input from '@material-ui/core/Input'; 8 | import InputLabel from '@material-ui/core/InputLabel'; 9 | 10 | import { PasswordField, ITextFieldOptions } from 'rev-models/lib/fields'; 11 | import { IFieldComponentProps } from 'rev-ui/lib/fields/Field'; 12 | import { getGridWidthProps } from './utils'; 13 | 14 | export const MUITextField: React.StatelessComponent = (props) => { 15 | 16 | const gridWidthProps = getGridWidthProps(props); 17 | const fieldId = props.field.name; 18 | 19 | let type = 'text'; 20 | if (props.field instanceof PasswordField) { 21 | type = 'password'; 22 | } 23 | 24 | let error = props.errors.length > 0; 25 | let errorText = ''; 26 | props.errors.forEach((err) => { 27 | errorText += err.message + '. '; 28 | }); 29 | 30 | const opts: ITextFieldOptions = props.field.options; 31 | const mlOptions: any = {}; 32 | if (opts.multiLine) { 33 | mlOptions.multiline = true; 34 | mlOptions.rowsMax = 5; 35 | mlOptions.rows = 5; 36 | } 37 | 38 | return ( 39 | 40 | 41 | 42 | 46 | {props.label} 47 | 48 | props.onChange(event.target.value)} 53 | error={error} 54 | disabled={props.disabled} 55 | {...mlOptions} 56 | /> 57 | {errorText && 58 | 59 | {errorText} 60 | } 61 | 62 | 63 | 64 | ); 65 | }; 66 | --------------------------------------------------------------------------------