├── .gitignore ├── README.md ├── assests ├── repositoryarch.jpg └── repositoryworkfollow.jpg ├── example ├── reduxrepository │ ├── .eslintrc.js │ ├── .prettierrc.js │ ├── README.md │ ├── babel.config.js │ ├── metro.config.js │ ├── package.json │ ├── repocgcnf.json │ ├── src │ │ ├── App.js │ │ ├── models │ │ │ ├── AppState.ts │ │ │ └── Todo.ts │ │ ├── repositories │ │ │ ├── concretes │ │ │ │ └── TodoRepository.ts │ │ │ └── interfaces │ │ │ │ └── ITodoRepository.ts │ │ ├── services │ │ │ ├── TodoService.ts │ │ │ └── UnitOfWork.ts │ │ └── store │ │ │ ├── actions │ │ │ └── todoFacade.ts │ │ │ ├── middleware │ │ │ └── middleware.js │ │ │ └── reducer │ │ │ ├── AppReducerService.ts │ │ │ ├── ReducerServices.ts │ │ │ ├── TitleReducer.ts │ │ │ └── reducers.js │ └── tsconfig.json └── repository │ ├── .eslintrc.js │ ├── .prettierrc.js │ ├── README.md │ ├── babel.config.js │ ├── metro.config.js │ ├── package.json │ ├── repocgcnf.json │ ├── src │ ├── models │ │ └── Todo.ts │ ├── repositories │ │ ├── concretes │ │ │ └── TodoRepository.ts │ │ └── interfaces │ │ │ └── ITodoRepository.ts │ └── services │ │ ├── TodoService.ts │ │ └── UnitOfWork.ts │ └── tsconfig.json └── src ├── azureCosmos ├── .eslintrc.js ├── .prettierrc.js ├── AzureCosmosEntityMetaDataContext.ts ├── AzureCosmosFetch.ts ├── AzureCosmosRepository.ts ├── AzureFetchEntityMetaData.ts ├── AzureGermlinEntityMetaData.ts ├── AzureGermlinFetch.ts ├── AzureGermlinRepository.ts ├── README.md ├── init.ts └── package.json ├── codegenerator ├── README.md ├── bin │ └── index.js ├── package.json └── tsconfig.json ├── firestore ├── .eslintrc.js ├── .prettierrc.js ├── FirestoreEntityMetaData.ts ├── FirestoreEntityMetaDataContext.ts ├── FirestoreRepository.ts ├── README.md └── package.json ├── redux ├── .eslintrc.js ├── .prettierrc.js ├── BaseReducer.ts ├── BaseReduxService.ts ├── DefaultReducer.ts ├── DefualtReducerService.ts ├── IReducer.ts ├── IReduxService.ts ├── README.md └── package.json └── repository ├── .eslintrc.js ├── .prettierrc.js ├── BaseSerivce.ts ├── NullNetworking.ts ├── README.md ├── RepositoryBase.ts ├── RepositoryHttpBase.ts ├── interfaces ├── INetworking.ts ├── IRepository.ts └── IService.ts ├── models ├── Param.ts ├── QueryContext.ts ├── Result.ts ├── ResultArray.ts ├── interfaces │ ├── IEntityMetaData.ts │ └── IResult.ts └── nullValue.ts └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | src/repository/node_modules/ 2 | src/redux/node_modules/ 3 | src/firestore/node_modules/ 4 | src/codegenerator/bin/index.ts 5 | src/codegenerator/node_modules/ 6 | src/azureCosmos/node_modules/ 7 | 8 | 9 | 10 | src/repository/package-lock.json 11 | src/redux/package-lock.json 12 | src/firestore/package-lock.json 13 | src/codegenerator/package-lock.json 14 | src/azureCosmos/package-lock.json 15 | 16 | 17 | example/repository/node_modules/ 18 | example/reduxrepository/node_modules/ 19 | 20 | 21 | 22 | example/repository/package-lock.json 23 | example/reduxrepository/package-lock.json 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Map customData = new HashMap<>(); 3 | customData.put("CUSTOM_DATA_KEY", "{\"WIDGET_ID_KEY\":\"expectedWidgetId\"}"); 4 | 5 | 6 | import static org.mockito.Mockito.*; 7 | 8 | ConsumerRecord mockRecord = mock(new TypeToken>(){}.getRawType()); 9 | 10 | import static org.junit.Assert.*; 11 | import static org.mockito.Mockito.*; 12 | 13 | import org.json.JSONObject; 14 | import org.junit.Test; 15 | 16 | public class MessageStoreUtilTest { 17 | 18 | @Test 19 | public void testShouldStoreMessage_WithMatchingAppKey_ReturnsTrue() { 20 | // Arrange 21 | String incomingAppKey = "appKey"; 22 | PushProcessorEvent event = mock(PushProcessorEvent.class); 23 | when(event.getAppUID()).thenReturn(incomingAppKey); 24 | when(event.getData()).thenReturn(null); 25 | 26 | // Act 27 | boolean result = MessageStoreUtil.shouldStoreMessage(incomingAppKey, event); 28 | 29 | // Assert 30 | assertTrue(result); 31 | } 32 | 33 | @Test 34 | public void testShouldStoreMessage_WithCustomDataAndMatchingWidgetId_ReturnsTrue() { 35 | // Arrange 36 | JSONObject customData = new JSONObject(); 37 | customData.put("WIDGET_ID_KEY", "expectedWidgetId"); 38 | 39 | PushProcessorEvent event = mock(PushProcessorEvent.class); 40 | when(event.getData()).thenReturn(customData); 41 | when(event.getAppUID()).thenReturn("differentAppKey"); 42 | 43 | // Act 44 | boolean result = MessageStoreUtil.shouldStoreMessage("appKey", event); 45 | 46 | // Assert 47 | assertTrue(result); 48 | } 49 | 50 | @Test 51 | public void testShouldStoreMessage_WithNonMatchingConditions_ReturnsFalse() { 52 | // Arrange 53 | PushProcessorEvent event = mock(PushProcessorEvent.class); 54 | when(event.getAppUID()).thenReturn("differentAppKey"); 55 | when(event.getData()).thenReturn(null); 56 | 57 | // Act 58 | boolean result = MessageStoreUtil.shouldStoreMessage("appKey", event); 59 | 60 | // Assert 61 | assertFalse(result); 62 | } 63 | 64 | // Additional tests can be added to cover other branches and edge cases 65 | } 66 | 67 | 68 | 69 | # js-frontend-repository 70 | The Repository pattern is a well-documented way of working with a data source. In this library I used this pattern for manage API calls from javascript base frontend applications 71 | 72 | # Benefits 73 | 74 | - It centralizes API calls 75 | - It gives a substitution point for the unit tests. 76 | - Provides a flexible architecture 77 | - It reduces redundancy of code 78 | - It force programmer to work using the same pattern 79 | - If you use this pattern then it is easy to maintain the centralized data access logic 80 | - centralized API call 81 | - centralized error handeling 82 | - Can use diffrents API Gatways in same project with no change in Service level 83 | - Adding SOLID principals to your project 84 | 85 | 86 | # Diagram 87 | 88 | 89 | # Install 90 | 91 | ```bash 92 | npm i js-frontend-repository 93 | ``` 94 | # How to use it 95 | 96 | 97 | ## Models 98 | For each API call response you should add a model based on data you fetched from API for example if I want to fetch Todo list from API with properties of title and isDone you should add a model like this : 99 | 100 | ```javascript 101 | export class Todo { 102 | title? : string; 103 | 104 | isDone? : boolean; 105 | } 106 | ``` 107 | 108 | ## IRepository 109 | For each model you should add one generic IRepository model of that entity, this Interface will be use on Service concretes of the entity and with help of this IRepository you can avoid changes in Service layer if you want to change Repository layer 110 | 111 | ```javascript 112 | import { IRepository } from 'js-frontend-repository/interfaces/IRepository'; 113 | import { Todo } from '@/src/models/Todo'; 114 | 115 | export interface ITodoRepository extends IRepository { 116 | } 117 | 118 | ``` 119 | 120 | ## Repository concrete 121 | 122 | This file will contain all logic related to API calls for example if you are using GraphQL API, all queries should be developed in this class. Or if you are using Firestore API all related queries should be developed in this class. 123 | 124 | Usually instead of inherit from IRepository in this class it is better practice to inherit from one base repository. 125 | current project have already three base repository developed, you can use this base repository in scenario you want to fetch data against Firestore , Azure Cosmos or Azure Cosmos Graph Gremlin if you want to use another service or your own custom API call you should develop the base Repository related to that first 126 | 127 | For more information about implemented Repository base class please refer to each Repository base page : 128 | 129 | - Firestore Repository base 130 | - Azure cosmos and Azure cosmos Gremlin Repository base 131 | 132 | ```javascript 133 | 134 | import { FirestoreRepository } from 'react-native-firesotre-repository/FirestoreRepository'; 135 | import { Todo } from '@/src/models/Todo'; 136 | import { FirestoreEntityMetaData } from 'react-native-firesotre-repository/FirestoreEntityMetaData'; 137 | import { FirestoreEntityMetaDataContext } from 'react-native-firesotre-repository/FirestoreEntityMetaDataContext'; 138 | 139 | export class TodoRepository extends FirestoreRepository { 140 | constructor() { 141 | super(Todo, new FirestoreEntityMetaData(new FirestoreEntityMetaDataContext())); 142 | } 143 | } 144 | ``` 145 | 146 | you can find example project : Here 147 | 148 | ## Service 149 | For each entity you should add one service class too. Service class is responsible for logic across one entity, for example if you want to fetch Todo with Id = 2 and then change the isDone to true and update the result you should develop the logic of that in this class. 150 | Service is for logic across one entity, and Repository class is for query of those logic. 151 | 152 | ```javascript 153 | import { ITodoRepository } from '@/src/repositories/interfaces/ITodoRepository'; 154 | import { Todo } from '@/src/models/Todo'; 155 | import { BaseService } from 'js-frontend-repository/BaseSerivce'; 156 | 157 | export class TodoService extends BaseService { 158 | async updateTodo(id: string) { 159 | const todo = await (await this.repository.getById(id)).entity; 160 | todo.isDone = true; 161 | await this.repository.update(todo) 162 | } 163 | } 164 | 165 | ``` 166 | As you can see if you change the repository behind the Todo model this file will not change 167 | 168 | ## UnitOfWork.ts 169 | This singleton file holds all instances of services, and also in this file you can assign a concert repository for each service. With help of this file you can change the repository of services easily and this change will not need to change any logic on service and other layers of application. 170 | 171 | ```javascript 172 | // new service import placeholder 173 | // new service import placeholder 174 | import { TodoRepository } from '@/src/repositories/concretes/TodoRepository'; 175 | import { TodoService } from '@/src/services/TodoService'; 176 | 177 | export class UnitOfWork { 178 | // new service placeholder 179 | todoService: TodoService 180 | 181 | private static innerInstance: UnitOfWork; 182 | 183 | public static instance(): UnitOfWork { 184 | if (!UnitOfWork.innerInstance) { 185 | UnitOfWork.innerInstance = new UnitOfWork(); 186 | } 187 | return UnitOfWork.innerInstance; 188 | } 189 | 190 | private constructor() { 191 | // new service init placeholder 192 | this.todoService = new TodoService(TodoRepository); 193 | } 194 | } 195 | ``` 196 | 197 | # Code Generator 198 | As you can see for adding new Entity in the repository pattern you have to add 3 files and modify one, to develop faster and avoid duplicate work you can use the “Repository Code Generator” package. 199 | 200 | ## Install 201 | 202 | ```bash 203 | npm i -g rpcodegen 204 | ``` 205 | 206 | For more information please see Repository Code Generator page : 207 | Repository Code Generator 208 | 209 | 210 | # Redux 211 | If you want to use Redux in your application and you want to manage the API call with repository pattern and automatically dispatch them on State and also if your looking for better solution to design your Actions and Routers in Redux you can use this package 212 | 213 | ```bash 214 | npm i react-redux-repository 215 | ``` 216 | 217 | For more information ablut Redux Repository please visit: 218 | React Redux Repository 219 | 220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /assests/repositoryarch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blazerroadg/js-frontend-repository/d4bb39d3fe58a0f4de8b02707530a5bf8b5d5b84/assests/repositoryarch.jpg -------------------------------------------------------------------------------- /assests/repositoryworkfollow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blazerroadg/js-frontend-repository/d4bb39d3fe58a0f4de8b02707530a5bf8b5d5b84/assests/repositoryworkfollow.jpg -------------------------------------------------------------------------------- /example/reduxrepository/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/react", 11 | "airbnb" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2019, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/no-empty-interface": "off", 28 | "import/extensions": [ 29 | "error", 30 | "ignorePackages", 31 | { 32 | "js": "never", 33 | "jsx": "never", 34 | "ts": "never", 35 | "tsx": "never" 36 | } 37 | ], 38 | "no-unused-vars": "off", 39 | "@typescript-eslint/no-unused-vars": ["error"], 40 | "import/prefer-default-export" : "warn", 41 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 42 | "class-methods-use-this" : "off" 43 | }, 44 | "settings": { 45 | "import/resolver": { 46 | "node": { 47 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 48 | } 49 | } 50 | 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /example/reduxrepository/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /example/reduxrepository/README.md: -------------------------------------------------------------------------------- 1 | # chikoappgen2 -------------------------------------------------------------------------------- /example/reduxrepository/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/reduxrepository/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | babelTransformerPath: require.resolve('react-native-typescript-transformer') 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /example/reduxrepository/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repositorysample", 3 | "version": "0.0.1", 4 | "scripts": {}, 5 | "dependencies": { 6 | "js-azurecosmos-client-repository": "latest", 7 | "react-redux-repository": "latest", 8 | "@types/react-redux": "^7.1.11", 9 | "react-native-firesotre-repository": "^1.0.0", 10 | "react-native-typescript-transformer": "^1.2.13", 11 | "react-redux": "^7.2.2", 12 | "redux": "^4.0.5" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.0.5", 16 | "metro-react-native-babel-preset": "0.59.0", 17 | "eslint-config-airbnb": "^18.2.1", 18 | "eslint-config-prettier": "^7.0.0", 19 | "eslint-plugin-import": "^2.22.1", 20 | "eslint-plugin-jsx-a11y": "^6.4.1", 21 | "eslint-plugin-prettier": "^3.2.0", 22 | "prettier": "^2.2.1", 23 | "@typescript-eslint/eslint-plugin": "^4.9.1", 24 | "@typescript-eslint/parser": "^4.9.1", 25 | "eslint": "^7.15.0", 26 | "eslint-plugin-react": "^7.21.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/reduxrepository/repocgcnf.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths" : { 3 | "iRepositoryURI": "/src/repositories/interfaces/", 4 | "repositoryURI": "/src/repositories/concretes/", 5 | "serviceURI": "/src/services/" 6 | }, 7 | "modelPath": "@/src/models/", 8 | "baseSeviceType": "base", 9 | "baseRepositories": [ 10 | { 11 | "alias": "firestore", 12 | "name": "FirestoreRepository", 13 | "path": "react-native-firesotre-repository/FirestoreRepository" 14 | }, 15 | { 16 | "alias": "cosmos", 17 | "name": "AzureCosmosRepository", 18 | "path": "js-azurecosmos-client-repository/AzureCosmosRepository" 19 | }, 20 | { 21 | "alias": "cosmosg", 22 | "name": "AzureGermlinRepository", 23 | "path": "js-azurecosmos-client-repository/AzureGermlinRepository" 24 | } 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /example/reduxrepository/src/App.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import { crashReporter, vanillaPromise, readyStatePromise } from "./store/middleware/middleware"; 6 | 7 | import { appReducer } from './store/reducers/reducersFacad' 8 | import { Services } from "./store/actions/services/UnitOfWork" 9 | 10 | const store = createStore(appReducer, applyMiddleware(crashReporter, vanillaPromise, readyStatePromise)); 11 | Services.init(store); 12 | 13 | export default function App() { 14 | return ( 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/reduxrepository/src/models/AppState.ts: -------------------------------------------------------------------------------- 1 | import { Todo } from './Todo'; 2 | 3 | export class AppState { 4 | todo: Todo = new Todo(); 5 | } 6 | -------------------------------------------------------------------------------- /example/reduxrepository/src/models/Todo.ts: -------------------------------------------------------------------------------- 1 | export class Todo { 2 | title? : string; 3 | 4 | isDone? : boolean; 5 | } 6 | -------------------------------------------------------------------------------- /example/reduxrepository/src/repositories/concretes/TodoRepository.ts: -------------------------------------------------------------------------------- 1 | import { FirestoreRepository } from 'react-native-firesotre-repository/FirestoreRepository'; 2 | import { Todo } from '@/src/models/Todo'; 3 | import { FirestoreEntityMetaData } from 'react-native-firesotre-repository/FirestoreEntityMetaData'; 4 | import { FirestoreEntityMetaDataContext } from 'react-native-firesotre-repository/FirestoreEntityMetaDataContext'; 5 | 6 | export class TodoRepository extends FirestoreRepository { 7 | constructor() { 8 | super(Todo, new FirestoreEntityMetaData(new FirestoreEntityMetaDataContext())); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/reduxrepository/src/repositories/interfaces/ITodoRepository.ts: -------------------------------------------------------------------------------- 1 | import { IRepository } from 'js-frontend-repository/interfaces/IRepository'; 2 | import { Todo } from '@/src/models/Todo'; 3 | 4 | export interface ITodoRepository extends IRepository { 5 | } 6 | -------------------------------------------------------------------------------- /example/reduxrepository/src/services/TodoService.ts: -------------------------------------------------------------------------------- 1 | import { ITodoRepository } from '@/src/repositories/interfaces/ITodoRepository'; 2 | import { Todo } from '@/src/models/Todo'; 3 | import { BaseReduxService } from 'react-redux-repository/BaseReduxService'; 4 | 5 | export class TodoService extends BaseReduxService { 6 | } 7 | -------------------------------------------------------------------------------- /example/reduxrepository/src/services/UnitOfWork.ts: -------------------------------------------------------------------------------- 1 | // new service import placeholder 2 | import { TodoRepository } from '@/src/repositories/concretes/TodoRepository'; 3 | import { TodoService } from '@/src/services/TodoService'; 4 | import { Store } from 'redux'; 5 | import { AppState } from '../models/AppState'; 6 | 7 | export class UnitOfWork { 8 | public store: Store; 9 | 10 | // new service placeholder 11 | todoService: TodoService 12 | 13 | public static instance: UnitOfWork; 14 | 15 | public static init(store: Store) { 16 | UnitOfWork.instance = new UnitOfWork(store); 17 | } 18 | 19 | private constructor(store: Store) { 20 | this.store = store; 21 | 22 | // new service init placeholder 23 | this.todoService = new TodoService(TodoRepository); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/reduxrepository/src/store/actions/todoFacade.ts: -------------------------------------------------------------------------------- 1 | import { UnitOfWork } from '@/src/services/UnitOfWork'; 2 | 3 | export const getTodo = async () => { 4 | await UnitOfWork.instance.todoService.all('SETTITLE'); 5 | }; 6 | -------------------------------------------------------------------------------- /example/reduxrepository/src/store/middleware/middleware.js: -------------------------------------------------------------------------------- 1 | 2 | export const crashReporter = store => next => action => { 3 | try { 4 | return next(action) 5 | } catch (err) { 6 | console.error('Caught an exception!', err) 7 | throw err 8 | } 9 | } 10 | 11 | export const thunk = store => next => action => { 12 | if (typeof action === 'function') { 13 | action(store.dispatch, store.getState) 14 | return; 15 | } 16 | next(action) 17 | } 18 | 19 | 20 | export const vanillaPromise = store => next => action => { 21 | if (typeof action.then !== 'function') { 22 | return next(action) 23 | } 24 | return Promise.resolve(action); 25 | } 26 | 27 | 28 | export const readyStatePromise = store => next => action => { 29 | if (!action.promise) { 30 | return next(action) 31 | } 32 | 33 | 34 | function makeAction(ready, data) { 35 | const newAction = Object.assign({}, action, { ready }, data) 36 | delete newAction.promise 37 | return newAction 38 | } 39 | 40 | next(makeAction(false)) 41 | return action.promise.then( 42 | result => next(makeAction(true, { result })), 43 | error => next(makeAction(true, { error })) 44 | ) 45 | } -------------------------------------------------------------------------------- /example/reduxrepository/src/store/reducer/AppReducerService.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from '@/src/models/AppState'; 2 | import { DefualtReducerService } from 'react-redux-repository/DefualtReducerService'; 3 | import { TitleReducer } from './TitleReducer'; 4 | 5 | export class AppReducerService extends DefualtReducerService { 6 | constructor() { 7 | const appState = new AppState(); 8 | super(appState, [ 9 | new TitleReducer(appState, ['SETTITLE']), 10 | ]); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/reduxrepository/src/store/reducer/ReducerServices.ts: -------------------------------------------------------------------------------- 1 | import { AppReducerService } from './AppReducerService'; 2 | 3 | export class ReducerServices { 4 | public static _instance: ReducerServices; 5 | 6 | public static instance(): ReducerServices { 7 | if (!ReducerServices._instance) { 8 | ReducerServices._instance = new ReducerServices(); 9 | } 10 | return ReducerServices._instance; 11 | } 12 | 13 | app: AppReducerService = new AppReducerService(); 14 | 15 | private constructor() { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/reduxrepository/src/store/reducer/TitleReducer.ts: -------------------------------------------------------------------------------- 1 | import { BaseReducer } from 'react-redux-repository/BaseReducer'; 2 | import { AppState } from '../../models/AppState'; 3 | 4 | export class TitleReducer extends BaseReducer { 5 | reduce(state: AppState, action: any): AppState { 6 | const innerState = state; 7 | innerState.todo.title = action.title; 8 | return innerState; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/reduxrepository/src/store/reducer/reducers.js: -------------------------------------------------------------------------------- 1 | import { ReducerServices } from './ReducerServices' 2 | 3 | 4 | export const appReducer = (state, action) => { 5 | return ReducerServices.instance().app.reduce(state,action) 6 | } 7 | -------------------------------------------------------------------------------- /example/reduxrepository/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "isolatedModules": true, 7 | "jsx": "react", 8 | "lib": [ 9 | "es6" 10 | ], 11 | "baseUrl": "./", 12 | "moduleResolution": "node", 13 | "noEmit": true, 14 | "strict": true, 15 | "target": "esnext", 16 | "resolveJsonModule": true, 17 | "paths": { 18 | "@/*": [ 19 | "./*" 20 | ], 21 | } 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "babel.config.js", 26 | "metro.config.js", 27 | "jest.config.js" 28 | ] 29 | } -------------------------------------------------------------------------------- /example/repository/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/react", 11 | "airbnb" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2019, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/no-empty-interface": "off", 28 | "import/extensions": [ 29 | "error", 30 | "ignorePackages", 31 | { 32 | "js": "never", 33 | "jsx": "never", 34 | "ts": "never", 35 | "tsx": "never" 36 | } 37 | ], 38 | "no-unused-vars": "off", 39 | "@typescript-eslint/no-unused-vars": ["error"], 40 | "import/prefer-default-export" : "warn", 41 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 42 | "class-methods-use-this" : "off" 43 | }, 44 | "settings": { 45 | "import/resolver": { 46 | "node": { 47 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 48 | } 49 | } 50 | 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /example/repository/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /example/repository/README.md: -------------------------------------------------------------------------------- 1 | # chikoappgen2 -------------------------------------------------------------------------------- /example/repository/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/repository/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | babelTransformerPath: require.resolve('react-native-typescript-transformer') 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /example/repository/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repositorysample", 3 | "version": "0.0.1", 4 | "scripts": {}, 5 | "dependencies": { 6 | "js-frontend-repository": "latest", 7 | "js-azurecosmos-client-repository": "latest", 8 | "react-native-firesotre-repository": "latest", 9 | "react-native-typescript-transformer": "latest" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^4.0.5", 13 | "metro-react-native-babel-preset": "0.59.0", 14 | "eslint-config-airbnb": "^18.2.1", 15 | "eslint-config-prettier": "^7.0.0", 16 | "eslint-plugin-import": "^2.22.1", 17 | "eslint-plugin-jsx-a11y": "^6.4.1", 18 | "eslint-plugin-prettier": "^3.2.0", 19 | "prettier": "^2.2.1", 20 | "@typescript-eslint/eslint-plugin": "^4.9.1", 21 | "@typescript-eslint/parser": "^4.9.1", 22 | "eslint": "^7.15.0", 23 | "eslint-plugin-react": "^7.21.5" 24 | } 25 | } -------------------------------------------------------------------------------- /example/repository/repocgcnf.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths" : { 3 | "iRepositoryURI": "/src/repositories/interfaces/", 4 | "repositoryURI": "/src/repositories/concretes/", 5 | "serviceURI": "/src/services/" 6 | }, 7 | "modelPath": "@/src/models/", 8 | "baseSeviceType": "base", 9 | "baseRepositories": [ 10 | { 11 | "alias": "firestore", 12 | "name": "FirestoreRepository", 13 | "path": "react-native-firesotre-repository/FirestoreRepository" 14 | }, 15 | { 16 | "alias": "cosmos", 17 | "name": "AzureCosmosRepository", 18 | "path": "js-azurecosmos-client-repository/AzureCosmosRepository" 19 | }, 20 | { 21 | "alias": "cosmosg", 22 | "name": "AzureGermlinRepository", 23 | "path": "js-azurecosmos-client-repository/AzureGermlinRepository" 24 | } 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /example/repository/src/models/Todo.ts: -------------------------------------------------------------------------------- 1 | export class Todo { 2 | title? : string; 3 | 4 | isDone? : boolean; 5 | } 6 | -------------------------------------------------------------------------------- /example/repository/src/repositories/concretes/TodoRepository.ts: -------------------------------------------------------------------------------- 1 | import { FirestoreRepository } from 'react-native-firesotre-repository/FirestoreRepository'; 2 | import { Todo } from '@/src/models/Todo'; 3 | import { FirestoreEntityMetaData } from 'react-native-firesotre-repository/FirestoreEntityMetaData'; 4 | import { FirestoreEntityMetaDataContext } from 'react-native-firesotre-repository/FirestoreEntityMetaDataContext'; 5 | 6 | export class TodoRepository extends FirestoreRepository { 7 | constructor() { 8 | super(Todo, new FirestoreEntityMetaData(new FirestoreEntityMetaDataContext())); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/repository/src/repositories/interfaces/ITodoRepository.ts: -------------------------------------------------------------------------------- 1 | import { IRepository } from 'js-frontend-repository/interfaces/IRepository'; 2 | import { Todo } from '@/src/models/Todo'; 3 | 4 | export interface ITodoRepository extends IRepository { 5 | } 6 | -------------------------------------------------------------------------------- /example/repository/src/services/TodoService.ts: -------------------------------------------------------------------------------- 1 | import { ITodoRepository } from '@/src/repositories/interfaces/ITodoRepository'; 2 | import { Todo } from '@/src/models/Todo'; 3 | import { BaseService } from 'js-frontend-repository/BaseSerivce'; 4 | 5 | export class TodoService extends BaseService { 6 | } 7 | -------------------------------------------------------------------------------- /example/repository/src/services/UnitOfWork.ts: -------------------------------------------------------------------------------- 1 | // new service import placeholder 2 | import { TodoRepository } from '@/src/repositories/concretes/TodoRepository'; 3 | import { TodoService } from '@/src/services/TodoService'; 4 | 5 | export class UnitOfWork { 6 | // new service placeholder 7 | todoService: TodoService 8 | 9 | private static innerInstance: UnitOfWork; 10 | 11 | public static instance(): UnitOfWork { 12 | if (!UnitOfWork.innerInstance) { 13 | UnitOfWork.innerInstance = new UnitOfWork(); 14 | } 15 | return UnitOfWork.innerInstance; 16 | } 17 | 18 | private constructor() { 19 | // new service init placeholder 20 | this.todoService = new TodoService(TodoRepository); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/repository/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "isolatedModules": true, 7 | "jsx": "react", 8 | "lib": [ 9 | "es6" 10 | ], 11 | "baseUrl": "./", 12 | "moduleResolution": "node", 13 | "noEmit": true, 14 | "strict": true, 15 | "target": "esnext", 16 | "resolveJsonModule": true, 17 | "paths": { 18 | "@/*": [ 19 | "./*" 20 | ], 21 | } 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "babel.config.js", 26 | "metro.config.js", 27 | "jest.config.js" 28 | ] 29 | } -------------------------------------------------------------------------------- /src/azureCosmos/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/react", 11 | "airbnb" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2019, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/no-empty-interface": "off", 28 | "import/extensions": [ 29 | "error", 30 | "ignorePackages", 31 | { 32 | "js": "never", 33 | "jsx": "never", 34 | "ts": "never", 35 | "tsx": "never" 36 | } 37 | ], 38 | "no-unused-vars": "off", 39 | "@typescript-eslint/no-unused-vars": ["error"], 40 | "import/prefer-default-export" : "warn", 41 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 42 | "class-methods-use-this" : "off" 43 | }, 44 | "settings": { 45 | "import/resolver": { 46 | "node": { 47 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 48 | } 49 | } 50 | 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/azureCosmos/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureCosmosEntityMetaDataContext.ts: -------------------------------------------------------------------------------- 1 | export class AzureCosmosEntityMetaDataContext { 2 | dbName: string; 3 | 4 | col: string; 5 | 6 | entityType: string; 7 | 8 | constructor(dbName = '', col = '', entityType = "'") { 9 | this.dbName = dbName; 10 | this.col = col; 11 | this.entityType = entityType; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureCosmosFetch.ts: -------------------------------------------------------------------------------- 1 | import { INetworking } from 'js-frontend-repository/interfaces/INetworking'; 2 | import { BaseFetchParam, azurefetch } from 'react-native-azure-cosmos/azurecosmos'; 3 | 4 | export class AzureCosmosFetch implements INetworking { 5 | fetch(param: BaseFetchParam): Promise { 6 | return azurefetch(param); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureCosmosRepository.ts: -------------------------------------------------------------------------------- 1 | import { QueryContext } from 'js-frontend-repository/models/QueryContext'; 2 | import { Result } from 'js-frontend-repository/models/Result'; 3 | import { ResultArray } from 'js-frontend-repository/models/ResultArray'; 4 | import { RepositoryHttpBase } from 'js-frontend-repository/RepositoryHttpBase'; 5 | import { BaseFetchParamDefualt, UpdateFetchParamDefault } from 'react-native-azure-cosmos'; 6 | import { AzureFetchEntityMetaData } from './AzureFetchEntityMetaData'; 7 | 8 | export class AzureCosmosRepository extends RepositoryHttpBase { 9 | metaData: AzureFetchEntityMetaData; 10 | 11 | constructor(entityType: new () => TEntity, metaData: AzureFetchEntityMetaData) { 12 | super(entityType); 13 | this.metaData = metaData; 14 | } 15 | 16 | async getById(id: string, options?: any): Promise> { 17 | if (options?.partitionKey) throw Error('partitionKey is null or empty'); 18 | const response = await this.metaData.networking.fetch( 19 | new UpdateFetchParamDefault( 20 | this.metaData.context.col, 21 | undefined, 22 | 'ById', 23 | options!.partitionKey, 24 | `${this.metaData.context.entityType}.${id}.${options!.partitionKey}`, 25 | id, 26 | this.metaData.context.dbName, 27 | ), 28 | ); 29 | const entity = await this.map(response); 30 | return this.first(entity); 31 | } 32 | 33 | async all(options?: any): Promise> { 34 | if (options?.partitionKey) throw Error('partitionKey is null or empty'); 35 | const response = await this.metaData.networking.fetch( 36 | new BaseFetchParamDefualt( 37 | this.metaData.context.col, 38 | undefined, 39 | 'AllCols', 40 | options!.partitionKey, 41 | `All${this.metaData.context.col}`, 42 | this.metaData.context.dbName, 43 | ), 44 | ); 45 | const entities = this.map(response); 46 | return entities; 47 | } 48 | 49 | async query(context: QueryContext, options?: any): Promise> { 50 | if (options.partitionKey) throw Error('options.partitionKey is not defiend'); 51 | if (options.actionname) throw Error('options.actionname is not defiend'); 52 | 53 | const response = await this.metaData.networking.fetch( 54 | new BaseFetchParamDefualt( 55 | this.metaData.context.col, 56 | { 57 | query: context.query, 58 | parameters: context.parameters, 59 | }, 60 | 'Query', 61 | options.partitionKey, 62 | options.actionname, 63 | this.metaData.context.dbName, 64 | ), 65 | ); 66 | const entities = this.map(response); 67 | return entities; 68 | } 69 | 70 | async add(entity: TEntity, options?: any): Promise> { 71 | if (options.partitionKey) throw Error('options.partitionKey is null or not defiend'); 72 | const response = await this.metaData.networking.fetch( 73 | new BaseFetchParamDefualt( 74 | this.metaData.context.col, 75 | entity, 76 | 'Insert', 77 | options.partitionKey, 78 | '', 79 | this.metaData.context.dbName, 80 | ), 81 | ); 82 | 83 | const result = new Result(); 84 | const resData = await response.json(); 85 | result.status = response.status; 86 | result.ok = response.ok; 87 | result.message = JSON.stringify(resData); 88 | return result; 89 | } 90 | 91 | async update(entity: TEntity, options?: any): Promise> { 92 | if (options.partitionKey) throw Error('options.partitionKey is null or not defiend'); 93 | if (options.id) throw Error('options.partitionKey is null or not defiend'); 94 | const response = await this.metaData.networking.fetch( 95 | new UpdateFetchParamDefault( 96 | this.metaData.context.col, 97 | entity, 98 | 'Update', 99 | options.partitionKey, 100 | '', 101 | options.id, 102 | this.metaData.context.dbName, 103 | ), 104 | ); 105 | const mapped = await this.map(response); 106 | return this.first(mapped); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureFetchEntityMetaData.ts: -------------------------------------------------------------------------------- 1 | import { IEntityMetaData } from 'js-frontend-repository/models/interfaces/IEntityMetaData'; 2 | import { AzureCosmosEntityMetaDataContext } from './AzureCosmosEntityMetaDataContext'; 3 | import { AzureCosmosFetch } from './AzureCosmosFetch'; 4 | 5 | export class AzureFetchEntityMetaData implements IEntityMetaData { 6 | context: AzureCosmosEntityMetaDataContext; 7 | 8 | networking: AzureCosmosFetch = new AzureCosmosFetch(); 9 | 10 | constructor(context: AzureCosmosEntityMetaDataContext) { 11 | this.context = context; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureGermlinEntityMetaData.ts: -------------------------------------------------------------------------------- 1 | import { IEntityMetaData } from 'js-frontend-repository/models/interfaces/IEntityMetaData'; 2 | import { AzureCosmosEntityMetaDataContext } from './AzureCosmosEntityMetaDataContext'; 3 | import { AzureGermlinFetch } from './AzureGermlinFetch'; 4 | 5 | export class AzureGermlinEntityMetaData implements IEntityMetaData { 6 | context: AzureCosmosEntityMetaDataContext; 7 | 8 | networking: AzureGermlinFetch = new AzureGermlinFetch(); 9 | 10 | constructor(context: AzureCosmosEntityMetaDataContext) { 11 | this.context = context; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureGermlinFetch.ts: -------------------------------------------------------------------------------- 1 | import { INetworking } from 'js-frontend-repository/interfaces/INetworking'; 2 | import { FetchParam, azuregermlinfetch } from 'react-native-azure-cosmos-gremlin/germlin'; 3 | 4 | export class AzureGermlinFetch implements INetworking { 5 | fetch(param: FetchParam): Promise { 6 | return azuregermlinfetch(param); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/azureCosmos/AzureGermlinRepository.ts: -------------------------------------------------------------------------------- 1 | import { nullValue } from 'js-frontend-repository/models/nullValue'; 2 | import { QueryContext } from 'js-frontend-repository/models/QueryContext'; 3 | import { Result } from 'js-frontend-repository/models/Result'; 4 | import { ResultArray } from 'js-frontend-repository/models/ResultArray'; 5 | import { RepositoryHttpBase } from 'js-frontend-repository/RepositoryHttpBase'; 6 | import { FetchParamDefualt, Param } from 'react-native-azure-cosmos-gremlin/germlin'; 7 | import { AzureGermlinEntityMetaData } from './AzureGermlinEntityMetaData'; 8 | 9 | export class AzureGermlinRepository extends RepositoryHttpBase { 10 | metaData: AzureGermlinEntityMetaData; 11 | 12 | constructor(entityType: new () => TEntity, metaData: AzureGermlinEntityMetaData) { 13 | super(entityType); 14 | this.metaData = metaData; 15 | } 16 | 17 | async getById(id: string, options?: any): Promise> { 18 | const query = `g.V('${id})`; 19 | const response = await this.metaData.networking.fetch(new FetchParamDefualt(query, 20 | nullValue, id, options?.auth)); 21 | const mapped = await this.map(response); 22 | return this.first(mapped); 23 | } 24 | 25 | async all(options?: any): Promise> { 26 | const query = `g.V().hasLabel('${this.metaData.context.col}')`; 27 | const response = await this.metaData.networking.fetch( 28 | new FetchParamDefualt(query, undefined as any, this.metaData.context.col, options?.auth), 29 | ); 30 | const entities = this.map(response); 31 | return entities; 32 | } 33 | 34 | async query(context: QueryContext, options?: any): Promise> { 35 | if (options.actionname) throw Error('options.actionname is not defiend'); 36 | const response = await this.metaData.networking.fetch(new FetchParamDefualt(context.query, 37 | context.parameters, options?.actionname, options?.auth)); 38 | const entities = this.map(response); 39 | return entities; 40 | } 41 | 42 | async add(entity: TEntity, options?: any): Promise> { 43 | let query = `g.addV('${this.metaData.context.col}')`; 44 | const parameters = new Array(); 45 | Object.keys(entity).forEach((key) => { 46 | if (!entity[key]) return; 47 | const param = `@${key}`; 48 | parameters.push(new Param(param, String(entity[key]))); 49 | query += `.property('${key}','${param}')`; 50 | }); 51 | const response = await this.metaData.networking.fetch( 52 | new FetchParamDefualt(query, parameters, '', options?.auth), 53 | ); 54 | const mapped = await this.map(response); 55 | return this.first(mapped); 56 | } 57 | 58 | async update(entity: TEntity, options?: any): Promise> { 59 | if (options.id) throw Error('options.partitionKey is null or not defiend'); 60 | let query = 'g.V(\'@id\')'; 61 | const parameters = new Array(); 62 | parameters.push(new Param('@id', options.id)); 63 | Object.keys(entity).forEach((key) => { 64 | if (!entity[key]) return; 65 | const param = `@${key}`; 66 | parameters.push(new Param(param, String(entity[key]))); 67 | query += `.property('${key}','${param}')`; 68 | }); 69 | 70 | const response = await this.metaData.networking.fetch( 71 | new FetchParamDefualt(query, parameters, '', options?.auth), 72 | ); 73 | const mapped = await this.map(response); 74 | return this.first(mapped); 75 | } 76 | 77 | async handelMap(response: Response): Promise { 78 | const result = new ResultArray(); 79 | const resData = await response.json(); 80 | if (!response || !resData) { 81 | result.status = response.status; 82 | result.ok = response.ok; 83 | result.message = JSON.stringify(resData); 84 | return result; 85 | } 86 | 87 | if (!resData || !resData.result || !Array.isArray(resData.result)) { 88 | result.status = 501; 89 | return result; 90 | } 91 | return this.innerMap(resData); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/azureCosmos/README.md: -------------------------------------------------------------------------------- 1 | # js-azurecosmos-client-repository 2 | This package provides the base Repository class to fetch data from Azure cosmos and Azure cosmos Gremlin Database. 3 | 4 | For more information about how to use please refer to each dependency 5 | 6 | 7 | # Dependencies 8 | - js-frontend-repository 9 | - react-native-azure-cosmos 10 | - react-native-azure-cosmos-gremlin 11 | - 12 | # Install 13 | ```bash 14 | npm i js-azurecosmos-client-repository 15 | ``` 16 | 17 | ## how to use 18 | 19 | ```javascript 20 | import { AzureGermlinRepository } from "js-azurecosmos-client-repository/AzureGermlinRepository" 21 | import { AzureGermlinEntityMetaData } from "js-azurecosmos-client-repository/AzureGermlinEntityMetaData"; 22 | import { AzureCosmosEntityMetaDataContext } from "js-azurecosmos-client-repository/AzureCosmosEntityMetaDataContext"; 23 | import { Todo } from '@/src/models/Todo'; 24 | 25 | export class TodoRepository extends AzureGermlinRepository { 26 | constructor() { 27 | super(Todo, new AzureGermlinEntityMetaData(new AzureCosmosEntityMetaDataContext("","","Todo"))); 28 | } 29 | } 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /src/azureCosmos/init.ts: -------------------------------------------------------------------------------- 1 | import { AzureConfig, initAzureCosmos } from 'react-native-azure-cosmos'; 2 | import { GermlinConfig, initCosmosGermlin } from 'react-native-azure-cosmos-gremlin/germlin'; 3 | 4 | export const initGermlin = (config: GermlinConfig) : void => { 5 | initCosmosGermlin(config); 6 | }; 7 | 8 | export const initCosmos = (config: AzureConfig) : void => { 9 | initAzureCosmos(config); 10 | }; 11 | -------------------------------------------------------------------------------- /src/azureCosmos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-azurecosmos-client-repository", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/blazerroad/js-frontend-repository..git" 12 | }, 13 | "author": "Blazerroad", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/blazerroad/js-frontend-repository./issues" 17 | }, 18 | "homepage": "https://github.com/blazerroad/js-frontend-repository/src/azureCosmos.#readme", 19 | "dependencies": { 20 | "js-frontend-repository": "latest", 21 | "react-native-azure-cosmos": "latest", 22 | "react-native-azure-cosmos-gremlin": "latest" 23 | }, 24 | "devDependencies": { 25 | "eslint-config-airbnb": "^18.2.1", 26 | "eslint-config-prettier": "^7.0.0", 27 | "eslint-plugin-import": "^2.22.1", 28 | "eslint-plugin-jsx-a11y": "^6.4.1", 29 | "eslint-plugin-prettier": "^3.2.0", 30 | "prettier": "^2.2.1", 31 | "typescript": "^4.1.2", 32 | "@typescript-eslint/eslint-plugin": "^4.9.1", 33 | "@typescript-eslint/parser": "^4.9.1", 34 | "eslint": "^7.15.0", 35 | "eslint-plugin-react": "^7.21.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/codegenerator/README.md: -------------------------------------------------------------------------------- 1 | # rpcodegen 2 | This package generates classes (Repository Interface, Repository Concert, Service, UnitOfWork.ts) required in the 3 | js-frontend-repository 4 | package. 5 | 6 | For more information about js-frontend-repository please visit : js-frontend-repository 7 | 8 | # Install 9 | ## Step 1 - Install package 10 | ```bash 11 | npm i -g rpcodegen 12 | ``` 13 | ## Step 2 - Adding Config file 14 | Add repocgcnf.json file to root of your project : 15 | 16 | ```json 17 | { 18 | "paths" : { 19 | "iRepositoryURI": "/src/repositories/interfaces/", 20 | "repositoryURI": "/src/repositories/concretes/", 21 | "serviceURI": "/src/services/" 22 | }, 23 | "modelPath": "@/src/models/", 24 | "baseSeviceType": "base", 25 | "baseRepositories": [ 26 | { 27 | "alias": "firestore", 28 | "name": "FirestoreRepository", 29 | "path": "react-native-firesotre-repository/FirestoreRepository" 30 | }, 31 | { 32 | "alias": "cosmos", 33 | "name": "AzureCosmosRepository", 34 | "path": "js-azurecosmos-client-repository/AzureCosmosRepository" 35 | }, 36 | { 37 | "alias": "cosmosg", 38 | "name": "AzureGermlinRepository", 39 | "path": "js-azurecosmos-client-repository/AzureGermlinRepository" 40 | } 41 | ] 42 | } 43 | 44 | 45 | ``` 46 | | Config | | 47 | | :---: | :-: | 48 | | iRepositoryURI | Path of Folder of Repository Interfaces | 49 | | repositoryURI | Path of Folder of Concerts Repositories | 50 | | serviceURI | Path of Folder of Services | 51 | | modelPath | Path of Folder of Models | 52 | | baseSeviceType | "base" : Services inherits from BaseService | 53 | | | "redux" : Services inherits from BaseReduxService | 54 | |baseRepositories| "alias" : Alias will use on cli code generation | 55 | || "name" : Name of Repository base class | 56 | || "path" : path for import repository base class| 57 | 58 | 59 | ## Step 3 - Absoult path 60 | ### Requirement 61 | 62 | ```javascript 63 | // Meh 64 | import config from '../../../../../../src/models/Todo'; 65 | 66 | // Awesome! 67 | import config from '@/src/models/Todo'; 68 | ``` 69 | 70 | ### Add this babel plugin package 71 | 72 | ```bash 73 | npm i --dev babel-plugin-module-resolver 74 | ``` 75 | ### babel.config.js 76 | ```javascript 77 | module.exports = { 78 | presets: ['module:metro-react-native-babel-preset'], 79 | plugins: [ 80 | [ 81 | require.resolve('babel-plugin-module-resolver'), 82 | { 83 | cwd: 'babelrc', 84 | extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js'], 85 | alias: { 86 | '@cuteapp': './app' 87 | } 88 | } 89 | ], 90 | 'jest-hoist' 91 | ] 92 | }; 93 | ``` 94 | 95 | ### tsconfig.json 96 | 97 | ```javascrip 98 | { 99 | "compilerOptions": { 100 | 101 | ... 102 | 103 | "baseUrl": "./", 104 | "paths": { 105 | "@/*": [ 106 | "./*" 107 | ], 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | ### cli 114 | run this command on root of your project will generate the code 115 | 116 | ``` bash 117 | rpcodegen -m Todo -r firestore -s Todo 118 | ``` 119 | | options | | 120 | | :---: | :-: | 121 | | -m | Model Name | 122 | | -r | Base repository Alias | 123 | | -s (optional) | create file in sub folder | 124 | 125 | -------------------------------------------------------------------------------- /src/codegenerator/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var __extends = (this && this.__extends) || (function () { 4 | var extendStatics = function (d, b) { 5 | extendStatics = Object.setPrototypeOf || 6 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 7 | function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; 8 | return extendStatics(d, b); 9 | }; 10 | return function (d, b) { 11 | extendStatics(d, b); 12 | function __() { this.constructor = d; } 13 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 14 | }; 15 | })(); 16 | var yargs = require("yargs"); 17 | var fs = require('fs'); 18 | var UnitOfWork = "UnitOfWork.ts"; 19 | var options = yargs 20 | .usage("Usage: -m ") 21 | .usage("Usage: -r ") 22 | .usage("Usage: -s ") 23 | .option("m", { 24 | alias: "model", describe: "Your model", type: "string", 25 | demandOption: true 26 | }) 27 | .option("r", { 28 | alias: "repository", describe: "Base Repository", type: "string", 29 | demandOption: true 30 | }) 31 | .option("s", { 32 | alias: "subdir", describe: "Sub Direcotry", type: "string", 33 | demandOption: false 34 | }).argv; 35 | var OptionsInit = /** @class */ (function () { 36 | function OptionsInit() { 37 | } 38 | OptionsInit.prototype.init = function () { 39 | var subDir = options.subdir ? options.subdir + "/" : ""; 40 | var result = new Options(options.model, options.repository, subDir); 41 | return result; 42 | }; 43 | return OptionsInit; 44 | }()); 45 | var ConfigInit = /** @class */ (function () { 46 | function ConfigInit() { 47 | } 48 | ConfigInit.prototype.init = function () { 49 | var configPath = process.cwd() + "/repocgcnf.json"; 50 | if (!fs.existsSync(configPath)) { 51 | throw Error("Config file is not exist, Please create file with name of repocgcnf.json"); 52 | } 53 | var rawdata = fs.readFileSync(configPath); 54 | var config = JSON.parse(rawdata); 55 | return config; 56 | }; 57 | return ConfigInit; 58 | }()); 59 | var Options = /** @class */ (function () { 60 | function Options(model, repository, subDir) { 61 | if (subDir === void 0) { subDir = ""; } 62 | this.modelSubFolder = ""; 63 | this.model = model; 64 | this.repository = repository; 65 | this.subdir = subDir; 66 | var modelSubFolder = []; 67 | if (model.indexOf("/") > -1) { 68 | modelSubFolder = model.split('/'); 69 | if (modelSubFolder.length > 2) { 70 | throw Error("Just one sub folder is allow for models"); 71 | } 72 | this.model = modelSubFolder[1]; 73 | this.modelSubFolder = modelSubFolder[0] + "/"; 74 | } 75 | } 76 | return Options; 77 | }()); 78 | var Context = /** @class */ (function () { 79 | function Context(options, config) { 80 | this.baseUrl = process.cwd(); 81 | this.options = options; 82 | this.config = config; 83 | } 84 | return Context; 85 | }()); 86 | var ValidateConfig = /** @class */ (function () { 87 | function ValidateConfig() { 88 | } 89 | ValidateConfig.prototype.validate = function (context) { 90 | var directoryValid = true; 91 | Object.keys(context.config.paths).forEach(function (key) { 92 | var path = "" + context.baseUrl + context.config.paths[key]; 93 | if (!fs.existsSync(path)) { 94 | console.log("no such file or directory, " + path); 95 | directoryValid = false; 96 | } 97 | }); 98 | if (!fs.existsSync("" + process.cwd() + context.config.paths.serviceURI + UnitOfWork)) { 99 | console.log("UnitOfWork.ts file is not exists"); 100 | directoryValid = false; 101 | } 102 | if (!directoryValid) 103 | throw Error("Bad config!"); 104 | }; 105 | return ValidateConfig; 106 | }()); 107 | var SubDir = /** @class */ (function () { 108 | function SubDir() { 109 | } 110 | SubDir.prototype.create = function (context) { 111 | if (context.options.subdir.length > 0) { 112 | Object.keys(context.config.paths).forEach(function (key) { 113 | var path = "" + context.baseUrl + context.config.paths[key] + context.options.subdir; 114 | if (!fs.existsSync(path)) { 115 | fs.mkdirSync(path); 116 | } 117 | }); 118 | } 119 | }; 120 | return SubDir; 121 | }()); 122 | var CodeGenStrategy = /** @class */ (function () { 123 | function CodeGenStrategy() { 124 | } 125 | CodeGenStrategy.prototype.write = function () { 126 | var path = this.path(); 127 | var content = this.content(); 128 | var logMsg = this.logMsg(); 129 | fs.writeFile(path, content, { flag: 'wx' }, function (err) { 130 | if (err) { 131 | console.log(err); 132 | } 133 | else { 134 | console.log(logMsg); 135 | } 136 | }); 137 | }; 138 | return CodeGenStrategy; 139 | }()); 140 | var IRepositoryStrategy = /** @class */ (function (_super) { 141 | __extends(IRepositoryStrategy, _super); 142 | function IRepositoryStrategy(context) { 143 | var _this = _super.call(this) || this; 144 | _this.context = context; 145 | return _this; 146 | } 147 | IRepositoryStrategy.prototype.path = function () { 148 | return "" + this.context.baseUrl + this.context.config.paths.iRepositoryURI + this.context.options.subdir + "I" + this.context.options.model + "Repository.ts"; 149 | }; 150 | IRepositoryStrategy.prototype.content = function () { 151 | return "\nimport {IRepository} from 'js-frontend-repository/interfaces/IRepository'\nimport { " + this.context.options.model + " } from '" + this.context.config.modelPath + this.context.options.modelSubFolder + this.context.options.model + "'\nexport interface I" + this.context.options.model + "Repository extends IRepository<" + this.context.options.model + "> {\n}\n"; 152 | }; 153 | IRepositoryStrategy.prototype.logMsg = function () { 154 | return "I" + this.context.options.model + "Repository is created successfully."; 155 | }; 156 | return IRepositoryStrategy; 157 | }(CodeGenStrategy)); 158 | var RepositoryStrategy = /** @class */ (function (_super) { 159 | __extends(RepositoryStrategy, _super); 160 | function RepositoryStrategy(context) { 161 | var _this = _super.call(this) || this; 162 | _this.context = context; 163 | return _this; 164 | } 165 | RepositoryStrategy.prototype.path = function () { 166 | return "" + this.context.baseUrl + this.context.config.paths.repositoryURI + this.context.options.subdir + this.context.options.model + "Repository.ts"; 167 | }; 168 | RepositoryStrategy.prototype.content = function () { 169 | var baseRepository = this.context.config.baseRepositories.find(function (t) { return t.alias == options.repository; }); 170 | if (!baseRepository) { 171 | throw Error(this.context.options.repository + " Base repository is not exists\non config file"); 172 | } 173 | return "\nimport { " + baseRepository.name + " } from '" + baseRepository.path + "'\nimport { " + this.context.options.model + " } from '" + this.context.config.modelPath + this.context.options.modelSubFolder + this.context.options.model + "'\nexport class " + this.context.options.model + "Repository extends " + baseRepository.name + "<" + this.context.options.model + "> {\nconstructor() { super(" + this.context.options.model + ", null);\n} }\n"; 174 | }; 175 | RepositoryStrategy.prototype.logMsg = function () { 176 | return this.context.options.model + "Repository is created successfully."; 177 | }; 178 | return RepositoryStrategy; 179 | }(CodeGenStrategy)); 180 | var ServiceStrategy = /** @class */ (function (_super) { 181 | __extends(ServiceStrategy, _super); 182 | function ServiceStrategy(context) { 183 | var _this = _super.call(this) || this; 184 | _this.context = context; 185 | return _this; 186 | } 187 | ServiceStrategy.prototype.path = function () { 188 | return "" + this.context.baseUrl + this.context.config.paths.serviceURI + this.context.options.subdir + this.context.options.model + "Service.ts"; 189 | }; 190 | ServiceStrategy.prototype.content = function () { 191 | var baseService = this.context.config.baseSeviceType === "base" ? "import { BaseService } from 'js-frontend-repository/BaseSerivce'" : "import { BaseService } from 'js-frontend-repository/BaseSerivce'"; 192 | return "\nimport { I" + this.context.options.model + "Repository } from '@" + this.context.config.paths.iRepositoryURI + this.context.options.subdir + "I" + this.context.options.model + "Repository'\nimport { " + this.context.options.model + " } from '" + this.context.config.modelPath + this.context.options.modelSubFolder + this.context.options.model + "'\n" + baseService + "\nexport class " + this.context.options.model + "Service extends BaseService<" + this.context.options.model + ",I" + this.context.options.model + "Repository> {\n} "; 193 | }; 194 | ServiceStrategy.prototype.logMsg = function () { 195 | return this.context.options.model + "Service is created successfully."; 196 | }; 197 | return ServiceStrategy; 198 | }(CodeGenStrategy)); 199 | var UnitOfWorkStrategy = /** @class */ (function (_super) { 200 | __extends(UnitOfWorkStrategy, _super); 201 | function UnitOfWorkStrategy(context) { 202 | var _this = _super.call(this) || this; 203 | _this.context = context; 204 | return _this; 205 | } 206 | UnitOfWorkStrategy.prototype.path = function () { 207 | return ""; 208 | }; 209 | UnitOfWorkStrategy.prototype.content = function () { 210 | return ""; 211 | }; 212 | UnitOfWorkStrategy.prototype.logMsg = function () { 213 | return this.context.options.model + "Service is created successfully."; 214 | }; 215 | UnitOfWorkStrategy.prototype.toLower = function (val) { 216 | return val.charAt(0).toLowerCase() + val.slice(1); 217 | }; 218 | UnitOfWorkStrategy.prototype.write = function () { 219 | var self = this; 220 | fs.readFile("" + this.context.baseUrl + this.context.config.paths.serviceURI + UnitOfWork, 'utf8', function (err, data) { 221 | if (err) { 222 | console.log(err); 223 | } 224 | else if (data.indexOf(self.context.options.model + "Repository") > -1) { 225 | console.log("This model exists on UnitOfWork"); 226 | } 227 | else { 228 | var imported = data.replace("//new service import placeholder", "//new service import placeholder\nimport { " + self.context.options.model + "Repository} from '@" + self.context.config.paths.repositoryURI + self.context.options.subdir + self.context.options.model + "Repository'\nimport { " + self.context.options.model + "Service } from '@" + self.context.config.paths.serviceURI + self.context.options.subdir + self.context.options.model + "Service'"); 229 | var properties = imported.replace("//new service placeholder", "//new service placeholder\n" + self.toLower(self.context.options.model) + "Service: " + self.context.options.model + "Service"); 230 | var constractors = properties.replace("//new service init placeholder", "//new service init placeholder \n this." + self.toLower(self.context.options.model) + "Service = new " + self.context.options.model + "Service(" + self.context.options.model + "Repository)"); 231 | fs.writeFile("" + self.context.baseUrl + self.context.config.paths.serviceURI + UnitOfWork, constractors, function (err) { 232 | if (err) { 233 | console.log(err); 234 | } 235 | else { 236 | console.log('UnitOFWork is created successfully.'); 237 | } 238 | }); 239 | } 240 | }); 241 | }; 242 | return UnitOfWorkStrategy; 243 | }(CodeGenStrategy)); 244 | var Facad = /** @class */ (function () { 245 | function Facad() { 246 | this.options = new OptionsInit(); 247 | this.config = new ConfigInit(); 248 | } 249 | Facad.prototype.generate = function () { 250 | var options = this.options.init(); 251 | var config = this.config.init(); 252 | var context = new Context(options, config); 253 | (new ValidateConfig()).validate(context); 254 | (new SubDir()).create(context); 255 | (new IRepositoryStrategy(context)).write(); 256 | (new RepositoryStrategy(context)).write(); 257 | (new ServiceStrategy(context)).write(); 258 | (new UnitOfWorkStrategy(context)).write(); 259 | }; 260 | return Facad; 261 | }()); 262 | (new Facad()).generate(); 263 | -------------------------------------------------------------------------------- /src/codegenerator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rpcodegen", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "bin/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tsc": "tsc" 9 | }, 10 | "keywords": [], 11 | "author": "Blazerroad", 12 | "license": "", 13 | "bin": { 14 | "rpcodegen": "./bin/index.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/blazerroad/js-frontend-repository.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/blazerroad/js-frontend-repository/issues" 22 | }, 23 | "homepage": "https://github.com/blazerroad/js-frontend-repository/codegenerator#readme", 24 | "dependencies": { 25 | "yargs": "^16.1.1", 26 | "@types/node": "^14.14.10" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^14.14.10", 30 | "typescript": "^4.1.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/codegenerator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/firestore/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/react", 11 | "airbnb" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2019, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/no-empty-interface": "off", 28 | "import/extensions": [ 29 | "error", 30 | "ignorePackages", 31 | { 32 | "js": "never", 33 | "jsx": "never", 34 | "ts": "never", 35 | "tsx": "never" 36 | } 37 | ], 38 | "no-unused-vars": "off", 39 | "@typescript-eslint/no-unused-vars": ["error"], 40 | "import/prefer-default-export" : "warn", 41 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 42 | "class-methods-use-this" : "off" 43 | }, 44 | "settings": { 45 | "import/resolver": { 46 | "node": { 47 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 48 | } 49 | } 50 | 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/firestore/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /src/firestore/FirestoreEntityMetaData.ts: -------------------------------------------------------------------------------- 1 | import { IEntityMetaData } from 'js-frontend-repository/models/interfaces/IEntityMetaData'; 2 | import { NullNetworking } from 'js-frontend-repository/NullNetworking'; 3 | import { nullValue } from 'js-frontend-repository/models/nullValue'; 4 | import { FirestoreEntityMetaDataContext } from './FirestoreEntityMetaDataContext'; 5 | 6 | export class FirestoreEntityMetaData implements IEntityMetaData { 7 | context: FirestoreEntityMetaDataContext; 8 | 9 | networking: NullNetworking = nullValue; 10 | 11 | constructor(context: FirestoreEntityMetaDataContext) { 12 | this.context = context; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/firestore/FirestoreEntityMetaDataContext.ts: -------------------------------------------------------------------------------- 1 | export class FirestoreEntityMetaDataContext { 2 | collectionName: string; 3 | 4 | constructor(collectionName = '') { 5 | this.collectionName = collectionName; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/firestore/FirestoreRepository.ts: -------------------------------------------------------------------------------- 1 | import { Result } from 'js-frontend-repository/models/Result'; 2 | import { ResultArray } from 'js-frontend-repository/models/ResultArray'; 3 | import { RepositoryBase } from 'js-frontend-repository/RepositoryBase'; 4 | import { cache } from 'react-native-fetch-cache/cacheResolver'; 5 | import firestore from '@react-native-firebase/firestore'; 6 | import { QueryContext } from 'js-frontend-repository/models/QueryContext'; 7 | import { FirestoreEntityMetaData } from './FirestoreEntityMetaData'; 8 | 9 | export class FirestoreRepository extends RepositoryBase { 10 | metaData: FirestoreEntityMetaData; 11 | 12 | constructor(entityType: new () => TEntity, metaData: FirestoreEntityMetaData) { 13 | super(entityType); 14 | this.metaData = metaData; 15 | } 16 | 17 | cacheResponse(): ResultArray { 18 | const result = new ResultArray(); 19 | result.status = 304; 20 | return result; 21 | } 22 | 23 | async getById(id: string): Promise> { 24 | const isCached = cache(`${this.metaData.context.collectionName}::${id}`, undefined); 25 | if (isCached) return this.first(this.cacheResponse()); 26 | const fire = await firestore().collection(this.metaData.context.collectionName).doc(id).get(); 27 | const entity = await this.map(fire); 28 | return this.first(entity); 29 | } 30 | 31 | async all(): Promise> { 32 | const isCached = cache(`${this.metaData.context.collectionName}::all`, undefined); 33 | if (isCached) return this.cacheResponse(); 34 | const fire = await firestore().collection(this.metaData.context.collectionName).get(); 35 | const entity = await this.map(fire.docs); 36 | return entity; 37 | } 38 | 39 | async query(context: QueryContext): Promise> { 40 | const query = firestore().collection(this.metaData.context.collectionName); 41 | context.parameters.forEach((t) => { 42 | const oper = t.name.split(':'); 43 | query.where(oper[0], oper[1] as any, t.value); 44 | }); 45 | const fire = await query.get(); 46 | const entities = await this.map(fire.docs); 47 | return entities; 48 | } 49 | 50 | async add(entity: TEntity, options?: any): Promise> { 51 | if (!options.id) throw Error('options.id is null or not defiend'); 52 | await firestore() 53 | .collection(this.metaData.context.collectionName) 54 | .doc(options.id) 55 | .set(entity); 56 | const result = new Result(); 57 | result.entity = entity; 58 | return result; 59 | } 60 | 61 | async update(entity: TEntity, options?: any): Promise> { 62 | if (!options.id) throw Error('options.id is null or not defiend'); 63 | await firestore() 64 | .collection(this.metaData.context.collectionName) 65 | .doc(options.id) 66 | .update(entity); 67 | const result = new Result(); 68 | result.entity = entity; 69 | return result; 70 | } 71 | 72 | async handelMap(response: any): Promise> { 73 | const result = new ResultArray(); 74 | if (!response) { 75 | result.status = response.status; 76 | result.ok = response.ok; 77 | result.message = JSON.stringify(response); 78 | return result; 79 | } 80 | const mapped = await this.innerMap(response); 81 | result.ok = true; 82 | result.status = 200; 83 | result.message = ''; 84 | result.entity = mapped; 85 | return result; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/firestore/README.md: -------------------------------------------------------------------------------- 1 | # react-native-firesotre-repository 2 | This package provides the base Repository class to fetch data from Firestore. 3 | 4 | For more information about how to use please refer to each dependency 5 | 6 | # Dependencies 7 | - js-frontend-repository 8 | - React Native Firebase 9 | 10 | # Install 11 | ```bash 12 | npm i react-native-firesotre-repository 13 | ``` 14 | 15 | ## how to use 16 | 17 | ```javascript 18 | import { FirestoreRepository } from 'react-native-firesotre-repository/FirestoreRepository'; 19 | import { Todo } from '@/src/models/Todo'; 20 | import { FirestoreEntityMetaData } from 'react-native-firesotre-repository/FirestoreEntityMetaData'; 21 | import { FirestoreEntityMetaDataContext } from 'react-native-firesotre-repository/FirestoreEntityMetaDataContext'; 22 | 23 | export class TodoRepository extends FirestoreRepository { 24 | constructor() { 25 | super(Todo, new FirestoreEntityMetaData(new FirestoreEntityMetaDataContext())); 26 | } 27 | } 28 | 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /src/firestore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-firesotre-repository", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/blazerroad/js-frontend-repository.git" 12 | }, 13 | "author": "Blazerroad", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/blazerroad/js-frontend-repository/issues" 17 | }, 18 | "homepage": "https://github.com/blazerroad/js-frontend-repository/firestore#readme", 19 | "dependencies": { 20 | "@react-native-firebase/app": "^8.4.7", 21 | "@react-native-firebase/firestore": "^10.1.0", 22 | "js-frontend-repository": "^1.0.3", 23 | "react-native-fetch-cache": "^1.0.1" 24 | }, 25 | "devDependencies": { 26 | "eslint-config-airbnb": "^18.2.1", 27 | "eslint-config-prettier": "^7.0.0", 28 | "eslint-plugin-import": "^2.22.1", 29 | "eslint-plugin-jsx-a11y": "^6.4.1", 30 | "eslint-plugin-prettier": "^3.2.0", 31 | "prettier": "^2.2.1", 32 | "typescript": "^4.1.2", 33 | "@typescript-eslint/eslint-plugin": "^4.9.1", 34 | "@typescript-eslint/parser": "^4.9.1", 35 | "eslint": "^7.15.0", 36 | "eslint-plugin-react": "^7.21.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/redux/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/react", 11 | "airbnb" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2019, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/no-empty-interface": "off", 28 | "import/extensions": [ 29 | "error", 30 | "ignorePackages", 31 | { 32 | "js": "never", 33 | "jsx": "never", 34 | "ts": "never", 35 | "tsx": "never" 36 | } 37 | ], 38 | "no-unused-vars": "off", 39 | "@typescript-eslint/no-unused-vars": ["error"], 40 | "import/prefer-default-export" : "warn", 41 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 42 | "class-methods-use-this" : "off" 43 | }, 44 | "settings": { 45 | "import/resolver": { 46 | "node": { 47 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 48 | } 49 | } 50 | 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/redux/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /src/redux/BaseReducer.ts: -------------------------------------------------------------------------------- 1 | import { nullValue } from 'js-frontend-repository/models/nullValue'; 2 | import { IReducer } from './IReducer'; 3 | 4 | export abstract class BaseReducer implements IReducer { 5 | state: TState; 6 | 7 | acceptableActions: Array; 8 | 9 | protected successor: BaseReducer = nullValue; 10 | 11 | constructor(state: TState, acceptableActions: Array) { 12 | this.state = state; 13 | this.acceptableActions = acceptableActions; 14 | } 15 | 16 | isAcceptable(actionName: string): boolean { 17 | return this.acceptableActions.some((t) => t === actionName); 18 | } 19 | 20 | public setSuccessor(successor: BaseReducer): void { 21 | this.successor = successor; 22 | } 23 | 24 | public handleRequest(state: TState, action: any): TState { 25 | if (this.isAcceptable(action.type)) { 26 | return this.reduce(state, action); 27 | } 28 | if (this.successor) { 29 | return this.successor.handleRequest(state, action); 30 | } 31 | return state; 32 | } 33 | 34 | abstract reduce(state: TState, action: any): TState 35 | } 36 | -------------------------------------------------------------------------------- /src/redux/BaseReduxService.ts: -------------------------------------------------------------------------------- 1 | import { IRepository } from 'js-frontend-repository/interfaces/IRepository'; 2 | import { IResult } from 'js-frontend-repository/models/interfaces/IResult'; 3 | import { IReduxService } from './IReduxService'; 4 | 5 | /* eslint indent: "off" */ 6 | export class BaseReduxService> 7 | implements IReduxService { 8 | repository: TRepository; 9 | 10 | reduxDispatch: any; 11 | 12 | constructor(private RepositoryType: new () => TRepository, dispatch: any) { 13 | this.repository = new this.RepositoryType(); 14 | this.reduxDispatch = dispatch; 15 | } 16 | 17 | dispatch(actionName: string, result: IResult): void { 18 | if (result.status === 304) return; 19 | if (!result.ok) { 20 | throw new Error('There is some thing wrong from API'); 21 | } 22 | this.reduxDispatch({ type: actionName, entity: result.entity }); 23 | } 24 | 25 | async getById(actionName: string, id: string, options?: any): Promise { 26 | const response = await this.repository.getById(id, options); 27 | this.dispatch(actionName, response); 28 | } 29 | 30 | async all(actionName: string, options?: any): Promise { 31 | const response = await this.repository.all(options); 32 | this.dispatch(actionName, response); 33 | } 34 | 35 | async add(entity: TEntity, options?: any): Promise { 36 | await this.repository.add(entity, options); 37 | } 38 | 39 | async update(entity: TEntity, options?: any): Promise { 40 | await this.repository.update(entity, options); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/redux/DefaultReducer.ts: -------------------------------------------------------------------------------- 1 | import { BaseReducer } from './BaseReducer'; 2 | 3 | export class DefaultReducer extends BaseReducer { 4 | constructor(state: TState) { 5 | super(state, []); 6 | this.fillAcceptable(state); 7 | } 8 | 9 | fillAcceptable(state: TState) { 10 | Object.keys(state).forEach((key) => { 11 | this.acceptableActions.push(key); 12 | }); 13 | } 14 | 15 | reduce(state: TState, action: any): TState { 16 | return { ...state, [action.type]: action.entity }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/redux/DefualtReducerService.ts: -------------------------------------------------------------------------------- 1 | import { nullValue } from 'js-frontend-repository/models/nullValue'; 2 | import { BaseReducer } from './BaseReducer'; 3 | import { DefaultReducer } from './DefaultReducer'; 4 | 5 | export class DefualtReducerService { 6 | state: TState; 7 | 8 | reducers: Array>; 9 | 10 | reducer: BaseReducer = nullValue; 11 | 12 | constructor(state: TState, reducers: Array>) { 13 | this.state = state; 14 | this.reducers = reducers; 15 | this.chain(); 16 | } 17 | 18 | pairwise(arr: Array>, func: any) { 19 | for (let i = 0; i < arr.length; i++) { 20 | if (i === arr.length) { 21 | arr[i].setSuccessor(new DefaultReducer(this.state)); 22 | return; 23 | } 24 | func(arr[i], arr[i + 1]); 25 | } 26 | } 27 | 28 | chain(): void { 29 | const defaultSuccessor = new DefaultReducer(this.state); 30 | if (this.reducers && this.reducers.length > 0) { 31 | this.pairwise(this.reducers, (current: BaseReducer, next: BaseReducer) => { 32 | const succeror = next || defaultSuccessor; 33 | current.setSuccessor(succeror); 34 | }); 35 | this.reducer = this.reducers[0]; 36 | return; 37 | } 38 | this.reducer = defaultSuccessor; 39 | } 40 | 41 | reduce(state: TState = this.state, action: any): TState { 42 | return this.reducer.handleRequest(state, action); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/redux/IReducer.ts: -------------------------------------------------------------------------------- 1 | export interface IReducer { 2 | state: TState; 3 | acceptableActions: Array; 4 | reduce(state: TState, action: any): TState 5 | } 6 | -------------------------------------------------------------------------------- /src/redux/IReduxService.ts: -------------------------------------------------------------------------------- 1 | import { IResult } from 'js-frontend-repository/models/interfaces/IResult'; 2 | 3 | export interface IReduxService { 4 | getById(actionName: string, id: string, options?: any): Promise; 5 | all(actionName: string, options?: any): Promise; 6 | add(entity: TEntity, options?: any): Promise; 7 | update(entity: TEntity, options?: any): Promise; 8 | dispatch(actionName: string, result: IResult): void 9 | } 10 | -------------------------------------------------------------------------------- /src/redux/README.md: -------------------------------------------------------------------------------- 1 | # react-native-repository 2 | if you are searching for way to write cleaner code, prevent using fetch all over your ACTIONS and you are not happy with very big long REDUCER switch this is solution for you 3 | for example app visit : https://github.com/blazerroad/workwolf/ 4 | 5 | # Diagram 6 | 7 | 8 | 9 | # Why 10 | This example show you how you can manage your code better with redux pattern if you are involved in mid-size or bigger application, with help of Repository pattern you can achieve SOLID principal and make cleaner, extendable, easy to change 11 | 12 | # How 13 | 14 | ## Step 1 15 | ### Install 16 | ```bash 17 | npm i react-native-repository --save 18 | ``` 19 | 20 | ## Step 2 21 | For actions you should add two folders : 22 | ```bash 23 | -respositories 24 | -services 25 | ``` 26 | repository pattern is base on type of entity, for each entity you should add : 27 | - model 28 | - respository 29 | - service 30 | 31 | #### Model 32 | each model should be extends IEntity, DefaultEntity is default calss implemented IEntity you can use DefaultEntity or implement your own 33 | ```javascript 34 | import {DefaultEntity } from 'react-native-repository/repository' 35 | 36 | export class TopHashtag extends DefaultEntity { 37 | 38 | id : string; 39 | title : string; 40 | 41 | constructor(id? : string, title? : string) { 42 | super(); 43 | this.id = id; 44 | this.title = title; 45 | } 46 | } 47 | ``` 48 | 49 | #### Repository 50 | each repository should extends IRepository at react-native-repository I developed two repository for "Azure cosmos" and "azure germlin cosmos" for react-redux-libarary you should implement your own base repository base the backend service which your are using. 51 | 52 | ```javascript 53 | import { AzureCosmosRepository,AzureFetchEntityMetaData } from "react-native-repository/repository" 54 | import { TopHashtag } from '../../models/TopHashtag' 55 | 56 | export class TopHashtagRepository extends AzureCosmosRepository 57 | { 58 | constructor() 59 | { 60 | const metaData = new AzureFetchEntityMetaData("TopHashtag","Hashtag","Chiko"); 61 | super(metaData); 62 | } 63 | async map(response: Response): Promise> { 64 | const mapping = this.innerMap(response, new TopHashtag(), new Array()); 65 | return mapping; 66 | } 67 | } 68 | ``` 69 | 70 | #### service 71 | each service should extends IService for REDUX I implemented BaseReduxService but you can impliment any Base service. 72 | 73 | ```javascript 74 | import { BaseReduxService } from "react-native-repository/repository" 75 | import { TopHashtag } from '../../models/TopHashtag' 76 | import {TopHashtagRepository} from '../repositories/TopHastagsRepository' 77 | 78 | export class TopHashtagsService extends BaseReduxService 79 | { 80 | constructor(dispatch: any) 81 | { 82 | const repository = new TopHashtagRepository(); 83 | super(dispatch,repository); 84 | } 85 | 86 | } 87 | ``` 88 | 89 | #### service FACAD 90 | this class is contains instance of all services which created. 91 | ```javascript 92 | 93 | import { TopHashtagsService } from "./TopHashtagsService"; 94 | import { UploadImage } from "./UploadImage"; 95 | import { initAzureCosmos } from 'react-native-azure-cosmos/azurecosmos' 96 | 97 | export class Services { 98 | public static instance: Services; 99 | 100 | public static init(dispatch: any) { 101 | Services.instance = new Services(dispatch); 102 | } 103 | 104 | public topHashtage: TopHashtagsService 105 | public uploadImage: UploadImage 106 | 107 | private constructor(dispatch: any) { 108 | this.topHashtage = new TopHashtagsService(dispatch); 109 | this.uploadImage = new UploadImage(); 110 | 111 | } 112 | } 113 | ``` 114 | #### add service FACAD to REDUX 115 | 116 | after creating your store call Services.init(store.dispatch) 117 | ```javascript 118 | import { Services } from './store/actions/services/services' 119 | 120 | const store = createStore(rootReducer, applyMiddleware(crashReporter, thunk, vanillaPromise, readyStatePromise)); 121 | Services.init(store.dispatch); 122 | ``` 123 | 124 | -------------------------------------------------------------------------------- /src/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-repository", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/blazerroad/js-frontend-repository.git" 12 | }, 13 | "author": "Blazerroad", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/blazerroad/js-frontend-repository/issues" 17 | }, 18 | "homepage": "https://github.com/blazerroad/js-frontend-repository/redux#readme", 19 | "dependencies": { 20 | "js-frontend-repository": "latest" 21 | }, 22 | "devDependencies": { 23 | "eslint-config-airbnb": "^18.2.1", 24 | "eslint-config-prettier": "^7.0.0", 25 | "eslint-plugin-import": "^2.22.1", 26 | "eslint-plugin-jsx-a11y": "^6.4.1", 27 | "eslint-plugin-prettier": "^3.2.0", 28 | "prettier": "^2.2.1", 29 | "typescript": "^4.1.2", 30 | "@typescript-eslint/eslint-plugin": "^4.9.1", 31 | "@typescript-eslint/parser": "^4.9.1", 32 | "eslint": "^7.15.0", 33 | "eslint-plugin-react": "^7.21.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/repository/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/react", 11 | "airbnb" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2019, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/no-empty-interface": "off", 28 | "import/extensions": [ 29 | "error", 30 | "ignorePackages", 31 | { 32 | "js": "never", 33 | "jsx": "never", 34 | "ts": "never", 35 | "tsx": "never" 36 | } 37 | ], 38 | "no-unused-vars": "off", 39 | "@typescript-eslint/no-unused-vars": ["error"], 40 | "import/prefer-default-export" : "warn" 41 | }, 42 | "settings": { 43 | "import/resolver": { 44 | "node": { 45 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 46 | } 47 | } 48 | 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/repository/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /src/repository/BaseSerivce.ts: -------------------------------------------------------------------------------- 1 | import { IRepository } from "./interfaces/IRepository"; 2 | import { IService } from "./interfaces/IService"; 3 | import { Result } from "./models/Result"; 4 | import { ResultArray } from "./models/ResultArray"; 5 | 6 | export class BaseService> implements IService { 7 | repository: TRepository 8 | constructor(private repositoryType: new () => TRepository,) { 9 | this.repository = new this.repositoryType(); 10 | } 11 | 12 | getById(id: string, options?: any): Promise> { 13 | return this.repository.getById(id, options); 14 | } 15 | all(options?: any): Promise> { 16 | return this.repository.all(options); 17 | } 18 | add(entity: TEntity, options?: any): Promise> { 19 | return this.repository.add(entity, options); 20 | } 21 | update(entity: TEntity, options?: any): Promise> { 22 | return this.repository.update(entity, options); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/repository/NullNetworking.ts: -------------------------------------------------------------------------------- 1 | import { INetworking } from "./interfaces/INetworking"; 2 | 3 | export class NullNetworking implements INetworking { 4 | 5 | } -------------------------------------------------------------------------------- /src/repository/README.md: -------------------------------------------------------------------------------- 1 | # js-frontend-repository 2 | The Repository pattern is a well-documented way of working with a data source. In this library I used this pattern for manage API calls from javascript base frontend applications 3 | 4 | # Benefits 5 | 6 | - It centralizes API calls 7 | - It gives a substitution point for the unit tests. 8 | - Provides a flexible architecture 9 | - It reduces redundancy of code 10 | - It force programmer to work using the same pattern 11 | - If you use this pattern then it is easy to maintain the centralized data access logic 12 | - centralized API call 13 | - centralized error handeling 14 | - Can use diffrents API Gatways in same project with no change in Service level 15 | - Adding SOLID principals to your project 16 | 17 | 18 | # Diagram 19 | 20 | 21 | # Install 22 | 23 | ```bash 24 | npm i js-frontend-repository 25 | ``` 26 | # How to use it 27 | 28 | 29 | ## Models 30 | For each API call response you should add a model based on data you fetched from API for example if I want to fetch Todo list from API with properties of title and isDone you should add a model like this : 31 | 32 | ```javascript 33 | export class Todo { 34 | title? : string; 35 | 36 | isDone? : boolean; 37 | } 38 | ``` 39 | 40 | ## IRepository 41 | For each model you should add one generic IRepository model of that entity, this Interface will be use on Service concretes of the entity and with help of this IRepository you can avoid changes in Service layer if you want to change Repository layer 42 | 43 | ```javascript 44 | import { IRepository } from 'js-frontend-repository/interfaces/IRepository'; 45 | import { Todo } from '@/src/models/Todo'; 46 | 47 | export interface ITodoRepository extends IRepository { 48 | } 49 | 50 | ``` 51 | 52 | ## Repository concrete 53 | 54 | This file will contain all logic related to API calls for example if you are using GraphQL API, all queries should be developed in this class. Or if you are using Firestore API all related queries should be developed in this class. 55 | 56 | Usually instead of inherit from IRepository in this class it is better practice to inherit from one base repository. 57 | current project have already three base repository developed, you can use this base repository in scenario you want to fetch data against Firestore , Azure Cosmos or Azure Cosmos Graph Gremlin if you want to use another service or your own custom API call you should develop the base Repository related to that first 58 | 59 | For more information about implemented Repository base class please refer to each Repository base page : 60 | 61 | - Firestore Repository base 62 | - Azure cosmos and Azure cosmos Gremlin Repository base 63 | 64 | ```javascript 65 | 66 | import { FirestoreRepository } from 'react-native-firesotre-repository/FirestoreRepository'; 67 | import { Todo } from '@/src/models/Todo'; 68 | import { FirestoreEntityMetaData } from 'react-native-firesotre-repository/FirestoreEntityMetaData'; 69 | import { FirestoreEntityMetaDataContext } from 'react-native-firesotre-repository/FirestoreEntityMetaDataContext'; 70 | 71 | export class TodoRepository extends FirestoreRepository { 72 | constructor() { 73 | super(Todo, new FirestoreEntityMetaData(new FirestoreEntityMetaDataContext())); 74 | } 75 | } 76 | ``` 77 | 78 | you can find example project : Here 79 | 80 | ## Service 81 | For each entity you should add one service class too. Service class is responsible for logic across one entity, for example if you want to fetch Todo with Id = 2 and then change the isDone to true and update the result you should develop the logic of that in this class. 82 | Service is for logic across one entity, and Repository class is for query of those logic. 83 | 84 | ```javascript 85 | import { ITodoRepository } from '@/src/repositories/interfaces/ITodoRepository'; 86 | import { Todo } from '@/src/models/Todo'; 87 | import { BaseService } from 'js-frontend-repository/BaseSerivce'; 88 | 89 | export class TodoService extends BaseService { 90 | async updateTodo(id: string) { 91 | const todo = await (await this.repository.getById(id)).entity; 92 | todo.isDone = true; 93 | await this.repository.update(todo) 94 | } 95 | } 96 | 97 | ``` 98 | As you can see if you change the repository behind the Todo model this file will not change 99 | 100 | ## UnitOfWork.ts 101 | This singleton file holds all instances of services, and also in this file you can assign a concert repository for each service. With help of this file you can change the repository of services easily and this change will not need to change any logic on service and other layers of application. 102 | 103 | ```javascript 104 | // new service import placeholder 105 | // new service import placeholder 106 | import { TodoRepository } from '@/src/repositories/concretes/TodoRepository'; 107 | import { TodoService } from '@/src/services/TodoService'; 108 | 109 | export class UnitOfWork { 110 | // new service placeholder 111 | todoService: TodoService 112 | 113 | private static innerInstance: UnitOfWork; 114 | 115 | public static instance(): UnitOfWork { 116 | if (!UnitOfWork.innerInstance) { 117 | UnitOfWork.innerInstance = new UnitOfWork(); 118 | } 119 | return UnitOfWork.innerInstance; 120 | } 121 | 122 | private constructor() { 123 | // new service init placeholder 124 | this.todoService = new TodoService(TodoRepository); 125 | } 126 | } 127 | ``` 128 | 129 | # Code Generator 130 | As you can see for adding new Entity in the repository pattern you have to add 3 files and modify one, to develop faster and avoid duplicate work you can use the “Repository Code Generator” package. 131 | 132 | ## Install 133 | 134 | ```bash 135 | npm i -g rpcodegen 136 | ``` 137 | 138 | For more information please see Repository Code Generator page : 139 | Repository Code Generator 140 | 141 | 142 | # Redux 143 | If you want to use Redux in your application and you want to manage the API call with repository pattern and automatically dispatch them on State and also if your looking for better solution to design your Actions and Routers in Redux you can use this package 144 | 145 | ```bash 146 | npm i react-redux-repository 147 | ``` 148 | 149 | For more information ablut Redux Repository please visit: 150 | React Redux Repository 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src/repository/RepositoryBase.ts: -------------------------------------------------------------------------------- 1 | import { IRepository } from "./interfaces/IRepository"; 2 | import { nullValue } from "./models/nullValue"; 3 | import { QueryContext } from "./models/QueryContext"; 4 | import { Result } from "./models/Result"; 5 | import { ResultArray } from "./models/ResultArray"; 6 | 7 | export abstract class RepositoryBase implements IRepository { 8 | 9 | constructor(private entityType: new () => TEntity) { 10 | } 11 | 12 | getNew(): TEntity { 13 | return new this.entityType(); 14 | } 15 | 16 | abstract getById(id: string, options?: any): Promise>; 17 | abstract all(options?: any): Promise>; 18 | abstract query(context: QueryContext, options?: any): Promise>; 19 | abstract add(entity: TEntity, options?: any): Promise>; 20 | abstract update(entity: TEntity, options?: any): Promise>; 21 | abstract handelMap(data: any): Promise>; 22 | 23 | async innerMap(resData: any): Promise> { 24 | const datas = resData.Documents; 25 | const entities = new Array(); 26 | for (let index = 0; index < datas.length; index++) { 27 | const rowEntity = this.getNew(); 28 | for (const key in rowEntity) { 29 | rowEntity[key] = datas[index][key] 30 | } 31 | entities.push(rowEntity) 32 | } 33 | return entities; 34 | } 35 | first(val: ResultArray): Result { 36 | const result = new Result(); 37 | result.message = val.message; 38 | result.ok = val.ok; 39 | result.status = val.status; 40 | result.entity = val.entity && val.entity.length > 0 ? val.entity[0] : nullValue; 41 | return result; 42 | } 43 | 44 | map(response: any): Promise> { 45 | return this.handelMap(response); 46 | } 47 | } -------------------------------------------------------------------------------- /src/repository/RepositoryHttpBase.ts: -------------------------------------------------------------------------------- 1 | import { ResultArray } from "./models/ResultArray"; 2 | import { RepositoryBase } from "./RepositoryBase"; 3 | 4 | export abstract class RepositoryHttpBase extends RepositoryBase { 5 | async handelMap(response: Response): Promise> { 6 | const result = new ResultArray(); 7 | let entity = this.getNew(); 8 | let entities = new Array(); 9 | const resData = await response.json(); 10 | if (!response.ok || response.status === 304) { 11 | result.status = response.status; 12 | result.ok = response.ok; 13 | result.message = JSON.stringify(resData) 14 | return result; 15 | } 16 | if (!resData || !resData.Documents || !Array.isArray(resData.Documents)) { 17 | result.ok = false; 18 | result.status = 501; 19 | result.message = JSON.stringify(resData) 20 | return result; 21 | } 22 | 23 | entities = await this.innerMap(resData); 24 | result.ok = true; 25 | result.status = 200; 26 | result.message = ""; 27 | result.entity = entities; 28 | return result; 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /src/repository/interfaces/INetworking.ts: -------------------------------------------------------------------------------- 1 | export interface INetworking { 2 | } 3 | -------------------------------------------------------------------------------- /src/repository/interfaces/IRepository.ts: -------------------------------------------------------------------------------- 1 | import { QueryContext } from '../models/QueryContext'; 2 | import { Result } from '../models/Result'; 3 | import { ResultArray } from '../models/ResultArray'; 4 | 5 | export interface IRepository { 6 | getById(id: string, options?: any): Promise>; 7 | all(options?: any): Promise>; 8 | query(context: QueryContext, options?: any): Promise>; 9 | add(entity: TEntity, options?: any): Promise>; 10 | update(entity: TEntity, options?: any): Promise>; 11 | map(response: any): Promise>; 12 | } 13 | -------------------------------------------------------------------------------- /src/repository/interfaces/IService.ts: -------------------------------------------------------------------------------- 1 | import { Result } from '../models/Result'; 2 | import { ResultArray } from '../models/ResultArray'; 3 | 4 | export interface IService { 5 | getById(id: string, options?: any): Promise>; 6 | all(options?: any): Promise>; 7 | add(entity: TEntity, options?: any): Promise>; 8 | update(entity: TEntity, options?: any): Promise>; 9 | } 10 | -------------------------------------------------------------------------------- /src/repository/models/Param.ts: -------------------------------------------------------------------------------- 1 | export class Param { 2 | name: string; 3 | 4 | value: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/repository/models/QueryContext.ts: -------------------------------------------------------------------------------- 1 | import Param from './Param'; 2 | 3 | export default class QueryContext { 4 | query: string; 5 | 6 | parameters: Array 7 | 8 | constructor(query: string, parameters: Array) { 9 | this.query = query; 10 | this.parameters = parameters; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/repository/models/Result.ts: -------------------------------------------------------------------------------- 1 | import { IResult } from './interfaces/IResult'; 2 | import { nullValue } from './nullValue'; 3 | 4 | export class Result implements IResult { 5 | ok = true; 6 | 7 | status = 200; 8 | 9 | message = ''; 10 | 11 | entity: TEntity = nullValue; 12 | } 13 | -------------------------------------------------------------------------------- /src/repository/models/ResultArray.ts: -------------------------------------------------------------------------------- 1 | import { IResult } from './interfaces/IResult'; 2 | import { nullValue } from './nullValue'; 3 | 4 | export class ResultArray implements IResult { 5 | ok = true; 6 | 7 | status = 200; 8 | 9 | message = ''; 10 | 11 | entity: Array = nullValue; 12 | } 13 | -------------------------------------------------------------------------------- /src/repository/models/interfaces/IEntityMetaData.ts: -------------------------------------------------------------------------------- 1 | import { INetworking } from "../../interfaces/INetworking"; 2 | 3 | export interface IEntityMetaData { 4 | context: any; 5 | networking: TTNetworking; 6 | } -------------------------------------------------------------------------------- /src/repository/models/interfaces/IResult.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IResult { 3 | ok: boolean; 4 | status: number; 5 | message: string; 6 | entity: TEntity | Array; 7 | } -------------------------------------------------------------------------------- /src/repository/models/nullValue.ts: -------------------------------------------------------------------------------- 1 | export const nullValue = null as any; 2 | -------------------------------------------------------------------------------- /src/repository/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-frontend-repository", 3 | "version": "1.0.5", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/blazerroad/js-frontend-repository.git" 12 | }, 13 | "author": "Blazerroad", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/blazerroad/js-frontend-repository/issues" 17 | }, 18 | "homepage": "https://github.com/blazerroad/js-frontend-repository#readme", 19 | "dependencies": {}, 20 | "devDependencies": { 21 | "eslint-config-airbnb": "^18.2.1", 22 | "eslint-config-prettier": "^7.0.0", 23 | "eslint-plugin-import": "^2.22.1", 24 | "eslint-plugin-jsx-a11y": "^6.4.1", 25 | "eslint-plugin-prettier": "^3.2.0", 26 | "prettier": "^2.2.1", 27 | "typescript": "^4.1.2", 28 | "@typescript-eslint/eslint-plugin": "^4.9.1", 29 | "@typescript-eslint/parser": "^4.9.1", 30 | "eslint": "^7.15.0", 31 | "eslint-plugin-react": "^7.21.5" 32 | } 33 | } --------------------------------------------------------------------------------