├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── checks.yml │ └── publish.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── _templates ├── generator │ ├── help │ │ └── index.ejs.t │ ├── new │ │ └── hello.ejs.t │ └── with-prompt │ │ ├── hello.ejs.t │ │ └── prompt.ejs.t └── package │ └── new │ ├── README.ejs.t │ ├── index.ejs.t │ ├── package.ejs.t │ ├── prompt.js │ └── tsconfig.ejs.t ├── bin ├── build-package.sh ├── link-packages.sh ├── publish.sh ├── run.sh ├── test.sh ├── try-publish.sh ├── unlink-packages.sh └── watch-package.sh ├── lerna.json ├── package.json ├── packages ├── core │ ├── 8base-sdk │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── test │ │ │ └── __tests__ │ │ │ │ └── index.test.ts │ │ └── tsconfig.json │ ├── api-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── Client.ts │ │ │ ├── RefreshTokenInvalidError.ts │ │ │ ├── constants.ts │ │ │ ├── exportTables.ts │ │ │ ├── importData.ts │ │ │ ├── importTables.ts │ │ │ ├── importWorkspace.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── test │ │ │ ├── .eslintrc.yml │ │ │ ├── __fixtures__ │ │ │ │ └── index.ts │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── exportTables.test.ts.snap │ │ │ │ │ ├── importData.test.ts.snap │ │ │ │ │ ├── importTables.test.ts.snap │ │ │ │ │ └── index.test.ts.snap │ │ │ │ ├── exportTables.test.ts │ │ │ │ ├── importData.test.ts │ │ │ │ ├── importTables.test.ts │ │ │ │ └── index.test.ts │ │ │ ├── jest.setup.ts │ │ │ ├── tsconfig.json │ │ │ ├── types │ │ │ │ └── global.d.ts │ │ │ └── utils │ │ │ │ ├── index.ts │ │ │ │ └── mockRequest.ts │ │ └── tsconfig.json │ ├── api-token-auth-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── ApiTokenAuthClient.ts │ │ │ └── index.ts │ │ ├── test │ │ │ └── __tests__ │ │ │ │ └── api-token-auth-client.test.ts │ │ └── tsconfig.json │ ├── apollo-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── test │ │ │ ├── .eslintrc.yml │ │ │ ├── __tests__ │ │ │ │ └── index.test.ts │ │ │ └── jest.setup.ts │ │ └── tsconfig.json │ ├── apollo-links │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── AuthHeadersLink.ts │ │ │ ├── AuthLink.ts │ │ │ ├── SignUpLink.ts │ │ │ ├── SubscriptionLink.ts │ │ │ ├── SuccessLink.ts │ │ │ ├── TokenRefreshLink.ts │ │ │ ├── graphql │ │ │ │ └── mutations.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ └── utils │ │ │ │ └── index.ts │ │ ├── test │ │ │ ├── .eslintrc.yml │ │ │ ├── __tests__ │ │ │ │ ├── auth-headers-link.test.ts │ │ │ │ ├── auth-link.test.ts │ │ │ │ ├── sign-up-link.test.ts │ │ │ │ ├── success-link.test.ts │ │ │ │ └── token-refresh-link.test.ts │ │ │ └── jest.setup.ts │ │ └── tsconfig.json │ ├── auth │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── Auth.ts │ │ │ ├── SubscribableDecorator.ts │ │ │ ├── constants.ts │ │ │ └── index.ts │ │ ├── test │ │ │ └── __tests__ │ │ │ │ ├── Auth.test.ts │ │ │ │ └── SubscribableDecorator.test.ts │ │ └── tsconfig.json │ ├── tsconfig.settings.json │ ├── utils │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── Publisher.ts │ │ │ ├── StorageAPI.ts │ │ │ ├── constants │ │ │ │ ├── countryCodes.ts │ │ │ │ ├── index.ts │ │ │ │ └── schemaConstants.ts │ │ │ ├── errors │ │ │ │ ├── SDKError.ts │ │ │ │ ├── codes.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.ts │ │ │ │ └── packages.ts │ │ │ ├── formatters │ │ │ │ ├── formatDataAfterQuery.ts │ │ │ │ ├── formatDataForMutation.ts │ │ │ │ ├── formatFieldData.ts │ │ │ │ ├── formatFieldDataForMutation.ts │ │ │ │ ├── formatFieldDataList.ts │ │ │ │ ├── formatFieldDataListItem.ts │ │ │ │ ├── formatOptimisticResponse.ts │ │ │ │ ├── gqlPrettify.ts │ │ │ │ ├── index.ts │ │ │ │ └── omitDeep.ts │ │ │ ├── index.ts │ │ │ ├── queryGenerators │ │ │ │ ├── createQueryColumnsList.ts │ │ │ │ ├── createQueryString.ts │ │ │ │ ├── index.ts │ │ │ │ └── queryGenerators.ts │ │ │ ├── selectors │ │ │ │ ├── applicationsListSelectors.ts │ │ │ │ ├── index.ts │ │ │ │ ├── tableFieldSelectors.ts │ │ │ │ ├── tableSelectors.ts │ │ │ │ └── tablesListSelectors.ts │ │ │ ├── types.ts │ │ │ └── verifiers │ │ │ │ ├── hasIdTokenExpiredError.ts │ │ │ │ ├── index.ts │ │ │ │ ├── isAddressField.ts │ │ │ │ ├── isBigInt.ts │ │ │ │ ├── isEmptyAddress.ts │ │ │ │ ├── isEmptyNumber.ts │ │ │ │ ├── isEmptyPhone.ts │ │ │ │ ├── isFileField.ts │ │ │ │ ├── isFilesTable.ts │ │ │ │ ├── isGeoField.ts │ │ │ │ ├── isJSONField.ts │ │ │ │ ├── isListField.ts │ │ │ │ ├── isMetaField.ts │ │ │ │ ├── isNumberField.ts │ │ │ │ ├── isPhoneField.ts │ │ │ │ └── isRelationField.ts │ │ ├── test │ │ │ ├── .eslintrc.yml │ │ │ ├── __fixtures__ │ │ │ │ └── index.ts │ │ │ ├── __tests__ │ │ │ │ ├── Publisher.test.ts │ │ │ │ ├── StorageAPI.test.ts │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── formatDataAfterQuery.test.ts.snap │ │ │ │ │ ├── formatDataForMutation.test.ts.snap │ │ │ │ │ └── queryTableGenerator.test.ts.snap │ │ │ │ ├── errorsHelpers.ts │ │ │ │ ├── formatDataAfterQuery.test.ts │ │ │ │ ├── formatDataForMutation.test.ts │ │ │ │ ├── omitDeep.test.ts │ │ │ │ ├── queryTableGenerator.test.ts │ │ │ │ ├── tableSelectors.test.ts │ │ │ │ ├── tablesListSelectors.test.ts │ │ │ │ └── verifiers.test.ts │ │ │ └── jest.setup.ts │ │ └── tsconfig.json │ ├── validate │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── validator.constants.ts │ │ │ └── validator.ts │ │ ├── test │ │ │ ├── .eslintrc.yml │ │ │ ├── __tests__ │ │ │ │ ├── dateField.test.ts │ │ │ │ ├── fileField.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── jsonField.test.ts │ │ │ │ ├── numberField.test.ts │ │ │ │ ├── relationField.test.ts │ │ │ │ ├── switchField.test.ts │ │ │ │ └── textField.test.ts │ │ │ ├── jest.setup.ts │ │ │ └── utils │ │ │ │ ├── index.ts │ │ │ │ └── mockField.ts │ │ └── tsconfig.json │ ├── web-auth0-auth-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── WebAuth0AuthClient.ts │ │ │ └── index.ts │ │ ├── test │ │ │ └── __tests__ │ │ │ │ └── web-auth0-auth-client.test.ts │ │ └── tsconfig.json │ ├── web-cognito-auth-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── WebAuthCognitoClient.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── web-native-auth-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── WebNativeAuthClient.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ └── web-oauth-client │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ ├── WebOAuthClient.ts │ │ └── index.ts │ │ ├── tests │ │ └── __tests__ │ │ │ └── web-aouth-client.ts │ │ └── tsconfig.json └── react │ ├── 8base-react-sdk │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ └── __tests__ │ │ │ └── index.test.ts │ └── tsconfig.json │ ├── README.md │ ├── app-provider │ ├── .npmignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── ApolloContainer.tsx │ │ ├── AppProvider.tsx │ │ ├── EightBaseAppProvider.__deprecated.tsx │ │ ├── FragmentsSchemaContainer.ts │ │ ├── index.ts │ │ └── types.ts │ ├── test │ │ ├── .eslintrc.yml │ │ ├── __tests__ │ │ │ └── AppProvider.test.tsx │ │ └── jest.setup.ts │ └── tsconfig.json │ ├── auth │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── AuthContext.tsx │ │ ├── AuthProvider.tsx │ │ ├── index.ts │ │ ├── useAuth.ts │ │ └── withAuth.tsx │ ├── test │ │ ├── __tests__ │ │ │ ├── auth-context.test.tsx │ │ │ ├── use-auth.test.tsx │ │ │ └── with-auth.test.tsx │ │ └── utils.ts │ └── tsconfig.json │ ├── crud │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.MD │ ├── package.json │ ├── src │ │ ├── RecordCreate.tsx │ │ ├── RecordCreateMany.tsx │ │ ├── RecordCrud.tsx │ │ ├── RecordData.tsx │ │ ├── RecordDelete.tsx │ │ ├── RecordUpdate.tsx │ │ ├── RecordsList.tsx │ │ ├── fragments.ts │ │ └── index.ts │ └── tsconfig.json │ ├── file-input │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── FileInput.ts │ │ ├── index.ts │ │ └── types.ts │ ├── test │ │ └── __tests__ │ │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ │ └── index.test.tsx │ └── tsconfig.json │ ├── forms │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Field.tsx │ │ ├── FieldArray.tsx │ │ ├── Fieldset.tsx │ │ ├── Form.tsx │ │ ├── FormContext.tsx │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── getFieldSchemaName.ts │ │ │ ├── index.ts │ │ │ ├── log.ts │ │ │ └── renderComponent.ts │ ├── test │ │ ├── .eslintrc.yml │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ └── index.test.tsx │ │ ├── jest.setup.ts │ │ ├── tsconfig.json │ │ ├── types │ │ │ └── global.d.ts │ │ └── utils │ │ │ └── index.ts │ └── tsconfig.json │ ├── permissions-provider │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── IfAllowed.tsx │ │ ├── IfAllowedRoles.tsx │ │ ├── PermissionsContext.ts │ │ ├── PermissionsProvider.tsx │ │ ├── index.ts │ │ ├── isAllowed.ts │ │ ├── types.ts │ │ ├── useAllowedRoles.ts │ │ ├── usePermissions.ts │ │ ├── utils.ts │ │ └── withPermissions.tsx │ ├── test │ │ └── __tests__ │ │ │ ├── IfAllowedRoles.test.tsx │ │ │ ├── index.test.tsx │ │ │ └── useAllowedRoles.test.tsx │ └── tsconfig.json │ ├── table-schema-provider │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Features.ts │ │ ├── TableConsumer.tsx │ │ ├── TableSchemaContext.tsx │ │ ├── TableSchemaProvider.tsx │ │ ├── index.ts │ │ ├── useApplicationsList.ts │ │ ├── useTableSchema.ts │ │ ├── useTablesList.ts │ │ ├── withApplicationsList.tsx │ │ └── withTablesList.tsx │ ├── test │ │ └── __tests__ │ │ │ └── index.test.tsx │ └── tsconfig.json │ ├── tsconfig.settings.json │ └── utils │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── getDisplayName.ts │ └── index.ts │ └── tsconfig.json ├── tslint.json └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 14 20 | 21 | - name: Install Dependencies 22 | run: yarn --frozen-lockfile 23 | 24 | - name: Build packages 25 | run: yarn build-packages 26 | 27 | - name: Run linter 28 | run: yarn lint 29 | 30 | - name: Run prettier 31 | run: yarn prettier:check 32 | 33 | - name: Run tests 34 | run: yarn test 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | env: 8 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 9 | NPM_AUTH_TOKEN_ORG: ${{ secrets.NPM_AUTH_TOKEN_ORG }} 10 | 11 | jobs: 12 | publish: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 14 20 | 21 | - name: Install Dependencies 22 | run: yarn --frozen-lockfile 23 | 24 | - name: Build packages 25 | run: yarn build-packages 26 | 27 | - name: Run linter 28 | run: yarn lint 29 | 30 | - name: Run prettier 31 | run: yarn prettier:check 32 | 33 | - name: Run tests 34 | run: yarn test 35 | 36 | - name: Publish beta 37 | if: github.event.release.prerelease != false 38 | run: sh ./bin/publish.sh beta 39 | 40 | - name: Publish 41 | if: github.event.release.prerelease != true 42 | run: sh ./bin/publish.sh 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .env.test 4 | .eslintcache 5 | .idea 6 | .vscode 7 | build 8 | coverage 9 | es 10 | lib 11 | dist 12 | lerna-debug.log 13 | node_modules 14 | npm-debug.log 15 | package-lock.json 16 | packages/local-* 17 | yarn-error.log 18 | yerna.log 19 | tsconfig.tsbuildinfo 20 | package.json.lerna_backup 21 | .graphqlconfig 22 | schema.graphql 23 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "bracketSpacing": true, 4 | "singleQuote": true, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /_templates/generator/help/index.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | message: | 3 | hygen {bold generator new} --name [NAME] --action [ACTION] 4 | hygen {bold generator with-prompt} --name [NAME] --action [ACTION] 5 | --- -------------------------------------------------------------------------------- /_templates/generator/new/hello.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t 3 | --- 4 | --- 5 | to: app/hello.js 6 | --- 7 | const hello = ``` 8 | Hello! 9 | This is your first hygen template. 10 | 11 | Learn what it can do here: 12 | 13 | https://github.com/jondot/hygen 14 | ``` 15 | 16 | console.log(hello) 17 | 18 | 19 | -------------------------------------------------------------------------------- /_templates/generator/with-prompt/hello.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t 3 | --- 4 | --- 5 | to: app/hello.js 6 | --- 7 | const hello = ``` 8 | Hello! 9 | This is your first prompt based hygen template. 10 | 11 | Learn what it can do here: 12 | 13 | https://github.com/jondot/hygen 14 | ``` 15 | 16 | console.log(hello) 17 | 18 | 19 | -------------------------------------------------------------------------------- /_templates/generator/with-prompt/prompt.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: _templates/<%= name %>/<%= action || 'new' %>/prompt.js 3 | --- 4 | 5 | // see types of prompts: 6 | // https://github.com/enquirer/enquirer/tree/master/examples 7 | // 8 | module.exports = [ 9 | { 10 | type: 'input', 11 | name: 'message', 12 | message: "What's your message?" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /_templates/package/new/README.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: packages/<%= name %>/README.md 3 | --- 4 | # <%= Name %> 5 | -------------------------------------------------------------------------------- /_templates/package/new/index.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: packages/<%= name %>/src/index.ts 3 | --- 4 | const <%= name %> = () => { 5 | console.log('Hello World!'); 6 | }; 7 | 8 | export { <%= name %> }; 9 | -------------------------------------------------------------------------------- /_templates/package/new/package.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: packages/<%= name %>/package.json 3 | --- 4 | { 5 | "name": "@8base/<%= name %>", 6 | "version": "<%= version %>", 7 | "repository": "https://github.com/8base/sdk", 8 | "main": "dist/cjs/index.js", 9 | "types": "dist/mjs/index.d.ts", 10 | "module": "dist/mjs/index.js", 11 | "scripts": { 12 | "build": "../../../bin/build-package.sh", 13 | "watch": "../../../bin/watch-package.sh", 14 | "test": "NPM_ENV=test jest" 15 | }, 16 | "dependencies": {}, 17 | "devDependencies": { 18 | "jest": "24.7.1", 19 | "jest-localstorage-mock": "^2.2.0", 20 | "ts-jest": "^24.0.2", 21 | "typescript": "^3.5.1" 22 | }, 23 | "jest": { 24 | "globals": { 25 | "ts-jest": { 26 | "tsConfig": "/tsconfig.json" 27 | } 28 | }, 29 | "setupFiles": [ 30 | "jest-localstorage-mock" 31 | ], 32 | "collectCoverageFrom": [ 33 | "/src/**", 34 | "!/**/__tests__/**" 35 | ], 36 | "moduleNameMapper": { 37 | "@8base/sdk-core": "/../../core/8base-sdk/src/index.ts", 38 | "@8base/api-client": "/../../core/api-client/src/index.ts", 39 | "@8base/api-token-auth-client": "/../../core/api-token-auth-client/src/index.ts", 40 | "@8base/apollo-client": "/../../core/apollo-client/src/index.ts", 41 | "@8base/apollo-links": "/../../core/apollo-links/src/index.ts", 42 | "@8base/auth": "/../../core/auth/src/index.ts", 43 | "@8base/utils": "/../../core/utils/src/index.ts", 44 | "@8base/validate": "/../../core/validate/src/index.ts", 45 | "@8base/web-auth0-auth-client": "/../../core/web-auth0-auth-client/src/index.ts", 46 | "@8base/web-cognito-auth-client": "/../../core/web-cognito-auth-client/src/index.ts", 47 | "@8base/web-native-auth-client": "/../../core/web-native-auth-client/src/index.ts", 48 | "@8base/web-oauth-client": "/../../core/web-oauth-client/src/index.ts", 49 | "@8base/sdk-react": "/../../react/8base-react-sdk/src/index.ts", 50 | "@8base/react-app-provider": "/../../react/app-provider/src/index.ts", 51 | "@8base/react-auth": "/../../react/auth/src/index.ts", 52 | "@8base/react-crud": "/../../react/crud/src/index.ts", 53 | "@8base/react-file-input": "/../../react/file-input/src/index.ts", 54 | "@8base/react-forms": "/../../react/forms/src/index.ts", 55 | "@8base/react-permissions-provider": "/../../react/permissions-provider/src/index.ts", 56 | "@8base/react-table-schema-provider": "/../../react/table-schema-provider/src/index.ts", 57 | "@8base/react-utils": "/../../react/utils/src/index.ts" 58 | }, 59 | "moduleFileExtensions": [ 60 | "ts", 61 | "tsx", 62 | "js", 63 | "jsx" 64 | ], 65 | "transform": { 66 | "^.+\\.(ts|tsx)$": "ts-jest" 67 | }, 68 | "testMatch": [ 69 | "**/__tests__/**/*.[jt]s?(x)", 70 | "**/?(*.)+(spec|test).[jt]s?(x)" 71 | ] 72 | }, 73 | "license": "MIT" 74 | } 75 | -------------------------------------------------------------------------------- /_templates/package/new/prompt.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | type: 'input', 4 | name: 'version', 5 | message: "What's version of the package?" 6 | } 7 | ]; 8 | -------------------------------------------------------------------------------- /_templates/package/new/tsconfig.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: packages/<%= name %>/tsconfig.json 3 | --- 4 | { 5 | "extends": "../tsconfig.settings.json", 6 | "compilerOptions": { 7 | "outDir": "dist", 8 | "rootDir": "src", 9 | "baseUrl": "." 10 | }, 11 | "include": [ 12 | "src/**/*" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /bin/build-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "$(tput setaf 3)" 4 | echo "Starting \"build\" for \"${PWD##*/}\"" 5 | echo "$(tput setaf 7)" 6 | 7 | rimraf dist 8 | tsc --outDir ./dist/mjs 9 | tsc --module commonjs --outDir ./dist/cjs 10 | -------------------------------------------------------------------------------- /bin/link-packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lerna exec -- yarn link > /dev/null && lerna list | xargs -I {} echo yarn link {} 4 | 5 | -------------------------------------------------------------------------------- /bin/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | beta=$1 3 | status=0 4 | 5 | (./bin/try-publish.sh "core" "api-client" "$beta") || status=1 6 | (./bin/try-publish.sh "core" "apollo-client" "$beta") || status=1 7 | (./bin/try-publish.sh "core" "apollo-links" "$beta") || status=1 8 | (./bin/try-publish.sh "core" "auth" "$beta") || status=1 9 | (./bin/try-publish.sh "core" "utils" "$beta") || status=1 10 | (./bin/try-publish.sh "core" "validate" "$beta") || status=1 11 | (./bin/try-publish.sh "core" "api-token-auth-client" "$beta") || status=1 12 | (./bin/try-publish.sh "core" "web-auth0-auth-client" "$beta") || status=1 13 | (./bin/try-publish.sh "core" "web-native-auth-client" "$beta") || status=1 14 | (./bin/try-publish.sh "core" "web-cognito-auth-client" "$beta") || status=1 15 | (./bin/try-publish.sh "core" "web-oauth-client" "$beta") || status=1 16 | (./bin/try-publish.sh "core" "8base-sdk" "$beta") || status=1 17 | 18 | (./bin/try-publish.sh "react" "app-provider" "$beta") || status=1 19 | (./bin/try-publish.sh "react" "auth" "$beta") || status=1 20 | (./bin/try-publish.sh "react" "crud" "$beta") || status=1 21 | (./bin/try-publish.sh "react" "file-input" "$beta") || status=1 22 | (./bin/try-publish.sh "react" "forms" "$beta") || status=1 23 | (./bin/try-publish.sh "react" "permissions-provider" "$beta") || status=1 24 | (./bin/try-publish.sh "react" "table-schema-provider" "$beta") || status=1 25 | (./bin/try-publish.sh "react" "utils" "$beta") || status=1 26 | (./bin/try-publish.sh "react" "8base-react-sdk" "$beta") || status=1 27 | 28 | exit $status 29 | -------------------------------------------------------------------------------- /bin/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scope=$1 4 | package=$2 5 | title=$3 6 | cmd=$4 7 | 8 | status=0 9 | 10 | echo -e "\033[0;33mStarting \"${title}\" for \"${package}\"\033[0m\n" 11 | 12 | cd ./packages/$scope/$package 13 | 14 | eval "${cmd}; if [ \"\$?\" != \"0\" ]; then status=1; fi" 15 | 16 | sleep 2 17 | 18 | if [ "$status" != "0" ]; then 19 | echo -e "\n\033[0;31mThe \"${title}\" for \"${package}\" exited with $status\033[0m\n" 20 | else 21 | echo -e "\n\033[0;32mThe \"${title}\" for \"${package}\" exited with 0\033[0m\n" 22 | fi 23 | 24 | exit $status -------------------------------------------------------------------------------- /bin/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | status=0 4 | 5 | (./bin/run.sh "core" "api-client" "test" "yarn test --verbose") || status=1 6 | (./bin/run.sh "core" "apollo-links" "test" "yarn test --verbose") || status=1 7 | (./bin/run.sh "core" "apollo-client" "test" "yarn test --verbose") || status=1 8 | (./bin/run.sh "core" "utils" "test" "yarn test --verbose") || status=1 9 | (./bin/run.sh "core" "validate" "test" "yarn test --verbose") || status=1 10 | (./bin/run.sh "core" "auth" "test" "yarn test --verbose") || status=1 11 | (./bin/run.sh "core" "web-oauth-client" "test" "yarn test --verbose") || status=1 12 | (./bin/run.sh "core" "web-auth0-auth-client" "test" "yarn test --verbose") || status=1 13 | (./bin/run.sh "core" "api-token-auth-client" "test" "yarn test --verbose") || status=1 14 | (./bin/run.sh "core" "8base-sdk" "test" "yarn test --verbose") || status=1 15 | 16 | (./bin/run.sh "react" "app-provider" "test" "yarn test --verbose") || status=1 17 | (./bin/run.sh "react" "file-input" "test" "yarn test --verbose") || status=1 18 | (./bin/run.sh "react" "table-schema-provider" "test" "yarn test --verbose") || status=1 19 | (./bin/run.sh "react" "forms" "test" "yarn test --verbose") || status=1 20 | (./bin/run.sh "react" "permissions-provider" "test" "yarn test --verbose") || status=1 21 | (./bin/run.sh "react" "auth" "test" "yarn test --verbose") || status=1 22 | (./bin/run.sh "react" "8base-react-sdk" "test" "yarn test --verbose") || status=1 23 | 24 | exit $status 25 | -------------------------------------------------------------------------------- /bin/try-publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scope=$1 4 | package=$2 5 | beta=$3 6 | 7 | status=0 8 | 9 | echo -e "\033[0;33mStarting try to publish for \"${package}\"\033[0m\n" 10 | 11 | cd ./packages/$scope/$package 12 | 13 | name=$(cat package.json | grep name | head -n 1 | cut -d'"' -f 4) 14 | version=$(cat package.json | grep version | head -n 1 | cut -d'"' -f 4) 15 | published=$(npm info $name version 2> /dev/null) 16 | 17 | if [ -z "$published" ]; then 18 | published="0.0.0" 19 | fi 20 | 21 | if [ "$published" != "$version" ]; then 22 | echo "Try to publish $version version of the $name package." 23 | 24 | echo "//registry.npmjs.org/:_authToken=\${NPM_AUTH_TOKEN_ORG}" > .npmrc 25 | 26 | if [ "$beta" != "beta" ]; then 27 | npm publish --access public; 28 | else 29 | npm publish --tag beta --access public; 30 | fi 31 | 32 | if [ "$?" != "0" ]; then status=1; fi 33 | else 34 | echo "Current version of the package already published to the NPM." 35 | fi 36 | 37 | sleep 2 38 | 39 | if [ "$status" != "0" ]; then 40 | echo -e "\n\033[0;31mThe try to publish for \"${package}\" exited with $status\033[0m\n" 41 | else 42 | echo -e "\n\033[0;32mThe try to publish for \"${package}\" exited with 0\033[0m\n" 43 | fi 44 | 45 | exit $status 46 | -------------------------------------------------------------------------------- /bin/unlink-packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lerna exec -- yarn unlink > /dev/null && lerna list | xargs -I {} echo yarn unlink {} 4 | 5 | -------------------------------------------------------------------------------- /bin/watch-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn tsc --watch 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "3.22.1", 3 | "packages": [ 4 | "packages/core/*", 5 | "packages/react/*" 6 | ], 7 | "npmClient": "yarn", 8 | "version": "3.0.0-beta.3", 9 | "useWorkspaces": true, 10 | "command": { 11 | "version": { 12 | "message": "chore(release): %s", 13 | "conventionalCommits": true, 14 | "yes": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "postinstall": "lerna bootstrap", 5 | "build-packages": "lerna run build", 6 | "link-packages": "./bin/link-packages.sh", 7 | "unlink-packages": "./bin/unlink-packages.sh", 8 | "test": "./bin/test.sh", 9 | "lint": "tslint packages/**/*/{src,test}/{.,**}/*.{ts,tsx}", 10 | "prettier:base": "prettier --parser typescript --single-quote", 11 | "prettier:check": "npm run prettier:base -- --list-different \"packages/**/*/{src,test}/**/*.{ts,tsx}\"", 12 | "prettier:write": "npm run prettier:base -- --write \"packages/**/*/{src,test}/**/*.{ts,tsx}\"", 13 | "bump": "lerna version --force-publish", 14 | "package:new": "hygen package new" 15 | }, 16 | "devDependencies": { 17 | "hygen": "^6.0.4", 18 | "lerna": "^3.22.1", 19 | "prettier": "^2.2.1", 20 | "rimraf": "^3.0.2", 21 | "tslint": "^6.1.3", 22 | "tslint-config-prettier": "^1.18.0", 23 | "tslint-config-standard": "^9.0.0", 24 | "tslint-react": "^5.0.0", 25 | "typescript": "^4.1.3", 26 | "utility-types": "^3.10.0" 27 | }, 28 | "version": "0.0.0", 29 | "workspaces": [ 30 | "packages/core/*", 31 | "packages/react/*" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/8base-sdk/README.md: -------------------------------------------------------------------------------- 1 | # 8base-sdk 2 | 3 | This package consist of all @8base packages. 4 | 5 | -------------------------------------------------------------------------------- /packages/core/8base-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@8base/sdk-core", 3 | "version": "3.0.0-beta.3", 4 | "repository": "https://github.com/8base/sdk", 5 | "main": "dist/cjs/index.js", 6 | "types": "dist/mjs/index.d.ts", 7 | "module": "dist/mjs/index.js", 8 | "scripts": { 9 | "build": "../../../bin/build-package.sh", 10 | "watch": "../../../bin/watch-package.sh", 11 | "test": "NPM_ENV=test jest" 12 | }, 13 | "dependencies": { 14 | "@8base/apollo-client": "^3.0.0-beta.3", 15 | "@8base/apollo-links": "^3.0.0-beta.3", 16 | "@8base/auth": "^3.0.0-beta.3", 17 | "@8base/utils": "^3.0.0-beta.3", 18 | "@8base/validate": "^3.0.0-beta.3" 19 | }, 20 | "devDependencies": { 21 | "jest": "26.6.3", 22 | "jest-localstorage-mock": "^2.4.6", 23 | "ts-jest": "^26.4.4", 24 | "typescript": "^4.1.3" 25 | }, 26 | "jest": { 27 | "globals": { 28 | "ts-jest": { 29 | "tsconfig": "/tsconfig.json" 30 | } 31 | }, 32 | "setupFiles": [ 33 | "jest-localstorage-mock" 34 | ], 35 | "collectCoverageFrom": [ 36 | "/src/**", 37 | "!/**/__tests__/**" 38 | ], 39 | "moduleNameMapper": { 40 | "@8base/sdk-core": "/../../core/8base-sdk/src/index.ts", 41 | "@8base/api-client": "/../../core/api-client/src/index.ts", 42 | "@8base/api-token-auth-client": "/../../core/api-token-auth-client/src/index.ts", 43 | "@8base/apollo-client": "/../../core/apollo-client/src/index.ts", 44 | "@8base/apollo-links": "/../../core/apollo-links/src/index.ts", 45 | "@8base/auth": "/../../core/auth/src/index.ts", 46 | "@8base/utils": "/../../core/utils/src/index.ts", 47 | "@8base/validate": "/../../core/validate/src/index.ts", 48 | "@8base/web-auth0-auth-client": "/../../core/web-auth0-auth-client/src/index.ts", 49 | "@8base/web-cognito-auth-client": "/../../core/web-cognito-auth-client/src/index.ts", 50 | "@8base/web-native-auth-client": "/../../core/web-native-auth-client/src/index.ts", 51 | "@8base/web-oauth-client": "/../../core/web-oauth-client/src/index.ts", 52 | "@8base/sdk-react": "/../../react/8base-react-sdk/src/index.ts", 53 | "@8base/react-app-provider": "/../../react/app-provider/src/index.ts", 54 | "@8base/react-auth": "/../../react/auth/src/index.ts", 55 | "@8base/react-crud": "/../../react/crud/src/index.ts", 56 | "@8base/react-file-input": "/../../react/file-input/src/index.ts", 57 | "@8base/react-forms": "/../../react/forms/src/index.ts", 58 | "@8base/react-permissions-provider": "/../../react/permissions-provider/src/index.ts", 59 | "@8base/react-table-schema-provider": "/../../react/table-schema-provider/src/index.ts", 60 | "@8base/react-utils": "/../../react/utils/src/index.ts" 61 | }, 62 | "moduleFileExtensions": [ 63 | "ts", 64 | "tsx", 65 | "js", 66 | "jsx" 67 | ], 68 | "transform": { 69 | "^.+\\.(ts|tsx)$": "ts-jest" 70 | }, 71 | "testMatch": [ 72 | "**/__tests__/**/*.[jt]s?(x)", 73 | "**/?(*.)+(spec|test).[jt]s?(x)" 74 | ] 75 | }, 76 | "license": "MIT" 77 | } 78 | -------------------------------------------------------------------------------- /packages/core/8base-sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@8base/apollo-client'; 2 | export * from '@8base/apollo-links'; 3 | export * from '@8base/auth'; 4 | export * from '@8base/utils'; 5 | export * from '@8base/validate'; 6 | -------------------------------------------------------------------------------- /packages/core/8base-sdk/test/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as utilsExport from '@8base/utils'; 2 | import * as validateExport from '@8base/validate'; 3 | import * as authExport from '@8base/auth'; 4 | import * as apolloLinksExport from '@8base/apollo-links'; 5 | import * as apolloClientExport from '@8base/apollo-client'; 6 | 7 | describe('8base-sdk', () => { 8 | const rootExport = require('../../src'); 9 | 10 | it('contains @8base/utils', () => { 11 | expect(rootExport).toMatchObject(utilsExport); 12 | }); 13 | 14 | it('contains @8base/validate', () => { 15 | expect(rootExport).toMatchObject(validateExport); 16 | }); 17 | 18 | it('contains @8base/auth', () => { 19 | expect(rootExport).toMatchObject(authExport); 20 | }); 21 | 22 | it('contains @8base/apollo-links', () => { 23 | expect(rootExport).toMatchObject(apolloLinksExport); 24 | }); 25 | 26 | it('contains @8base/apollo-client', () => { 27 | expect(rootExport).toMatchObject(apolloClientExport); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/core/8base-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/api-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/api-client/README.md: -------------------------------------------------------------------------------- 1 | # 8base SDK - Client Module 2 | The 8base SDK provides a convient way of initializing an API client to start making GraphQL calls to a workspace. 3 | 4 | This client library is used by the other 8base service packages to make requests to the 8base API. You can also use it independently to make custom requests to the API. 5 | 6 | ## Usage 7 | The `Client` module exposes a number of different methods that can be used to configure itself. Those functions are listed below with relevant descriptions. 8 | 9 | In the most basic case, the client can be used to query public resources from a given workspace. 10 | 11 | ```javascript 12 | /* Import client module */ 13 | import { Client } from '@8base/api-client'; 14 | 15 | /* Instantiate new instance with workspace endpoint */ 16 | const client = new Client('https://api.8base.com/cjz1n2qrk00f901jt2utcc3m0'); 17 | 18 | /* Run a query with a callback handler */ 19 | client.request(` 20 | query { 21 | __schema { 22 | types { 23 | name 24 | } 25 | } 26 | } 27 | `).then(console.log); 28 | ``` 29 | 30 | Should an `idToken` or `apiToken` need to get set, the `client.setIdToken(tk)` method can be used. Under the hood, this will set the supplied value as a Bearer token header on subsequent requests. 31 | 32 | ```javascript 33 | /* Set the Token */ 34 | client.setIdToken('MY_API_TOKEN') 35 | 36 | /* Run a query with a callback handler */ 37 | client.request(` 38 | query { 39 | privateResourceList { 40 | items { 41 | id 42 | } 43 | } 44 | } 45 | `).then(console.log); 46 | ``` 47 | 48 | ## Client Methods 49 | 50 | #### setIdToken(token: String!) 51 | Update the id token. 52 | 53 | #### setRefreshToken(token: String!) 54 | Update the refresh token. 55 | 56 | #### setEmail(email: String!) 57 | Update the user email. 58 | 59 | #### setWorkspaceId(id: String!) 60 | Update the workspace identifier. 61 | 62 | #### request(query: GraphqlString!, variables: Object) 63 | Send request to the API with variables that will be used when executing the query. 64 | . Returns promise to be resolved. 65 | 66 | ```javascript 67 | /* Set variables */ 68 | const variables = { 69 | search: "ste" 70 | } 71 | 72 | /* Set query */ 73 | const query = /* GraphQL */` 74 | query($search: String!) { 75 | resourceList(filter: { 76 | name: { 77 | contains: $search 78 | } 79 | }) { 80 | items { 81 | id 82 | } 83 | } 84 | } 85 | ` 86 | 87 | /* Run a query with a callback handler */ 88 | client.request(query, variables).then(console.log); 89 | ``` 90 | 91 | ## Alternatives 92 | There any a number of ways developers can connect to their workspace and begin executing queries. The `Client` module is only one of them! If you're curious about alternatives for how you can create a client, check out the following video. However, remember that all GraphQL calls are only HTTP post requests – and connecting to your 8base workspace is no different! 93 | 94 | [![Connecting to the API](https://miro.medium.com/max/4200/1*T13c_GK0ED6DluR7Wgrrxw.png)](https://www.youtube.com/watch?v=gLM-Fc6gWlE) -------------------------------------------------------------------------------- /packages/core/api-client/src/RefreshTokenInvalidError.ts: -------------------------------------------------------------------------------- 1 | class RefreshTokenInvalidError extends Error { 2 | constructor() { 3 | super(`Can't refresh token.`); 4 | } 5 | } 6 | 7 | export { RefreshTokenInvalidError }; 8 | -------------------------------------------------------------------------------- /packages/core/api-client/src/exportTables.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | 3 | import { TABLES_LIST_QUERY } from './constants'; 4 | import { SchemaResponse } from './types'; 5 | 6 | type ExportTablesConfig = { 7 | withSystemTables?: boolean; 8 | }; 9 | 10 | export const exportTables = async ( 11 | request: (query: string | DocumentNode, variables?: object) => Promise, 12 | config: ExportTablesConfig = {}, 13 | ) => { 14 | const variables = config.withSystemTables 15 | ? {} 16 | : { 17 | filter: { 18 | onlyUserTables: true, 19 | }, 20 | }; 21 | 22 | const tablesListData = await request(TABLES_LIST_QUERY, variables); 23 | 24 | return tablesListData.tablesList.items; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/core/api-client/src/importWorkspace.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | import * as R from 'ramda'; 3 | 4 | import { importData } from './importData'; 5 | import { importTables } from './importTables'; 6 | 7 | export const importWorkspace = async ( 8 | request: (query: string | DocumentNode, variables?: object) => Promise, 9 | workspace: object, 10 | ) => { 11 | await importTables(request, R.propOr({}, 'tables', workspace)); 12 | await importData(request, R.propOr({}, 'data', workspace)); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/core/api-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export { importTables } from './importTables'; 2 | export { importData } from './importData'; 3 | export { importWorkspace } from './importWorkspace'; 4 | export { exportTables } from './exportTables'; 5 | export { Client } from './Client'; 6 | -------------------------------------------------------------------------------- /packages/core/api-client/src/types.ts: -------------------------------------------------------------------------------- 1 | import { TableSchema } from '@8base/utils'; 2 | 3 | export type SchemaResponse = { 4 | tablesList: { 5 | items: TableSchema[]; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/api-client/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | globals: 2 | mockRequest: true -------------------------------------------------------------------------------- /packages/core/api-client/test/__tests__/exportTables.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | 3 | import { Client, exportTables } from '../../src'; 4 | import { TABLES } from '../__fixtures__'; 5 | 6 | beforeEach(() => { 7 | nock.cleanAll(); 8 | }); 9 | 10 | it('As a developer, I can export schema of the user tables.', async () => { 11 | const mock = global.mockRequest('https://api.test.8base.com', 200, { 12 | data: { 13 | tablesList: { 14 | items: TABLES, 15 | }, 16 | }, 17 | }); 18 | 19 | const client = new Client('https://api.test.8base.com'); 20 | 21 | client.setIdToken('idToken'); 22 | client.setWorkspaceId('workspaceId'); 23 | 24 | const schemaTables = await exportTables(client.request.bind(client)); 25 | 26 | expect(schemaTables).toMatchSnapshot(); 27 | expect(await mock).toMatchSnapshot(); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/core/api-client/test/__tests__/importData.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | 3 | import { TABLES, DATA } from '../__fixtures__'; 4 | import { Client, importData } from '../../src'; 5 | 6 | beforeEach(() => { 7 | nock.cleanAll(); 8 | }); 9 | 10 | it('As a developer, I can export schema of the user tables.', async () => { 11 | const mocks = [ 12 | global.mockRequest('https://api.test.8base.com', 200, { 13 | data: { 14 | tablesList: { 15 | items: TABLES, 16 | }, 17 | }, 18 | }), 19 | global.mockRequest('https://api.test.8base.com', 200, { 20 | data: { 21 | fileUploadInfo: { 22 | apiKey: 'apiKey', 23 | policy: 'policy', 24 | signature: 'signature', 25 | path: 'path', 26 | }, 27 | }, 28 | }), 29 | global.mockRequest('https://api.test.8base.com', 200, { 30 | data: { 31 | field: { 32 | id: 'remote-client-1', 33 | }, 34 | }, 35 | }), 36 | global.mockRequest('https://api.test.8base.com', 200, { 37 | data: { 38 | field: { 39 | id: 'remote-order-1', 40 | }, 41 | }, 42 | }), 43 | global.mockRequest('https://api.test.8base.com', 200, { 44 | data: { 45 | field: { 46 | id: 'remote-order-2', 47 | }, 48 | }, 49 | }), 50 | global.mockRequest('https://api.test.8base.com', 200, { 51 | data: { 52 | user: { 53 | id: 'USER_ID', 54 | }, 55 | }, 56 | }), 57 | global.mockRequest('https://api.test.8base.com'), 58 | global.mockRequest('https://api.test.8base.com'), 59 | global.mockRequest('https://api.test.8base.com'), 60 | ]; 61 | 62 | const client = new Client('https://api.test.8base.com'); 63 | 64 | client.setIdToken('idToken'); 65 | client.setWorkspaceId('workspaceId'); 66 | 67 | await importData(client.request.bind(client), DATA); 68 | 69 | expect(await Promise.all(mocks)).toMatchSnapshot(); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/core/api-client/test/__tests__/importTables.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | 3 | import { Client, importTables } from '../../src'; 4 | import { TABLES, USER_TABLES } from '../__fixtures__'; 5 | 6 | beforeEach(() => { 7 | nock.cleanAll(); 8 | }); 9 | 10 | it('As a developer, I can export schema of the user tables.', async () => { 11 | const mocks = [ 12 | global.mockRequest('https://api.test.8base.com'), 13 | global.mockRequest('https://api.test.8base.com'), 14 | global.mockRequest('https://api.test.8base.com', 200, { 15 | data: { 16 | tablesList: { 17 | items: TABLES, 18 | }, 19 | }, 20 | }), 21 | global.mockRequest('https://api.test.8base.com'), 22 | global.mockRequest('https://api.test.8base.com'), 23 | global.mockRequest('https://api.test.8base.com'), 24 | ]; 25 | 26 | const client = new Client('https://api.test.8base.com'); 27 | 28 | client.setIdToken('idToken'); 29 | client.setWorkspaceId('workspaceId'); 30 | 31 | await importTables(client.request.bind(client), USER_TABLES as any); 32 | 33 | expect(await Promise.all(mocks)).toMatchSnapshot(); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/core/api-client/test/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import { mockRequest } from './utils'; 2 | 3 | // @ts-ignore 4 | global.mockRequest = mockRequest; 5 | -------------------------------------------------------------------------------- /packages/core/api-client/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": [ 4 | "./**/*" 5 | ] 6 | } -------------------------------------------------------------------------------- /packages/core/api-client/test/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | declare namespace NodeJS { 3 | export interface Global { 4 | mockRequest: (endpoint: string, status?: number, response?: { [key: string]: any }) => Promise; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/api-client/test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { mockRequest } from './mockRequest'; 2 | -------------------------------------------------------------------------------- /packages/core/api-client/test/utils/mockRequest.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | 3 | function mockRequest( 4 | endpoint: string, 5 | status: number = 200, 6 | response: { data: { [key: string]: any } } = { data: {} }, 7 | ): Promise { 8 | let requestBody: any = null; 9 | 10 | return new Promise((resolve) => { 11 | nock(endpoint) 12 | .post('/', (body: any) => { 13 | requestBody = body; 14 | 15 | return true; 16 | }) 17 | .reply(status, function reply() { 18 | resolve({ 19 | body: requestBody, 20 | headers: (this as any).req.headers, 21 | }); 22 | 23 | return response; 24 | }); 25 | }); 26 | } 27 | 28 | export { mockRequest }; 29 | -------------------------------------------------------------------------------- /packages/core/api-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/README.md: -------------------------------------------------------------------------------- 1 | # 8base api token auth client 2 | 3 | The 8base api token auth client for the `AuthProvider`. 4 | 5 | ## ApiTokenAuthClient 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [ApiTokenAuthClient](#apitokenauthclient) 12 | - [Parameters](#parameters) 13 | 14 | ### ApiTokenAuthClient 15 | 16 | Create instance of the api token auth client 17 | 18 | #### Parameters 19 | 20 | - `apiToken` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Api Token generated in 8base app. 21 | - `apiToken.apiToken` 22 | 23 | ## Usage 24 | 25 | ```js 26 | import { AuthContext, AuthProvider, type AuthContextProps } from '@8base/react-auth'; 27 | import { ApiTokenAuthClient } form '@8base/api-token-auth-client'; 28 | 29 | const authClient = new ApiTokenAuthClient({ 30 | apiToken: 'api-token', 31 | }); 32 | 33 | ... 34 | 35 | 36 | ... 37 | 38 | { 39 | (auth: AuthContextProps) => (
) 40 | } 41 | 42 | ... 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@8base/api-token-auth-client", 3 | "version": "3.0.0-beta.3", 4 | "author": "8base", 5 | "repository": "https://github.com/8base/sdk", 6 | "homepage": "https://github.com/8base/sdk/tree/master/packages/core/api-token-auth-client", 7 | "main": "dist/cjs/index.js", 8 | "types": "dist/mjs/index.d.ts", 9 | "module": "dist/mjs/index.js", 10 | "scripts": { 11 | "build": "../../../bin/build-package.sh", 12 | "watch": "../../../bin/watch-package.sh", 13 | "test": "NPM_ENV=test jest" 14 | }, 15 | "dependencies": { 16 | "@8base/utils": "^3.0.0-beta.3", 17 | "jwt-decode": "^3.1.2" 18 | }, 19 | "devDependencies": { 20 | "@types/jest": "^26.0.20", 21 | "jest": "26.6.3", 22 | "jest-localstorage-mock": "^2.4.6", 23 | "prettier": "^2.2.1", 24 | "ts-jest": "^26.4.4", 25 | "typescript": "^4.1.3" 26 | }, 27 | "jest": { 28 | "globals": { 29 | "ts-jest": { 30 | "tsconfig": "/tsconfig.json" 31 | } 32 | }, 33 | "collectCoverageFrom": [ 34 | "/src/**", 35 | "!/**/__tests__/**" 36 | ], 37 | "moduleNameMapper": { 38 | "@8base/sdk-core": "/../../core/8base-sdk/src/index.ts", 39 | "@8base/api-client": "/../../core/api-client/src/index.ts", 40 | "@8base/api-token-auth-client": "/../../core/api-token-auth-client/src/index.ts", 41 | "@8base/apollo-client": "/../../core/apollo-client/src/index.ts", 42 | "@8base/apollo-links": "/../../core/apollo-links/src/index.ts", 43 | "@8base/auth": "/../../core/auth/src/index.ts", 44 | "@8base/utils": "/../../core/utils/src/index.ts", 45 | "@8base/validate": "/../../core/validate/src/index.ts", 46 | "@8base/web-auth0-auth-client": "/../../core/web-auth0-auth-client/src/index.ts", 47 | "@8base/web-cognito-auth-client": "/../../core/web-cognito-auth-client/src/index.ts", 48 | "@8base/web-native-auth-client": "/../../core/web-native-auth-client/src/index.ts", 49 | "@8base/web-oauth-client": "/../../core/web-oauth-client/src/index.ts", 50 | "@8base/sdk-react": "/../../react/8base-react-sdk/src/index.ts", 51 | "@8base/react-app-provider": "/../../react/app-provider/src/index.ts", 52 | "@8base/react-auth": "/../../react/auth/src/index.ts", 53 | "@8base/react-crud": "/../../react/crud/src/index.ts", 54 | "@8base/react-file-input": "/../../react/file-input/src/index.ts", 55 | "@8base/react-forms": "/../../react/forms/src/index.ts", 56 | "@8base/react-permissions-provider": "/../../react/permissions-provider/src/index.ts", 57 | "@8base/react-table-schema-provider": "/../../react/table-schema-provider/src/index.ts", 58 | "@8base/react-utils": "/../../react/utils/src/index.ts" 59 | }, 60 | "moduleFileExtensions": [ 61 | "ts", 62 | "tsx", 63 | "js", 64 | "jsx" 65 | ], 66 | "transform": { 67 | "^.+\\.(ts|tsx)$": "ts-jest" 68 | }, 69 | "testMatch": [ 70 | "**/__tests__/**/*.[jt]s?(x)", 71 | "**/?(*.)+(spec|test).[jt]s?(x)" 72 | ] 73 | }, 74 | "license": "MIT" 75 | } 76 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/src/ApiTokenAuthClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAuthState, 3 | IAuthClient, 4 | PACKAGES, 5 | StorageAPI, 6 | throwIfMissingRequiredParameters, 7 | IStorageOptions, 8 | } from '@8base/utils'; 9 | import jwtDecode from 'jwt-decode'; 10 | 11 | interface IApiTokenAuthClientOptions { 12 | apiToken: string; 13 | } 14 | 15 | /** 16 | * Creates instacne of the api token auth client 17 | */ 18 | class ApiTokenAuthClient implements IAuthClient { 19 | private storageAPI: StorageAPI; 20 | private readonly apiToken: string; 21 | 22 | constructor(options: IApiTokenAuthClientOptions, storageOptions: IStorageOptions = {}) { 23 | throwIfMissingRequiredParameters(['apiToken'], PACKAGES.API_TOKEN_AUTH_CLIENT, options); 24 | 25 | const { apiToken } = options; 26 | 27 | this.storageAPI = new StorageAPI( 28 | storageOptions.storage || window.localStorage, 29 | storageOptions.storageKey || 'auth', 30 | storageOptions.initialState, 31 | ); 32 | this.apiToken = apiToken; 33 | 34 | this.storageAPI.setState({ token: apiToken }); 35 | } 36 | 37 | public getState(): IAuthState { 38 | return { 39 | ...this.storageAPI.getState(), 40 | token: this.apiToken, 41 | }; 42 | } 43 | 44 | public getTokenInfo() { 45 | if (!this.apiToken) { 46 | return undefined; 47 | } 48 | 49 | return jwtDecode(this.apiToken) || undefined; 50 | } 51 | 52 | public setState(state: IAuthState): void { 53 | this.storageAPI.setState({ 54 | ...state, 55 | token: this.apiToken, 56 | }); 57 | } 58 | 59 | public purgeState(): void { 60 | this.storageAPI.purgeState(); 61 | this.storageAPI.setState({ token: this.apiToken }); 62 | } 63 | 64 | public checkIsAuthorized(): boolean { 65 | return true; 66 | } 67 | } 68 | 69 | export { ApiTokenAuthClient }; 70 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ApiTokenAuthClient } from './ApiTokenAuthClient'; 2 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/test/__tests__/api-token-auth-client.test.ts: -------------------------------------------------------------------------------- 1 | import { ApiTokenAuthClient } from '../../src'; 2 | 3 | const API_TOKEN = 'api token'; 4 | const ANOTHER_API_TOKEN = 'another api token'; 5 | const WORKSPACE_ID = 'workspace id'; 6 | 7 | describe('ApiTokenClient', () => { 8 | it("Throws an error if apiToken haven't provided", () => { 9 | expect(() => { 10 | // @ts-ignore 11 | const temp = new ApiTokenAuthClient(); 12 | }).toThrow('Missing parameter: apiToken'); 13 | }); 14 | 15 | const authClient = new ApiTokenAuthClient({ apiToken: API_TOKEN }); 16 | 17 | it('As a developer, I can get api token from the state', async () => { 18 | expect(authClient.getState()).toEqual({ 19 | token: API_TOKEN, 20 | }); 21 | }); 22 | 23 | it("As a developer, I can't rewrite token in the state", async () => { 24 | authClient.setState({ 25 | token: ANOTHER_API_TOKEN, 26 | workspaceId: WORKSPACE_ID, 27 | }); 28 | 29 | expect(authClient.getState()).toEqual({ 30 | token: API_TOKEN, 31 | workspaceId: WORKSPACE_ID, 32 | }); 33 | }); 34 | 35 | it("As a developer, I can't purge token from the state", async () => { 36 | authClient.purgeState(); 37 | 38 | expect(authClient.getState()).toEqual({ 39 | token: API_TOKEN, 40 | }); 41 | }); 42 | 43 | it("ApiTokenClient's instance is always authorized", async () => { 44 | expect(authClient.checkIsAuthorized()).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/core/api-token-auth-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/apollo-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/apollo-client/README.md: -------------------------------------------------------------------------------- 1 | # 8base Create Apollo Client 2 | 3 | The Apollo Client library contains an extended implementation of [ApolloClient](https://www.apollographql.com/docs/react/api/apollo-client.html) that includes several links to work with 8base services. 4 | 5 | ## EightBaseApolloClient 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [EightBaseApolloClient](#eightbaseapolloclient) 12 | - [Parameters](#parameters) 13 | 14 | ### EightBaseApolloClient 15 | 16 | **Extends ApolloClient** 17 | 18 | Extends Apollo Client by 8base several links. 19 | 20 | #### Parameters 21 | 22 | - `config` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The Apollo Client config. 23 | - `config.uri` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Endpoint of the GraphQl server. 24 | - `config.getAuthState` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Used to retrieve authentication state. 25 | - `config.getRefreshTokenParameters` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Used to retrieve the refresh token parameters. 26 | - `config.onAuthSuccess` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Callback that is executed when an attempt to refresh authentication is successful. 27 | - `config.onAuthError` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Callback which is executed when an attempt to refresh authentication fails. 28 | - `config.onIdTokenExpired` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Callback which is executed when id token is expired. 29 | - `config.onRequestSuccess` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Callback which is executed when a request is successful. 30 | - `config.onRequestError` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Callback which is executed when a request fails 31 | - `config.extendLinks` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Function that extends standard array of links. 32 | 33 | Returns **any** instance of the Apollo Client 34 | -------------------------------------------------------------------------------- /packages/core/apollo-client/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jest: true -------------------------------------------------------------------------------- /packages/core/apollo-client/test/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from '@apollo/client'; 2 | import { AuthLink, SignUpLink } from '@8base/apollo-links'; 3 | import { ApolloClient } from '../../src'; 4 | 5 | jest.mock('@8base/apollo-links', () => { 6 | return { 7 | AuthLink: jest.fn(() => new ApolloLink()), 8 | SuccessLink: jest.fn(() => new ApolloLink()), 9 | SignUpLink: jest.fn(() => new ApolloLink()), 10 | }; 11 | }); 12 | 13 | describe('ApolloClient', () => { 14 | const onRequestError = jest.fn(); 15 | const onRequestSuccess = jest.fn(); 16 | const getAuthState = jest.fn(); 17 | const getRefreshTokenParameters = jest.fn(); 18 | const onAuthSuccess = jest.fn(); 19 | const onAuthError = jest.fn(); 20 | const onIdTokenExpired = jest.fn(); 21 | const uri = 'http://8base.com'; 22 | const authProfileId = 'someProfileId'; 23 | 24 | beforeEach(() => { 25 | jest.clearAllMocks(); 26 | }); 27 | 28 | it('should create ApolloClient with auth', () => { 29 | const temp = new ApolloClient({ 30 | uri, 31 | onRequestError, 32 | onRequestSuccess, 33 | getAuthState, 34 | getRefreshTokenParameters, 35 | onAuthSuccess, 36 | onAuthError, 37 | onIdTokenExpired, 38 | autoSignUp: true, 39 | authProfileId, 40 | }); 41 | 42 | expect((AuthLink as any).mock.calls[0][0]).toEqual({ 43 | getAuthState, 44 | getRefreshTokenParameters, 45 | onAuthError, 46 | onAuthSuccess, 47 | onIdTokenExpired, 48 | }); 49 | 50 | expect((SignUpLink as any).mock.calls[0][0]).toEqual({ 51 | getAuthState, 52 | authProfileId, 53 | }); 54 | }); 55 | 56 | it('should create ApolloClient without auto sign up', () => { 57 | const temp = new ApolloClient({ 58 | uri, 59 | onRequestError, 60 | onRequestSuccess, 61 | getAuthState, 62 | getRefreshTokenParameters, 63 | onAuthSuccess, 64 | onAuthError, 65 | onIdTokenExpired, 66 | autoSignUp: false, 67 | }); 68 | 69 | expect((AuthLink as any).mock.calls[0][0]).toEqual({ 70 | getAuthState, 71 | getRefreshTokenParameters, 72 | onAuthError, 73 | onAuthSuccess, 74 | onIdTokenExpired, 75 | }); 76 | 77 | expect(SignUpLink).not.toHaveBeenCalled(); 78 | }); 79 | 80 | it('should create ApolloClient without auth', () => { 81 | const temp = new ApolloClient({ 82 | getAuthState, 83 | getRefreshTokenParameters, 84 | onAuthSuccess, 85 | onRequestError, 86 | onRequestSuccess, 87 | uri, 88 | withAuth: false, 89 | }); 90 | 91 | expect(AuthLink).not.toHaveBeenCalled(); 92 | 93 | expect(SignUpLink).not.toHaveBeenCalled(); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /packages/core/apollo-client/test/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import * as fetch from 'jest-fetch-mock'; 2 | 3 | (global as any).fetch = fetch; 4 | -------------------------------------------------------------------------------- /packages/core/apollo-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/apollo-links/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/AuthHeadersLink.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import { ApolloLink, Operation, NextLink, Observable, FetchResult } from '@apollo/client'; 3 | 4 | import { AuthHeadersLinkParameters, AuthState } from './types'; 5 | 6 | const assocWhenNotEmpty = (key: string, value?: string | null) => 7 | R.when(R.always(R.complement(R.either(R.isNil, R.isEmpty))(value)), R.assoc(key, value)); 8 | 9 | export class AuthHeadersLink extends ApolloLink { 10 | public getAuthState: (options?: { operation: Operation }) => AuthState; 11 | 12 | constructor({ getAuthState }: AuthHeadersLinkParameters) { 13 | super(); 14 | 15 | this.getAuthState = getAuthState; 16 | } 17 | 18 | public request(operation: Operation, forward: NextLink): Observable { 19 | return new Observable((observer) => { 20 | const { token, workspaceId } = this.getAuthState({ operation }); 21 | 22 | operation.setContext( 23 | R.over( 24 | R.lensProp('headers'), 25 | R.pipe( 26 | assocWhenNotEmpty('authorization', token ? `Bearer ${token}` : null), 27 | assocWhenNotEmpty('workspace', workspaceId), 28 | ), 29 | ), 30 | ); 31 | 32 | forward(operation).subscribe({ 33 | complete: (...args) => observer.complete(...args), 34 | error: (...args) => observer.error(...args), 35 | next: (...args) => observer.next(...args), 36 | }); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/AuthLink.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, Operation, NextLink, Observable, FetchResult } from '@apollo/client'; 2 | 3 | import { AuthHeadersLink } from './AuthHeadersLink'; 4 | import { TokenRefreshLink } from './TokenRefreshLink'; 5 | 6 | import { AuthLinkParameters } from './types'; 7 | 8 | export class AuthLink extends ApolloLink { 9 | public link: ApolloLink; 10 | 11 | constructor(authLinkParameters: AuthLinkParameters) { 12 | super(); 13 | 14 | const authHeadersLink = new AuthHeadersLink(authLinkParameters); 15 | const tokenRefreshLink = new TokenRefreshLink(authLinkParameters); 16 | 17 | this.link = ApolloLink.from([tokenRefreshLink, authHeadersLink]); 18 | } 19 | 20 | public request(operation: Operation, forward: NextLink): Observable | null { 21 | return this.link.request(operation, forward); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/SubscriptionLink.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketLink } from '@apollo/client/link/ws'; 2 | import { IAuthState } from '@8base/utils'; 3 | 4 | import { isIdTokenExpiredError, isRefreshTokenExpiredError } from './utils'; 5 | 6 | import { SubscriptionLinkParameters } from './types'; 7 | 8 | /** 9 | * Subscription Link 10 | * @param {SubscriptionLinkParameters} options - The subscription link options. 11 | * @param {Function} options.uri - The uri which used for WebSocket connection. 12 | * @param {Function} [options.onAuthError] - The callback which called when attempt to refresh authentication is failed. 13 | * @param {Function} [options.onIdTokenExpired] - The callback which called when id token is expired. 14 | */ 15 | export class SubscriptionLink extends WebSocketLink { 16 | public getAuthState: () => IAuthState; 17 | public onAuthError?: (error?: {}) => void; 18 | public onIdTokenExpired?: () => Promise; 19 | 20 | private expired: boolean; 21 | 22 | constructor({ uri, getAuthState, onIdTokenExpired, onAuthError }: SubscriptionLinkParameters) { 23 | super({ 24 | uri, 25 | options: { 26 | connectionParams: () => this.getConnectionsParams(), 27 | connectionCallback: (payload?: any) => this.connectionCallback(payload), 28 | reconnect: true, 29 | reconnectionAttempts: 5, 30 | lazy: true, 31 | }, 32 | // tslint:disable-next-line 33 | webSocketImpl: class WebSocketWithoutProtocol extends WebSocket { 34 | constructor(url: string) { 35 | super(url); // ignore protocol 36 | } 37 | }, 38 | }); 39 | 40 | this.expired = false; 41 | this.getAuthState = getAuthState; 42 | this.onAuthError = onAuthError; 43 | this.onIdTokenExpired = onIdTokenExpired; 44 | } 45 | 46 | public handleTokenExpired() { 47 | if (typeof this.onIdTokenExpired === 'function') { 48 | return this.onIdTokenExpired(); 49 | } 50 | 51 | return Promise.reject(); 52 | } 53 | 54 | public handleAuthFailed(err?: object) { 55 | if (typeof this.onAuthError === 'function') { 56 | this.onAuthError(err); 57 | } 58 | } 59 | 60 | private async getConnectionsParams() { 61 | if (this.expired) { 62 | await this.handleTokenExpired(); 63 | 64 | this.expired = false; 65 | } 66 | 67 | const authState = this.getAuthState(); 68 | 69 | return authState; 70 | } 71 | 72 | private connectionCallback(payload: any = {}) { 73 | if (isIdTokenExpiredError(payload)) { 74 | this.expired = true; 75 | } else if (isRefreshTokenExpiredError(payload)) { 76 | this.handleAuthFailed(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/SuccessLink.ts: -------------------------------------------------------------------------------- 1 | import { not, has } from 'ramda'; 2 | import { ApolloLink, Observable, Operation, NextLink, FetchResult } from '@apollo/client'; 3 | 4 | type SuccessHandler = (options: { operation: Operation; data: any }) => void; 5 | 6 | type SuccessLinkParameters = { 7 | successHandler: SuccessHandler; 8 | }; 9 | 10 | export class SuccessLink extends ApolloLink { 11 | public successHandler: SuccessHandler; 12 | 13 | constructor({ successHandler }: SuccessLinkParameters) { 14 | super(); 15 | 16 | this.successHandler = successHandler; 17 | } 18 | 19 | public request(operation: Operation, forward: NextLink): Observable { 20 | return new Observable((observer) => { 21 | forward(operation).subscribe({ 22 | complete: (...args) => observer.complete(...args), 23 | error: (...args) => observer.error(...args), 24 | next: (data) => { 25 | if (not(has('errors', data))) { 26 | this.successHandler({ operation, data }); 27 | } 28 | 29 | observer.next(data); 30 | }, 31 | }); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/graphql/mutations.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const SIGNUP_MUTATION = gql` 4 | mutation UserSignUpMutation($user: UserCreateInput!, $authProfileId: ID) { 5 | userSignUp(user: $user, authProfileId: $authProfileId) { 6 | id 7 | } 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/index.ts: -------------------------------------------------------------------------------- 1 | export { TokenRefreshLink } from './TokenRefreshLink'; 2 | export { SubscriptionLink } from './SubscriptionLink'; 3 | export { AuthLink } from './AuthLink'; 4 | export { AuthHeadersLink } from './AuthHeadersLink'; 5 | export { SuccessLink } from './SuccessLink'; 6 | export { SignUpLink } from './SignUpLink'; 7 | export { isSubscriptionRequest } from './utils'; 8 | export * from './types'; 9 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/types.ts: -------------------------------------------------------------------------------- 1 | import { IAuthState } from '@8base/utils'; 2 | import { Operation } from '@apollo/client'; 3 | 4 | export type ErrorObject = { 5 | code: string; 6 | message: string; 7 | }; 8 | 9 | export type RefreshTokenQueryInput = { 10 | refreshToken: string; 11 | email: string; 12 | }; 13 | 14 | type GraphQLError = { 15 | code: string; 16 | details: {}; 17 | message: string; 18 | }; 19 | 20 | export type TokenRefreshLinkParameters = { 21 | onAuthError?: (error?: {}) => void; 22 | onIdTokenExpired?: () => Promise; 23 | }; 24 | 25 | export type SubscriptionLinkParameters = { 26 | uri: string; 27 | getAuthState: () => IAuthState; 28 | onAuthError?: (error?: {}) => void; 29 | onIdTokenExpired?: () => Promise; 30 | }; 31 | 32 | export type ErrorLinkParameters = { 33 | onGraphQLErrors?: (error: GraphQLError[]) => void; 34 | onNetworkError?: (error: {}) => void; 35 | }; 36 | 37 | export type AuthState = { 38 | workspaceId?: string; 39 | token?: string; 40 | email?: string; 41 | }; 42 | 43 | export type AuthHeadersLinkParameters = { 44 | getAuthState: (options?: { operation: Operation }) => IAuthState; 45 | }; 46 | 47 | export type AuthLinkParameters = TokenRefreshLinkParameters & AuthHeadersLinkParameters; 48 | 49 | export type SignUpLinkParameters = { 50 | getAuthState: () => IAuthState; 51 | authProfileId: string; 52 | }; 53 | -------------------------------------------------------------------------------- /packages/core/apollo-links/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import errorCodes from '@8base/error-codes'; 3 | import { DocumentNode } from 'graphql'; 4 | import { getMainDefinition } from '@apollo/client/utilities'; 5 | 6 | export const isIdTokenExpiredError = R.allPass([ 7 | R.propEq('code', errorCodes.TokenExpiredErrorCode), 8 | R.propEq('message', 'Token expired'), 9 | ]); 10 | 11 | export const hasIdTokenExpiredError = R.any(isIdTokenExpiredError); 12 | 13 | export const hasTokenInvalidError = R.any(R.propEq('code', errorCodes.InvalidTokenErrorCode)); 14 | 15 | export const isRefreshTokenExpiredError = R.allPass([ 16 | R.propEq('code', errorCodes.TokenExpiredErrorCode), 17 | R.propEq('message', 'Refresh Token has expired'), 18 | ]); 19 | 20 | export const hasRefreshTokenExpiredError = R.any(isRefreshTokenExpiredError); 21 | 22 | export const hasUserNotFoundError = R.any(R.propEq('code', errorCodes.EntityNotFoundErrorCode)); 23 | 24 | export const isSubscriptionRequest = ({ query }: { query: DocumentNode }) => { 25 | const definition = getMainDefinition(query); 26 | 27 | return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/core/apollo-links/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jest: true -------------------------------------------------------------------------------- /packages/core/apollo-links/test/__tests__/auth-headers-link.test.ts: -------------------------------------------------------------------------------- 1 | import { Observable, execute, ApolloLink, DocumentNode, Operation, gql } from '@apollo/client'; 2 | 3 | import { AuthHeadersLink } from '../../src/AuthHeadersLink'; 4 | 5 | describe("As a developer, I can use 'AuthHeadersLink' to add authorization headers to operation's context", () => { 6 | const query: DocumentNode = gql` 7 | mutation { 8 | sample { 9 | id 10 | } 11 | } 12 | `; 13 | const workspaceId: string = 'some workspace id'; 14 | const token: string = 'some id token'; 15 | const stubLink: any = jest.fn(() => Observable.of()); 16 | const getAuthState = jest.fn(); 17 | const authHeadersLink: ApolloLink = new AuthHeadersLink({ getAuthState }); 18 | const links: ApolloLink = ApolloLink.from([authHeadersLink, stubLink]); 19 | 20 | it('adds authorization headers', () => 21 | new Promise((resolve, reject) => { 22 | getAuthState.mockReturnValueOnce({ 23 | workspaceId, 24 | token, 25 | }); 26 | 27 | execute(links, { query }).subscribe( 28 | () => null, 29 | () => reject(), 30 | () => { 31 | const operation = stubLink.mock.calls[0][0]; 32 | const context = operation.getContext(); 33 | 34 | // $FlowFixMe 35 | expect(context).toStrictEqual({ 36 | headers: { 37 | workspace: workspaceId, 38 | authorization: `Bearer ${token}`, 39 | }, 40 | }); 41 | 42 | resolve(undefined); 43 | }, 44 | ); 45 | })); 46 | 47 | it("doesn't add headers if they don't exist", () => 48 | new Promise((resolve, reject) => { 49 | stubLink.mockClear(); 50 | 51 | getAuthState.mockReturnValueOnce({ 52 | workspaceId: '', 53 | token: 'some-id-token', 54 | }); 55 | 56 | execute(links, { query }).subscribe( 57 | () => null, 58 | () => reject(), 59 | () => { 60 | const operation: Operation = stubLink.mock.calls[0][0]; 61 | const context = operation.getContext(); 62 | 63 | // $FlowFixMe 64 | expect(context).toStrictEqual({ 65 | headers: { 66 | authorization: 'Bearer some-id-token', 67 | }, 68 | }); 69 | 70 | resolve(undefined); 71 | }, 72 | ); 73 | })); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/core/apollo-links/test/__tests__/auth-link.test.ts: -------------------------------------------------------------------------------- 1 | import { Observable, execute, ApolloLink, DocumentNode, Operation, gql } from '@apollo/client'; 2 | 3 | import { AuthLink } from '../../src/AuthLink'; 4 | 5 | describe("As a developer, I can use 'AuthLink' to send authorized requests and refresh token when it required", () => { 6 | const query: DocumentNode = gql` 7 | mutation { 8 | sample { 9 | id 10 | } 11 | } 12 | `; 13 | const workspaceId: string = 'some workspace id'; 14 | const token: string = 'some id token'; 15 | const stubLink: any = jest.fn(() => Observable.of()); 16 | const onIdTokenExpired = jest.fn(); 17 | const authLink: ApolloLink = new AuthLink({ 18 | getAuthState: () => ({ 19 | workspaceId, 20 | token, 21 | }), 22 | onIdTokenExpired, 23 | }); 24 | const links: ApolloLink = ApolloLink.from([authLink, stubLink]); 25 | 26 | it('adds headers for the authorization by default', () => 27 | new Promise((resolve, reject) => { 28 | execute(links, { query }).subscribe( 29 | () => null, 30 | () => reject(), 31 | () => { 32 | const operation: Operation = stubLink.mock.calls[0][0]; 33 | const context = operation.getContext(); 34 | 35 | // $FlowFixMe 36 | expect(context).toStrictEqual({ 37 | headers: { 38 | workspace: workspaceId, 39 | authorization: `Bearer ${token}`, 40 | }, 41 | }); 42 | 43 | resolve(undefined); 44 | }, 45 | ); 46 | })); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/core/apollo-links/test/__tests__/success-link.test.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, Observable, execute } from '@apollo/client'; 2 | import gql from 'graphql-tag'; 3 | import errorCodes from '@8base/error-codes'; 4 | 5 | import { SuccessLink } from '../../src'; 6 | 7 | const TEST_QUERY = gql` 8 | query { 9 | test { 10 | id 11 | } 12 | } 13 | `; 14 | 15 | describe('As a developer i can use SuccessLink to handle success graphql operations', () => { 16 | it('calls handler on successful operation', (done) => { 17 | const terminatingLink: any = () => Observable.of({}); 18 | const successHandler = jest.fn(); 19 | 20 | const links = ApolloLink.from([new SuccessLink({ successHandler }), terminatingLink]); 21 | 22 | execute(links, { query: TEST_QUERY, variables: {} }).subscribe({ 23 | complete: () => { 24 | expect(successHandler).toHaveBeenCalled(); 25 | 26 | done(); 27 | }, 28 | }); 29 | }); 30 | 31 | it("doesn't call handler on operation with error", (done) => { 32 | const terminatingLink: any = () => 33 | Observable.of({ 34 | errors: [ 35 | { 36 | code: errorCodes.TokenExpiredErrorCode, 37 | message: 'Token expired', 38 | details: { 39 | token: 'jwt expired', 40 | }, 41 | }, 42 | ], 43 | }); 44 | const successHandler = jest.fn(); 45 | 46 | const links = ApolloLink.from([new SuccessLink({ successHandler }), terminatingLink]); 47 | 48 | execute(links, { query: TEST_QUERY, variables: {} }).subscribe({ 49 | complete: () => { 50 | expect(successHandler).not.toHaveBeenCalled(); 51 | 52 | done(); 53 | }, 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/core/apollo-links/test/__tests__/token-refresh-link.test.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, execute, Observable } from '@apollo/client'; 2 | import { TokenRefreshLink } from '../../src'; 3 | import gql from 'graphql-tag'; 4 | import errorCodes from '@8base/error-codes'; 5 | 6 | const DYNO_QUERY = gql` 7 | query { 8 | sample { 9 | success 10 | } 11 | } 12 | `; 13 | 14 | describe('As a developer, I can use token refresh link for auto-refresh authentication token.', () => { 15 | let onAuthError: any = null; 16 | let onIdTokenExpired: any = null; 17 | let tokenRefreshLink: any = null; 18 | let stub: any = null; 19 | let link: any = null; 20 | 21 | beforeEach(() => { 22 | onAuthError = jest.fn(); 23 | 24 | onIdTokenExpired = jest.fn(); 25 | 26 | tokenRefreshLink = new TokenRefreshLink({ 27 | onAuthError, 28 | onIdTokenExpired, 29 | }); 30 | 31 | stub = jest.fn(); 32 | 33 | stub.mockReturnValueOnce( 34 | Observable.of({ 35 | errors: [ 36 | { 37 | code: errorCodes.TokenExpiredErrorCode, 38 | message: 'Token expired', 39 | details: { 40 | token: 'jwt expired', 41 | }, 42 | }, 43 | ], 44 | }), 45 | ); 46 | 47 | link = ApolloLink.from([tokenRefreshLink, stub]); 48 | }); 49 | 50 | it('When Apollo Link catch a token expired error - link should send request to refresh token.', () => { 51 | onIdTokenExpired.mockImplementation(() => Promise.resolve()); 52 | stub.mockReturnValueOnce( 53 | Observable.of({ 54 | data: { 55 | sample: { 56 | success: true, 57 | }, 58 | }, 59 | }), 60 | ); 61 | 62 | return new Promise((resolve, reject) => 63 | execute(link, { query: DYNO_QUERY }).subscribe( 64 | (data) => { 65 | expect(data).toEqual({ 66 | data: { 67 | sample: { 68 | success: true, 69 | }, 70 | }, 71 | }); 72 | }, 73 | () => reject(), 74 | () => { 75 | expect(onIdTokenExpired).toHaveBeenCalledTimes(1); 76 | expect(stub).toHaveBeenCalledTimes(2); 77 | 78 | resolve(undefined); 79 | }, 80 | ), 81 | ); 82 | }); 83 | 84 | it('When Apollo Link catch a refresh token error - auth failed callback should be called.', () => { 85 | onIdTokenExpired.mockImplementation(() => Promise.reject()); 86 | 87 | return new Promise((resolve, reject) => 88 | execute(link, { query: DYNO_QUERY }).subscribe( 89 | () => null, 90 | () => reject(), 91 | () => { 92 | expect(onIdTokenExpired).toHaveBeenCalledTimes(1); 93 | expect(onAuthError).toHaveBeenCalledTimes(1); 94 | expect(stub).toHaveBeenCalledTimes(1); 95 | 96 | resolve(undefined); 97 | }, 98 | ), 99 | ); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /packages/core/apollo-links/test/jest.setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/core/apollo-links/test/jest.setup.ts -------------------------------------------------------------------------------- /packages/core/apollo-links/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/auth/src/Auth.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAuthClient, 3 | IStorage, 4 | throwIfMissingRequiredParameters, 5 | showWarningIfDeprecatedParameters, 6 | PACKAGES, 7 | } from '@8base/utils'; 8 | import { WebAuth0AuthClient } from '@8base/web-auth0-auth-client'; 9 | import { WebNativeAuthClient } from '@8base/web-native-auth-client'; 10 | import { WebCognitoAuthClient } from '@8base/web-cognito-auth-client'; 11 | import { WebOAuthClient } from '@8base/web-oauth-client'; 12 | import { ApiTokenAuthClient } from '@8base/api-token-auth-client'; 13 | 14 | import { AUTH_STRATEGIES } from './constants'; 15 | import { SubscribableDecorator } from './SubscribableDecorator'; 16 | 17 | interface IAuthClientCreateOptions { 18 | strategy: AUTH_STRATEGIES | string; 19 | storageOptions?: { 20 | storage?: IStorage; 21 | storageKey?: string; 22 | initialState?: Object; 23 | }; 24 | subscribable?: boolean; 25 | } 26 | 27 | const getAuthClientConstructor = (strategy: AUTH_STRATEGIES | string): any => { 28 | switch (strategy) { 29 | case AUTH_STRATEGIES.API_TOKEN: { 30 | return ApiTokenAuthClient; 31 | } 32 | case AUTH_STRATEGIES.WEB_8BASE: 33 | case AUTH_STRATEGIES.WEB_8BASE_AUTH0: 34 | case AUTH_STRATEGIES.WEB_AUTH0: { 35 | return WebAuth0AuthClient; 36 | } 37 | case AUTH_STRATEGIES.WEB_OAUTH: { 38 | return WebOAuthClient; 39 | } 40 | case AUTH_STRATEGIES.WEB_8BASE_COGNITO: 41 | case AUTH_STRATEGIES.WEB_COGNITO: { 42 | return WebCognitoAuthClient; 43 | } 44 | case AUTH_STRATEGIES.WEB_8BASE_NATIVE: { 45 | return WebNativeAuthClient; 46 | } 47 | } 48 | }; 49 | 50 | export class Auth { 51 | public static createClient(options: IAuthClientCreateOptions, clientOptions: any): IAuthClient { 52 | throwIfMissingRequiredParameters(['strategy'], PACKAGES.AUTH, options); 53 | showWarningIfDeprecatedParameters(['storage', 'storageKey'], PACKAGES.AUTH, options); 54 | 55 | const { strategy, subscribable } = options; 56 | 57 | const storageOptions = !!options.storageOptions 58 | ? options.storageOptions 59 | : { 60 | // @ts-ignore 61 | storage: options.storage, 62 | // @ts-ignore 63 | storageKey: options.storageKey, 64 | }; 65 | 66 | const Constructor = getAuthClientConstructor(strategy); 67 | 68 | let authClient: IAuthClient = new Constructor(clientOptions, storageOptions); 69 | 70 | if (subscribable) { 71 | authClient = SubscribableDecorator.decorate(authClient); 72 | } 73 | 74 | return authClient; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/core/auth/src/SubscribableDecorator.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import { 3 | IAuthClient, 4 | IAuthState, 5 | ISubscriber, 6 | IPublisher, 7 | Publisher, 8 | SDKError, 9 | ERROR_CODES, 10 | PACKAGES, 11 | } from '@8base/utils'; 12 | 13 | export interface ISubscribableAuthClient extends IAuthClient, IPublisher {} 14 | 15 | export class SubscribableDecorator { 16 | public static hasConflicts(authClient: IAuthClient): boolean { 17 | return ( 18 | Reflect.has(authClient, 'publisher') || 19 | Reflect.has(authClient, 'subscribe') || 20 | Reflect.has(authClient, 'notify') || 21 | Reflect.has(authClient, 'batch') 22 | ); 23 | } 24 | 25 | public static decorate(authClient: IAuthClient): ISubscribableAuthClient { 26 | if (SubscribableDecorator.hasConflicts(authClient)) { 27 | throw new SDKError( 28 | ERROR_CODES.PROPERTY_CONFLICT, 29 | PACKAGES.AUTH, 30 | "authClient has property conflict, it shouldn't have 'publisher', 'subscribe', 'notify' and 'batch' properties", 31 | ); 32 | } 33 | 34 | const decoratedAuthClient = { 35 | publisher: new Publisher(), 36 | subscribe(subscriber: ISubscriber) { 37 | return this.publisher.subscribe(subscriber); 38 | }, 39 | notify(state: IAuthState) { 40 | this.publisher.notify(state); 41 | }, 42 | batch(fn: () => void) { 43 | this.publisher.batch(fn); 44 | }, 45 | setState(state: IAuthState) { 46 | super.setState(state); 47 | 48 | // @ts-ignore 49 | const newState = this.getState(); 50 | 51 | this.notify(newState); 52 | }, 53 | purgeState() { 54 | super.purgeState(); 55 | 56 | // @ts-ignore 57 | const newState = this.getState(); 58 | 59 | this.notify(newState); 60 | }, 61 | }; 62 | 63 | Object.setPrototypeOf(decoratedAuthClient, authClient); 64 | 65 | // @ts-ignore 66 | return decoratedAuthClient; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/auth/src/constants.ts: -------------------------------------------------------------------------------- 1 | export enum AUTH_STRATEGIES { 2 | /** 3 | * @deprecated - please use instead declarative variables like `WEB_8BASE_AUTH0` or `WEB_8BASE_COGNITO` 4 | */ 5 | WEB_8BASE = 'web_8base', 6 | WEB_8BASE_AUTH0 = 'web_8base_auth0', 7 | WEB_8BASE_NATIVE = 'web_8base_native', 8 | WEB_8BASE_COGNITO = 'web_8base_cognito', 9 | WEB_AUTH0 = 'web_auth0', 10 | WEB_COGNITO = 'web_cognito', 11 | WEB_OAUTH = 'web_oauth', 12 | API_TOKEN = 'api_token', 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/auth/src/index.ts: -------------------------------------------------------------------------------- 1 | export { SubscribableDecorator, ISubscribableAuthClient } from './SubscribableDecorator'; 2 | export { Auth } from './Auth'; 3 | export { AUTH_STRATEGIES } from './constants'; 4 | -------------------------------------------------------------------------------- /packages/core/auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "diagnostics": true, 4 | "module": "esnext", 5 | "target": "es2016", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "lib": [ 14 | "es2016", 15 | "esnext.asynciterable", 16 | "es2016.array.include", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/utils/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /packages/core/utils/README.md: -------------------------------------------------------------------------------- 1 | # 8base Utils 2 | 3 | This library contains utils used by the other 8base service packages. 4 | 5 | ## API 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [formatDataForMutation](#formatdataformutation) 12 | - [Parameters](#parameters) 13 | 14 | ### formatDataForMutation 15 | 16 | Formats entity data for create or update mutation based on passed schema. 17 | 18 | #### Parameters 19 | 20 | - `type` **MutationType** The type of the mutation. 21 | - `tableName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the table from the 8base API. 22 | - `data` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The entity data for format. 23 | - `schema` **Schema** The schema of the used tables from the 8base API. 24 | -------------------------------------------------------------------------------- /packages/core/utils/src/Publisher.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import { Unsubscribe, ISubscriber, IPublisher } from './types'; 3 | 4 | export class Publisher implements IPublisher { 5 | private subscribers: ISubscriber[]; 6 | private inBatch: boolean; 7 | private pendingState: T | null; 8 | 9 | constructor() { 10 | this.subscribers = []; 11 | this.inBatch = false; 12 | this.pendingState = null; 13 | } 14 | 15 | public subscribe(subscriber: ISubscriber): Unsubscribe { 16 | if (!this.subscribers.includes(subscriber)) { 17 | this.subscribers = [...this.subscribers, subscriber]; 18 | } 19 | 20 | return () => { 21 | const subscriberIndex = this.subscribers.indexOf(subscriber); 22 | 23 | if (subscriberIndex >= 0) { 24 | this.subscribers = R.remove(subscriberIndex, 1, this.subscribers); 25 | } 26 | }; 27 | } 28 | 29 | public notify(state: T): void { 30 | if (this.inBatch) { 31 | this.pendingState = state; 32 | return; 33 | } 34 | 35 | this.subscribers.forEach((subscriber) => { 36 | subscriber(state); 37 | }); 38 | } 39 | 40 | public batch(fn: () => void): void { 41 | this.inBatch = true; 42 | fn(); 43 | this.inBatch = false; 44 | 45 | if (this.pendingState !== null) { 46 | this.notify(this.pendingState); 47 | this.pendingState = null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/core/utils/src/StorageAPI.ts: -------------------------------------------------------------------------------- 1 | import { IStorage, IStorageAPI } from './types'; 2 | 3 | export class StorageAPI implements IStorageAPI { 4 | private storage: IStorage; 5 | private readonly storageKey: string; 6 | 7 | constructor(storage: IStorage, storageKey: string, initialState?: T) { 8 | this.storage = storage; 9 | this.storageKey = storageKey; 10 | 11 | if (!!initialState) { 12 | this.setState(initialState); 13 | } 14 | } 15 | 16 | public getState(): T { 17 | const auth = JSON.parse(this.storage.getItem(this.storageKey) || '{}'); 18 | 19 | return auth || {}; 20 | } 21 | 22 | public setState(newState: T): void { 23 | const currentState = this.getState(); 24 | const mergedState = { 25 | ...currentState, 26 | ...newState, 27 | }; 28 | 29 | this.storage.setItem(this.storageKey, JSON.stringify(mergedState)); 30 | } 31 | 32 | public purgeState(): void { 33 | this.storage.removeItem(this.storageKey); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/utils/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schemaConstants'; 2 | export * from './countryCodes'; 3 | -------------------------------------------------------------------------------- /packages/core/utils/src/errors/SDKError.ts: -------------------------------------------------------------------------------- 1 | import { ERROR_CODES } from './codes'; 2 | import { PACKAGES } from './packages'; 3 | 4 | class SDKError extends Error { 5 | public name: string; 6 | public code: ERROR_CODES; 7 | public packageName: PACKAGES; 8 | public message: string; 9 | 10 | constructor(code: ERROR_CODES, packageName: PACKAGES, message: string) { 11 | super(message); 12 | 13 | this.name = 'SDKError'; 14 | this.code = code; 15 | this.packageName = packageName; 16 | this.message = message; 17 | } 18 | 19 | public toString() { 20 | return `${this.name} in ${this.packageName}: ${this.code}, ${this.message}`; 21 | } 22 | 23 | public toJSON() { 24 | return { 25 | code: this.code, 26 | message: this.message, 27 | name: this.name, 28 | packageName: this.packageName, 29 | }; 30 | } 31 | } 32 | 33 | export { SDKError }; 34 | -------------------------------------------------------------------------------- /packages/core/utils/src/errors/codes.ts: -------------------------------------------------------------------------------- 1 | export enum ERROR_CODES { 2 | MISSING_PARAMETER = 'MissingParameter', 3 | INVALID_ARGUMENT = 'InvalidArgument', 4 | TABLE_NOT_FOUND = 'TableNotFound', 5 | UNSUPPORTED_FIELD_TYPE = 'UnsupportedFieldType', 6 | AUTH_FAILED = 'AuthFailed', 7 | NOT_IMPLEMENTED = 'NotImplemented', 8 | PROPERTY_CONFLICT = 'PropertyConflict', 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/utils/src/errors/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import { SDKError } from './SDKError'; 3 | import { ERROR_CODES } from './codes'; 4 | import { PACKAGES } from './packages'; 5 | 6 | const hasParameter = R.ifElse(R.is(String), R.has, R.hasPath); 7 | 8 | const stringifyPath = R.ifElse(R.is(String), (path) => path, R.join('.')); 9 | 10 | type ParameterPathType = string[] | string; 11 | 12 | export const throwIfMissingRequiredParameters = ( 13 | requiredParameterPaths: ParameterPathType[], 14 | packageName: PACKAGES, 15 | parameters: {} = {}, 16 | ): void => { 17 | requiredParameterPaths.forEach((parameterPath) => { 18 | const isMissing = !hasParameter(parameterPath)(parameters); 19 | 20 | if (isMissing) { 21 | throw new SDKError( 22 | ERROR_CODES.MISSING_PARAMETER, 23 | packageName, 24 | `Missing parameter: ${stringifyPath(parameterPath)}`, 25 | ); 26 | } 27 | }); 28 | }; 29 | 30 | export const showWarningIfDeprecatedParameters = ( 31 | deprecatedParameterPaths: ParameterPathType[], 32 | packageName: PACKAGES, 33 | parameters: {} = {}, 34 | ): void => { 35 | deprecatedParameterPaths.forEach((parameterPath) => { 36 | const isExist = hasParameter(parameterPath)(parameters); 37 | 38 | if (isExist) { 39 | // tslint:disable-next-line:no-console 40 | console.warn( 41 | `Deprecated parameter: ${stringifyPath( 42 | parameterPath, 43 | )}. Please, replace it with appropriate API https://www.npmjs.com/package/${packageName}`, 44 | ); 45 | } 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /packages/core/utils/src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export { SDKError } from './SDKError'; 2 | export { ERROR_CODES } from './codes'; 3 | export { PACKAGES } from './packages'; 4 | export { throwIfMissingRequiredParameters, showWarningIfDeprecatedParameters } from './helpers'; 5 | -------------------------------------------------------------------------------- /packages/core/utils/src/errors/packages.ts: -------------------------------------------------------------------------------- 1 | export enum PACKAGES { 2 | API_CLIENT = '@8base/api-client', 3 | APOLLO_CLIENT = '@8base/apollo-client', 4 | APOLLO_LINKS = '@8base/apollo-links', 5 | APP_PROVIDER = '@8base/app-provider', 6 | REACT_AUTH = '@8base/react-auth', 7 | AUTH = '@8base/auth', 8 | CRUD = '@8base/crud', 9 | FILE_INPUT = '@8base/file-input', 10 | FORMS = '@8base/forms', 11 | PERMISSIONS_PROVIDER = '@8base/permissions-provider', 12 | TABLE_SCHEMA_PROVIDER = '@8base/table-schema-provider', 13 | UTILS = '@8base/utils', 14 | VALIDATE = '@8base/validate', 15 | API_TOKEN_AUTH_CLIENT = '@8base/api-token-auth-client', 16 | WEB_AUTH0_AUTH_CLIENT = '@8base/web-auth0-auth-client', 17 | WEB_COGNITO_AUTH_CLIENT = '@8base/web-cognito-auth-client', 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/utils/src/formatters/formatFieldData.ts: -------------------------------------------------------------------------------- 1 | import { MutationType, FieldSchema, Schema, FormatDataForMutationOptions } from '../types'; 2 | import { formatFieldDataListItem } from './formatFieldDataListItem'; 3 | 4 | interface IFormatFieldDataMeta { 5 | fieldSchema: FieldSchema; 6 | schema: Schema; 7 | initialData?: any; 8 | } 9 | 10 | export const formatFieldData = ( 11 | type: MutationType, 12 | data: any, 13 | { fieldSchema, schema, initialData }: IFormatFieldDataMeta, 14 | options?: FormatDataForMutationOptions, 15 | ) => { 16 | const nextData = formatFieldDataListItem(type, data, { fieldSchema, schema, initialData }, options); 17 | 18 | return nextData 19 | ? { 20 | [nextData.type]: nextData.data, 21 | } 22 | : null; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/utils/src/formatters/formatFieldDataForMutation.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import * as verifiers from '../verifiers'; 4 | import { MutationType, FieldSchema, Schema, FormatDataForMutationOptions } from '../types'; 5 | import { formatFieldDataList } from './formatFieldDataList'; 6 | import { formatFieldData } from './formatFieldData'; 7 | 8 | const formatJSON = (data: any) => { 9 | if (typeof data === 'string' && data.length === 0) { 10 | return null; 11 | } 12 | 13 | let formattedData = data; 14 | 15 | try { 16 | formattedData = JSON.parse(data); 17 | // tslint:disable-next-line 18 | } catch (e) {} 19 | 20 | return formattedData; 21 | }; 22 | 23 | interface IFormatFieldDataForMutationMeta { 24 | fieldSchema: FieldSchema; 25 | schema: Schema; 26 | initialData?: any; 27 | } 28 | 29 | const formatFieldDataForMutation = ( 30 | type: MutationType, 31 | data: any, 32 | { fieldSchema, schema, initialData }: IFormatFieldDataForMutationMeta, 33 | options?: FormatDataForMutationOptions, 34 | ) => { 35 | let nextData = data; 36 | 37 | if (verifiers.isFileField(fieldSchema) || verifiers.isRelationField(fieldSchema)) { 38 | if (verifiers.isListField(fieldSchema)) { 39 | nextData = formatFieldDataList(type, data, { fieldSchema, schema, initialData }, options); 40 | } else { 41 | nextData = formatFieldData(type, data, { fieldSchema, schema, initialData }, options); 42 | } 43 | } else if (verifiers.isAddressField(fieldSchema)) { 44 | if (verifiers.isListField(fieldSchema)) { 45 | if (Array.isArray(nextData)) { 46 | nextData = R.reject(verifiers.isEmptyAddress, nextData); 47 | } 48 | } else { 49 | if (verifiers.isEmptyAddress(nextData)) { 50 | nextData = null; 51 | } 52 | } 53 | } else if (verifiers.isPhoneField(fieldSchema)) { 54 | if (verifiers.isListField(fieldSchema)) { 55 | if (Array.isArray(nextData)) { 56 | nextData = R.reject(verifiers.isEmptyPhone, nextData); 57 | } 58 | } else { 59 | if (verifiers.isEmptyPhone(nextData)) { 60 | nextData = null; 61 | } 62 | } 63 | } else if (verifiers.isNumberField(fieldSchema)) { 64 | if (verifiers.isListField(fieldSchema)) { 65 | if (Array.isArray(nextData)) { 66 | nextData = R.reject(verifiers.isEmptyNumber, nextData); 67 | 68 | if (!verifiers.isBigInt(fieldSchema)) { 69 | nextData = R.map(Number, nextData); 70 | } 71 | } 72 | } else { 73 | if (verifiers.isEmptyNumber(nextData)) { 74 | nextData = null; 75 | } else if (!verifiers.isBigInt(fieldSchema)) { 76 | nextData = Number(nextData); 77 | } 78 | } 79 | } else if (verifiers.isJSONField(fieldSchema)) { 80 | if (verifiers.isListField(fieldSchema)) { 81 | nextData = R.map(formatJSON, nextData); 82 | } else { 83 | nextData = formatJSON(nextData); 84 | } 85 | } else if (verifiers.isGeoField(fieldSchema) && Array.isArray(nextData)) { 86 | if (verifiers.isListField(fieldSchema)) { 87 | nextData = R.map(R.map(Number), nextData); 88 | } else { 89 | nextData = R.map(Number, nextData); 90 | } 91 | } 92 | 93 | return nextData; 94 | }; 95 | 96 | export { formatFieldDataForMutation }; 97 | -------------------------------------------------------------------------------- /packages/core/utils/src/formatters/formatFieldDataList.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { MutationType, FieldSchema, Schema, FormatDataForMutationOptions } from '../types'; 4 | import { formatFieldDataListItem } from './formatFieldDataListItem'; 5 | 6 | interface IFormatFieldDataListMeta { 7 | fieldSchema: FieldSchema; 8 | schema: Schema; 9 | initialData?: any; 10 | } 11 | 12 | const bindDataWithInitialData = (list: any[] = [], initialList: any[] = []) => { 13 | const result = []; 14 | const leftInitialList = [...initialList]; 15 | 16 | const toResultObject = (data: any, initialData: any) => ({ data, initialData }); 17 | 18 | const findById = (id: string) => (el: any) => R.path(['id'], el) === id; 19 | 20 | for (const item of list) { 21 | if (!item || (!item.id && typeof item !== 'string')) { 22 | result.push(toResultObject(item, null)); 23 | continue; 24 | } 25 | 26 | const initialDataInx = leftInitialList.findIndex(item.id ? findById(item.id) : R.equals(item)); 27 | let initialData = null; 28 | 29 | if (initialDataInx !== -1) { 30 | initialData = leftInitialList[initialDataInx]; 31 | leftInitialList.splice(initialDataInx, 1); 32 | } 33 | 34 | result.push(toResultObject(item, initialData)); 35 | } 36 | 37 | for (const item of leftInitialList) { 38 | if (!item || (!item.id && typeof item !== 'string')) { 39 | continue; 40 | } 41 | 42 | result.push(toResultObject(null, item)); 43 | } 44 | 45 | return result; 46 | }; 47 | 48 | export const formatFieldDataList = ( 49 | type: MutationType, 50 | data: any, 51 | { fieldSchema, schema, initialData }: IFormatFieldDataListMeta, 52 | options?: FormatDataForMutationOptions, 53 | ) => 54 | R.pipe( 55 | (data: any) => bindDataWithInitialData(data, initialData), 56 | // // @ts-ignore 57 | // data => console.log('binded data', data) || data, 58 | R.map((item) => 59 | formatFieldDataListItem( 60 | type, 61 | item.data, 62 | { 63 | fieldSchema, 64 | schema, 65 | initialData: item.initialData, 66 | initialListData: initialData, 67 | }, 68 | options, 69 | ), 70 | ), 71 | R.filter((item) => Boolean(item)), 72 | R.groupBy(R.prop('type')), 73 | R.mapObjIndexed(R.map(R.prop('data'))), 74 | )(data); 75 | -------------------------------------------------------------------------------- /packages/core/utils/src/formatters/gqlPrettify.ts: -------------------------------------------------------------------------------- 1 | import prettier from 'prettier'; 2 | import parserGraphql from 'prettier/parser-graphql'; 3 | 4 | export const gqlPrettify = (gqlString: string): string => { 5 | return prettier.format(gqlString, { 6 | parser: 'graphql', 7 | plugins: [parserGraphql], 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/core/utils/src/formatters/index.ts: -------------------------------------------------------------------------------- 1 | export { formatDataForMutation } from './formatDataForMutation'; 2 | export { formatDataAfterQuery } from './formatDataAfterQuery'; 3 | export { formatOptimisticResponse } from './formatOptimisticResponse'; 4 | export { gqlPrettify } from './gqlPrettify'; 5 | -------------------------------------------------------------------------------- /packages/core/utils/src/formatters/omitDeep.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | const omitDeep = (omitedProps: string[], objectForOmitting?: any) => { 4 | if ( 5 | !objectForOmitting || 6 | !R.is(Object, objectForOmitting) || 7 | typeof objectForOmitting === 'number' || 8 | typeof objectForOmitting === 'string' 9 | ) { 10 | return objectForOmitting; 11 | } 12 | 13 | const currentLevelOmitedObject: any = Array.isArray(objectForOmitting) 14 | ? R.map((value) => omitDeep(omitedProps, value), objectForOmitting) 15 | : R.omit(omitedProps, objectForOmitting); 16 | 17 | const omitValue = (value: any): any => { 18 | if (R.is(Array, value)) { 19 | return value.map((item: any) => omitDeep(omitedProps, item)); 20 | } else if (R.is(Object, value)) { 21 | return omitDeep(omitedProps, value); 22 | } 23 | return value; 24 | }; 25 | 26 | const fullOmitedObject = Array.isArray(currentLevelOmitedObject) 27 | ? R.map(omitValue, currentLevelOmitedObject) 28 | : R.mapObjIndexed(omitValue, currentLevelOmitedObject); 29 | 30 | return fullOmitedObject; 31 | }; 32 | 33 | export { omitDeep }; 34 | -------------------------------------------------------------------------------- /packages/core/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './formatters'; 3 | export * from './selectors'; 4 | export * from './verifiers'; 5 | export * from './queryGenerators'; 6 | export * from './errors'; 7 | export * from './types'; 8 | export * from './StorageAPI'; 9 | export * from './Publisher'; 10 | -------------------------------------------------------------------------------- /packages/core/utils/src/queryGenerators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './queryGenerators'; 2 | export { createQueryColumnsList } from './createQueryColumnsList'; 3 | -------------------------------------------------------------------------------- /packages/core/utils/src/selectors/applicationsListSelectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, ParametricSelector } from 'reselect'; 2 | import { APP_STATUS } from '../constants'; 3 | 4 | import { Application } from '../types'; 5 | 6 | export const getApplicationsList: ParametricSelector = ( 7 | applicationsList: Application[], 8 | ) => applicationsList || []; 9 | 10 | export const getApplications: ParametricSelector< 11 | Application[], 12 | any, 13 | Application[] 14 | > = createSelector(getApplicationsList, (applications) => applications.filter(({ name }) => name !== null)); 15 | 16 | export const getActiveApplications: ParametricSelector< 17 | Application[], 18 | any, 19 | Application[] 20 | > = createSelector(getApplications, (applications) => 21 | applications.filter(({ status }) => status === APP_STATUS.ACTIVE), 22 | ); 23 | 24 | export const hasConnectedApplications: ParametricSelector = createSelector( 25 | getActiveApplications, 26 | (applications) => applications.length > 0, 27 | ); 28 | 29 | export const getApplication: ParametricSelector = createSelector( 30 | getApplicationsList, 31 | (_: any, id: string) => id, 32 | (applications, appId) => applications.find(({ id }) => id === appId), 33 | ); 34 | -------------------------------------------------------------------------------- /packages/core/utils/src/selectors/index.ts: -------------------------------------------------------------------------------- 1 | import * as tablesListSelectors from './tablesListSelectors'; 2 | import * as tableSelectors from './tableSelectors'; 3 | import * as tableFieldSelectors from './tableFieldSelectors'; 4 | import * as applicationsListSelectors from './applicationsListSelectors'; 5 | 6 | export { tableSelectors, applicationsListSelectors, tableFieldSelectors, tablesListSelectors }; 7 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/hasIdTokenExpiredError.ts: -------------------------------------------------------------------------------- 1 | import errorCodes from '@8base/error-codes'; 2 | import * as R from 'ramda'; 3 | 4 | export const hasIdTokenExpiredError = R.any( 5 | R.allPass([R.propEq('code', errorCodes.TokenExpiredErrorCode), R.propEq('message', 'Token expired')]), 6 | ); 7 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/index.ts: -------------------------------------------------------------------------------- 1 | export { isAddressField } from './isAddressField'; 2 | export { isBigInt } from './isBigInt'; 3 | export { isEmptyAddress } from './isEmptyAddress'; 4 | export { isEmptyNumber } from './isEmptyNumber'; 5 | export { isEmptyPhone } from './isEmptyPhone'; 6 | export { isFileField } from './isFileField'; 7 | export { isFilesTable } from './isFilesTable'; 8 | export { isJSONField } from './isJSONField'; 9 | export { isListField } from './isListField'; 10 | export { isMetaField } from './isMetaField'; 11 | export { isNumberField } from './isNumberField'; 12 | export { isPhoneField } from './isPhoneField'; 13 | export { isRelationField } from './isRelationField'; 14 | export { isGeoField } from './isGeoField'; 15 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isAddressField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE, SMART_FORMATS } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isAddressField: (fieldSchema: FieldSchema) => boolean = R.allPass([ 7 | R.propEq('fieldType', FIELD_TYPE.SMART), 8 | R.pathEq(['fieldTypeAttributes', 'format'], SMART_FORMATS.ADDRESS), 9 | ]); 10 | 11 | export { isAddressField }; 12 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isBigInt.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FieldSchema } from '../types'; 4 | 5 | const isBigInt: (fieldSchema: FieldSchema) => boolean = R.pathEq(['fieldTypeAttributes', 'isBigInt'], true); 6 | 7 | export { isBigInt }; 8 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isEmptyAddress.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | const isEmpty = R.anyPass([R.isEmpty, R.isNil]); 4 | 5 | export const isEmptyAddress = R.anyPass([ 6 | isEmpty, 7 | R.allPass([ 8 | R.propSatisfies(isEmpty, 'street1'), 9 | R.propSatisfies(isEmpty, 'street2'), 10 | R.propSatisfies(isEmpty, 'zip'), 11 | R.propSatisfies(isEmpty, 'city'), 12 | R.propSatisfies(isEmpty, 'state'), 13 | R.propSatisfies(isEmpty, 'country'), 14 | ]), 15 | ]); 16 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isEmptyNumber.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | export const isEmptyNumber = R.anyPass([R.isEmpty, R.isNil]); 4 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isEmptyPhone.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | const isEmpty = R.anyPass([R.isEmpty, R.isNil]); 4 | 5 | export const isEmptyPhone = R.anyPass([ 6 | isEmpty, 7 | R.allPass([R.propSatisfies(isEmpty, 'code'), R.propSatisfies(isEmpty, 'number')]), 8 | ]); 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isFileField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isFileField: (fieldSchema: FieldSchema) => boolean = R.propEq('fieldType', FIELD_TYPE.FILE); 7 | 8 | export { isFileField }; 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isFilesTable.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { SYSTEM_TABLES } from '../constants'; 4 | import { TableSchema } from '../types'; 5 | 6 | const isFilesTable: (tableSchema: TableSchema) => boolean = R.propEq('name', SYSTEM_TABLES.FILES); 7 | 8 | export { isFilesTable }; 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isGeoField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isGeoField: (fieldSchema: FieldSchema) => boolean = R.propEq('fieldType', FIELD_TYPE.GEO); 7 | 8 | export { isGeoField }; 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isJSONField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isJSONField: (fieldSchema: FieldSchema) => boolean = R.propEq('fieldType', FIELD_TYPE.JSON); 7 | 8 | export { isJSONField }; 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isListField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FieldSchema } from '../types'; 4 | 5 | const isListField: (fieldSchema: FieldSchema) => boolean = R.propEq('isList', true); 6 | 7 | export { isListField }; 8 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isMetaField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FieldSchema } from '../types'; 4 | 5 | const isMetaField: (fieldSchema: FieldSchema) => boolean = R.propEq('isMeta', true); 6 | 7 | export { isMetaField }; 8 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isNumberField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isNumberField: (fieldSchema: FieldSchema) => boolean = R.propEq('fieldType', FIELD_TYPE.NUMBER); 7 | 8 | export { isNumberField }; 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isPhoneField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE, SMART_FORMATS } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isPhoneField: (fieldSchema: FieldSchema) => boolean = R.allPass([ 7 | R.propEq('fieldType', FIELD_TYPE.SMART), 8 | R.pathEq(['fieldTypeAttributes', 'format'], SMART_FORMATS.PHONE), 9 | ]); 10 | 11 | export { isPhoneField }; 12 | -------------------------------------------------------------------------------- /packages/core/utils/src/verifiers/isRelationField.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | import { FIELD_TYPE } from '../constants'; 4 | import { FieldSchema } from '../types'; 5 | 6 | const isRelationField: (fieldSchema: FieldSchema) => boolean = R.propEq('fieldType', FIELD_TYPE.RELATION); 7 | 8 | export { isRelationField }; 9 | -------------------------------------------------------------------------------- /packages/core/utils/test/.eslintrc.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/core/utils/test/.eslintrc.yml -------------------------------------------------------------------------------- /packages/core/utils/test/__tests__/Publisher.test.ts: -------------------------------------------------------------------------------- 1 | import { Publisher, Unsubscribe } from '../../src'; 2 | 3 | describe('Publisher', () => { 4 | const publisher = new Publisher(); 5 | const firstSubscriber = jest.fn(); 6 | const secondSubscriber = jest.fn(); 7 | let firstUnsubscribe: Unsubscribe | null = null; 8 | let secondUnsubscribe: Unsubscribe | null = null; 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks(); 12 | 13 | if (firstUnsubscribe) { 14 | firstUnsubscribe(); 15 | firstUnsubscribe = null; 16 | } 17 | 18 | if (secondUnsubscribe) { 19 | secondUnsubscribe(); 20 | secondUnsubscribe = null; 21 | } 22 | }); 23 | 24 | it('As a developer, I can subscribe to publisher', () => { 25 | firstUnsubscribe = publisher.subscribe(firstSubscriber); 26 | 27 | publisher.notify('state'); 28 | 29 | expect(firstSubscriber).toHaveBeenCalledWith('state'); 30 | }); 31 | 32 | it('As a developer, I can add multiple subsribers', () => { 33 | firstUnsubscribe = publisher.subscribe(firstSubscriber); 34 | secondUnsubscribe = publisher.subscribe(secondSubscriber); 35 | 36 | publisher.notify('newState'); 37 | 38 | expect(firstSubscriber).toHaveBeenCalledTimes(1); 39 | expect(firstSubscriber).toHaveBeenCalledWith('newState'); 40 | expect(secondSubscriber).toHaveBeenCalledWith('newState'); 41 | }); 42 | 43 | it('As a developer, I can unsubscribe from publisher', () => { 44 | firstUnsubscribe = publisher.subscribe(firstSubscriber); 45 | 46 | firstUnsubscribe(); 47 | 48 | publisher.notify('anotherState'); 49 | 50 | expect(firstSubscriber).not.toHaveBeenCalled(); 51 | }); 52 | 53 | it('As a developer, I can batch updates', () => { 54 | firstUnsubscribe = publisher.subscribe(firstSubscriber); 55 | secondUnsubscribe = publisher.subscribe(secondSubscriber); 56 | 57 | publisher.batch(() => { 58 | publisher.notify('someTempState'); 59 | publisher.notify('yetAnotherState'); 60 | }); 61 | 62 | expect(firstSubscriber).toHaveBeenCalledTimes(1); 63 | expect(firstSubscriber).toHaveBeenCalledWith('yetAnotherState'); 64 | expect(secondSubscriber).toHaveBeenCalledTimes(1); 65 | expect(secondSubscriber).toHaveBeenCalledWith('yetAnotherState'); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/core/utils/test/__tests__/StorageAPI.test.ts: -------------------------------------------------------------------------------- 1 | import { StorageAPI } from '../../src/StorageAPI'; 2 | 3 | const STORAGE_KEY = 'test'; 4 | 5 | const storageObj = {}; 6 | 7 | const storage = { 8 | getItem: jest.fn((key: string) => Reflect.get(storageObj, key)), 9 | setItem: jest.fn((key: string, value: string) => { 10 | Reflect.set(storageObj, key, value); 11 | }), 12 | removeItem: jest.fn((key: string) => { 13 | Reflect.deleteProperty(storageObj, key); 14 | }), 15 | }; 16 | 17 | describe('StorageAPI', () => { 18 | const storageAPI = new StorageAPI(storage, STORAGE_KEY); 19 | 20 | it('As a developer, I can getstate', () => { 21 | const state = storageAPI.getState(); 22 | 23 | expect(state).toEqual({}); 24 | }); 25 | 26 | it('As a developer, I can set state', () => { 27 | storageAPI.setState({ 28 | someKey: 'someValue', 29 | }); 30 | 31 | expect(storageAPI.getState()).toEqual({ 32 | someKey: 'someValue', 33 | }); 34 | }); 35 | 36 | it('As a developer, I can supplement state', () => { 37 | storageAPI.setState({ 38 | someAnotherKey: 'someAnotherValue', 39 | }); 40 | 41 | expect(storageAPI.getState()).toEqual({ 42 | someKey: 'someValue', 43 | someAnotherKey: 'someAnotherValue', 44 | }); 45 | }); 46 | 47 | it('As a develiper, I can rewrite state', () => { 48 | storageAPI.setState({ 49 | someKey: 'theValue', 50 | yetAnotherKey: 'yetAnotherValue', 51 | }); 52 | 53 | expect(storageAPI.getState()).toEqual({ 54 | someKey: 'theValue', 55 | someAnotherKey: 'someAnotherValue', 56 | yetAnotherKey: 'yetAnotherValue', 57 | }); 58 | }); 59 | 60 | it('As a developer, I can purge state', () => { 61 | storageAPI.purgeState(); 62 | 63 | expect(storageAPI.getState()).toEqual({}); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/core/utils/test/__tests__/errorsHelpers.ts: -------------------------------------------------------------------------------- 1 | import { throwIfMissingRequiredParameters, SDKError, ERROR_CODES } from '../../src'; 2 | 3 | describe('throwIfMissingRequiredOption', () => { 4 | const testFunction = (parameters: any) => { 5 | // @ts-ignore 6 | throwIfMissingRequiredParameters(['key2', ['key1', 'nestedKey1']], '@8base/test', parameters); 7 | }; 8 | 9 | it('throws if there is a missing option', () => { 10 | expect(() => { 11 | testFunction({ 12 | key1: { 13 | nestedKey1: 'someValue', 14 | }, 15 | key3: 'someValue', 16 | }); 17 | // @ts-ignore 18 | }).toThrow(new SDKError(ERROR_CODES.MISSING_PARAMETER, '@8base/test', 'Missing parameter: key2')); 19 | }); 20 | 21 | it('throws if there is a missing option: nested check', () => { 22 | expect(() => { 23 | testFunction({ 24 | key2: 'someValue', 25 | key3: 'someValue', 26 | }); 27 | // @ts-ignore 28 | }).toThrow(new SDKError(ERROR_CODES.MISSING_PARAMETER, '@8base/test', 'Missing parameter: key1.nestedKey1')); 29 | }); 30 | 31 | it("doesn't throw if there isn't a missing option", () => { 32 | expect(() => { 33 | testFunction({ 34 | key1: { 35 | nestedKey1: 'someValue', 36 | }, 37 | key2: 'someValue', 38 | key3: 'someValue', 39 | }); 40 | }).not.toThrow(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/core/utils/test/__tests__/omitDeep.test.ts: -------------------------------------------------------------------------------- 1 | import { omitDeep } from '../../src/formatters/omitDeep'; 2 | 3 | describe('omitDeep', () => { 4 | it('should omit properties from the object', () => { 5 | const sourceObject = { 6 | prop1: 'prop1', 7 | prop2: { 8 | prop2_1: 'prop2_1', 9 | prop2_2: 'prop2_2', 10 | __typename: '__typename', 11 | }, 12 | prop3: ['prop3_3', 'prop3_4'], 13 | prop4: [ 14 | { 15 | prop4_1: 'prop4_1', 16 | __typename: '__typename', 17 | }, 18 | ], 19 | __typename: '__typename', 20 | }; 21 | 22 | const resultObject = { 23 | prop1: 'prop1', 24 | prop2: { 25 | prop2_1: 'prop2_1', 26 | prop2_2: 'prop2_2', 27 | }, 28 | prop3: ['prop3_3', 'prop3_4'], 29 | prop4: [ 30 | { 31 | prop4_1: 'prop4_1', 32 | }, 33 | ], 34 | }; 35 | 36 | expect(omitDeep(['__typename'], sourceObject)).toEqual(resultObject); 37 | }); 38 | 39 | it('should not transform array to object', () => { 40 | const value = [['123', 555], { a: 1 }]; 41 | 42 | expect(omitDeep(['__typename'], value)).toEqual(value); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/core/utils/test/__tests__/tablesListSelectors.test.ts: -------------------------------------------------------------------------------- 1 | import * as selectors from '../../src/selectors/tablesListSelectors'; 2 | 3 | const tablesSchema: any = [ 4 | { 5 | id: 'TABLE_ID', 6 | name: 'TABLE_NAME', 7 | fields: [ 8 | { 9 | id: 'f-1', 10 | name: 'createdAt', 11 | isMeta: true, 12 | fieldType: 'RELATION', 13 | }, 14 | { 15 | id: 'f-2', 16 | name: 'some-name', 17 | isMeta: false, 18 | fieldType: 'FILE', 19 | }, 20 | ], 21 | }, 22 | ]; 23 | 24 | it('Should returns table by id', () => { 25 | expect(selectors.getTableById(tablesSchema, 'TABLE_ID')).toEqual(tablesSchema[0]); 26 | }); 27 | 28 | it('Should returns undefined for non-existed table id', () => { 29 | expect(selectors.getTableById(tablesSchema, 'NON_EXISTED_TABLE_ID')).toEqual(undefined); 30 | }); 31 | 32 | it('Should returns table by name', () => { 33 | expect(selectors.getTableByName(tablesSchema, 'TABLE_NAME')).toEqual(tablesSchema[0]); 34 | }); 35 | 36 | it('Should returns undefined for non-existed table name', () => { 37 | expect(selectors.getTableByName(tablesSchema, 'NON_EXISTED_TABLE_NAME')).toEqual(undefined); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/utils/test/__tests__/verifiers.test.ts: -------------------------------------------------------------------------------- 1 | import { isEmptyAddress, isEmptyPhone } from '../../src'; 2 | 3 | const EMPTY_ADDRESSES = [ 4 | ['null', null], 5 | ['undefined', undefined], 6 | ['empty object', {}], 7 | ['empty string', ''], 8 | [ 9 | 'full address with all empty subfields', 10 | { 11 | city: '', 12 | country: '', 13 | state: '', 14 | street1: '', 15 | street2: undefined, 16 | zip: null, 17 | }, 18 | ], 19 | ]; 20 | 21 | const EMPTY_PHONES = [ 22 | ['null', null], 23 | ['undefined', undefined], 24 | ['empty object', {}], 25 | ['empty string', ''], 26 | [ 27 | 'full phone with all empty subfields', 28 | { 29 | code: '', 30 | number: undefined, 31 | }, 32 | ], 33 | ]; 34 | 35 | const NOT_EMPTY_ADDRESSES = [ 36 | [ 37 | 'full address', 38 | { 39 | city: 'Backbitingly', 40 | country: 'Interdine', 41 | state: 'Muddybreast', 42 | street1: 'Blessed', 43 | street2: 'Oliphant', 44 | zip: 'Turbinatoconcave', 45 | }, 46 | ], 47 | [ 48 | 'partial address', 49 | { 50 | city: 'Backbitingly', 51 | country: 'Interdine', 52 | state: 'Muddybreast', 53 | street1: '', 54 | street2: undefined, 55 | zip: null, 56 | }, 57 | ], 58 | ]; 59 | 60 | const NOT_EMPTY_PHONES = [ 61 | [ 62 | 'full phone', 63 | { 64 | code: '+78', 65 | number: '5637821', 66 | }, 67 | ], 68 | [ 69 | 'partial address', 70 | { 71 | code: '+78', 72 | number: '', 73 | }, 74 | ], 75 | ]; 76 | 77 | it.each(EMPTY_ADDRESSES)('Should return `true` if address is empty (%s)', (name, value) => { 78 | expect(isEmptyAddress(value)).toBeTruthy(); 79 | }); 80 | 81 | it.each(NOT_EMPTY_ADDRESSES)('Should return `false` if address is not empty (%s)', (name, value) => { 82 | expect(isEmptyAddress(value)).toBeFalsy(); 83 | }); 84 | 85 | it.each(EMPTY_PHONES)('Should return `true` if phone is empty (%s)', (name, value) => { 86 | expect(isEmptyPhone(value)).toBeTruthy(); 87 | }); 88 | 89 | it.each(NOT_EMPTY_PHONES)('Should return `false` if phone is not empty (%s)', (name, value) => { 90 | expect(isEmptyPhone(value)).toBeFalsy(); 91 | }); 92 | -------------------------------------------------------------------------------- /packages/core/utils/test/jest.setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/core/utils/test/jest.setup.ts -------------------------------------------------------------------------------- /packages/core/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": ".", 7 | "skipLibCheck": true, 8 | "paths": { 9 | "*": [ 10 | "types/*" 11 | ] 12 | } 13 | }, 14 | "include": [ 15 | "src/**/*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/validate/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/validate/README.md: -------------------------------------------------------------------------------- 1 | # 8base Validate 2 | 3 | This library is used by the other 8base service packages to validate forms. You can also use it to make your own form validation etc. 4 | 5 | ## API 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [validatorFacade](#validatorfacade) 12 | - [Parameters](#parameters) 13 | 14 | ### validatorFacade 15 | 16 | ValidatorFacade creates a validation function based on field metadata 17 | 18 | Type: function (field: Field): function (value: any): ValidationError? 19 | 20 | #### Parameters 21 | 22 | - `field` Field metadata 23 | 24 | Returns **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Validation function 25 | -------------------------------------------------------------------------------- /packages/core/validate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@8base/validate", 3 | "version": "3.0.0-beta.3", 4 | "repository": "https://github.com/8base/sdk", 5 | "homepage": "https://github.com/8base/sdk/tree/master/packages/core/validate", 6 | "main": "dist/cjs/index.js", 7 | "types": "dist/mjs/index.d.ts", 8 | "module": "dist/mjs/index.js", 9 | "scripts": { 10 | "build": "../../../bin/build-package.sh", 11 | "watch": "../../../bin/watch-package.sh", 12 | "test": "NPM_ENV=test jest" 13 | }, 14 | "dependencies": { 15 | "@8base/utils": "^3.0.0-beta.3", 16 | "decimal.js": "^10.2.1", 17 | "ramda": "^0.27.1" 18 | }, 19 | "devDependencies": { 20 | "@types/jest": "^26.0.20", 21 | "@types/ramda": "^0.27.36", 22 | "jest": "26.6.3", 23 | "ts-jest": "^26.4.4", 24 | "typescript": "^4.1.3" 25 | }, 26 | "jest": { 27 | "globals": { 28 | "ts-jest": { 29 | "tsconfig": "/tsconfig.json" 30 | } 31 | }, 32 | "setupFiles": [ 33 | "/test/jest.setup.ts" 34 | ], 35 | "collectCoverageFrom": [ 36 | "/src/**", 37 | "!/**/__tests__/**" 38 | ], 39 | "moduleNameMapper": { 40 | "@8base/sdk-core": "/../../core/8base-sdk/src/index.ts", 41 | "@8base/api-client": "/../../core/api-client/src/index.ts", 42 | "@8base/api-token-auth-client": "/../../core/api-token-auth-client/src/index.ts", 43 | "@8base/apollo-client": "/../../core/apollo-client/src/index.ts", 44 | "@8base/apollo-links": "/../../core/apollo-links/src/index.ts", 45 | "@8base/auth": "/../../core/auth/src/index.ts", 46 | "@8base/utils": "/../../core/utils/src/index.ts", 47 | "@8base/validate": "/../../core/validate/src/index.ts", 48 | "@8base/web-auth0-auth-client": "/../../core/web-auth0-auth-client/src/index.ts", 49 | "@8base/web-cognito-auth-client": "/../../core/web-cognito-auth-client/src/index.ts", 50 | "@8base/web-native-auth-client": "/../../core/web-native-auth-client/src/index.ts", 51 | "@8base/web-oauth-client": "/../../core/web-oauth-client/src/index.ts", 52 | "@8base/sdk-react": "/../../react/8base-react-sdk/src/index.ts", 53 | "@8base/react-app-provider": "/../../react/app-provider/src/index.ts", 54 | "@8base/react-auth": "/../../react/auth/src/index.ts", 55 | "@8base/react-crud": "/../../react/crud/src/index.ts", 56 | "@8base/react-file-input": "/../../react/file-input/src/index.ts", 57 | "@8base/react-forms": "/../../react/forms/src/index.ts", 58 | "@8base/react-permissions-provider": "/../../react/permissions-provider/src/index.ts", 59 | "@8base/react-table-schema-provider": "/../../react/table-schema-provider/src/index.ts", 60 | "@8base/react-utils": "/../../react/utils/src/index.ts" 61 | }, 62 | "moduleFileExtensions": [ 63 | "ts", 64 | "tsx", 65 | "js", 66 | "jsx" 67 | ], 68 | "transform": { 69 | "^.+\\.(ts|tsx)$": "ts-jest" 70 | }, 71 | "testMatch": [ 72 | "**/__tests__/**/*.[jt]s?(x)", 73 | "**/?(*.)+(spec|test).[jt]s?(x)" 74 | ] 75 | }, 76 | "license": "MIT" 77 | } 78 | -------------------------------------------------------------------------------- /packages/core/validate/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validator'; 2 | -------------------------------------------------------------------------------- /packages/core/validate/src/validator.constants.ts: -------------------------------------------------------------------------------- 1 | import { TEXT_FORMATS, DATE_FORMATS, SMART_FORMATS } from '@8base/utils'; 2 | import { Format } from '@8base/utils'; 3 | 4 | export const FORMAT_PATTERN: Partial> = { 5 | [TEXT_FORMATS.UNFORMATTED]: /.*/, 6 | [TEXT_FORMATS.NAME]: /.*/, 7 | [SMART_FORMATS.ADDRESS]: /.*/, 8 | [TEXT_FORMATS.EMAIL]: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, 9 | [TEXT_FORMATS.PHONE]: /^(0|[1-9][0-9]{9})$/i, 10 | [TEXT_FORMATS.SSN]: /^(?!666|000|9\d{2})\d{3}[- ]{0,1}(?!00)\d{2}[- ]{0,1}(?!0{4})\d{4}$/, 11 | [TEXT_FORMATS.EIN]: /^\d{2}[- ]{0,1}\d{7}$/, 12 | [DATE_FORMATS.DATE]: /\d{4}-[01]\d-[0-3]\d/, 13 | [DATE_FORMATS.DATETIME]: /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/, 14 | }; 15 | 16 | export const VALIDATION_ERROR = { 17 | [DATE_FORMATS.DATE]: (): string => 'Invalid date.', 18 | [DATE_FORMATS.DATETIME]: (): string => 'Invalid datetime.', 19 | [SMART_FORMATS.ADDRESS]: (): string => '', 20 | [TEXT_FORMATS.EIN]: (): string => 'Invalid Employer Identification Number.', 21 | [TEXT_FORMATS.EMAIL]: (): string => 'Invalid email.', 22 | [TEXT_FORMATS.NAME]: (): string => '', 23 | [TEXT_FORMATS.PHONE]: (): string => 'Invalid phone number.', 24 | [TEXT_FORMATS.SSN]: (): string => 'Invalid Social Security Number.', 25 | [TEXT_FORMATS.UNFORMATTED]: (): string => '', 26 | FORMAT: (format: Format): string => `Value doesn't match ${format} format.`, 27 | IS_REQUIRED: (): string => 'Value is required', 28 | MAX_FIELD_SIZE: (maxFieldSize: number): string => `Maximum allowed field size is ${maxFieldSize}. It was exceeded.`, 29 | MAX_PRECISION: (maxPrecision: number): string => `Maximum allowed precision is ${maxPrecision}. It was exceeded.`, 30 | MAX_VALUE: (maxValue: number): string => `Value is greater than maximum allowed value ${maxValue}.`, 31 | MIN_VALUE: (minValue: number): string => `Value is lower than minimum allowed value ${minValue}.`, 32 | NOT_A_JSON: (parseError: string): string => `Value isn't a valid JSON: ${parseError}.`, 33 | NOT_A_NUMBER: (): string => `Value isn't a number`, 34 | }; 35 | 36 | export type ValidationError = keyof typeof VALIDATION_ERROR; 37 | -------------------------------------------------------------------------------- /packages/core/validate/test/.eslintrc.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/core/validate/test/.eslintrc.yml -------------------------------------------------------------------------------- /packages/core/validate/test/__tests__/fileField.test.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_TYPE } from '@8base/utils'; 2 | 3 | import { validatorFacade as validator } from '../../src/validator'; 4 | 5 | import { VALIDATION_ERROR } from '../../src/validator.constants'; 6 | 7 | import { mockField } from '../utils/'; 8 | 9 | const mockFileField = mockField(FIELD_TYPE.FILE); 10 | 11 | describe('As developer, i can create file field vaidator', () => { 12 | it('should check invalid value by "isRequired" attribute and provide error message', () => { 13 | const fileField = mockFileField({}); 14 | fileField.isRequired = true; 15 | 16 | const validate = validator(fileField); 17 | 18 | expect(validate(null)).toBe(VALIDATION_ERROR.IS_REQUIRED()); 19 | }); 20 | 21 | it('should check empty value by "isRequired" attribute and provide error message', () => { 22 | const fileField = mockFileField({}); 23 | fileField.isRequired = true; 24 | 25 | const validate = validator(fileField); 26 | 27 | expect(validate('')).toBe(VALIDATION_ERROR.IS_REQUIRED()); 28 | }); 29 | 30 | it('should check valid value by "isRequired" attribute and return undefined', () => { 31 | const fileField = mockFileField({}); 32 | fileField.isRequired = true; 33 | 34 | const validate = validator(fileField); 35 | 36 | expect(validate('fileValue')).toBeUndefined(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/validate/test/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { SDKError, ERROR_CODES, PACKAGES } from '@8base/utils'; 2 | import { validatorFacade } from '../../src/validator'; 3 | 4 | import { mockField } from '../utils'; 5 | 6 | describe("As a developer, i can't create unsupported field validator", () => { 7 | it('throws error', () => { 8 | expect(() => { 9 | validatorFacade((mockField as any)('UNSUPPORTED_FIELD')()); 10 | }).toThrow( 11 | new SDKError( 12 | ERROR_CODES.UNSUPPORTED_FIELD_TYPE, 13 | PACKAGES.VALIDATE, 14 | "Validator doesn't support field type UNSUPPORTED_FIELD", 15 | ), 16 | ); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/core/validate/test/__tests__/jsonField.test.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_TYPE } from '@8base/utils'; 2 | 3 | import { validatorFacade as validator } from '../../src/validator'; 4 | 5 | import { VALIDATION_ERROR } from '../../src/validator.constants'; 6 | 7 | import { mockField } from '../utils/'; 8 | 9 | const mockJSONField = mockField(FIELD_TYPE.JSON); 10 | 11 | describe('As developer, i can create JSON field vaidator', () => { 12 | it('should check invalid JSON value and provide error message', () => { 13 | const JSONField = mockJSONField({}); 14 | 15 | const validate = validator(JSONField); 16 | 17 | expect(validate('{ "key": }')).toBe(VALIDATION_ERROR.NOT_A_JSON('Unexpected token } in JSON at position 9')); 18 | }); 19 | 20 | it('should check invalid value by "isRequired" attribute and provide error message', () => { 21 | const JSONField = mockJSONField({}); 22 | JSONField.isRequired = true; 23 | 24 | const validate = validator(JSONField); 25 | 26 | expect(validate(null)).toBe(VALIDATION_ERROR.IS_REQUIRED()); 27 | }); 28 | 29 | it('should check empty value by "isRequired" attribute and provide error message', () => { 30 | const JSONField = mockJSONField({}); 31 | JSONField.isRequired = true; 32 | 33 | const validate = validator(JSONField); 34 | 35 | expect(validate('')).toBe(VALIDATION_ERROR.IS_REQUIRED()); 36 | }); 37 | 38 | it('should check valid value by "isRequired" attribute and return undefined', () => { 39 | const JSONField = mockJSONField({}); 40 | JSONField.isRequired = true; 41 | 42 | const validate = validator(JSONField); 43 | 44 | expect(validate('{ "key": "value" }')).toBeUndefined(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/core/validate/test/__tests__/relationField.test.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_TYPE } from '@8base/utils'; 2 | 3 | import { validatorFacade as validator } from '../../src/validator'; 4 | 5 | import { VALIDATION_ERROR } from '../../src/validator.constants'; 6 | 7 | import { mockField } from '../utils/'; 8 | 9 | const mockRelationField = mockField(FIELD_TYPE.RELATION); 10 | 11 | describe('As developer, i can create relation field vaidator', () => { 12 | it('should check invalid value by "isRequired" attribute and provide error message', () => { 13 | const relationField = mockRelationField({}); 14 | relationField.isRequired = true; 15 | 16 | const validate = validator(relationField); 17 | 18 | expect(validate(null)).toBe(VALIDATION_ERROR.IS_REQUIRED()); 19 | }); 20 | 21 | it('should check empty value by "isRequired" attribute and provide error message', () => { 22 | const relationField = mockRelationField({}); 23 | relationField.isRequired = true; 24 | 25 | const validate = validator(relationField); 26 | 27 | expect(validate('')).toBe(VALIDATION_ERROR.IS_REQUIRED()); 28 | }); 29 | 30 | it('should check valid value by "isRequired" attribute and return undefined', () => { 31 | const relationField = mockRelationField({}); 32 | relationField.isRequired = true; 33 | 34 | const validate = validator(relationField); 35 | 36 | expect(validate('relationValue')).toBeUndefined(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/validate/test/__tests__/switchField.test.ts: -------------------------------------------------------------------------------- 1 | import { FIELD_TYPE } from '@8base/utils'; 2 | 3 | import { validatorFacade as validator } from '../../src/validator'; 4 | 5 | import { VALIDATION_ERROR } from '../../src/validator.constants'; 6 | 7 | import { mockField } from '../utils/'; 8 | 9 | const mockSwitchField = mockField(FIELD_TYPE.SWITCH); 10 | 11 | describe('As developer, i can create switch field vaidator', () => { 12 | it('should check invalid value by "isRequired" attribute and provide error message', () => { 13 | const switchField = mockSwitchField({}); 14 | switchField.isRequired = true; 15 | 16 | const validate = validator(switchField); 17 | 18 | expect(validate(null)).toBe(VALIDATION_ERROR.IS_REQUIRED()); 19 | }); 20 | 21 | it('should check empty value by "isRequired" attribute and provide error message', () => { 22 | const switchField = mockSwitchField({}); 23 | switchField.isRequired = true; 24 | 25 | const validate = validator(switchField); 26 | 27 | expect(validate('')).toBe(VALIDATION_ERROR.IS_REQUIRED()); 28 | }); 29 | 30 | it('should check valid value by "isRequired" attribute and return undefined', () => { 31 | const switchField = mockSwitchField({}); 32 | switchField.isRequired = true; 33 | 34 | const validate = validator(switchField); 35 | 36 | expect(validate('switchValue')).toBeUndefined(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/validate/test/jest.setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/core/validate/test/jest.setup.ts -------------------------------------------------------------------------------- /packages/core/validate/test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mockField'; 2 | -------------------------------------------------------------------------------- /packages/core/validate/test/utils/mockField.ts: -------------------------------------------------------------------------------- 1 | import { FieldType } from '@8base/utils'; 2 | import { Field } from '../../src/validator'; 3 | 4 | export const mockField = (fieldType: FieldType) => (fieldTypeAttributes: {} = {}): Field => ({ 5 | fieldType, 6 | fieldTypeAttributes, 7 | isRequired: false, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/core/validate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/web-auth0-auth-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/web-auth0-auth-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './WebAuth0AuthClient'; 2 | -------------------------------------------------------------------------------- /packages/core/web-auth0-auth-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/web-cognito-auth-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/web-cognito-auth-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './WebAuthCognitoClient'; 2 | -------------------------------------------------------------------------------- /packages/core/web-cognito-auth-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/web-native-auth-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/web-native-auth-client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [3.0.0-beta.3](https://github.com/8base/sdk/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2023-04-12) 7 | 8 | **Note:** Version bump only for package @8base/web-native-auth-client 9 | 10 | 11 | 12 | 13 | 14 | # [3.0.0-beta.2](https://github.com/8base/sdk/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2023-04-12) 15 | 16 | **Note:** Version bump only for package @8base/web-native-auth-client 17 | 18 | 19 | 20 | 21 | 22 | # [3.0.0-beta.1](https://github.com/8base/sdk/compare/v3.0.0-beta.0...v3.0.0-beta.1) (2023-04-12) 23 | 24 | **Note:** Version bump only for package @8base/web-native-auth-client 25 | 26 | 27 | 28 | 29 | 30 | # [3.0.0-beta.0](https://github.com/8base/sdk/compare/v2.6.6...v3.0.0-beta.0) (2022-06-22) 31 | 32 | **Note:** Version bump only for package @8base/web-native-auth-client 33 | 34 | 35 | 36 | 37 | 38 | ## [2.6.6](https://github.com/8base/sdk/compare/v2.6.5...v2.6.6) (2022-03-18) 39 | 40 | **Note:** Version bump only for package @8base/web-native-auth-client 41 | 42 | 43 | 44 | 45 | 46 | ## [2.6.5](https://github.com/8base/sdk/compare/v2.6.4...v2.6.5) (2021-10-29) 47 | 48 | **Note:** Version bump only for package @8base/web-native-auth-client 49 | 50 | 51 | 52 | 53 | 54 | ## [2.6.4](https://github.com/8base/sdk/compare/v2.6.3...v2.6.4) (2021-10-29) 55 | 56 | **Note:** Version bump only for package @8base/web-native-auth-client 57 | 58 | 59 | 60 | 61 | 62 | ## [2.6.3](https://github.com/8base/sdk/compare/v2.6.2...v2.6.3) (2021-10-29) 63 | 64 | **Note:** Version bump only for package @8base/web-native-auth-client 65 | 66 | 67 | 68 | 69 | 70 | ## [2.6.2](https://github.com/8base/sdk/compare/v2.6.1...v2.6.2) (2021-10-29) 71 | 72 | **Note:** Version bump only for package @8base/web-native-auth-client 73 | 74 | 75 | 76 | 77 | 78 | ## [2.6.1](https://github.com/8base/sdk/compare/v2.6.0...v2.6.1) (2021-10-29) 79 | 80 | **Note:** Version bump only for package @8base/web-native-auth-client 81 | 82 | 83 | 84 | 85 | 86 | # [2.6.0](https://github.com/8base/sdk/compare/v2.5.2...v2.6.0) (2021-10-29) 87 | 88 | **Note:** Version bump only for package @8base/web-native-auth-client 89 | 90 | 91 | 92 | 93 | 94 | ## [2.5.1](https://github.com/8base/sdk/compare/v2.5.0...v2.5.1) (2021-08-23) 95 | 96 | 97 | ### Features 98 | 99 | * add web-native strategy to the auth ([c5e02dc](https://github.com/8base/sdk/commit/c5e02dcd2b77d8fb0b14c1c412c218bfdc69d67f)) 100 | 101 | 102 | 103 | 104 | 105 | # [2.5.0](https://github.com/8base/sdk/compare/v2.4.0...v2.5.0) (2021-08-23) 106 | 107 | 108 | ### Features 109 | 110 | * add web native auth client ([7d61180](https://github.com/8base/sdk/commit/7d61180086bb8cb6adde65e0930f220a715b8a4f)) 111 | -------------------------------------------------------------------------------- /packages/core/web-native-auth-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export { WebNativeAuthClient } from './WebNativeAuthClient'; 2 | -------------------------------------------------------------------------------- /packages/core/web-native-auth-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/web-oauth-client/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/core/web-oauth-client/README.md: -------------------------------------------------------------------------------- 1 | # 8base api token auth client 2 | 3 | The 8base web oauth client for the `AuthProvider`. 4 | 5 | ## WebOAuthClient 6 | 7 | #### Table of Contents 8 | 9 | - [WebOAuthClient](#webOAuthClient) 10 | - [Parameters](#parameters) 11 | 12 | ### WebOAuthClient 13 | 14 | Create instance of the web oauth client 15 | 16 | #### Parameters 17 | 18 | - `authorize` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function)** Function used to describe authorize logic. 19 | 20 | ## Usage 21 | ### Firebase oauth 22 | ```js 23 | import firebase from 'firebase'; 24 | import { WebOAuthClient } from '@8base/web-oauth-client'; 25 | 26 | const FIREBASE_CONFIGURATION = { 27 | apiKey: "", 28 | authDomain: "", 29 | databaseURL: "", 30 | projectId: "", 31 | storageBucket: "", 32 | messagingSenderId: "", 33 | appId: "" 34 | }; 35 | 36 | const firebaseAuth = firebase.initializeApp(FIREBASE_CONFIGURATION).auth(); 37 | 38 | const authClient = new WebOAuthClient({ 39 | authorize (email, password) { 40 | return firebaseAuth.signInWithEmailAndPassword( 41 | email, 42 | password, 43 | ) 44 | .then(() => firebaseAuth.currentUser.getIdToken()) 45 | .then((token) => { 46 | return token; 47 | }) 48 | }, 49 | logout() { 50 | window.addEventListener('unload', () => { 51 | this.purgeState(); 52 | }); 53 | 54 | window.location.href = '/'; 55 | } 56 | }); 57 | ``` 58 | ## Examples 59 | 60 | [Firebase oauth example](https://github.com/8base/8base-firebase-auth-example) 61 | [IBM cloud oauth example](https://github.com/8base/8base-ibm-app-id-example) 62 | -------------------------------------------------------------------------------- /packages/core/web-oauth-client/src/WebOAuthClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAuthState, 3 | IAuthClient, 4 | IStorageOptions, 5 | PACKAGES, 6 | StorageAPI, 7 | throwIfMissingRequiredParameters, 8 | } from '@8base/utils'; 9 | import jwtDecode from 'jwt-decode'; 10 | 11 | interface IWebOAuthClientOptions { 12 | authorize: (this: WebOAuthClient, ...rest: any) => any | Promise; 13 | logout?: (this: WebOAuthClient, ...rest: any) => any | Promise; 14 | } 15 | 16 | /** 17 | * Creates instance of the web oauth client 18 | */ 19 | class WebOAuthClient implements IAuthClient { 20 | private options: IWebOAuthClientOptions; 21 | private storageAPI: StorageAPI; 22 | 23 | constructor(options: IWebOAuthClientOptions, storageOptions: IStorageOptions = {}) { 24 | throwIfMissingRequiredParameters(['authorize'], PACKAGES.WEB_AUTH0_AUTH_CLIENT, options); 25 | 26 | this.storageAPI = new StorageAPI( 27 | storageOptions.storage || window.localStorage, 28 | storageOptions.storageKey || 'auth', 29 | storageOptions.initialState, 30 | ); 31 | 32 | this.options = options; 33 | } 34 | 35 | public setState(state: IAuthState) { 36 | this.storageAPI.setState(state); 37 | } 38 | 39 | public getState(): IAuthState { 40 | return this.storageAPI.getState(); 41 | } 42 | 43 | public purgeState() { 44 | this.storageAPI.purgeState(); 45 | } 46 | 47 | public checkIsAuthorized() { 48 | const { token } = this.getState(); 49 | 50 | return token !== '' && token !== null && token !== undefined; 51 | } 52 | 53 | public getTokenInfo() { 54 | const { token } = this.getState(); 55 | 56 | if (!token) { 57 | return undefined; 58 | } 59 | 60 | return jwtDecode(token) || undefined; 61 | } 62 | 63 | public authorize(...args: any) { 64 | return this.options.authorize.apply(this, args); 65 | } 66 | 67 | public logout(...args: any) { 68 | if (typeof this.options.logout === 'function') { 69 | return this.options.logout.apply(this, args); 70 | } 71 | } 72 | } 73 | 74 | export { WebOAuthClient }; 75 | -------------------------------------------------------------------------------- /packages/core/web-oauth-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export { WebOAuthClient } from './WebOAuthClient'; 2 | -------------------------------------------------------------------------------- /packages/core/web-oauth-client/tests/__tests__/web-aouth-client.ts: -------------------------------------------------------------------------------- 1 | import { WebOAuthClient } from '../../src'; 2 | 3 | const API_TOKEN = 'api token'; 4 | const WORKSPACE_ID = 'workspace id'; 5 | 6 | describe('ApiTokenClient', () => { 7 | it("Throws an error if authorize method haven't provided", () => { 8 | expect(() => { 9 | // @ts-ignore 10 | const temp = new WebOAuthClient(); 11 | }).toThrow('Missing parameter: authorize'); 12 | }); 13 | 14 | // tslint:disable-next-line:only-arrow-functions 15 | const authorizeFn = jest.fn(function () { 16 | return 'login' 17 | }); 18 | 19 | const logoutFn = jest.fn(function(this: WebOAuthClient) { 20 | this.purgeState(); 21 | return 'logout' 22 | }); 23 | 24 | const authClient = new WebOAuthClient({ authorize: authorizeFn, logout: logoutFn }); 25 | 26 | it('As a developer, I can call authorize', async () => { 27 | expect(authClient.authorize()).toBe('login'); 28 | }); 29 | 30 | it("As a developer, I can set auth state", async () => { 31 | authClient.setState({ 32 | token: API_TOKEN, 33 | workspaceId: WORKSPACE_ID, 34 | }); 35 | 36 | expect(authClient.getState()).toEqual({ 37 | token: API_TOKEN, 38 | workspaceId: WORKSPACE_ID, 39 | }); 40 | }); 41 | 42 | it("As a developer, I can check authorize", async () => { 43 | expect(authClient.checkIsAuthorized()).toBe(true); 44 | }); 45 | 46 | it("As a developer, I can purge auth state", async () => { 47 | authClient.purgeState(); 48 | 49 | expect(authClient.getState()).toEqual({}); 50 | }); 51 | 52 | it('As a developer, I can logout', async () => { 53 | authClient.setState({ 54 | token: API_TOKEN, 55 | }); 56 | 57 | expect(authClient.logout()).toBe('logout'); 58 | expect(authClient.getState()).toEqual({}); 59 | expect(authClient.checkIsAuthorized()).toBe(false); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/core/web-oauth-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/8base-react-sdk/README.md: -------------------------------------------------------------------------------- 1 | # 8base-react-sdk 2 | -------------------------------------------------------------------------------- /packages/react/8base-react-sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@8base/react-app-provider'; 2 | export * from '@8base/react-file-input'; 3 | export * from '@8base/react-crud'; 4 | export * from '@8base/react-forms'; 5 | export * from '@8base/react-permissions-provider'; 6 | export * from '@8base/react-auth'; 7 | export * from '@8base/react-table-schema-provider'; 8 | export * from '@8base/react-utils'; 9 | export { default as gql } from 'graphql-tag'; 10 | -------------------------------------------------------------------------------- /packages/react/8base-react-sdk/test/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as appProviderExport from '@8base/react-app-provider'; 2 | import * as fileInputExport from '@8base/react-file-input'; 3 | import * as crudExport from '@8base/react-crud'; 4 | import * as formsExport from '@8base/react-forms'; 5 | import * as permissionsProviderExport from '@8base/react-permissions-provider'; 6 | import * as authExport from '@8base/react-auth'; 7 | import * as tableSchemaProviderExport from '@8base/react-table-schema-provider'; 8 | import * as utilsExport from '@8base/react-utils'; 9 | 10 | describe('8base-react-sdk', () => { 11 | const rootExport = require('../../src'); 12 | 13 | it('contains @8base/react-app-provider', () => { 14 | expect(rootExport).toMatchObject(appProviderExport); 15 | }); 16 | 17 | it('contains @8base/react-file-input', () => { 18 | expect(rootExport).toMatchObject(fileInputExport); 19 | }); 20 | 21 | it('contains @8base/react-crud', () => { 22 | expect(rootExport).toMatchObject(crudExport); 23 | }); 24 | 25 | it('contains @8base/react-forms', () => { 26 | expect(rootExport).toMatchObject(formsExport); 27 | }); 28 | 29 | it('contains @8base/react-permissions-provider', () => { 30 | expect(rootExport).toMatchObject(permissionsProviderExport); 31 | }); 32 | 33 | it('contains @8base/react-auth', () => { 34 | expect(rootExport).toMatchObject(authExport); 35 | }); 36 | 37 | it('contains @8base/react-table-schema-provider', () => { 38 | expect(rootExport).toMatchObject(tableSchemaProviderExport); 39 | }); 40 | 41 | it('contains @8base/react-utils', () => { 42 | expect(rootExport).toMatchObject(utilsExport); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/react/8base-react-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | # 8base React SDK 2 | 3 | This repository contains a set of SDK packages to make it easier to use 8base with React. 4 | 5 | ## Build 6 | ``` 7 | yarn build-packages 8 | ``` 9 | 10 | ## Deploy 11 | ``` 12 | yarn bump 13 | ``` 14 | 15 | ## Examples 16 | 17 | - [Guest App](https://codesandbox.io/s/github/8base/react-sdk/tree/master/examples/guest-app) 18 | - [With Api Token App](https://codesandbox.io/s/github/8base/react-sdk/tree/master/examples/with-api-token-app) 19 | - [With Authorization App](https://codesandbox.io/s/github/8base/react-sdk/tree/master/examples/with-authorization-app) 20 | - [With Protected Routes](https://codesandbox.io/s/github/8base/react-sdk/tree/master/examples/with-protected-routes) 21 | -------------------------------------------------------------------------------- /packages/react/app-provider/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/app-provider/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "bracketSpacing": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/react/app-provider/README.md: -------------------------------------------------------------------------------- 1 | # 8base App Provider 2 | 3 | Universal 8base App Provider loads fragments schema and provides it to Apollo client, along with authentication and table schema. 4 | 5 | # API 6 | 7 | 8 | 9 | ### Table of Contents 10 | 11 | - [EightBaseAppProvider](#eightbaseappprovider) 12 | - [Parameters](#parameters) 13 | - [Properties](#properties) 14 | 15 | ## EightBaseAppProvider 16 | 17 | `EightBaseAppProvider` universal provider which loads fragments schema and provides it to Apollo client, along with authentication and table schema. 18 | 19 | ### Properties 20 | 21 | - `uri` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The 8base API field schema. 22 | - `authClient` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** The 8base auth client. 23 | - `onRequestSuccess` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Callback which is executed when a request is successful. 24 | - `onRequestError` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Callback which is executed when a request fails. 25 | - `extendLinks` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Function to extend the standard array of links. 26 | - `children` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** The render function. 27 | 28 | # Usage 29 | 30 | ```js 31 | import React from 'react'; 32 | import { BrowserRouter } from 'react-router-dom'; 33 | import { EightBaseAppProvider } from '@8base/app-provider'; 34 | import { WebAuth0AuthClient } from '@8base/web-auth0-auth-client'; 35 | import { EightBaseBoostProvider, Loader } from '@8base/boost'; 36 | 37 | import { Routes } from './routes'; 38 | 39 | const authClient = new WebAuth0AuthClient({ 40 | domain: AUTH_DOMAIN, 41 | clientId: AUTH_CLIENT_ID, 42 | redirectUri: `${window.location.origin}/auth/callback`, 43 | logoutRedirectUri: `${window.location.origin}/auth`, 44 | workspaceId: 'workspace-id', 45 | }); 46 | 47 | const Application = () => ( 48 | 49 | 50 | 51 | { 52 | ({ loading }) => loading ? : 53 | } 54 | 55 | 56 | 57 | ); 58 | 59 | export { Application }; 60 | ``` 61 | -------------------------------------------------------------------------------- /packages/react/app-provider/src/EightBaseAppProvider.__deprecated.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { AppProvider, AppProviderProps } from './AppProvider'; 5 | 6 | export class EightBaseAppProvider extends React.Component { 7 | public componentDidMount() { 8 | // tslint:disable 9 | console.error( 10 | 'DEPRECATION WARNING: EightBaseAppProvider has been renamed to AppProvider.', 11 | ); 12 | // tslint:enabe 13 | } 14 | 15 | public render() { 16 | return ; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react/app-provider/src/FragmentsSchemaContainer.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as R from 'ramda'; 3 | import { PossibleTypesMap } from '@apollo/client'; 4 | 5 | type FragmentsSchemaContainerProps = { 6 | uri: string; 7 | children: (args: { 8 | loading: boolean; 9 | introspectionQueryResultData: object | null; 10 | }) => React.ReactNode; 11 | }; 12 | 13 | type FragmentsSchemaContainerState = { 14 | loading: boolean; 15 | introspectionQueryResultData: null | Object; 16 | }; 17 | 18 | class FragmentsSchemaContainer extends React.PureComponent< 19 | FragmentsSchemaContainerProps, 20 | FragmentsSchemaContainerState 21 | > { 22 | public state: FragmentsSchemaContainerState = { 23 | introspectionQueryResultData: null, 24 | loading: true, 25 | }; 26 | 27 | public async componentDidMount() { 28 | const { uri } = this.props; 29 | 30 | this.setState({ loading: true }); 31 | 32 | const introspectionQueryResultData = await fetchFragmentsSchema(uri); 33 | 34 | this.setState({ loading: false, introspectionQueryResultData }); 35 | } 36 | 37 | public render() { 38 | const { loading, introspectionQueryResultData } = this.state; 39 | const { children } = this.props; 40 | 41 | return children({ loading, introspectionQueryResultData }); 42 | } 43 | } 44 | 45 | const fetchFragmentsSchema = async (uri: string): Promise => { 46 | const result: { data: any } = await fetch(uri, { 47 | body: JSON.stringify({ 48 | operationName: 'FragmentsSchema', 49 | query: ` 50 | query FragmentsSchema { 51 | __schema { 52 | types { 53 | kind 54 | name 55 | possibleTypes { 56 | name 57 | } 58 | } 59 | } 60 | } 61 | `, 62 | }), 63 | headers: { 'Content-Type': 'application/json' }, 64 | method: 'POST', 65 | }) 66 | .then((result) => result.json()) 67 | .catch(() => ({ data: null })); 68 | 69 | if (R.isNil(result.data)) { 70 | return null; 71 | } 72 | 73 | const possibleTypes: PossibleTypesMap = {}; 74 | 75 | result.data.__schema?.types?.forEach((supertype: any) => { 76 | if (supertype.possibleTypes) { 77 | possibleTypes[supertype.name] = supertype.possibleTypes.map( 78 | (subtype: any) => subtype.name, 79 | ); 80 | } 81 | }); 82 | 83 | return possibleTypes; 84 | }; 85 | 86 | export { FragmentsSchemaContainer }; 87 | -------------------------------------------------------------------------------- /packages/react/app-provider/src/index.ts: -------------------------------------------------------------------------------- 1 | export { AppProvider } from './AppProvider'; 2 | export { EightBaseAppProvider } from './EightBaseAppProvider.__deprecated'; 3 | export { FragmentsSchemaContainer } from './FragmentsSchemaContainer'; 4 | export * from '@8base/react-auth'; 5 | -------------------------------------------------------------------------------- /packages/react/app-provider/src/types.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, InMemoryCacheConfig } from '@apollo/client'; 2 | import { IAuthState, TableSchema, Application } from '@8base/utils'; 3 | 4 | export type ApolloContainerPassedProps = { 5 | uri: string; 6 | autoSignUp?: boolean; 7 | withSubscriptions?: boolean; 8 | withBatching?: boolean; 9 | authProfileId?: string; 10 | onRequestSuccess: (request: { [key: string]: any }) => void; 11 | onRequestError: (request: { [key: string]: any }) => void; 12 | extendLinks?: ( 13 | links: ApolloLink[], 14 | options: { getAuthState?: () => IAuthState }, 15 | ) => ApolloLink[]; 16 | introspectionQueryResultData?: Object; 17 | tablesList?: TableSchema[]; 18 | applicationsList?: Application[]; 19 | cacheOptions?: InMemoryCacheConfig; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/react/app-provider/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jest: true -------------------------------------------------------------------------------- /packages/react/app-provider/test/jest.setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/react/app-provider/test/jest.setup.ts -------------------------------------------------------------------------------- /packages/react/app-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/auth/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/auth/README.md: -------------------------------------------------------------------------------- 1 | # 8base Auth 2 | 3 | The 8base React Auth package contains a provider with authentication state and auth helpers. 4 | 5 | ## AuthProvider 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [AuthProvider](#authprovider) 12 | - [Parameters](#parameters) 13 | - [Properties](#properties) 14 | 15 | ### AuthProvider 16 | 17 | **Extends React.Component** 18 | 19 | Provides access to the authentication state. 20 | 21 | #### Parameters 22 | 23 | - `props` **AuthProviderProps** 24 | 25 | #### Properties 26 | 27 | - `children` **React$Node** Children of the provider. 28 | - `authClient` **AuthClient** Instance of the auth client. 29 | 30 | ## Usage 31 | 32 | ```js 33 | import { AuthContext, AuthProvider, type AuthContextProps } from '@8base/react-auth'; 34 | import { WebAuth0AuthClient } form '@8base/web-auth0-auth-client'; 35 | 36 | const authClient = new WebAuth0AuthClient({ 37 | domain: 'domain', 38 | clientId: 'client-id', 39 | redirectUri: `${window.location.origin}/auth/callback`, 40 | logoutRedirectUri: `${window.location.origin}/auth`, 41 | workspaceId: 'workspace-id', 42 | }); 43 | 44 | 45 | ... 46 | 47 | { 48 | (auth: AuthContextProps) => (
) 49 | } 50 | 51 | ... 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/react/auth/src/AuthContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IAuthState } from '@8base/utils'; 3 | import { ISubscribableAuthClient } from '@8base/auth'; 4 | 5 | export type AuthContextProps = { 6 | isAuthorized: boolean; 7 | isEmailVerified?: boolean; 8 | authState: IAuthState; 9 | authClient: ISubscribableAuthClient; 10 | }; 11 | 12 | const AuthContext = React.createContext({ 13 | authState: {}, 14 | isAuthorized: false, 15 | } as any); 16 | 17 | export { AuthContext }; 18 | -------------------------------------------------------------------------------- /packages/react/auth/src/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthContext } from './AuthContext'; 2 | export { AuthProvider } from './AuthProvider'; 3 | export { withAuth } from './withAuth'; 4 | export { useAuth } from './useAuth'; 5 | 6 | export { WithAuthProps } from './withAuth'; 7 | export { AuthContextProps } from './AuthContext'; 8 | export { IAuthState } from '@8base/utils'; 9 | -------------------------------------------------------------------------------- /packages/react/auth/src/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { AuthContext } from './AuthContext'; 4 | 5 | function useAuth() { 6 | const auth = useContext(AuthContext); 7 | 8 | return auth; 9 | } 10 | 11 | export { useAuth }; 12 | -------------------------------------------------------------------------------- /packages/react/auth/src/withAuth.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Subtract } from 'utility-types'; 3 | import { getDisplayName } from '@8base/react-utils'; 4 | 5 | import { AuthContext, AuthContextProps } from './AuthContext'; 6 | 7 | export type WithAuthProps = { 8 | auth: AuthContextProps; 9 | }; 10 | 11 | const withAuth = (WrappedComponent: React.ComponentType) => 12 | class WithAuth extends React.Component> { 13 | public static displayName = `withAuth(${getDisplayName(WrappedComponent)})`; 14 | 15 | public render() { 16 | return ( 17 | 18 | {(auth) => } 19 | 20 | ); 21 | } 22 | }; 23 | 24 | export { withAuth }; 25 | -------------------------------------------------------------------------------- /packages/react/auth/test/__tests__/use-auth.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TestRenderer from 'react-test-renderer'; 3 | import { SubscribableDecorator } from '@8base/auth'; 4 | 5 | import { AuthProvider, useAuth } from '../../src'; 6 | import { DummyAuthClient } from '../utils'; 7 | 8 | const StubComponent = (props: any) =>
; 9 | 10 | const EnhancedStubComponent = () => { 11 | const auth = useAuth(); 12 | 13 | return ; 14 | }; 15 | 16 | describe('useAuth', () => { 17 | it('passes auth props to an component', () => { 18 | const authClient = DummyAuthClient(); 19 | const subscribableAuthClient = SubscribableDecorator.decorate(authClient); 20 | const testRenderer = TestRenderer.create( 21 | 22 | 23 | , 24 | ); 25 | const testInstance = testRenderer.root; 26 | 27 | const { props } = testInstance.findByType(StubComponent); 28 | 29 | expect(props.auth.isAuthorized).toBe(false); 30 | expect(props.auth.authState).toEqual({}); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/react/auth/test/__tests__/with-auth.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TestRenderer from 'react-test-renderer'; 3 | import { getDisplayName } from '@8base/react-utils'; 4 | import { SubscribableDecorator } from '@8base/auth'; 5 | 6 | import { AuthProvider, withAuth, WithAuthProps } from '../../src'; 7 | import { DummyAuthClient } from '../utils'; 8 | 9 | type StubComponentProps = { 10 | foo: number; 11 | } & WithAuthProps; 12 | 13 | const NotAuthorizedComponent = () => I am not authorized; 14 | 15 | const AuthorizedComponent = () => I am authorized; 16 | 17 | const StubComponent = ({ auth: { isAuthorized }, foo }: StubComponentProps) => ( 18 |
19 | {isAuthorized ? : } 20 | {foo} 21 |
22 | ); 23 | 24 | const EnhancedStubComponent = withAuth(StubComponent); 25 | 26 | describe('withAuth', () => { 27 | const authClient = DummyAuthClient(); 28 | const subscribableAuthClient = SubscribableDecorator.decorate(authClient); 29 | const testRenderer = TestRenderer.create( 30 | 31 | 32 | , 33 | ); 34 | const testInstance = testRenderer.root; 35 | 36 | it('sets wrapped display name for HOC', () => { 37 | expect(getDisplayName(EnhancedStubComponent)).toBe('withAuth(StubComponent)'); 38 | }); 39 | 40 | it('passes auth props to an enhanced component', () => { 41 | const { props } = testInstance.findByType(StubComponent); 42 | 43 | expect(props.auth.isAuthorized).toBe(false); 44 | expect(props.auth.authState).toEqual({}); 45 | }); 46 | 47 | it('passes all other props to an enhanced component', () => { 48 | const { props } = testInstance.findByType(StubComponent); 49 | 50 | expect(props.foo).toBe(42); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/react/auth/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { IAuthClient, IStorage, StorageAPI, IAuthState } from '@8base/utils'; 2 | 3 | export const authState = {}; 4 | 5 | export const externalAuth = { 6 | login: jest.fn(() => ({ token: 'some-token' })), 7 | logout: jest.fn(() => ({ token: null })), 8 | }; 9 | 10 | export const authStorage: IStorage = { 11 | getItem: (key) => Reflect.get(authState, key), 12 | setItem: (key, value) => { 13 | Reflect.set(authState, key, value); 14 | }, 15 | removeItem: (key) => { 16 | Reflect.deleteProperty(authState, key); 17 | }, 18 | }; 19 | 20 | export const DummyAuthClient = (): IAuthClient => { 21 | const storageAPI = new StorageAPI(authStorage, 'auth'); 22 | 23 | const getState = jest.fn(() => { 24 | return storageAPI.getState(); 25 | }); 26 | 27 | const setState = jest.fn((newState: IAuthState) => { 28 | storageAPI.setState(newState); 29 | }); 30 | 31 | const purgeState = jest.fn(() => { 32 | storageAPI.purgeState(); 33 | }); 34 | 35 | const checkIsAuthorized = jest.fn(() => { 36 | const state = getState(); 37 | 38 | return !!state.token; 39 | }); 40 | 41 | const login = jest.fn(() => { 42 | return externalAuth.login(); 43 | }); 44 | 45 | const logout = jest.fn(() => { 46 | return externalAuth.logout(); 47 | }); 48 | 49 | const getTokenInfo = jest.fn(() => { 50 | return {}; 51 | }); 52 | 53 | return { 54 | getTokenInfo, 55 | getState, 56 | setState, 57 | purgeState, 58 | checkIsAuthorized, 59 | login, 60 | logout, 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /packages/react/auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/crud/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/crud/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@8base/react-crud", 3 | "version": "3.0.0-beta.3", 4 | "repository": "https://github.com/8base/sdk", 5 | "homepage": "https://github.com/8base/sdk/tree/master/packages/react/crud", 6 | "main": "dist/cjs/index.js", 7 | "types": "dist/mjs/index.d.ts", 8 | "module": "dist/mjs/index.js", 9 | "scripts": { 10 | "build": "../../../bin/build-package.sh", 11 | "watch": "../../../bin/watch-package.sh" 12 | }, 13 | "peerDependencies": { 14 | "@apollo/client": "^3.3.7", 15 | "graphql": "^15.5.0", 16 | "react": "^17.0.1", 17 | "react-dom": "^17.0.1" 18 | }, 19 | "dependencies": { 20 | "@8base/react-permissions-provider": "^3.0.0-beta.3", 21 | "@8base/react-table-schema-provider": "^3.0.0-beta.3", 22 | "@8base/schema-name-generator": "^0.1.23", 23 | "@8base/utils": "^3.0.0-beta.3", 24 | "ramda": "^0.27.1" 25 | }, 26 | "devDependencies": { 27 | "@apollo/client": "^3.3.7", 28 | "@types/graphql": "^14.5.0", 29 | "@types/ramda": "^0.27.36", 30 | "@types/react": "^17.0.0", 31 | "@types/react-dom": "^17.0.0", 32 | "graphql": "^15.5.0", 33 | "react": "^17.0.1", 34 | "react-dom": "^17.0.1", 35 | "typescript": "^4.1.3" 36 | }, 37 | "license": "MIT" 38 | } 39 | -------------------------------------------------------------------------------- /packages/react/crud/src/RecordCreate.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { MutationResult, MutationFunction } from '@apollo/client'; 3 | import { TableConsumer, ITableConsumerRenderProps } from '@8base/react-table-schema-provider'; 4 | import { TableSchema, SDKError, ERROR_CODES, PACKAGES } from '@8base/utils'; 5 | 6 | import { RecordCrud } from './RecordCrud'; 7 | 8 | interface IChildrenPropObject { 9 | tableSchema: TableSchema | null; 10 | mutateResult: MutationResult; 11 | } 12 | 13 | type RecordCreateProps = { 14 | tableId?: string; 15 | includeColumns?: string[]; 16 | children: (mutateFunction: MutationFunction, result: IChildrenPropObject) => JSX.Element; 17 | }; 18 | 19 | /** 20 | * Component for creating the record of the table 21 | * 22 | * @prop {string} tableName - Name of the table 23 | * @prop {string} tableId - Id of the table 24 | * @prop {(Function, ChildrenPropObject) => React.ReactNode} children - Render prop with result of the queries 25 | */ 26 | export class RecordCreate extends Component { 27 | public renderQuery = ({ tableSchema, loading }: ITableConsumerRenderProps) => { 28 | const { children, ...rest } = this.props; 29 | 30 | if (!tableSchema && !loading) { 31 | throw new SDKError(ERROR_CODES.TABLE_NOT_FOUND, PACKAGES.CRUD, `Table doesn't find`); 32 | } 33 | 34 | if (!tableSchema) { 35 | return null; 36 | } 37 | 38 | return ( 39 | 40 | {(mutateFunction, mutateResult) => 41 | children(mutateFunction, { 42 | mutateResult, 43 | tableSchema, 44 | }) 45 | } 46 | 47 | ); 48 | }; 49 | 50 | public render() { 51 | const { tableId } = this.props; 52 | 53 | return {this.renderQuery}; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/react/crud/src/RecordCreateMany.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { MutationResult, MutationFunction } from '@apollo/client'; 3 | import { TableConsumer, ITableConsumerRenderProps } from '@8base/react-table-schema-provider'; 4 | import { TableSchema, SDKError, ERROR_CODES, PACKAGES } from '@8base/utils'; 5 | 6 | import { RecordCrud } from './RecordCrud'; 7 | 8 | interface IChildrenPropObject { 9 | tableSchema: TableSchema | null; 10 | mutateResult: MutationResult; 11 | } 12 | 13 | type RecordCreateManyProps = { 14 | tableId?: string; 15 | 16 | children: (mutateFunction: MutationFunction, result: IChildrenPropObject) => JSX.Element; 17 | }; 18 | 19 | /** 20 | * Component for creating many records of the table 21 | * 22 | * @prop {string} tableId - Id of the table 23 | * @prop {(Function, ChildrenPropObject) => React.ReactNode} children - Render prop with result of the queries 24 | */ 25 | export class RecordCreateMany extends Component { 26 | public renderQuery = ({ tableSchema, loading }: ITableConsumerRenderProps) => { 27 | const { children, ...rest } = this.props; 28 | 29 | if (!tableSchema && !loading) { 30 | throw new SDKError(ERROR_CODES.TABLE_NOT_FOUND, PACKAGES.CRUD, `Table doesn't find`); 31 | } 32 | 33 | if (!tableSchema) { 34 | return null; 35 | } 36 | 37 | return ( 38 | 39 | {(mutateFunction, mutateResult) => 40 | children(mutateFunction, { 41 | mutateResult, 42 | tableSchema, 43 | }) 44 | } 45 | 46 | ); 47 | }; 48 | 49 | public render() { 50 | const { tableId } = this.props; 51 | 52 | return {this.renderQuery}; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/react/crud/src/RecordCrud.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { MutationFunction, MutationResult, gql } from '@apollo/client'; 3 | // TODO: apollo Mutation component is deprecated 4 | import { Mutation } from '@apollo/client/react/components'; 5 | import { 6 | createTableRowCreateTag, 7 | createTableRowCreateManyTag, 8 | createTableRowUpdateTag, 9 | createTableRowDeleteTag, 10 | TableSchema, 11 | QueryGeneratorConfig, 12 | } from '@8base/utils'; 13 | import { PermissionsContext } from '@8base/react-permissions-provider'; 14 | 15 | type CrudModes = 'create' | 'createMany' | 'update' | 'delete'; 16 | 17 | type RecordCrudProps = { 18 | tableSchema: TableSchema; 19 | mode: CrudModes; 20 | includeColumns?: string[]; 21 | 22 | children: (mutateFunction: MutationFunction, mutateResult: MutationResult) => JSX.Element; 23 | }; 24 | 25 | const createRecordTag = (tableSchema: TableSchema, mode: CrudModes, options: QueryGeneratorConfig) => { 26 | switch (mode) { 27 | case 'create': 28 | return createTableRowCreateTag([tableSchema], tableSchema.id, options); 29 | case 'createMany': 30 | return createTableRowCreateManyTag([tableSchema], tableSchema.id); 31 | case 'update': 32 | return createTableRowUpdateTag([tableSchema], tableSchema.id, options); 33 | case 'delete': 34 | return createTableRowDeleteTag([tableSchema], tableSchema.id); 35 | default: 36 | return ''; 37 | } 38 | }; 39 | 40 | export class RecordCrud extends Component { 41 | public static contextType = PermissionsContext; 42 | 43 | public render() { 44 | const { tableSchema, children, mode, includeColumns, ...rest } = this.props; 45 | const mutation = gql( 46 | createRecordTag(tableSchema, mode, { 47 | permissions: this.context.permissions, 48 | includeColumns: includeColumns || null, 49 | }), 50 | ); 51 | 52 | return ( 53 | 54 | {(mutateFunction: MutationFunction, mutateResult: MutationResult) => children(mutateFunction, mutateResult)} 55 | 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/react/crud/src/RecordData.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import * as R from 'ramda'; 3 | import { QueryResult, gql } from '@apollo/client'; 4 | // TODO: apollo Query component is deprecated 5 | import { Query } from '@apollo/client/react/components'; 6 | import { SchemaNameGenerator } from '@8base/schema-name-generator'; 7 | import { PermissionsContext } from '@8base/react-permissions-provider'; 8 | import { createTableRowQueryTag, TableSchema, tableSelectors } from '@8base/utils'; 9 | 10 | type RecordDataProps = { 11 | tableId?: string; 12 | tableSchema: TableSchema; 13 | recordId: string; 14 | variables?: object; 15 | skip?: boolean; 16 | children: (recordData: QueryResult) => JSX.Element; 17 | }; 18 | 19 | export class RecordData extends Component { 20 | public static contextType = PermissionsContext; 21 | 22 | public getRecordData(tableSchema: TableSchema, data: any): any { 23 | const tableItemFieldName = SchemaNameGenerator.getTableItemFieldName(tableSchema.name); 24 | const tableAppName: string = tableSelectors.getTableAppName(tableSchema) as string; 25 | 26 | if (tableAppName) { 27 | return R.path([tableAppName, tableItemFieldName], data); 28 | } 29 | 30 | return R.path([tableItemFieldName], data); 31 | } 32 | 33 | public render() { 34 | const { tableId, variables, tableSchema, children, recordId, ...rest } = this.props; 35 | 36 | return ( 37 | 42 | {({ data, ...rest }: QueryResult) => 43 | children({ 44 | ...rest, 45 | data: this.getRecordData(tableSchema, data), 46 | }) 47 | } 48 | 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/react/crud/src/RecordDelete.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { MutationResult, MutationFunction } from '@apollo/client'; 3 | import { TableConsumer, ITableConsumerRenderProps } from '@8base/react-table-schema-provider'; 4 | import { TableSchema, SDKError, ERROR_CODES, PACKAGES } from '@8base/utils'; 5 | 6 | import { RecordCrud } from './RecordCrud'; 7 | 8 | interface IChildrenPropObject { 9 | tableSchema: TableSchema | null; 10 | mutateResult: MutationResult; 11 | } 12 | 13 | type RecordDeleteProps = { 14 | tableId?: string; 15 | 16 | children: (mutateFunction: MutationFunction, result: IChildrenPropObject) => JSX.Element; 17 | }; 18 | 19 | /** 20 | * Component for deleting the record of the table 21 | * 22 | * @prop {string} tableId - Id of the table 23 | * @prop {string} recordId - Id of the record 24 | * @prop {(Function, ChildrenPropObject) => React.ReactNode} children - Render prop with result of the queries 25 | */ 26 | 27 | export class RecordDelete extends Component { 28 | public renderQuery = ({ tableSchema, loading }: ITableConsumerRenderProps) => { 29 | const { children, ...rest } = this.props; 30 | 31 | if (!tableSchema && !loading) { 32 | throw new SDKError(ERROR_CODES.TABLE_NOT_FOUND, PACKAGES.CRUD, `Table doesn't find`); 33 | } 34 | 35 | if (!tableSchema) { 36 | return null; 37 | } 38 | 39 | return ( 40 | 41 | {(mutateFunction, mutateResult) => 42 | children(mutateFunction, { 43 | mutateResult, 44 | tableSchema, 45 | }) 46 | } 47 | 48 | ); 49 | }; 50 | public render() { 51 | const { tableId } = this.props; 52 | 53 | return {this.renderQuery}; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/react/crud/src/RecordUpdate.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { TableConsumer, ITableConsumerRenderProps } from '@8base/react-table-schema-provider'; 3 | import { MutationFunction, MutationResult } from '@apollo/client'; 4 | import { TableSchema, SDKError, ERROR_CODES, PACKAGES } from '@8base/utils'; 5 | 6 | import { RecordCrud } from './RecordCrud'; 7 | 8 | /** Results of the record update queries and mutation */ 9 | interface IChildrenPropObject { 10 | tableSchema: TableSchema | null; 11 | mutateResult: MutationResult; 12 | } 13 | 14 | type RecordUpdateProps = { 15 | tableId?: string; 16 | recordId: string; 17 | includeColumns?: string[]; 18 | children: (mutateFunction: MutationFunction, result: IChildrenPropObject) => JSX.Element; 19 | }; 20 | 21 | /** 22 | * Component for updating the record of the table 23 | * 24 | * @prop {string} tableId - Id of the table 25 | * @prop {string} recordId - Id of the record 26 | * @prop {(Function, ChildrenPropObject) => React.ReactNode} children - Render prop with result of the queries 27 | */ 28 | export class RecordUpdate extends Component { 29 | public renderQuery = ({ tableSchema, loading }: ITableConsumerRenderProps) => { 30 | const { tableId, children, recordId, includeColumns, ...rest } = this.props; 31 | 32 | if (!tableSchema && !loading) { 33 | throw new SDKError(ERROR_CODES.TABLE_NOT_FOUND, PACKAGES.CRUD, `Table doesn't find`); 34 | } 35 | 36 | if (!tableSchema) { 37 | return null; 38 | } 39 | 40 | return ( 41 | 42 | {(mutateFunction, mutateResult) => 43 | children(mutateFunction, { 44 | mutateResult, 45 | tableSchema, 46 | }) 47 | } 48 | 49 | ); 50 | }; 51 | 52 | public render() { 53 | const { tableId } = this.props; 54 | 55 | return {this.renderQuery}; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/react/crud/src/fragments.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const NumberFieldTypeAttributes = gql` 4 | fragment NumberFieldTypeAttributes on NumberFieldTypeAttributes { 5 | format 6 | precision 7 | currency 8 | minValue 9 | maxValue 10 | } 11 | `; 12 | 13 | export const TextFieldTypeAttributes = gql` 14 | fragment TextFieldTypeAttributes on TextFieldTypeAttributes { 15 | format 16 | fieldSize 17 | } 18 | `; 19 | 20 | export const FileFieldTypeAttributes = gql` 21 | fragment FileFieldTypeAttributes on FileFieldTypeAttributes { 22 | format 23 | maxSize 24 | typeRestrictions 25 | } 26 | `; 27 | 28 | export const DateFieldTypeAttributes = gql` 29 | fragment DateFieldTypeAttributes on DateFieldTypeAttributes { 30 | format 31 | } 32 | `; 33 | 34 | export const SwitchFieldTypeAttributes = gql` 35 | fragment SwitchFieldTypeAttributes on SwitchFieldTypeAttributes { 36 | format 37 | listOptions 38 | } 39 | `; 40 | 41 | export const TableFieldFragment = gql` 42 | fragment TableFieldFragment on TableField { 43 | id 44 | name 45 | displayName 46 | description 47 | fieldType 48 | fieldTypeAttributes { 49 | id 50 | ...TextFieldTypeAttributes 51 | ...NumberFieldTypeAttributes 52 | ...FileFieldTypeAttributes 53 | ...DateFieldTypeAttributes 54 | ...SwitchFieldTypeAttributes 55 | } 56 | isList 57 | isRequired 58 | isUnique 59 | defaultValue 60 | isSystem 61 | isMeta 62 | relation { 63 | id 64 | refFieldName 65 | refFieldDisplayName 66 | relationTableName 67 | relationFieldName 68 | refTable { 69 | id 70 | name 71 | displayName 72 | } 73 | refFieldIsList 74 | refFieldIsRequired 75 | } 76 | } 77 | 78 | ${DateFieldTypeAttributes} 79 | ${TextFieldTypeAttributes} 80 | ${NumberFieldTypeAttributes} 81 | ${FileFieldTypeAttributes} 82 | ${SwitchFieldTypeAttributes} 83 | `; 84 | 85 | export const TableFragment = gql` 86 | fragment TableFragment on Table { 87 | id 88 | name 89 | displayName 90 | isSystem 91 | fields { 92 | ...TableFieldFragment 93 | } 94 | } 95 | 96 | ${TableFieldFragment} 97 | `; 98 | -------------------------------------------------------------------------------- /packages/react/crud/src/index.ts: -------------------------------------------------------------------------------- 1 | export { RecordsList } from './RecordsList'; 2 | export { RecordCreate } from './RecordCreate'; 3 | export { RecordCreateMany } from './RecordCreateMany'; 4 | export { RecordUpdate } from './RecordUpdate'; 5 | export { RecordDelete } from './RecordDelete'; 6 | export { RecordData } from './RecordData'; 7 | -------------------------------------------------------------------------------- /packages/react/crud/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/file-input/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/file-input/README.md: -------------------------------------------------------------------------------- 1 | # 8base File Input 2 | 3 | File input integrated with Filestack and 8base 4 | -------------------------------------------------------------------------------- /packages/react/file-input/src/index.ts: -------------------------------------------------------------------------------- 1 | export { FileInput } from './FileInput'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/react/file-input/src/types.ts: -------------------------------------------------------------------------------- 1 | import { FetchPolicy } from '@apollo/client'; 2 | 3 | export type FileValue = { 4 | fileId: string; 5 | filename: string; 6 | id?: string; 7 | downloadUrl?: string; 8 | mimetype?: string; 9 | }; 10 | 11 | export type FileInputValue = FileValue | FileValue[]; 12 | 13 | export type OriginalFileInputValue = File | File[]; 14 | 15 | export type FileInputProps = { 16 | onChange?: (value: FileInputValue, originalFile: OriginalFileInputValue) => void; 17 | children: (args: { 18 | pick: (options: {}) => Promise; 19 | value: FileInputValue | null; 20 | originalFile: OriginalFileInputValue | null; 21 | error: object | null; 22 | }) => React.ReactNode; 23 | public?: boolean; 24 | fetchPolicy?: FetchPolicy; 25 | maxFiles?: number; 26 | sessionCache?: boolean; 27 | onUploadDone?: (value: FileInputValue, originalFile?: OriginalFileInputValue) => Promise; 28 | value?: FileInputValue | null; 29 | }; 30 | 31 | export type FileInputState = { 32 | path: string | null; 33 | error: object | null; 34 | value: FileInputValue | null; 35 | originalFile: OriginalFileInputValue | null; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/react/file-input/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/forms/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/forms/src/Field.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import * as R from 'ramda'; 3 | import { Field as FinalField, FieldProps } from 'react-final-form'; 4 | import { createValidate } from '@8base/validate'; 5 | import { tableSelectors } from '@8base/utils'; 6 | 7 | import { FormContext } from './FormContext'; 8 | import { getFieldSchemaName } from './utils'; 9 | 10 | // @ts-ignore 11 | const hackMultiple = (component) => ({ tempMultiple, ...props }) => 12 | React.createElement(component, { 13 | ...props, 14 | multiple: tempMultiple, 15 | }); 16 | 17 | /** 18 | * `Field` wrapper based on `Field` from the [`react-final-form`](https://github.com/final-form/react-final-form). That accept [`FieldProps`](https://github.com/final-form/react-final-form#fieldprops) props. 19 | */ 20 | const Field = (props: FieldProps) => { 21 | const { tableSchema } = useContext(FormContext); 22 | 23 | if (tableSchema) { 24 | const fieldSchema = tableSelectors.getFieldByName(tableSchema, getFieldSchemaName(props.name)); 25 | 26 | if (fieldSchema) { 27 | const fieldValidate = createValidate(fieldSchema); 28 | 29 | // Combine validation functions if needed 30 | if (typeof props.validate === 'function') { 31 | const { validate } = props; 32 | // @ts-ignore 33 | props = R.assoc('validate', (...args: any) => validate(...args, fieldValidate), props); 34 | } else { 35 | props = R.assoc('validate', fieldValidate, props); 36 | } 37 | 38 | // Provide field schema to non basic fields 39 | if (!R.propIs(String, 'component', props)) { 40 | props = R.assoc('fieldSchema', fieldSchema, props); 41 | } 42 | } 43 | } 44 | 45 | // Temp fix, waiting for update https://github.com/final-form/react-final-form/releases/tag/v6.3.1 46 | if (typeof props.component === 'function' && props.multiple) { 47 | props = R.assoc('tempMultiple', props.multiple, props); 48 | props = R.assoc('component', hackMultiple(props.component), props); 49 | } 50 | 51 | return ; 52 | }; 53 | 54 | export { Field }; 55 | -------------------------------------------------------------------------------- /packages/react/forms/src/FieldArray.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import * as R from 'ramda'; 3 | import { FieldArray as FinalFieldArray, FieldArrayProps } from 'react-final-form-arrays'; 4 | import { tableSelectors } from '@8base/utils'; 5 | 6 | import { FormContext } from './FormContext'; 7 | import { getFieldSchemaName } from './utils'; 8 | 9 | /** 10 | * `FieldArray` wrapper based on `FieldArray` from the [`react-final-form-arrays`](https://github.com/final-form/react-final-form-arrays). It accepts [`FieldArrayProps`](https://github.com/final-form/react-final-form-arrays#fieldarrayprops) props. 11 | */ 12 | const FieldArray = (props: FieldArrayProps) => { 13 | const { tableSchema } = useContext(FormContext); 14 | 15 | if (tableSchema) { 16 | const fieldSchema = tableSelectors.getFieldByName(tableSchema, getFieldSchemaName(props.name)); 17 | 18 | if (fieldSchema) { 19 | props = { 20 | fieldSchema, 21 | isEqual: props.isEqual || R.equals, 22 | ...props, 23 | }; 24 | } 25 | } 26 | 27 | return ; 28 | }; 29 | 30 | export { FieldArray }; 31 | -------------------------------------------------------------------------------- /packages/react/forms/src/Fieldset.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import * as R from 'ramda'; 3 | import { tablesListSelectors, TableSchema } from '@8base/utils'; 4 | import { TableSchemaContext } from '@8base/react-table-schema-provider'; 5 | 6 | import { FormContext } from './FormContext'; 7 | import { renderComponent } from './utils'; 8 | import { FieldsetProps } from './types'; 9 | 10 | /** 11 | * `Fieldset` passes relation table schema to the children fields. 12 | * @prop {string} [tableSchemaName] - The name of the 8base API table schema. Worked only if you provide schema by `SchemaContext`. 13 | */ 14 | const Fieldset = ({ tableSchemaName, ...props }: FieldsetProps) => { 15 | const { appName } = useContext(FormContext); 16 | const { tablesList } = useContext(TableSchemaContext); 17 | 18 | let tableSchema: TableSchema | void; 19 | 20 | if (tableSchemaName && tablesList) { 21 | tableSchema = tablesListSelectors.getTableByName(tablesList, tableSchemaName, appName); 22 | } 23 | 24 | props = R.assoc('tableSchema', tableSchema, props); 25 | 26 | return {renderComponent(props)}; 27 | }; 28 | 29 | export { Fieldset }; 30 | -------------------------------------------------------------------------------- /packages/react/forms/src/FormContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormContextValue } from './types'; 3 | 4 | const FormContext = React.createContext({}); 5 | 6 | export { FormContext }; 7 | -------------------------------------------------------------------------------- /packages/react/forms/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Form } from './Form'; 2 | export { Field } from './Field'; 3 | export { Fieldset } from './Fieldset'; 4 | export { FieldArray } from './FieldArray'; 5 | export { FormContext } from './FormContext'; 6 | export { FormSpy } from 'react-final-form'; 7 | export { FORM_ERROR } from 'final-form'; 8 | -------------------------------------------------------------------------------- /packages/react/forms/src/types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormApi } from 'final-form'; 3 | import { FormProps as FinalFormProps, FieldProps as FinalFieldProps } from 'react-final-form'; 4 | import { FieldType, TableSchema, FieldSchema, Schema } from '@8base/utils'; 5 | 6 | type RenderableProps = { 7 | component?: React.ComponentType; 8 | children?: ((props: object) => React.ReactNode) | React.ReactNode; 9 | render?: (props: object) => React.ReactNode; 10 | }; 11 | 12 | type FormContextValue = { 13 | tableSchema?: TableSchema | void; 14 | appName?: string; 15 | loading?: boolean; 16 | }; 17 | 18 | type FormProps = { 19 | tableSchemaName?: string; 20 | appName?: string; 21 | type?: 'CREATE' | 'UPDATE'; 22 | ignoreNonTableFields?: boolean; 23 | ignorePristineValues?: boolean; 24 | formatRelationToIds?: boolean; 25 | permissions?: any; 26 | onSuccess?: (result: any, form: FormApi) => void; 27 | beforeFormatDataForMutation?: (formValues: FormValues) => T_OUT; 28 | afterFormatDataForMutation?: (data: T_IN) => T_OUT; 29 | beforeFormatQueryData?: (data: T_IN) => T_OUT; 30 | afterFormatQueryData?: (data: T) => FormValues; 31 | } & FinalFormProps; 32 | 33 | type FieldsetProps = { 34 | tableSchemaName?: string; 35 | } & RenderableProps; 36 | 37 | type FieldProps = FinalFieldProps; 38 | 39 | export { 40 | FieldProps, 41 | FieldSchema, 42 | FieldsetProps, 43 | FieldType, 44 | FormContextValue, 45 | FormProps, 46 | Schema, 47 | TableSchema, 48 | RenderableProps, 49 | }; 50 | -------------------------------------------------------------------------------- /packages/react/forms/src/utils/getFieldSchemaName.ts: -------------------------------------------------------------------------------- 1 | const getFieldSchemaName = (name: string = ''): string => { 2 | let clearedName = name.replace(/\[\d+\]/g, ''); 3 | 4 | if (/\.?([^.]+)$/.test(clearedName)) { 5 | clearedName = RegExp.$1; 6 | } 7 | 8 | return clearedName; 9 | }; 10 | 11 | export { getFieldSchemaName }; 12 | -------------------------------------------------------------------------------- /packages/react/forms/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { getFieldSchemaName } from './getFieldSchemaName'; 2 | export * from './log'; 3 | export { renderComponent } from './renderComponent'; 4 | -------------------------------------------------------------------------------- /packages/react/forms/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const executeIfNotProduction = (func: () => void) => { 2 | if (process.env.NODE_ENV !== 'production') { 3 | func(); 4 | } 5 | }; 6 | 7 | const logError = (message: string): void => { 8 | executeIfNotProduction(() => { 9 | console.error(`Error: ${message}`); // tslint:disable-line 10 | }); 11 | }; 12 | 13 | const logWarning = (message: string): void => { 14 | executeIfNotProduction(() => { 15 | console.warn(`Warning: ${message}`); // tslint:disable-line 16 | }); 17 | }; 18 | 19 | export { logError, logWarning }; 20 | -------------------------------------------------------------------------------- /packages/react/forms/src/utils/renderComponent.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { logError } from './log'; 4 | import { RenderableProps } from '../types'; 5 | 6 | const renderComponent = (props: RenderableProps) => { 7 | const { render, children, component, ...rest } = props; 8 | 9 | let rendered = null; 10 | 11 | if (component) { 12 | rendered = React.createElement(component, { ...rest }, children); 13 | } 14 | 15 | if (render) { 16 | rendered = render({ ...rest, children }); 17 | } 18 | 19 | if (typeof children === 'function') { 20 | rendered = children(rest); 21 | } else if (children) { 22 | rendered = children; 23 | } else { 24 | logError('must specify either a render prop, a render function as children, or a component prop.'); 25 | } 26 | 27 | return rendered; 28 | }; 29 | 30 | export { renderComponent }; 31 | -------------------------------------------------------------------------------- /packages/react/forms/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | globals: 2 | mockRequest: true 3 | submitForm: true -------------------------------------------------------------------------------- /packages/react/forms/test/__tests__/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`As a developer, while I implementet a form, Form should be rendered. 1`] = ` 4 |
7 | 14 | 21 | 28 | 35 |
36 | `; 37 | -------------------------------------------------------------------------------- /packages/react/forms/test/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import * as TestUtils from 'react-dom/test-utils'; 2 | 3 | global.sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 4 | 5 | global.submitForm = (form) => { 6 | const submitButton = TestUtils.findRenderedDOMComponentWithTag(form, 'button'); 7 | 8 | TestUtils.Simulate.submit(submitButton); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/react/forms/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": [ 4 | "./**/*" 5 | ] 6 | } -------------------------------------------------------------------------------- /packages/react/forms/test/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | 3 | declare namespace NodeJS { 4 | export interface Global { 5 | sleep: (ms: number) => Promise; 6 | submitForm: (form: any) => any; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/react/forms/test/utils/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8base/sdk/0bc31033c39427e80a0bec0d3567c0806c73c5cc/packages/react/forms/test/utils/index.ts -------------------------------------------------------------------------------- /packages/react/forms/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/IfAllowed.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import * as R from 'ramda'; 3 | 4 | import { PermissionsContext } from './PermissionsContext'; 5 | import { isAllowed } from './isAllowed'; 6 | import { PermissionsContextValue } from './types'; 7 | 8 | type IfAllowedProps = { 9 | permissions: string[][]; 10 | children: React.ReactNode; 11 | }; 12 | 13 | const IfAllowed = ({ children, permissions }: IfAllowedProps) => { 14 | const context: PermissionsContextValue = useContext(PermissionsContext); 15 | 16 | let allowed = true; 17 | let result; 18 | 19 | result = permissions.map(([type, resource, permission]) => ({ 20 | allowed: isAllowed( 21 | { 22 | permission, 23 | resource, 24 | type, 25 | }, 26 | context.permissions, 27 | ), 28 | fields: R.pathOr({}, [type, resource, 'permission', permission, 'fields'], context.permissions), 29 | })); 30 | 31 | allowed = R.all(R.propEq('allowed', true), result); 32 | 33 | if (typeof children === 'function') { 34 | return children(allowed, result); 35 | } 36 | 37 | return allowed ? children : null; 38 | }; 39 | 40 | export { IfAllowed }; 41 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/IfAllowedRoles.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useAllowedRoles } from './useAllowedRoles'; 4 | 5 | type IfAllowedRolesProps = { 6 | roles: string[]; 7 | children: React.ReactNode; 8 | }; 9 | 10 | const IfAllowedRoles = ({ children, roles = [] }: IfAllowedRolesProps) => { 11 | const allowed = useAllowedRoles(roles); 12 | 13 | if (typeof children === 'function') { 14 | return children(allowed); 15 | } 16 | 17 | return allowed ? children : null; 18 | }; 19 | 20 | export { IfAllowedRoles }; 21 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/PermissionsContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PermissionsContextValue } from './types'; 3 | 4 | const PermissionsContext = React.createContext({ 5 | permissions: {}, 6 | roles: [], 7 | }); 8 | 9 | export { PermissionsContext }; 10 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/index.ts: -------------------------------------------------------------------------------- 1 | export { IfAllowed } from './IfAllowed'; 2 | export { isAllowed } from './isAllowed'; 3 | export { IfAllowedRoles } from './IfAllowedRoles'; 4 | export { useAllowedRoles } from './useAllowedRoles'; 5 | export { PermissionsContext } from './PermissionsContext'; 6 | export { PermissionsProvider } from './PermissionsProvider'; 7 | export { withPermissions } from './withPermissions'; 8 | export { usePermissions } from './usePermissions'; 9 | export * from './types'; 10 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/isAllowed.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import { TransformedPermissions } from './types'; 3 | 4 | type IsAllowedArgs = { 5 | resource?: string; 6 | type: string; 7 | permission?: string; 8 | field?: any; 9 | }; 10 | 11 | export const isAllowed = ( 12 | { resource, type, permission, field }: IsAllowedArgs, 13 | permissions: TransformedPermissions, 14 | ) => { 15 | if (!resource || !permission) { 16 | return false; 17 | } 18 | 19 | const path: string[] = [type, resource, 'permission', permission, 'allow']; 20 | 21 | if (field) { 22 | path.pop(); 23 | path.push('fields', field); 24 | } 25 | 26 | return R.pathOr(true, path, permissions); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/types.ts: -------------------------------------------------------------------------------- 1 | export type ApolloDataPermissionOperationTypes = 'create' | 'read' | 'update' | 'delete'; 2 | 3 | export type ApolloDataPermissionOperationFilter = { [key: string]: any }; 4 | 5 | export type ApolloDataPermissionOperationFields = { [key: string]: boolean }; 6 | 7 | export type ApolloDataPermissionOperation = { 8 | allow: boolean; 9 | filter: ApolloDataPermissionOperationFilter; 10 | fields: ApolloDataPermissionOperationFields; 11 | }; 12 | 13 | export type ApolloDataPermission = Record; 14 | 15 | export type ApolloPermission = { 16 | appId: 'system' | 'default'; 17 | resourceType: string; 18 | resource: string; 19 | permission: ApolloDataPermission; 20 | }; 21 | 22 | export type TransformedPermissions = { 23 | [key: string]: { 24 | [key: string]: ApolloPermission; 25 | }; 26 | }; 27 | 28 | export type RequestPermissions = { 29 | uset: { 30 | permissions: { 31 | items: ApolloPermission[]; 32 | }; 33 | }; 34 | }; 35 | 36 | export type PermissionsContextValue = { 37 | roles: string[]; 38 | permissions: TransformedPermissions; 39 | }; 40 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/useAllowedRoles.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import * as R from 'ramda'; 3 | 4 | import { PermissionsContext } from './PermissionsContext'; 5 | import { PermissionsContextValue } from './types'; 6 | 7 | export const useAllowedRoles = (roles: string[]) => { 8 | const context: PermissionsContextValue = useContext(PermissionsContext); 9 | 10 | const allowed: boolean = R.intersection(roles, context.roles).length !== 0; 11 | 12 | return allowed; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/usePermissions.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { PermissionsContext } from './PermissionsContext'; 4 | 5 | function usePermissions() { 6 | const permissions = useContext(PermissionsContext); 7 | 8 | return permissions; 9 | } 10 | 11 | export { usePermissions }; 12 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import { RequestPermissions, TransformedPermissions, ApolloPermission } from './types'; 3 | 4 | const PERMISSIONS_PATH = { 5 | teamMember: ['system', 'environmentMember'], 6 | user: ['user'], 7 | }; 8 | 9 | export const getPermissions = (data: RequestPermissions, type: 'teamMember' | 'user'): TransformedPermissions => 10 | R.pipe( 11 | R.pathOr([], PERMISSIONS_PATH[type]), 12 | R.pipe( 13 | R.pathOr([], ['permissions', 'items']), 14 | R.groupBy(R.prop('resourceType')), 15 | R.mapObjIndexed(R.indexBy(R.prop('resource'))), 16 | ), 17 | )(data); 18 | 19 | export const getRoles = (data: RequestPermissions, type: 'teamMember' | 'user'): string[] => 20 | R.pipe( 21 | R.pathOr([], [...PERMISSIONS_PATH[type], 'roles', 'items']), 22 | R.map(({ name }) => name), 23 | )(data); 24 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/src/withPermissions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Subtract } from 'utility-types'; 3 | import { getDisplayName } from '@8base/react-utils'; 4 | 5 | import { PermissionsContext } from './PermissionsContext'; 6 | import { TransformedPermissions } from './types'; 7 | 8 | export type WithPermissionsProps = { 9 | permissions: TransformedPermissions; 10 | }; 11 | 12 | const withPermissions = (WrappedComponent: React.ComponentType) => 13 | class WithPermissions extends React.Component> { 14 | public static displayName = `withPermissions(${getDisplayName(WrappedComponent)})`; 15 | 16 | public render() { 17 | return ( 18 | 19 | {({ permissions }) => } 20 | 21 | ); 22 | } 23 | }; 24 | 25 | export { withPermissions }; 26 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/test/__tests__/IfAllowedRoles.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as renderer from 'react-test-renderer'; 3 | 4 | import { PermissionsProvider, IfAllowedRoles } from '../../src'; 5 | 6 | const mockPermissionsData = { 7 | system: { 8 | environmentMember: { 9 | roles: { 10 | items: [ 11 | { 12 | id: '1', 13 | name: 'Admin', 14 | }, 15 | { 16 | id: '2', 17 | name: 'Support', 18 | }, 19 | ], 20 | }, 21 | }, 22 | }, 23 | }; 24 | 25 | jest.mock('@8base/react-auth', () => ({ 26 | withAuth: (Component: any) => (props: any) => ( 27 | 28 | ), 29 | })); 30 | 31 | jest.mock('@apollo/client/react/components', () => ({ 32 | Query: ({ children }: any) => children({ data: mockPermissionsData, loading: false }), 33 | })); 34 | 35 | it('As a developer, I can use `IfAllowedRoles` component for conditional rendering based roles.', () => { 36 | const tree = renderer.create( 37 | 38 | {() => ( 39 | 40 | Allowed 41 | Not Allowed 42 | Allowed 43 | 44 | )} 45 | , 46 | ); 47 | 48 | expect(tree.toJSON()).toEqual(['Allowed', 'Allowed']); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/test/__tests__/useAllowedRoles.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as renderer from 'react-test-renderer'; 3 | 4 | import { PermissionsProvider, useAllowedRoles, IfAllowedRoles } from '../../src'; 5 | 6 | const mockPermissionsData = { 7 | user: { 8 | roles: { 9 | items: [ 10 | { 11 | id: '1', 12 | name: 'Admin', 13 | }, 14 | { 15 | id: '2', 16 | name: 'Support', 17 | }, 18 | ], 19 | }, 20 | }, 21 | }; 22 | 23 | jest.mock('@8base/react-auth', () => ({ 24 | withAuth: (Component: any) => (props: any) => ( 25 | 26 | ), 27 | })); 28 | 29 | jest.mock('@apollo/client/react/components', () => ({ 30 | Query: ({ children }: any) => children({ data: mockPermissionsData, loading: false }), 31 | })); 32 | 33 | it('As a developer, I can use `useAllowedRoles` hook for conditional rendering based roles.', () => { 34 | const Test = () => { 35 | const adminRole = useAllowedRoles(['Admin']); 36 | const otherRole = useAllowedRoles(['Other role']); 37 | const bothRoles = useAllowedRoles(['Admin', 'Other role']); 38 | 39 | return
{`${adminRole} ${otherRole} ${bothRoles}`}
; 40 | }; 41 | 42 | const tree = renderer.create({() => }); 43 | 44 | expect(tree.toJSON()).toMatchInlineSnapshot(` 45 |
46 | true false true 47 |
48 | `); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/react/permissions-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | babel.config.js 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/README.md: -------------------------------------------------------------------------------- 1 | # 8base Table Schema Provider 2 | 3 | The Table Schema Provider fetches 8base table schemas and provides it via React Context. 4 | 5 | ## TableSchemaProvider 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [TableSchemaProvider](#tableschemaprovider) 12 | - [Properties](#properties) 13 | 14 | ### TableSchemaProvider 15 | 16 | **Extends React.Component** 17 | 18 | Provider for 8base table schemas 19 | 20 | #### Properties 21 | 22 | - `children` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Children of the provider. Could be either react node or function with loading state. 23 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/TableConsumer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TableSchema, tablesListSelectors } from '@8base/utils'; 3 | import { TableSchemaContext, ITableSchemaContext } from './TableSchemaContext'; 4 | 5 | export type ITableConsumerRenderProps = 6 | | { 7 | tableSchema: TableSchema | null; 8 | loading: false; 9 | } 10 | | { 11 | tableSchema: TableSchema | void; 12 | loading: true; 13 | }; 14 | 15 | export interface ITableConsumerProps { 16 | id?: string; 17 | app?: string; 18 | name?: string; 19 | children: (args: ITableConsumerRenderProps) => React.ReactNode; 20 | } 21 | 22 | class TableConsumer extends React.Component { 23 | public renderWithSchemaResponse = ({ tablesList, loading }: ITableSchemaContext) => { 24 | const { id, name, app, children } = this.props; 25 | 26 | let tableSchema: TableSchema | void | null; 27 | 28 | if (id) { 29 | tableSchema = tablesListSelectors.getTableById(tablesList, id); 30 | } else if (name) { 31 | tableSchema = tablesListSelectors.getTableByName(tablesList, name, app); 32 | } 33 | 34 | return loading 35 | ? children({ tableSchema: tableSchema || undefined, loading }) 36 | : children({ tableSchema: tableSchema || null, loading }); 37 | }; 38 | 39 | public render() { 40 | return {this.renderWithSchemaResponse}; 41 | } 42 | } 43 | 44 | export { TableConsumer }; 45 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/TableSchemaContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TableSchema, Application } from '@8base/utils'; 3 | import { ApolloError } from '@apollo/client'; 4 | 5 | export interface ITableSchemaContext { 6 | tablesList: TableSchema[]; 7 | applicationsList: Application[]; 8 | loading: boolean; 9 | error?: ApolloError; 10 | } 11 | 12 | const TableSchemaContext = React.createContext({ 13 | tablesList: [], 14 | applicationsList: [], 15 | loading: false, 16 | }); 17 | 18 | export { TableSchemaContext }; 19 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/index.ts: -------------------------------------------------------------------------------- 1 | export { TableSchemaContext, ITableSchemaContext } from './TableSchemaContext'; 2 | export { TABLES_SCHEMA_QUERY, TABLE_FRAGMENT, TABLE_FIELD_FRAGMENT, TableSchemaProvider } from './TableSchemaProvider'; 3 | export { TableConsumer, ITableConsumerRenderProps } from './TableConsumer'; 4 | export { withTablesList, WithTablesListProps } from './withTablesList'; 5 | export { withApplicationsList } from './withApplicationsList'; 6 | export { Features } from './Features'; 7 | export { useTableSchema } from './useTableSchema'; 8 | export { useTablesList } from './useTablesList'; 9 | export { useApplicationsList } from './useApplicationsList'; 10 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/useApplicationsList.ts: -------------------------------------------------------------------------------- 1 | import { useTableSchema } from './useTableSchema'; 2 | 3 | function useApplicationsList() { 4 | const { applicationsList, loading, error } = useTableSchema(); 5 | 6 | return { 7 | applicationsList, 8 | loading, 9 | error, 10 | }; 11 | } 12 | 13 | export { useApplicationsList }; 14 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/useTableSchema.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { TableSchemaContext } from './TableSchemaContext'; 4 | 5 | function useTableSchema() { 6 | const { tablesList, applicationsList, loading, error } = useContext(TableSchemaContext); 7 | 8 | return { 9 | tablesList, 10 | applicationsList, 11 | loading, 12 | error, 13 | }; 14 | } 15 | 16 | export { useTableSchema }; 17 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/useTablesList.ts: -------------------------------------------------------------------------------- 1 | import { useTableSchema } from './useTableSchema'; 2 | 3 | function useTablesList() { 4 | const { tablesList, loading, error } = useTableSchema(); 5 | 6 | return { 7 | tablesList, 8 | loading, 9 | error, 10 | }; 11 | } 12 | 13 | export { useTablesList }; 14 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/withApplicationsList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Application } from '@8base/utils'; 3 | import { getDisplayName } from '@8base/react-utils'; 4 | import { Subtract } from 'utility-types'; 5 | 6 | import { TableSchemaContext, ITableSchemaContext } from './TableSchemaContext'; 7 | 8 | export type WithApplicationsListProps = { 9 | applicationsList: Application[]; 10 | }; 11 | 12 | const withApplicationsList = (WrappedComponent: React.ComponentType) => { 13 | return class WithApplicationsList extends React.Component> { 14 | public static displayName = `withApplicationsList(${getDisplayName(WrappedComponent)})`; 15 | 16 | public renderContent = ({ applicationsList }: ITableSchemaContext) => ( 17 | 18 | ); 19 | 20 | public render() { 21 | return {this.renderContent}; 22 | } 23 | }; 24 | }; 25 | 26 | export { withApplicationsList }; 27 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/src/withTablesList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TableSchema } from '@8base/utils'; 3 | import { getDisplayName } from '@8base/react-utils'; 4 | import { Subtract } from 'utility-types'; 5 | 6 | import { TableSchemaContext, ITableSchemaContext } from './TableSchemaContext'; 7 | 8 | export type WithTablesListProps = { 9 | tablesList: TableSchema[]; 10 | }; 11 | 12 | const withTablesList = (WrappedComponent: React.ComponentType) => { 13 | return class WithTablesList extends React.Component> { 14 | public static displayName = `withTablesList(${getDisplayName(WrappedComponent)})`; 15 | 16 | public renderContent = ({ tablesList }: ITableSchemaContext) => ( 17 | 18 | ); 19 | 20 | public render() { 21 | return {this.renderContent}; 22 | } 23 | }; 24 | }; 25 | 26 | export { withTablesList }; 27 | -------------------------------------------------------------------------------- /packages/react/table-schema-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "diagnostics": true, 4 | "module": "esnext", 5 | "target": "es2016", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "lib": [ 15 | "es2016", 16 | "esnext.asynciterable", 17 | "es2016.array.include", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/react/utils/.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /packages/react/utils/README.md: -------------------------------------------------------------------------------- 1 | # 8base Utils 2 | 3 | This library contains utils used by the other 8base service packages. 4 | 5 | ## API 6 | 7 | 8 | 9 | #### Table of Contents 10 | 11 | - [formatDataForMutation](#formatdataformutation) 12 | - [Parameters](#parameters) 13 | 14 | ### formatDataForMutation 15 | 16 | Formats entity data for create or update mutation based on passed schema. 17 | 18 | #### Parameters 19 | 20 | - `type` **MutationType** The type of the mutation. 21 | - `tableName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the table from the 8base API. 22 | - `data` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The entity data for format. 23 | - `schema` **Schema** The schema of the used tables from the 8base API. 24 | -------------------------------------------------------------------------------- /packages/react/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@8base/react-utils", 3 | "version": "3.0.0-beta.3", 4 | "repository": "https://github.com/8base/sdk", 5 | "homepage": "https://github.com/8base/sdk/tree/master/packages/react/utils", 6 | "main": "dist/cjs/index.js", 7 | "types": "dist/mjs/index.d.ts", 8 | "module": "dist/mjs/index.js", 9 | "scripts": { 10 | "build": "../../../bin/build-package.sh", 11 | "watch": "../../../bin/watch-package.sh", 12 | "test": "NPM_ENV=test jest" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "^17.0.0", 16 | "typescript": "^4.1.3" 17 | }, 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /packages/react/utils/src/getDisplayName.ts: -------------------------------------------------------------------------------- 1 | export const getDisplayName = (WrappedComponent: React.ComponentType) => 2 | WrappedComponent.displayName || WrappedComponent.name || 'Component'; 3 | -------------------------------------------------------------------------------- /packages/react/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getDisplayName'; 2 | -------------------------------------------------------------------------------- /packages/react/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "ordered-imports": false, 8 | "ban-types": false, 9 | "interface-over-type-literal": false, 10 | "no-shadowed-variable": false, 11 | "object-literal-sort-keys": false 12 | }, 13 | "rulesDirectory": [] 14 | } 15 | --------------------------------------------------------------------------------