├── packages ├── acl │ ├── .gitignore │ ├── prettier.config.js │ ├── src │ │ ├── index.ts │ │ ├── modelDefault.ts │ │ ├── definitions.ts │ │ ├── utils.ts │ │ ├── rules.ts │ │ └── applyRules.ts │ ├── babel.config.js │ ├── __tests__ │ │ ├── matchingTypes.test.ts │ │ ├── prepare.ts │ │ └── modelField.test.ts │ └── package.json ├── type-wrap │ ├── .gitignore │ ├── tsconfig.json │ ├── babel.config.js │ ├── CHANGELOG.md │ └── package.json ├── schema-filter │ ├── .gitignore │ ├── prettier.config.js │ ├── tsconfig.json │ ├── babel.config.js │ ├── __tests__ │ │ └── defaultFields.test.ts │ ├── CHANGELOG.md │ ├── src │ │ ├── index.ts │ │ ├── utils.ts │ │ └── removeUnusedTypes.ts │ └── package.json ├── mongodb-executor │ ├── .gitignore │ ├── babel.config.js │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── package.json ├── example-now │ ├── .gitignore │ ├── .prettierrc │ ├── .babelrc │ ├── now.json │ ├── index.js │ └── package.json ├── example-server-sequelize │ ├── .gitignore │ ├── .prettierrc │ ├── nodemon.json │ ├── .env │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── model.ts │ │ ├── index.ts │ │ ├── models │ │ │ ├── user.model.ts │ │ │ └── post.model.ts │ │ └── db.ts │ └── package.json ├── core │ ├── src │ │ ├── execution │ │ │ ├── index.ts │ │ │ ├── context.ts │ │ │ ├── resultPromise │ │ │ │ ├── index.ts │ │ │ │ ├── transforms.ts │ │ │ │ ├── path.ts │ │ │ │ ├── groupBy.ts │ │ │ │ ├── indexBy.ts │ │ │ │ ├── distinct.ts │ │ │ │ ├── toDbRef.ts │ │ │ │ └── batch.ts │ │ │ ├── contexts │ │ │ │ ├── index.ts │ │ │ │ ├── fieldsSelection.ts │ │ │ │ ├── selector.ts │ │ │ │ ├── data.ts │ │ │ │ ├── fragment.ts │ │ │ │ ├── objectField.ts │ │ │ │ └── listValue.ts │ │ │ ├── utils.ts │ │ │ ├── operations │ │ │ │ ├── connectionOperation.ts │ │ │ │ ├── deleteOperation.ts │ │ │ │ ├── readOperation.ts │ │ │ │ ├── createOperation.ts │ │ │ │ ├── aggregateOperation.ts │ │ │ │ ├── deleteDbRefOperation.ts │ │ │ │ └── readDbRefOperation.ts │ │ │ ├── path.ts │ │ │ └── transaction.ts │ │ ├── schemaGeneration │ │ │ ├── common │ │ │ │ ├── utils.ts │ │ │ │ └── visitorHandlers.ts │ │ │ ├── federation │ │ │ │ ├── directives │ │ │ │ │ ├── federated.ts │ │ │ │ │ └── external.ts │ │ │ │ └── types │ │ │ │ │ ├── anyType.ts │ │ │ │ │ └── entityType.ts │ │ │ ├── model │ │ │ │ ├── directives │ │ │ │ │ ├── createdAt.ts │ │ │ │ │ ├── updatedAt.ts │ │ │ │ │ ├── timestamps.ts │ │ │ │ │ ├── id.ts │ │ │ │ │ ├── readonly.ts │ │ │ │ │ ├── default.ts │ │ │ │ │ ├── unique.ts │ │ │ │ │ ├── db.ts │ │ │ │ │ └── discriminator.ts │ │ │ │ ├── input │ │ │ │ │ ├── fieldFactories │ │ │ │ │ │ ├── utils.ts │ │ │ │ │ │ ├── create.ts │ │ │ │ │ │ ├── querySelectorComplex.ts │ │ │ │ │ │ ├── querySelector.ts │ │ │ │ │ │ └── update.ts │ │ │ │ │ ├── querySelectors │ │ │ │ │ │ ├── exists.ts │ │ │ │ │ │ ├── size.ts │ │ │ │ │ │ ├── not_size.ts │ │ │ │ │ │ ├── contains.ts │ │ │ │ │ │ ├── endsWith.ts │ │ │ │ │ │ ├── not.ts │ │ │ │ │ │ ├── startsWith.ts │ │ │ │ │ │ ├── gt.ts │ │ │ │ │ │ ├── lt.ts │ │ │ │ │ │ ├── gte.ts │ │ │ │ │ │ ├── lte.ts │ │ │ │ │ │ ├── some.ts │ │ │ │ │ │ ├── all.ts │ │ │ │ │ │ ├── exact.ts │ │ │ │ │ │ ├── not_in.ts │ │ │ │ │ │ └── in.ts │ │ │ │ │ └── inputTypes │ │ │ │ │ │ └── orderBy.ts │ │ │ │ ├── output │ │ │ │ │ ├── visitorHandlers.ts │ │ │ │ │ ├── fieldFactories │ │ │ │ │ │ ├── identity.ts │ │ │ │ │ │ ├── namedType.ts │ │ │ │ │ │ └── aggregateNumericFields.ts │ │ │ │ │ └── types │ │ │ │ │ │ └── connection.ts │ │ │ │ └── scalars │ │ │ │ │ ├── date.ts │ │ │ │ │ ├── JSON.ts │ │ │ │ │ └── objectID.ts │ │ │ ├── relations │ │ │ │ ├── directives │ │ │ │ │ ├── relation.ts │ │ │ │ │ └── extRelation.ts │ │ │ │ ├── fieldFactories │ │ │ │ │ ├── updateRelation.ts │ │ │ │ │ └── createRelation.ts │ │ │ │ └── visitorHandlers │ │ │ │ │ ├── abstract │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ ├── subdocuments │ │ │ │ ├── directives │ │ │ │ │ ├── subdocument.ts │ │ │ │ │ └── noArrayFilter.ts │ │ │ │ ├── inputTypes │ │ │ │ │ ├── updateOneNested.ts │ │ │ │ │ ├── createManyNested.ts │ │ │ │ │ └── createOneNested.ts │ │ │ │ └── fieldFactories │ │ │ │ │ └── createNested.ts │ │ │ ├── rootMethods │ │ │ │ ├── visitorHandlers │ │ │ │ │ └── attachDiscriminatorToOperationHandler.ts │ │ │ │ ├── createMutation.ts │ │ │ │ └── deleteOneMutation.ts │ │ │ └── externalRelations │ │ │ │ ├── inputTypes │ │ │ │ └── createOneRequiredRelationOutside.ts │ │ │ │ ├── fieldFactories │ │ │ │ ├── updateRelationOutside.ts │ │ │ │ └── createRelationOutside.ts │ │ │ │ └── directives │ │ │ │ └── relationOutside.ts │ │ ├── initialScheme.ts │ │ ├── serializer │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── sdlSyntaxException.ts │ │ ├── prepare │ │ │ ├── fillDbName.ts │ │ │ ├── defaultDirective.ts │ │ │ ├── createdAtDirective.ts │ │ │ ├── updatedAtDirective.ts │ │ │ ├── fieldFactories.ts │ │ │ ├── utils.ts │ │ │ ├── fillDiscriminators.ts │ │ │ └── fieldVisitorEvents.ts │ │ ├── appendField.ts │ │ ├── config │ │ │ ├── fieldFactories.ts │ │ │ ├── defaultConfig.ts │ │ │ └── typeFactories.ts │ │ ├── args │ │ │ ├── first.ts │ │ │ └── offset.ts │ │ ├── resolve.ts │ │ └── schemaInfo.ts │ ├── .gitignore │ ├── .travis.yml │ ├── .editorconfig │ ├── tsconfig.json │ ├── __tests__ │ │ ├── types │ │ │ ├── generateSchema.ts │ │ │ ├── default.test.ts │ │ │ ├── empty.test.ts │ │ │ ├── aggregation.test.ts │ │ │ └── where.test.ts │ │ ├── transaction │ │ │ ├── buildFederatedSchema.ts │ │ │ └── generateSchema.ts │ │ ├── utils │ │ │ └── prepareTransaction.ts │ │ └── integration-acl.test.ts │ └── babel.config.js ├── type-geojson │ ├── .npmignore │ ├── __tests__ │ │ └── model.ts │ ├── babel.config.js │ ├── src │ │ ├── create.ts │ │ ├── update.ts │ │ └── whereWithin.ts │ └── package.json ├── ra-data │ ├── README.md │ ├── src │ │ ├── constants │ │ │ ├── mutationsActions.ts │ │ │ └── mutations.ts │ │ ├── definitions.ts │ │ ├── utils │ │ │ ├── isList.ts │ │ │ ├── isRequired.ts │ │ │ ├── getFinalType.ts │ │ │ └── computeAddRemoveUpdate.ts │ │ └── introspectionOptions.ts │ ├── tsconfig.json │ ├── .gitignore │ ├── typings.d.ts │ ├── CHANGELOG.md │ ├── __tests__ │ │ ├── isList.test.ts │ │ ├── isRequired.test.ts │ │ ├── buildQuery.test.ts │ │ ├── getFinalType.test.ts │ │ ├── utils.ts │ │ └── introspectionResult.test.ts │ └── package.json ├── example-server │ ├── nodemon.json │ ├── README.md │ ├── tsconfig.json │ ├── src │ │ ├── roles │ │ │ └── anonymus.ts │ │ ├── db │ │ │ └── connection.ts │ │ ├── index.ts │ │ └── model.ts │ └── package.json ├── example-federation │ ├── nodemon.json │ ├── README.md │ ├── src │ │ ├── serviceB │ │ │ ├── model.ts │ │ │ ├── index.ts │ │ │ └── db │ │ │ │ └── connection.ts │ │ ├── serviceA │ │ │ ├── model.ts │ │ │ ├── index.ts │ │ │ └── db │ │ │ │ └── connection.ts │ │ ├── index.ts │ │ └── gateway │ │ │ └── index.ts │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── package.json ├── directive-inherit │ ├── babel.config.js │ ├── src │ │ └── index.js │ ├── CHANGELOG.md │ ├── package0.json │ └── package.json ├── directive-implements │ ├── babel.config.js │ ├── CHANGELOG.md │ ├── package.json │ └── src │ │ └── index.js ├── sequelize-executor │ ├── tsconfig.json │ ├── package.json │ └── CHANGELOG.md ├── ast-from-value │ ├── tsconfig.json │ ├── package.json │ └── CHANGELOG.md └── example-gateway │ ├── babel.config.js │ ├── default-server │ └── index.js │ ├── CHANGELOG.md │ ├── package.json │ └── src │ └── index.js ├── cover ├── cover.jpg └── cover.sketch ├── database ├── helpers │ └── index.ts ├── seeders │ └── 01-user.ts └── migrations │ └── 20210507120700-user.ts ├── .prettierrc ├── docker └── Dockerfile ├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── jest.config.js ├── lerna.json ├── tsconfig.migrations.json ├── .sequelizerc ├── babel.config.js ├── dbconfig.js ├── docker-compose.yaml ├── .eslintrc.json ├── README.md ├── .gitlab-ci.yml └── package.json /packages/acl/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /packages/type-wrap/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /packages/schema-filter/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /packages/mongodb-executor/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /packages/example-now/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /packages/core/src/execution/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contexts'; 2 | -------------------------------------------------------------------------------- /packages/type-geojson/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | __tests__ 3 | babel.config.js 4 | -------------------------------------------------------------------------------- /cover/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphexio/graphex/HEAD/cover/cover.jpg -------------------------------------------------------------------------------- /packages/ra-data/README.md: -------------------------------------------------------------------------------- 1 | Fork of https://github.com/dnhsoft/ra-data-prisma2 2 | -------------------------------------------------------------------------------- /cover/cover.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphexio/graphex/HEAD/cover/cover.sketch -------------------------------------------------------------------------------- /database/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export const isDryRun = () => process.env.MIGRATIONS_DRYRUN === 'true'; 2 | -------------------------------------------------------------------------------- /packages/example-now/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/common/utils.ts: -------------------------------------------------------------------------------- 1 | export { sameArguments } from '../../prepare/utils'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true, 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/acl/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | singleQuote: true, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | examples/example-lib 3 | 4 | node_modules/ 5 | 6 | \.DS_Store 7 | 8 | lib/ 9 | -------------------------------------------------------------------------------- /packages/core/src/execution/context.ts: -------------------------------------------------------------------------------- 1 | export class AMContext { 2 | toJSON() { 3 | return {}; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resultPromise'; 2 | export * from './transforms'; 3 | -------------------------------------------------------------------------------- /packages/example-server/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "js,ts", 4 | "exec": "ts-node src" 5 | } 6 | -------------------------------------------------------------------------------- /packages/schema-filter/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: "es5", 3 | singleQuote: true 4 | }; 5 | -------------------------------------------------------------------------------- /packages/example-federation/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "js,ts", 4 | "exec": "ts-node src" 5 | } 6 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.16.1 2 | 3 | WORKDIR /workspace/ 4 | 5 | RUN yarn add rimraf typescript sequelize-cli sequelize pg 6 | 7 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src", "../core/lib"], 3 | "ext": "js,ts", 4 | "exec": "ts-node src" 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | \.DS_Store 3 | 4 | node_modules/ 5 | distdb 6 | 7 | **/node_modules/ 8 | **/lib 9 | *.log 10 | .history 11 | coverage/ 12 | junit.xml -------------------------------------------------------------------------------- /packages/example-server-sequelize/.env: -------------------------------------------------------------------------------- 1 | DATABASE_NAME=mydbname 2 | DATABASE_HOST=localhost 3 | DATABASE_PORT=5432 4 | DATABASE_USER=mydbuser 5 | DATABASE_PASSWORD=donttellasoul -------------------------------------------------------------------------------- /packages/ra-data/src/constants/mutationsActions.ts: -------------------------------------------------------------------------------- 1 | export const PRISMA_NEW = 'new'; 2 | export const PRISMA_REMOVED = 'removed'; 3 | export const PRISMA_UPDATED = 'updated'; 4 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/federation/directives/federated.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDef = gql` 4 | directive @federated on OBJECT 5 | `; 6 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/federation/directives/external.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDef = gql` 4 | directive @external on FIELD_DEFINITION 5 | `; 6 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/createdAt.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDef = gql` 4 | directive @createdAt on FIELD_DEFINITION 5 | `; 6 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/updatedAt.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDef = gql` 4 | directive @updatedAt on FIELD_DEFINITION 5 | `; 6 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/common/visitorHandlers.ts: -------------------------------------------------------------------------------- 1 | export { 2 | defaultObjectFieldVisitorHandler, 3 | whereTypeVisitorHandler, 4 | } from '../model/input/inputTypes/visitorHandlers'; 5 | -------------------------------------------------------------------------------- /packages/example-server/README.md: -------------------------------------------------------------------------------- 1 | [![Edit graphex-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/graphexio/graphex/tree/master/packages/example-server) 2 | -------------------------------------------------------------------------------- /packages/example-federation/README.md: -------------------------------------------------------------------------------- 1 | [![Edit graphex-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/graphexio/graphex/tree/master/packages/example-server) 2 | -------------------------------------------------------------------------------- /packages/core/src/initialScheme.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | export default gql` 3 | type Query 4 | type Mutation 5 | type Cursor { 6 | first: Int! 7 | skip: Int! 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /packages/example-federation/src/serviceB/model.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | export default gql` 3 | type Api @model { 4 | id: ObjectID @id @unique @db(name: "_id") 5 | title: String 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // "editor.formatOnSave": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "debug.javascript.usePreview": true, 7 | "debug.node.autoAttach": "on" 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | branches: 6 | only: 7 | - master 8 | cache: 9 | directories: 10 | - node_modules 11 | install: 12 | - yarn install 13 | script: 14 | - yarn test -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './fieldsSelection'; 3 | export * from './fragment'; 4 | export * from './listValue'; 5 | export * from './objectField'; 6 | export * from './selector'; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('jest-config'); 2 | 3 | module.exports = { 4 | testEnvironment: 'node', 5 | testMatch: null, 6 | testRegex: '\\.test\\.(js|ts)$', 7 | testPathIgnorePatterns: ['/node_modules/', '/dist/'], 8 | }; 9 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "1.2.4", 6 | "npmClient": "yarn", 7 | "useWorkspaces": true, 8 | "command": { 9 | "publish": { 10 | "exact": true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": 3 | { 4 | "outDir": "./distdb", 5 | "declaration": false, 6 | "sourceMap": false, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["database"] 10 | } 11 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('dbconfig.js'), 5 | 'migrations-path': path.resolve('distdb','migrations'), 6 | 'seeders-path': path.resolve('distdb', 'seeders'), 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/src/execution/utils.ts: -------------------------------------------------------------------------------- 1 | import { AMContext } from './context'; 2 | import { AMOperation } from './operation'; 3 | 4 | export const isOperation = (item: AMContext): item is AMOperation => { 5 | return item instanceof AMOperation; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/directive-inherit/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/directive-implements/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/example-federation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "experimentalDecorators": true, 5 | "emitDecoratorMetadata": true, 6 | "moduleResolution": "node" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/example-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "experimentalDecorators": true, 5 | "emitDecoratorMetadata": true, 6 | "moduleResolution": "node" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/relations/directives/relation.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDef = gql` 4 | directive @relation( 5 | field: String = "_id" 6 | storeField: String = null 7 | ) on FIELD_DEFINITION 8 | `; 9 | -------------------------------------------------------------------------------- /packages/ra-data/src/constants/mutations.ts: -------------------------------------------------------------------------------- 1 | export const PRISMA_CONNECT = 'connect'; 2 | export const PRISMA_DISCONNECT = 'disconnect'; 3 | export const PRISMA_CREATE = 'create'; 4 | export const PRISMA_DELETE = 'delete'; 5 | export const PRISMA_UPDATE = 'update'; 6 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "experimentalDecorators": true, 5 | "emitDecoratorMetadata": true, 6 | "moduleResolution": "node" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/example-now/.babelrc: -------------------------------------------------------------------------------- 1 | //prettier-ignore 2 | { 3 | "presets": ["@babel/preset-env"], 4 | "plugins": [ 5 | ["import-graphql"],[ 6 | "@babel/transform-runtime", 7 | { 8 | "regenerator": true 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/sequelize-executor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["ES2019"], 5 | "moduleResolution": "node", 6 | "outDir": "lib", 7 | "declaration": true, 8 | "module": "commonjs" 9 | }, 10 | "include": ["src"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/fieldFactories/utils.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from 'util'; 2 | 3 | export const extractValue: (any) => any = input => Object.values(input)[0]; 4 | export const makeArray = input => { 5 | if (isArray(input)) return input; 6 | else return [input]; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/src/serializer/index.ts: -------------------------------------------------------------------------------- 1 | import prettyFormat from 'pretty-format'; 2 | 3 | export default { 4 | print(val: any) { 5 | return prettyFormat(val, { 6 | callToJSON: true, 7 | plugins: [], 8 | }); 9 | }, 10 | test() { 11 | return true; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/example-now/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "apollo-model-mongodb-example", 4 | "alias": "apollo-model-mongodb-example.now.sh", 5 | "regions": ["sfo1"], 6 | "builds": [ 7 | { 8 | "src": "index.js", 9 | "use": "@now/node" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/acl/src/index.ts: -------------------------------------------------------------------------------- 1 | export { applyRules } from './applyRules'; 2 | export { modelDefaultActions } from './modelDefaultActions'; 3 | export { modelField } from './modelField'; 4 | export { modelDefault } from './modelDefault'; 5 | export { applyRole } from './applyRole'; 6 | export * from './rules'; 7 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/relations/directives/extRelation.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDef = gql` 4 | directive @extRelation( 5 | field: String = "_id" 6 | storeField: String = null 7 | many: Boolean = false 8 | ) on FIELD_DEFINITION 9 | `; 10 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/timestamps.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 2 | 3 | export class TimestampDirective extends SchemaDirectiveVisitor { 4 | _setDate = fieldName => params => { 5 | return { 6 | [fieldName]: new Date(), 7 | }; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /packages/type-wrap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "CommonJS", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": false, 8 | "esModuleInterop": true 9 | }, 10 | "include": ["src/*"], 11 | "exclude": ["node_modules", "**/__tests__/*"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/type-wrap/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-typescript'], 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: 'current', 9 | }, 10 | }, 11 | ], 12 | ], 13 | plugins: ['@babel/plugin-proposal-class-properties'], 14 | }; 15 | -------------------------------------------------------------------------------- /packages/core/src/execution/operations/connectionOperation.ts: -------------------------------------------------------------------------------- 1 | import { RelationInfo } from '../../definitions'; 2 | import { AMOperation } from '../operation'; 3 | 4 | export class AMConnectionOperation extends AMOperation { 5 | public relationInfo: RelationInfo; 6 | 7 | async execute() { 8 | this._result.resolve({}); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/example-federation/src/serviceA/model.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | export default gql` 3 | type Api @federated { 4 | id: ObjectID @unique @external 5 | } 6 | 7 | type Collection @model { 8 | id: ObjectID @id @unique @db(name: "_id") 9 | title: String 10 | apis: [Api] @relationOutside 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /packages/core/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /packages/acl/src/modelDefault.ts: -------------------------------------------------------------------------------- 1 | import { ACLDefault } from './definitions'; 2 | import { modelField } from './modelField'; 3 | 4 | export function modelDefault(modelName, fieldName, access, fn): ACLDefault { 5 | return schema => { 6 | const cond = modelField(modelName, fieldName, access)(schema); 7 | return { 8 | cond, 9 | fn, 10 | }; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": false, 8 | "esModuleInterop": true, 9 | "lib": ["es2019"] 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", "**/__tests__/*", "**/*.test.js", "**/*.test.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/mongodb-executor/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | plugins: [ 13 | '@babel/plugin-proposal-class-properties', 14 | '@babel/plugin-proposal-nullish-coalescing-operator', 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /packages/ast-from-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": false, 8 | "esModuleInterop": true, 9 | "lib": ["es2019"] 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", "**/__tests__/*", "**/*.test.js", "**/*.test.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/__tests__/types/generateSchema.ts: -------------------------------------------------------------------------------- 1 | import AMM from '../../src'; 2 | import { AMOptions } from '../../src/definitions'; 3 | 4 | export const generateSchema = (typeDefs, options?: AMOptions) => { 5 | return new AMM({ options }).makeExecutableSchema({ 6 | resolverValidationOptions: { 7 | requireResolversForResolveType: false, 8 | }, 9 | typeDefs, 10 | }); 11 | }; -------------------------------------------------------------------------------- /packages/schema-filter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": false, 8 | "esModuleInterop": true, 9 | "lib": ["es2019"] 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", "**/__tests__/*", "**/*.test.js", "**/*.test.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/ast-from-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/ast-from-value", 3 | "version": "1.0.0", 4 | "main": "lib/index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "rimraf": "^2.6.3", 8 | "typescript": "^4.2.4" 9 | }, 10 | "scripts": { 11 | "prepare": "rimraf ./lib && tsc" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/mongodb-executor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": false, 8 | "esModuleInterop": true, 9 | "lib": ["es2019"] 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", "**/__tests__/*", "**/*.test.js", "**/*.test.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/ra-data/src/definitions.ts: -------------------------------------------------------------------------------- 1 | import { IntrospectionSchema, IntrospectionType } from 'graphql'; 2 | 3 | export interface Resource { 4 | type: IntrospectionType; 5 | [key: string]: any; 6 | } 7 | 8 | export interface IntrospectionResultData { 9 | types: IntrospectionType[]; 10 | queries: IntrospectionType[]; 11 | resources: Resource[]; 12 | schema: IntrospectionSchema; 13 | } 14 | -------------------------------------------------------------------------------- /packages/example-gateway/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | plugins: [ 13 | '@babel/plugin-proposal-class-properties', 14 | ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }], 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /packages/ra-data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": false, 8 | "esModuleInterop": true, 9 | "lib": ["esnext", "dom"] 10 | }, 11 | "include": ["src", "typings.d.ts"], 12 | "exclude": ["node_modules", "**/__tests__/*", "**/*.test.js", "**/*.test.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/acl/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-typescript'], 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: 'current', 9 | }, 10 | }, 11 | ], 12 | ], 13 | plugins: [ 14 | '@babel/plugin-proposal-class-properties', 15 | ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }], 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/output/visitorHandlers.ts: -------------------------------------------------------------------------------- 1 | import { AMFieldsSelectionContext } from '../../../execution'; 2 | 3 | export const defaultSelectionVisitorHandler = fieldName => ({ 4 | amEnter: (node, transaction, stack) => { 5 | const lastStackItem = stack.last(); 6 | if (lastStackItem instanceof AMFieldsSelectionContext) { 7 | lastStackItem.addField(fieldName); 8 | } 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/ra-data/src/utils/isList.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TypeKind, 3 | IntrospectionTypeRef, 4 | IntrospectionNonNullTypeRef, 5 | } from 'graphql'; 6 | 7 | const isList = (type: IntrospectionTypeRef): boolean => { 8 | if (type.kind === TypeKind.NON_NULL) { 9 | return isList((type as IntrospectionNonNullTypeRef).ofType); 10 | } 11 | 12 | return type.kind === TypeKind.LIST; 13 | }; 14 | 15 | export default isList; 16 | -------------------------------------------------------------------------------- /packages/schema-filter/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-typescript', 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: '8', 9 | }, 10 | }, 11 | ], 12 | ], 13 | plugins: [ 14 | ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }], 15 | ['@babel/plugin-proposal-class-properties'], 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /packages/ra-data/src/utils/isRequired.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TypeKind, 3 | IntrospectionTypeRef, 4 | IntrospectionListTypeRef, 5 | } from 'graphql'; 6 | 7 | const isRequired = (type: IntrospectionTypeRef): boolean => { 8 | if (type.kind === TypeKind.LIST) { 9 | return isRequired((type as IntrospectionListTypeRef).ofType); 10 | } 11 | 12 | return type.kind === TypeKind.NON_NULL; 13 | }; 14 | 15 | export default isRequired; 16 | -------------------------------------------------------------------------------- /packages/ra-data/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /lib 12 | /esm 13 | /dist 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | .idea 27 | -------------------------------------------------------------------------------- /packages/example-server/src/roles/anonymus.ts: -------------------------------------------------------------------------------- 1 | export const anonymusRole = { 2 | Category: { 3 | filter: { 4 | parentCategory: { title: 'root' }, 5 | }, 6 | delete: { 7 | allow: false, 8 | }, 9 | update: { allow: false }, 10 | create: { allow: false }, 11 | }, 12 | User: { 13 | delete: { 14 | allow: false, 15 | }, 16 | update: { allow: false }, 17 | create: { allow: false }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/core/__tests__/transaction/buildFederatedSchema.ts: -------------------------------------------------------------------------------- 1 | import * as DirectiveImplements from '@graphex/directive-implements'; 2 | import AMM from '../../src'; 3 | 4 | export const buildFederatedSchema = typeDefs => { 5 | return new AMM({ 6 | modules: [DirectiveImplements], 7 | }).buildFederatedSchema({ 8 | resolverValidationOptions: { 9 | requireResolversForResolveType: false, 10 | }, 11 | typeDefs: [typeDefs], 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/transforms.ts: -------------------------------------------------------------------------------- 1 | import { Distinct } from './distinct'; 2 | import { Path } from './path'; 3 | import { ToDbRef } from './toDbRef'; 4 | import { TransformArray } from './transformArray'; 5 | import { IndexBy } from './indexBy'; 6 | import { GroupBy } from './groupBy'; 7 | 8 | export const ResultPromiseTransforms = { 9 | Distinct, 10 | Path, 11 | ToDbRef, 12 | TransformArray, 13 | IndexBy, 14 | GroupBy, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/example-federation/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 | # [1.2.0](https://gitlab.com/graphexio/graphex/compare/v1.1.0...v1.2.0) (2021-06-07) 7 | 8 | 9 | ### Features 10 | 11 | * **example-federation:** add sample project with federation ([0a316dc](https://gitlab.com/graphexio/graphex/commit/0a316dc)) 12 | -------------------------------------------------------------------------------- /packages/type-geojson/__tests__/model.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | export default gql` 3 | interface Node @inherit { 4 | id: ObjectID! @id @unique @db(name: "_id") 5 | } 6 | 7 | interface Timestamp @inherit { 8 | createdAt: Date @createdAt @db(name: "created_at") 9 | updatedAt: Date @updatedAt 10 | } 11 | 12 | type Poi implements Node & Timestamp @model { 13 | title: String 14 | place: GeoJSONPoint 15 | area: GeoJSONPolygon 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/federation/types/anyType.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType } from 'graphql'; 2 | import { IAMTypeFactory } from '../../../definitions'; 3 | 4 | export const AMFederationAnyTypeFactory: IAMTypeFactory = { 5 | getTypeName() { 6 | return `_Any`; 7 | }, 8 | getType() { 9 | return new GraphQLScalarType({ 10 | name: '_Any', 11 | serialize(value) { 12 | return value; 13 | }, 14 | }); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/README.md: -------------------------------------------------------------------------------- 1 | # Sequelize Server Migrations 2 | 3 | ## Running DB Migrations 4 | - cd into the root of this repo 5 | - Build the migrations image 6 | ``` 7 | docker-compose build migrations 8 | ``` 9 | 10 | - Start the Postrges 11 | ``` 12 | docker-compose up -d postgres 13 | ``` 14 | 15 | - Run migrations 16 | ``` 17 | docker-compose run migrations yarn db:migrate 18 | ``` 19 | 20 | - Run seeds 21 | ``` 22 | docker-compose run migrations yarn db:seed:all 23 | ``` -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/id.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { AMModelField } from '../../../definitions'; 4 | 5 | export const typeDef = gql` 6 | directive @id on FIELD_DEFINITION 7 | `; 8 | 9 | class ID extends SchemaDirectiveVisitor { 10 | visitFieldDefinition(field: AMModelField) { 11 | field.isID = true; 12 | } 13 | } 14 | 15 | export const schemaDirectives = { 16 | id: ID, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/acl/src/definitions.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, GraphQLNamedType, GraphQLField } from 'graphql'; 2 | 3 | type ACLRuleInputParams = { 4 | type: GraphQLNamedType; 5 | field: GraphQLField; 6 | }; 7 | 8 | export type ACLRuleCondition = (params: ACLRuleInputParams) => boolean; 9 | export type ACLRule = (schema: GraphQLSchema) => ACLRuleCondition; 10 | 11 | export type ACLDefault = ( 12 | schema: GraphQLSchema 13 | ) => { 14 | cond: ACLRuleCondition; 15 | fn: () => any; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/core/src/sdlSyntaxException.ts: -------------------------------------------------------------------------------- 1 | export const UNMARKED_OBJECT_FIELD = 'unmarkedObjectField'; 2 | 3 | export default class SDLSyntaxException extends Error { 4 | description: string; 5 | code: string; 6 | relatedObjects: any[]; 7 | constructor(description: string, code: string, relatedObjects: any[]) { 8 | super(); 9 | this.description = description; 10 | this.code = code; 11 | this.relatedObjects = relatedObjects; 12 | } 13 | 14 | toString = () => this.description; 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/readonly.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { AMModelField } from '../../../definitions'; 4 | 5 | export const typeDef = gql` 6 | directive @readonly on FIELD_DEFINITION 7 | `; 8 | 9 | class ReadOnly extends SchemaDirectiveVisitor { 10 | visitFieldDefinition(field: AMModelField) { 11 | field.isReadOnly = true; 12 | } 13 | } 14 | 15 | export const schemaDirectives = { 16 | readonly: ReadOnly, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/__tests__/transaction/generateSchema.ts: -------------------------------------------------------------------------------- 1 | import * as DirectiveImplements from '@graphex/directive-implements'; 2 | import AMM from '../../src'; 3 | import { AMOptions } from '../../src/definitions'; 4 | 5 | export const generateSchema = (typeDefs, options?: AMOptions) => { 6 | return new AMM({ 7 | options, 8 | modules: [DirectiveImplements], 9 | }).makeExecutableSchema({ 10 | resolverValidationOptions: { 11 | requireResolversForResolveType: false, 12 | }, 13 | typeDefs: [typeDefs], 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-typescript', 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: 'current', 9 | }, 10 | }, 11 | ], 12 | ], 13 | plugins: [ 14 | 'import-graphql', 15 | '@babel/plugin-proposal-class-properties', 16 | '@babel/plugin-proposal-optional-chaining', 17 | '@babel/plugin-proposal-nullish-coalescing-operator', 18 | ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }], 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/fieldsSelection.ts: -------------------------------------------------------------------------------- 1 | import { AMContext } from '../context'; 2 | 3 | export class AMFieldsSelectionContext extends AMContext { 4 | fields: string[] = []; 5 | 6 | constructor(fields?: string[]) { 7 | super(); 8 | if (fields) { 9 | this.fields = fields; 10 | } 11 | } 12 | 13 | addField(fieldName: string) { 14 | if (!this.fields.includes(fieldName)) { 15 | this.fields.push(fieldName); 16 | } 17 | } 18 | 19 | toJSON() { 20 | return { fields: this.fields }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/schema-filter/__tests__/defaultFields.test.ts: -------------------------------------------------------------------------------- 1 | import DefaultFields from '../src/defaultFields'; 2 | 3 | test('defaultFields', () => { 4 | const defaults = DefaultFields(); 5 | defaults.add({ name: 'TypeName' }, { name: 'Field1' }, () => 'valueA'); 6 | defaults.add({ name: 'TypeName' }, { name: 'Field2' }, () => 'valueB'); 7 | 8 | expect(defaults.get({ name: 'TypeName' })).toEqual({ 9 | Field1: 'valueA', 10 | Field2: 'valueB', 11 | }); 12 | 13 | expect(defaults.get({ name: 'UnknowneName' })).toEqual(undefined); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/subdocuments/directives/subdocument.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { AMModelField } from '../../../definitions'; 4 | 5 | export const typeDef = gql` 6 | directive @subdocument on FIELD_DEFINITION 7 | `; 8 | 9 | class Subdocument extends SchemaDirectiveVisitor { 10 | visitFieldDefinition(field: AMModelField) { 11 | field.isSubdocument = true; 12 | } 13 | } 14 | 15 | export const schemaDirectives = { 16 | subdocument: Subdocument, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/src/prepare/fillDbName.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | isCompositeType, 4 | isObjectType, 5 | isInterfaceType, 6 | } from 'graphql'; 7 | import { AMModelField } from '../definitions'; 8 | 9 | export const fillDbName = (schema: GraphQLSchema) => { 10 | Object.values(schema.getTypeMap()).forEach(type => { 11 | if (isObjectType(type) || isInterfaceType(type)) { 12 | Object.values(type.getFields()).forEach((field: AMModelField) => { 13 | if (!field.dbName) field.dbName = field.name; 14 | }); 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/type-geojson/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignore: ['**/*.test.js'], 3 | presets: [ 4 | ['@babel/preset-typescript'], 5 | [ 6 | '@babel/preset-env', 7 | { 8 | targets: { 9 | node: 'current', 10 | }, 11 | }, 12 | ], 13 | ], 14 | 15 | plugins: [ 16 | '@babel/plugin-proposal-class-properties', 17 | '@babel/plugin-proposal-optional-chaining', 18 | [ 19 | '@babel/transform-runtime', 20 | { 21 | regenerator: true, 22 | }, 23 | ], 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/path.ts: -------------------------------------------------------------------------------- 1 | import { AMResultPromise, Transformation } from './resultPromise'; 2 | import { getPath } from './utils'; 3 | 4 | export class Path extends Transformation { 5 | constructor(public path: string) { 6 | super(); 7 | } 8 | 9 | transform(source: AMResultPromise, dest: AMResultPromise) { 10 | source.getPromise().then(async value => { 11 | const newValue = getPath(this.path.split('.'))(value); 12 | dest.resolve(newValue); 13 | }); 14 | source.getPromise().catch(dest.reject); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/example-server/src/db/connection.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, Db } from 'mongodb'; 2 | import { MongoMemoryServer } from 'mongodb-memory-server'; 3 | 4 | const mongod = new MongoMemoryServer({ 5 | binary: { 6 | version: '4.2.8', 7 | }, 8 | }); 9 | 10 | let DB: Db; 11 | let Client: MongoClient; 12 | 13 | export const getDb = async () => { 14 | if (Client?.isConnected()) { 15 | return DB; 16 | } 17 | Client = await MongoClient.connect(await mongod.getUri(), { 18 | useNewUrlParser: true, 19 | }); 20 | DB = Client.db(); 21 | return DB; 22 | }; 23 | -------------------------------------------------------------------------------- /dbconfig.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | dialect: process.env.DATABASE_DIALECT, 3 | host: process.env.DATABASE_HOST, 4 | username: process.env.DATABASE_USER, 5 | password: process.env.DATABASE_PASSWORD, 6 | database: process.env.DATABASE_NAME, 7 | }; 8 | 9 | // const config = { 10 | // username: 'mydbuser', 11 | // password: 'donttellasoul', 12 | // database: 'mydbname', 13 | // host: '127.0.0.1', 14 | // dialect: 'postgres', 15 | // }; 16 | 17 | module.exports = { 18 | development: config, 19 | test: config, 20 | local: config, 21 | production: config, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/groupBy.ts: -------------------------------------------------------------------------------- 1 | import { groupBy, prop } from 'ramda'; 2 | import { AMResultPromise, Transformation } from './resultPromise'; 3 | 4 | export class GroupBy extends Transformation { 5 | constructor(public params: { groupingField: string }) { 6 | super(); 7 | } 8 | 9 | transform(source: AMResultPromise, dest: AMResultPromise) { 10 | source.getPromise().then(async value => { 11 | dest.resolve(groupBy(prop(this.params.groupingField), value)); 12 | }); 13 | source.getPromise().catch(dest.reject); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/indexBy.ts: -------------------------------------------------------------------------------- 1 | import { indexBy, prop } from 'ramda'; 2 | import { AMResultPromise, Transformation } from './resultPromise'; 3 | 4 | export class IndexBy extends Transformation { 5 | constructor(public params: { groupingField: string }) { 6 | super(); 7 | } 8 | 9 | transform(source: AMResultPromise, dest: AMResultPromise) { 10 | source.getPromise().then(async value => { 11 | dest.resolve(indexBy(prop(this.params.groupingField), value)); 12 | }); 13 | source.getPromise().catch(dest.reject); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/subdocuments/directives/noArrayFilter.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { AMModelField } from '../../../definitions'; 4 | 5 | export const typeDef = gql` 6 | directive @noArrayFilter on FIELD_DEFINITION 7 | `; 8 | 9 | class DirectiveNoArrayFilter extends SchemaDirectiveVisitor { 10 | visitFieldDefinition(field: AMModelField) { 11 | field.noArrayFilter = true; 12 | } 13 | } 14 | 15 | export const schemaDirectives = { 16 | noArrayFilter: DirectiveNoArrayFilter, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/example-federation/src/serviceA/index.ts: -------------------------------------------------------------------------------- 1 | import Graphex from '@graphex/core'; 2 | 3 | import QueryExecutor from '@graphex/mongodb-executor'; 4 | 5 | import { ApolloServer } from 'apollo-server'; 6 | 7 | import { getDb } from './db/connection'; 8 | import typeDefs from './model'; 9 | 10 | const schema = new Graphex({}).buildFederatedSchema({ 11 | typeDefs, 12 | }); 13 | 14 | export const server = new ApolloServer({ 15 | schema, 16 | introspection: true, 17 | playground: true, 18 | context: () => ({ 19 | queryExecutor: QueryExecutor(getDb), 20 | }), 21 | }); 22 | -------------------------------------------------------------------------------- /packages/example-federation/src/serviceB/index.ts: -------------------------------------------------------------------------------- 1 | import Graphex from '@graphex/core'; 2 | 3 | import QueryExecutor from '@graphex/mongodb-executor'; 4 | 5 | import { ApolloServer } from 'apollo-server'; 6 | 7 | import { getDb } from './db/connection'; 8 | import typeDefs from './model'; 9 | 10 | const schema = new Graphex({}).buildFederatedSchema({ 11 | typeDefs, 12 | }); 13 | 14 | export const server = new ApolloServer({ 15 | schema, 16 | introspection: true, 17 | playground: true, 18 | context: () => ({ 19 | queryExecutor: QueryExecutor(getDb), 20 | }), 21 | }); 22 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/default.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { AMModelField } from '../../../definitions'; 4 | 5 | export const typeDef = gql` 6 | scalar DefaultValueType 7 | directive @default(value: DefaultValueType) on FIELD_DEFINITION 8 | `; 9 | 10 | class Default extends SchemaDirectiveVisitor { 11 | visitFieldDefinition(field: AMModelField) { 12 | field.defaultValue = this.args.value; 13 | } 14 | } 15 | 16 | export const schemaDirectives = { 17 | default: Default, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/example-gateway/default-server/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | help: String 6 | } 7 | `; 8 | 9 | const resolvers = { 10 | Query: { 11 | help: () => 12 | 'You should send accessToken with request headers to get available methods', 13 | }, 14 | }; 15 | 16 | let server = new ApolloServer({ 17 | typeDefs, 18 | resolvers, 19 | introspection: true, 20 | playground: true, 21 | }); 22 | 23 | server.listen(4002).then(({ url }) => { 24 | console.log(`🚀 Server ready at ${url}`); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/scalars/date.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType, Kind, StringValueNode } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | 4 | export const typeDef = gql` 5 | scalar Date 6 | `; 7 | 8 | export const resolvers = { 9 | Date: new GraphQLScalarType({ 10 | name: 'Date', 11 | description: 'Date type', 12 | serialize: (val: Date) => (val instanceof Date ? val.toISOString() : val), 13 | parseValue: (val: string) => new Date(val), 14 | parseLiteral: (ast: StringValueNode) => 15 | ast.kind === Kind.STRING ? new Date(ast.value) : ast.value, 16 | }), 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/src/execution/path.ts: -------------------------------------------------------------------------------- 1 | export class Path { 2 | static fromArray(arr: string[]) { 3 | return new Path(arr); 4 | } 5 | static fromString(path: string) { 6 | return new Path(path.split('.')); 7 | } 8 | 9 | constructor(private _pathArr: string[]) {} 10 | 11 | clone() { 12 | return new Path([...this._pathArr]); 13 | } 14 | 15 | asArray() { 16 | return this._pathArr; 17 | } 18 | asString() { 19 | return this._pathArr.join('.'); 20 | } 21 | 22 | pop() { 23 | return this._pathArr.pop(); 24 | } 25 | 26 | toJSON() { 27 | return this.asString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/example-federation/src/serviceA/db/connection.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, Db } from 'mongodb'; 2 | import { MongoMemoryServer } from 'mongodb-memory-server'; 3 | 4 | const mongod = new MongoMemoryServer({ 5 | binary: { 6 | version: '4.2.8', 7 | }, 8 | }); 9 | mongod.getUri().then(console.log); 10 | 11 | let DB: Db; 12 | let Client: MongoClient; 13 | 14 | export const getDb = async () => { 15 | if (Client?.isConnected()) { 16 | return DB; 17 | } 18 | Client = await MongoClient.connect(await mongod.getUri(), { 19 | useNewUrlParser: true, 20 | }); 21 | DB = Client.db(); 22 | return DB; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/example-federation/src/serviceB/db/connection.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, Db } from 'mongodb'; 2 | import { MongoMemoryServer } from 'mongodb-memory-server'; 3 | 4 | const mongod = new MongoMemoryServer({ 5 | binary: { 6 | version: '4.2.8', 7 | }, 8 | }); 9 | mongod.getUri().then(console.log); 10 | 11 | let DB: Db; 12 | let Client: MongoClient; 13 | 14 | export const getDb = async () => { 15 | if (Client?.isConnected()) { 16 | return DB; 17 | } 18 | Client = await MongoClient.connect(await mongod.getUri(), { 19 | useNewUrlParser: true, 20 | }); 21 | DB = Client.db(); 22 | return DB; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/selector.ts: -------------------------------------------------------------------------------- 1 | import { AMObjectFieldValueType } from '../../definitions'; 2 | import { AMContext } from '../context'; 3 | 4 | type Selector = { [key: string]: AMObjectFieldValueType }; 5 | 6 | export class AMSelectorContext extends AMContext { 7 | selector: Selector = {}; 8 | 9 | constructor(selector?: Selector) { 10 | super(); 11 | if (selector) { 12 | this.selector = selector; 13 | } 14 | } 15 | 16 | addValue(key: string, value: AMObjectFieldValueType) { 17 | this.selector[key] = value; 18 | } 19 | 20 | toJSON() { 21 | return this.selector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/exists.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLBoolean } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class ExistsSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable() { 7 | return true; 8 | } 9 | getFieldName(field: AMModelField) { 10 | return `${field.name}_exists`; 11 | } 12 | getFieldType() { 13 | return GraphQLBoolean; 14 | } 15 | transformValue(value: any) { 16 | return { 17 | $exists: value, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/example-federation/src/index.ts: -------------------------------------------------------------------------------- 1 | import { server as serverA } from './serviceA'; 2 | import { server as serverB } from './serviceB'; 3 | import { createGateway } from './gateway'; 4 | 5 | (async () => { 6 | await serverA.listen({ port: 4001 }).then(({ url }) => { 7 | console.log(`🚀 ServerA ready at ${url}`); 8 | }); 9 | 10 | await serverB.listen({ port: 4002 }).then(({ url }) => { 11 | console.log(`🚀 ServerB ready at ${url}`); 12 | }); 13 | 14 | await createGateway() 15 | .listen({ port: 4000 }) 16 | .then(({ url }) => { 17 | console.log(`🚀 Gateway ready at ${url}`); 18 | }); 19 | })(); 20 | -------------------------------------------------------------------------------- /packages/sequelize-executor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/sequelize-executor", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "prepare": "rimraf ./lib && tsc" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "rimraf": "^2.6.3" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "devDependencies": { 23 | "sequelize": "^6.6.2", 24 | "typescript": "^4.2.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/output/fieldFactories/identity.ts: -------------------------------------------------------------------------------- 1 | import { AMFieldFactory, AMModelField } from '../../../../definitions'; 2 | import { defaultSelectionVisitorHandler } from '../visitorHandlers'; 3 | 4 | export class AMIdentityFieldFactory extends AMFieldFactory { 5 | isApplicable() { 6 | return true; 7 | } 8 | getFieldName(field: AMModelField): string { 9 | return field.name; 10 | } 11 | getField(field: AMModelField) { 12 | return { 13 | type: field.type, 14 | dbName: field.dbName, 15 | ...defaultSelectionVisitorHandler(field.dbName), 16 | } as AMModelField; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/example-federation/src/gateway/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloGateway } from '@apollo/gateway'; 2 | import { ApolloServer } from 'apollo-server'; 3 | 4 | export const createGateway = () => { 5 | const gateway = new ApolloGateway({ 6 | serviceList: [ 7 | { name: 'serviceA', url: 'http://localhost:4001' }, 8 | { name: 'serviceB', url: 'http://localhost:4002' }, 9 | // Define additional services here 10 | ], 11 | }); 12 | 13 | // Pass the ApolloGateway to the ApolloServer constructor 14 | const server = new ApolloServer({ 15 | gateway, 16 | subscriptions: false, 17 | }); 18 | return server; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/core/__tests__/utils/prepareTransaction.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode, GraphQLSchema, validate } from 'graphql'; 2 | import { AMVisitor } from '../../src/execution/visitor'; 3 | import { AMTransaction } from '../../src/execution/transaction'; 4 | 5 | export const prepareTransaction = ( 6 | schema: GraphQLSchema, 7 | rq: DocumentNode, 8 | variables: Record = {} 9 | ) => { 10 | const errors = validate(schema, rq); 11 | if (errors.length > 0) { 12 | throw errors; 13 | } 14 | const transaction = new AMTransaction(new Map()); 15 | AMVisitor.visit(schema, rq, variables, transaction); 16 | return transaction; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/unique.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { AMModelField } from '../../../definitions'; 4 | 5 | export const typeDef = gql` 6 | directive @unique on FIELD_DEFINITION 7 | `; 8 | 9 | class Unique extends SchemaDirectiveVisitor { 10 | visitFieldDefinition(field: AMModelField, { objectType }) { 11 | field.isUnique = true; 12 | 13 | objectType.mmUniqueFields = objectType.mmUniqueFields ?? []; 14 | objectType.mmUniqueFields.push(field); 15 | } 16 | } 17 | 18 | export const schemaDirectives = { 19 | unique: Unique, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/distinct.ts: -------------------------------------------------------------------------------- 1 | import { AMResultPromise, Transformation } from './resultPromise'; 2 | import { getPath } from './utils'; 3 | 4 | export class Distinct extends Transformation { 5 | constructor(public path: string) { 6 | super(); 7 | } 8 | transform(source: AMResultPromise, dest: AMResultPromise) { 9 | const pathArr = this.path.split('.'); 10 | source.getPromise().then(value => { 11 | let dValue = getPath(pathArr)(value) || []; 12 | if (!Array.isArray(dValue)) dValue = [dValue]; 13 | dest.resolve(dValue); 14 | }); 15 | source.getPromise().catch(dest.reject); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/__tests__/types/default.test.ts: -------------------------------------------------------------------------------- 1 | import { printType } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | import { generateSchema } from './generateSchema'; 4 | 5 | describe('default', () => { 6 | const schema = generateSchema( 7 | gql` 8 | type Post @model { 9 | id: ID @id @unique @db(name: "_id") 10 | message: String! @default(value: "New post") 11 | } 12 | ` 13 | ); 14 | 15 | test('PostCreateInput', () => { 16 | expect(printType(schema.getType('PostCreateInput'))).toMatchInlineSnapshot(` 17 | "input PostCreateInput { 18 | message: String 19 | }" 20 | `); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/core/src/appendField.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLSchema } from 'graphql'; 2 | import { AMModelType, AMOptions, IAMFieldFactory } from './definitions'; 3 | import { makeSchemaInfo } from './schemaInfo'; 4 | 5 | export const appendField = ( 6 | schema: GraphQLSchema, 7 | targetType: GraphQLObjectType, 8 | fieldFactory: IAMFieldFactory, 9 | modelType: AMModelType, 10 | options: AMOptions 11 | ) => { 12 | const schemaInfo = makeSchemaInfo(schema, options); 13 | const fieldName = fieldFactory.getFieldName(modelType); 14 | targetType.getFields()[fieldName] = fieldFactory.getField( 15 | modelType, 16 | schemaInfo 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/scalars/JSON.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType, StringValueNode } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | 4 | export const typeDef = gql` 5 | scalar JSON 6 | `; 7 | 8 | export const resolvers = { 9 | JSON: new GraphQLScalarType({ 10 | name: 'JSON', 11 | description: 'JSON Scalar. returns ', 12 | serialize: (val: {}) => JSON.stringify(val), 13 | parseValue: (val: string) => JSON.parse(val), 14 | parseLiteral: (ast: StringValueNode) => { 15 | try { 16 | return JSON.parse(ast.value); 17 | } catch (e) { 18 | return ast.value; 19 | } 20 | }, 21 | }), 22 | }; 23 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/scalars/objectID.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType, Kind, StringValueNode } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | import { ObjectID } from 'mongodb'; 4 | 5 | export const typeDef = gql` 6 | scalar ObjectID 7 | `; 8 | 9 | export const resolvers = { 10 | ObjectID: new GraphQLScalarType({ 11 | name: 'ObjectID', 12 | description: 'MongoDB ObjectID type', 13 | serialize: (val: ObjectID) => val.toString(), 14 | parseValue: (val: string) => new ObjectID(val), 15 | parseLiteral: (ast: StringValueNode) => 16 | ast.kind === Kind.STRING ? new ObjectID(ast.value) : ast.value, 17 | }), 18 | }; 19 | -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/data.ts: -------------------------------------------------------------------------------- 1 | import { AMContext } from '../context'; 2 | import { AMObjectFieldValueType } from '../../definitions'; 3 | 4 | type Data = { [key: string]: AMObjectFieldValueType }; 5 | 6 | export class AMDataContext extends AMContext { 7 | data: Data; 8 | 9 | constructor(data?: Data) { 10 | super(); 11 | if (data) { 12 | this.data = data; 13 | } 14 | } 15 | 16 | addValue(key: string, value: AMObjectFieldValueType) { 17 | if (!this.data) this.data = {}; 18 | this.data[key] = value; 19 | } 20 | 21 | setData(value: Data) { 22 | this.data = value; 23 | } 24 | 25 | toJSON() { 26 | return this.data; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignore: ['**/*.test.js'], 3 | presets: [ 4 | ['@babel/preset-typescript'], 5 | [ 6 | '@babel/preset-env', 7 | { 8 | targets: { 9 | node: 'current', 10 | }, 11 | }, 12 | ], 13 | ], 14 | plugins: [ 15 | ['import-graphql'], 16 | '@babel/plugin-proposal-class-properties', 17 | '@babel/plugin-proposal-optional-chaining', 18 | [ 19 | 'babel-plugin-root-import', 20 | { 21 | rootPathSuffix: 'src/', 22 | }, 23 | ], 24 | [ 25 | '@babel/transform-runtime', 26 | { 27 | regenerator: true, 28 | }, 29 | ], 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/output/fieldFactories/namedType.ts: -------------------------------------------------------------------------------- 1 | import { AMFieldFactory, AMModelField } from '../../../../definitions'; 2 | import { defaultSelectionVisitorHandler } from '../visitorHandlers'; 3 | import { getNamedType } from 'graphql'; 4 | 5 | export class AMNamedTypeFieldFactory extends AMFieldFactory { 6 | isApplicable() { 7 | return true; 8 | } 9 | getFieldName(field: AMModelField): string { 10 | return field.name; 11 | } 12 | getField(field: AMModelField) { 13 | return { 14 | type: getNamedType(field.type), 15 | dbName: field.dbName, 16 | ...defaultSelectionVisitorHandler(field.dbName), 17 | } as AMModelField; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/directive-inherit/src/index.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import R from 'ramda'; 4 | 5 | export const typeDefs = gql` 6 | directive @inherit on INTERFACE 7 | `; 8 | 9 | class Inherit extends SchemaDirectiveVisitor { 10 | visitInterface(iface) { 11 | const { _typeMap: SchemaTypes } = this.schema; 12 | 13 | Object.values(SchemaTypes) 14 | .filter(type => type._interfaces && type._interfaces.includes(iface)) 15 | .forEach(type => { 16 | type._fields = { ...R.clone(iface._fields), ...type._fields }; 17 | }); 18 | } 19 | } 20 | 21 | export const schemaDirectives = { 22 | inherit: Inherit, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/__tests__/types/empty.test.ts: -------------------------------------------------------------------------------- 1 | import { printType } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | import { generateSchema } from './generateSchema'; 4 | 5 | describe('empty', () => { 6 | const schema = generateSchema(gql` 7 | type Empty @model { 8 | title: String 9 | } 10 | `); 11 | 12 | test('Query', () => { 13 | expect(printType(schema.getQueryType())).toMatchInlineSnapshot(` 14 | "type Query { 15 | empties(where: EmptyWhereInput, orderBy: EmptyOrderByInput, offset: Int, first: Int): [Empty!]! 16 | empty: Empty 17 | emptiesConnection(where: EmptyWhereInput, orderBy: EmptyOrderByInput, offset: Int, first: Int): EmptyConnection 18 | }" 19 | `); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/core/src/config/fieldFactories.ts: -------------------------------------------------------------------------------- 1 | import { AMIdentityFieldFactory } from '../schemaGeneration/model/output/fieldFactories/identity'; 2 | import { AMNamedTypeFieldFactory } from '../schemaGeneration/model/output/fieldFactories/namedType'; 3 | import { AMAggregateNumericFieldsFieldFactory } from '../schemaGeneration/model/output/fieldFactories/aggregateNumericFields'; 4 | 5 | export const fieldFactories = { 6 | aggregateNumericFields: { 7 | factory: AMAggregateNumericFieldsFieldFactory, 8 | links: { 9 | embedded: 'aggregateNumericFields', 10 | }, 11 | }, 12 | identity: { 13 | factory: AMIdentityFieldFactory, 14 | }, 15 | namedType: { 16 | factory: AMNamedTypeFieldFactory, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/core/src/config/defaultConfig.ts: -------------------------------------------------------------------------------- 1 | import { AMConfig } from '../definitions'; 2 | import { methodFactories } from './methodFactories'; 3 | import { typeFactories } from './typeFactories'; 4 | import { inputTypeFactories } from './inputTypeFactories'; 5 | import { inputFieldFactories } from './inputFieldFactories'; 6 | import { fieldFactories } from './fieldFactories'; 7 | 8 | const config = { 9 | _default: { 10 | methodFactories, 11 | typeFactories, 12 | fieldFactories, 13 | inputTypeFactories, 14 | inputFieldFactories, 15 | }, 16 | }; 17 | 18 | export function buildConfigType(config: T): T { 19 | return config; 20 | } 21 | 22 | export const defaultConfig = buildConfigType(config); 23 | -------------------------------------------------------------------------------- /packages/core/src/execution/transaction.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from './operation'; 2 | import { AMDBExecutor } from '../definitions'; 3 | 4 | export class AMTransaction { 5 | constructor(public fieldsRegistry: Map) {} 6 | 7 | operations: AMOperation[] = []; 8 | addOperation(operation: AMOperation) { 9 | this.operations.push(operation); 10 | } 11 | 12 | execute(executor: AMDBExecutor) { 13 | if (this.operations.length > 0) { 14 | this.operations.forEach(op => op.execute(executor)); 15 | return this.operations[0].getOutput(); 16 | } 17 | throw new Error('Empty transaction'); 18 | } 19 | 20 | toJSON() { 21 | return { operations: this.operations.map(op => op.toJSON()) }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/fragment.ts: -------------------------------------------------------------------------------- 1 | import { AMModelType } from '../../definitions'; 2 | import { AMContext } from '../context'; 3 | import { AMFieldsSelectionContext } from './fieldsSelection'; 4 | 5 | export class AMFragmentContext extends AMContext { 6 | constructor( 7 | private options?: { 8 | fieldsSelectionContext?: AMFieldsSelectionContext; 9 | contextType?: AMModelType; 10 | conditionType?: AMModelType; 11 | actualConditionType?: AMModelType; 12 | } 13 | ) { 14 | super(); 15 | } 16 | 17 | getFieldsSelectionContext() { 18 | return this.options.fieldsSelectionContext; 19 | } 20 | 21 | getActualConditionType() { 22 | return this.options.actualConditionType; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/size.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { GraphQLInt } from 'graphql'; 3 | import { AMModelField } from '../../../../definitions'; 4 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 5 | 6 | export class SizeSelector extends AMQuerySelectorFieldFactory { 7 | isApplicable(field: AMModelField) { 8 | const typeWrap = new TypeWrap(field.type); 9 | return typeWrap.isMany(); 10 | } 11 | getFieldName(field: AMModelField) { 12 | return `${field.name}_size`; 13 | } 14 | getFieldType() { 15 | return GraphQLInt; 16 | } 17 | transformValue(value: any) { 18 | return { 19 | $size: value, 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/ra-data/src/utils/getFinalType.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TypeKind, 3 | IntrospectionTypeRef, 4 | IntrospectionListTypeRef, 5 | IntrospectionNonNullTypeRef, 6 | IntrospectionNamedTypeRef, 7 | } from 'graphql'; 8 | 9 | /** 10 | * Ensure we get the real type even if the root type is NON_NULL or LIST 11 | * @param {GraphQLType} type 12 | */ 13 | const getFinalType = ( 14 | type: IntrospectionTypeRef 15 | ): IntrospectionNamedTypeRef => { 16 | if (type.kind === TypeKind.NON_NULL || type.kind === TypeKind.LIST) { 17 | return getFinalType( 18 | (type as IntrospectionListTypeRef | IntrospectionNonNullTypeRef).ofType 19 | ); 20 | } 21 | 22 | return type as IntrospectionNamedTypeRef; 23 | }; 24 | 25 | export default getFinalType; 26 | -------------------------------------------------------------------------------- /packages/type-wrap/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/type-wrap 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/type-wrap 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/type-wrap 25 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/src/model.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | export default gql` 3 | interface Node @inherit { 4 | id: Int! @id @unique 5 | } 6 | 7 | interface Timestamp @inherit { 8 | createdAt: Date @createdAt 9 | updatedAt: Date @updatedAt 10 | } 11 | 12 | type User implements Node & Timestamp @model(collection: "User") { 13 | username: String! @unique 14 | posts: [Post] @extRelation(storeField: "owner_id", field: "id", many: true) 15 | } 16 | 17 | type Post implements Node & Timestamp @model(collection: "Post") { 18 | title: String 19 | body: String 20 | owner: User @relation(storeField: "owner_id", field: "id") 21 | likes: [User] @relation(storeField: "like_ids", field: "id") 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /packages/ra-data/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ra-data-graphql' { 2 | export const QUERY_TYPES: string[]; 3 | type graphQLDataProvider = ( 4 | fetchType: string, 5 | resource: string, 6 | params: { [key: string]: any } 7 | ) => Promise; 8 | const buildDataProvider: (options: any) => Promise; 9 | 10 | export default buildDataProvider; 11 | } 12 | declare module 'react-admin' { 13 | export const GET_LIST: string; 14 | export const GET_ONE: string; 15 | export const GET_MANY: string; 16 | export const GET_MANY_REFERENCE: string; 17 | export const CREATE: string; 18 | export const UPDATE: string; 19 | export const DELETE: string; 20 | export const DELETE_MANY: string; 21 | export const UPDATE_MANY: string; 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/not_size.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { GraphQLInt } from 'graphql'; 3 | import { AMModelField } from '../../../../definitions'; 4 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 5 | 6 | export class NotSizeSelector extends AMQuerySelectorFieldFactory { 7 | isApplicable(field: AMModelField) { 8 | const typeWrap = new TypeWrap(field.type); 9 | return typeWrap.isMany(); 10 | } 11 | getFieldName(field: AMModelField) { 12 | return `${field.name}_not_size`; 13 | } 14 | getFieldType() { 15 | return GraphQLInt; 16 | } 17 | transformValue(value: any) { 18 | return { 19 | $not: { $size: value }, 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/ast-from-value/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/ast-from-value 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/ast-from-value 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/ast-from-value 25 | -------------------------------------------------------------------------------- /packages/schema-filter/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/schema-filter 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/schema-filter 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/schema-filter 25 | -------------------------------------------------------------------------------- /packages/schema-filter/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Transform } from '@apollo-model/graphql-tools'; 2 | import { GraphQLSchema } from 'graphql'; 3 | import { transformRequest } from './transformRequest'; 4 | import { transformSchema } from './transformSchema'; 5 | export { removeUnusedTypes } from './removeUnusedTypes'; 6 | 7 | export const SchemaFilter: (transformOptions: { 8 | filterFields; 9 | defaultFields; 10 | defaultArgs; 11 | }) => Transform = (transformOptions) => { 12 | const transformContext: { 13 | initialSchema?: GraphQLSchema; 14 | defaults?: any; 15 | } = {}; 16 | 17 | return { 18 | transformSchema: transformSchema(transformOptions, transformContext), 19 | transformRequest: transformRequest(transformOptions, transformContext), 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/example-gateway/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/example-gateway 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/example-gateway 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/example-gateway 25 | -------------------------------------------------------------------------------- /packages/core/src/prepare/defaultDirective.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, isObjectType, isInterfaceType } from 'graphql'; 2 | import { getDirectiveAST } from '../utils'; 3 | import { AMModelField, AMModelType } from '../definitions'; 4 | 5 | export const defaultDirective = (schema: GraphQLSchema) => { 6 | Object.values(schema.getTypeMap()).forEach((type: AMModelType) => { 7 | if (isObjectType(type) || isInterfaceType(type)) { 8 | Object.values(type.getFields()).forEach((field: AMModelField) => { 9 | const defaultDirectiveAST = getDirectiveAST(field, 'default'); 10 | if (defaultDirectiveAST) { 11 | if (!type.mmDefaultFields) type.mmDefaultFields = []; 12 | type.mmDefaultFields.push(field); 13 | } 14 | }); 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/mongodb-executor/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/mongodb-executor 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/mongodb-executor 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/mongodb-executor 25 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/toDbRef.ts: -------------------------------------------------------------------------------- 1 | import { AMResultPromise, Transformation } from './resultPromise'; 2 | import { DBRef, ObjectID } from 'mongodb'; 3 | 4 | export class ToDbRef extends Transformation { 5 | constructor(public collectionName: string) { 6 | super(); 7 | } 8 | transform( 9 | source: AMResultPromise, 10 | dest: AMResultPromise 11 | ) { 12 | source.getPromise().then(async value => { 13 | if (Array.isArray(value)) { 14 | dest.resolve(value.map(id => new DBRef(this.collectionName, id))); 15 | } else if (value instanceof ObjectID) { 16 | dest.resolve(new DBRef(this.collectionName, value)); 17 | } 18 | }); 19 | source.getPromise().catch(dest.reject); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/directive-inherit/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/directive-inherit 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/directive-inherit 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/directive-inherit 25 | -------------------------------------------------------------------------------- /packages/sequelize-executor/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/sequelize-executor 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/sequelize-executor 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/sequelize-executor 25 | -------------------------------------------------------------------------------- /packages/type-geojson/src/create.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AMInputFieldFactory, 3 | defaultObjectFieldVisitorHandler, 4 | AMModelField, 5 | } from '@graphex/core'; 6 | import { GraphQLInputType, GraphQLNamedType } from 'graphql'; 7 | 8 | export class AMGeoJSONCreateFieldFactory extends AMInputFieldFactory { 9 | isApplicable() { 10 | return true; 11 | } 12 | getFieldName(field: AMModelField) { 13 | return `${field.name}`; 14 | } 15 | getField(field: AMModelField) { 16 | return { 17 | name: this.getFieldName(field), 18 | type: this.schemaInfo.schema.getType( 19 | `${(field.type as GraphQLNamedType).name}Input` 20 | ) as GraphQLInputType, 21 | extensions: undefined, 22 | ...defaultObjectFieldVisitorHandler(field.dbName), 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/prepare/createdAtDirective.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, isObjectType, isInterfaceType } from 'graphql'; 2 | import { getDirectiveAST } from '../utils'; 3 | import { AMModelField, AMModelType } from '../definitions'; 4 | 5 | export const createdAtDirective = (schema: GraphQLSchema) => { 6 | Object.values(schema.getTypeMap()).forEach((type: AMModelType) => { 7 | if (isObjectType(type) || isInterfaceType(type)) { 8 | Object.values(type.getFields()).forEach((field: AMModelField) => { 9 | const createdAtDirectiveAST = getDirectiveAST(field, 'createdAt'); 10 | if (createdAtDirectiveAST) { 11 | if (!type.mmCreatedAtFields) type.mmCreatedAtFields = []; 12 | type.mmCreatedAtFields.push(field); 13 | } 14 | }); 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/src/prepare/updatedAtDirective.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, isObjectType, isInterfaceType } from 'graphql'; 2 | import { getDirectiveAST } from '../utils'; 3 | import { AMModelField, AMModelType } from '../definitions'; 4 | 5 | export const updatedAtDirective = (schema: GraphQLSchema) => { 6 | Object.values(schema.getTypeMap()).forEach((type: AMModelType) => { 7 | if (isObjectType(type) || isInterfaceType(type)) { 8 | Object.values(type.getFields()).forEach((field: AMModelField) => { 9 | const updatedAtDirectiveAST = getDirectiveAST(field, 'updatedAt'); 10 | if (updatedAtDirectiveAST) { 11 | if (!type.mmUpdatedAtFields) type.mmUpdatedAtFields = []; 12 | type.mmUpdatedAtFields.push(field); 13 | } 14 | }); 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/directive-implements/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 | # [1.0.0](https://gitlab.com/graphexio/graphex/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | **Note:** Version bump only for package @graphex/directive-implements 9 | 10 | 11 | 12 | 13 | 14 | # [0.8.0](https://gitlab.com/graphexio/graphex/compare/v0.7.0...v0.8.0) (2021-06-02) 15 | 16 | **Note:** Version bump only for package @graphex/directive-implements 17 | 18 | 19 | 20 | 21 | 22 | # [0.7.0-alpha.3](https://gitlab.com/graphexio/graphex/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/directive-implements 25 | -------------------------------------------------------------------------------- /packages/example-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/example-server", 3 | "version": "1.2.4", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "yarn dev", 8 | "dev": "nodemon", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "private": true, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@graphex/acl": "1.2.4", 17 | "@graphex/core": "1.2.4", 18 | "@graphex/directive-implements": "1.0.0", 19 | "apollo-server": "^2.2.6", 20 | "graphql": "14.6.0", 21 | "mongodb": "^3.1.10", 22 | "mongodb-memory-server": "^6.9.6" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^2.0.7", 26 | "ts-node": "^10.0.0", 27 | "typescript": "^4.3.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/prepare/fieldFactories.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | isObjectType, 4 | getNamedType, 5 | isInterfaceType, 6 | } from 'graphql'; 7 | import { getDirectiveAST } from '../utils'; 8 | import { AMModelField, AMModelType } from '../definitions'; 9 | 10 | export const fieldFactories = ( 11 | schema: GraphQLSchema, 12 | fieldFactoriesMap: {} 13 | ) => { 14 | Object.values(schema.getTypeMap()).forEach((type: AMModelType) => { 15 | if (isObjectType(type) || isInterfaceType(type)) { 16 | Object.values(type.getFields()).forEach((field: AMModelField) => { 17 | const fieldType = getNamedType(field.type); 18 | if (fieldFactoriesMap[fieldType.name]) { 19 | field.mmFieldFactories = fieldFactoriesMap[fieldType.name]; 20 | } 21 | }); 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | x-default-env: &default-env 4 | NODE_ENV: 'development' 5 | 6 | x-db-env: &db-env 7 | DATABASE_DIALECT: 'postgres' 8 | DATABASE_HOST: 'postgres' 9 | DATABASE_PORT: '5432' 10 | DATABASE_NAME: 'mydbname' 11 | DATABASE_USER: 'mydbuser' 12 | DATABASE_PASSWORD: 'donttellasoul' 13 | 14 | services: 15 | migrations: 16 | build: 17 | dockerfile: docker/Dockerfile 18 | context: ./ 19 | environment: 20 | << : *default-env 21 | << : *db-env 22 | depends_on: 23 | - postgres 24 | volumes: 25 | - .:/workspace 26 | 27 | postgres: 28 | image: postgres:11.8 29 | environment: 30 | POSTGRES_USER: 'mydbuser' 31 | POSTGRES_PASSWORD: 'donttellasoul' 32 | POSTGRES_DB: 'mydbname' 33 | ports: 34 | - 5432:5432 35 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/db.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import R from 'ramda'; 4 | 5 | export const typeDef = gql` 6 | directive @db(name: String!, defaultValue: String = null) on FIELD_DEFINITION 7 | `; 8 | 9 | class DirectiveDB extends SchemaDirectiveVisitor { 10 | visitFieldDefinition(field) { 11 | const { name } = this.args; 12 | field.dbName = name; 13 | } 14 | } 15 | 16 | const DirectiveDBResolver = (next, source, args, ctx, info) => { 17 | const { name } = args; 18 | source[info.fieldName] = R.path(R.split('.', name), source); 19 | return next(); 20 | }; 21 | 22 | export const schemaDirectives = { 23 | db: DirectiveDB, 24 | }; 25 | 26 | export const directiveResolvers = { 27 | db: DirectiveDBResolver, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/core/src/execution/resultPromise/batch.ts: -------------------------------------------------------------------------------- 1 | import { AMResultPromise, ResultPromise } from './resultPromise'; 2 | 3 | export class Batch extends AMResultPromise { 4 | dispatched = false; 5 | ids = []; 6 | 7 | constructor() { 8 | super(null); 9 | } 10 | 11 | private dispatch() { 12 | if (!this.dispatched) { 13 | this.dispatched = true; 14 | 15 | process.nextTick(() => { 16 | this.resolve(this.ids); 17 | }); 18 | } 19 | } 20 | 21 | addId(id: T) { 22 | this.dispatch(); 23 | if (!id) return; 24 | if (this.ids.includes(id)) return; 25 | 26 | this.ids.push(id); 27 | } 28 | 29 | addIds(ids: T[]) { 30 | this.dispatch(); 31 | ids?.forEach(id => this.ids.push(id)); 32 | } 33 | 34 | toJSON() { 35 | return new ResultPromise(''); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/type-wrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/type-wrap", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "prepare": "rimraf ./lib && tsc" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@babel/cli": "^7.1.2", 18 | "@babel/core": "^7.1.2", 19 | "@babel/node": "^7.2.2", 20 | "@babel/plugin-proposal-class-properties": "^7.5.5", 21 | "@babel/plugin-transform-runtime": "^7.2.0", 22 | "@babel/preset-env": "^7.5.5", 23 | "@babel/preset-typescript": "^7.9.0", 24 | "rimraf": "^2.6.3", 25 | "typescript": "^4.2.4" 26 | }, 27 | "publishConfig": { 28 | "access": "public" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/contains.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class ContainsSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | return getNamedType(field.type).toString() === 'String'; 8 | } 9 | getFieldName(field: AMModelField) { 10 | return `${field.name}_contains`; 11 | } 12 | getFieldType(field: AMModelField) { 13 | const namedType = getNamedType(field.type); 14 | 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $regex: new RegExp(value), 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/fieldFactories/create.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMInputFieldFactory } from '../../../../definitions'; 3 | import { defaultObjectFieldVisitorHandler } from '../inputTypes/visitorHandlers'; 4 | 5 | export class AMCreateFieldFactory extends AMInputFieldFactory { 6 | isApplicable(field) { 7 | return ( 8 | !isCompositeType(getNamedType(field.type)) && 9 | !field.isID && 10 | !field.isReadOnly 11 | ); 12 | } 13 | getFieldName(field) { 14 | return field.name; 15 | } 16 | getField(field) { 17 | return { 18 | name: this.getFieldName(field), 19 | extensions: undefined, 20 | type: field.defaultValue ? getNamedType(field.type) : field.type, 21 | ...defaultObjectFieldVisitorHandler(field.dbName), 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/endsWith.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class EndsWithSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | return getNamedType(field.type).toString() === 'String'; 8 | } 9 | getFieldName(field: AMModelField) { 10 | return `${field.name}_ends_with`; 11 | } 12 | getFieldType(field: AMModelField) { 13 | const namedType = getNamedType(field.type); 14 | 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $regex: new RegExp(`${value}$`), 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/src/index.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | import { ApolloServer } from 'apollo-server'; 4 | import AM from '@graphex/core'; 5 | import typeDefs from './model'; 6 | import SequelizeExecutor from '@graphex/sequelize-executor'; 7 | import SQ from './db'; 8 | 9 | const schema = new AM().makeExecutableSchema({ 10 | typeDefs, 11 | }); 12 | 13 | const Executor = SequelizeExecutor(SQ); 14 | 15 | const server = new ApolloServer({ 16 | schema, 17 | introspection: true, 18 | playground: true, 19 | context: () => ({ 20 | queryExecutor: async (params) => { 21 | // console.log(params); 22 | const res = await Executor(params); 23 | // console.log(res); 24 | return res; 25 | }, 26 | }), 27 | }); 28 | 29 | server.listen().then(({ url }) => { 30 | console.log(`🚀 Server ready at ${url}`); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/not.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class NotSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | const namedType = getNamedType(field.type); 8 | return !isCompositeType(namedType); 9 | } 10 | getFieldName(field: AMModelField) { 11 | return `${field.name}_not`; 12 | } 13 | getFieldType(field: AMModelField) { 14 | const namedType = getNamedType(field.type); 15 | 16 | if (!isCompositeType(namedType)) { 17 | return namedType; 18 | } 19 | } 20 | transformValue(value: any) { 21 | return { 22 | $not: { $eq: value }, 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/startsWith.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class StartsWithSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | return getNamedType(field.type).toString() === 'String'; 8 | } 9 | getFieldName(field: AMModelField) { 10 | return `${field.name}_starts_with`; 11 | } 12 | getFieldType(field: AMModelField) { 13 | const namedType = getNamedType(field.type); 14 | 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $regex: new RegExp(`^${value}`), 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/rootMethods/visitorHandlers/attachDiscriminatorToOperationHandler.ts: -------------------------------------------------------------------------------- 1 | import { AMModelType } from '../../../definitions'; 2 | import { AMSelectorContext } from '../../../execution'; 3 | import { AMOperation } from '../../../execution/operation'; 4 | import { isDiscriminatorRequiredForType } from '../../../utils'; 5 | 6 | export const attachDiscriminatorToOperationHandler = ( 7 | modelType: AMModelType 8 | ) => ({ 9 | amLeave(node, transaction, stack) { 10 | const context = stack.pop() as AMOperation; 11 | if (isDiscriminatorRequiredForType(modelType)) { 12 | if (!context.selector) { 13 | context.setSelector(new AMSelectorContext()); 14 | } 15 | 16 | context.selector.addValue( 17 | modelType.mmDiscriminatorField, 18 | modelType.mmDiscriminator 19 | ); 20 | } 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /packages/core/src/args/first.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInt } from 'graphql'; 2 | import { AMArgumet } from '../definitions'; 3 | import { AMObjectFieldContext } from '../execution/contexts/objectField'; 4 | import { AMOperation } from '../execution/operation'; 5 | 6 | export const firstArg: AMArgumet = { 7 | name: 'first', 8 | type: GraphQLInt, 9 | description: undefined, 10 | defaultValue: undefined, 11 | astNode: undefined, 12 | extensions: undefined, 13 | amEnter(node, transaction, stack) { 14 | const context = new AMObjectFieldContext('arg'); 15 | stack.push(context); 16 | }, 17 | amLeave(node, transaction, stack) { 18 | const context = stack.pop() as AMObjectFieldContext; 19 | const lastInStack = stack.last(); 20 | 21 | if (lastInStack instanceof AMOperation) { 22 | lastInStack.setFirst(context.value as number); 23 | } 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/core/src/args/offset.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInt } from 'graphql'; 2 | import { AMArgumet } from '../definitions'; 3 | import { AMObjectFieldContext } from '../execution/contexts/objectField'; 4 | import { AMOperation } from '../execution/operation'; 5 | 6 | export const offsetArg: AMArgumet = { 7 | name: 'offset', 8 | type: GraphQLInt, 9 | description: undefined, 10 | defaultValue: undefined, 11 | astNode: undefined, 12 | extensions: undefined, 13 | amEnter(node, transaction, stack) { 14 | const context = new AMObjectFieldContext('arg'); 15 | stack.push(context); 16 | }, 17 | amLeave(node, transaction, stack) { 18 | const context = stack.pop() as AMObjectFieldContext; 19 | const lastInStack = stack.last(); 20 | 21 | if (lastInStack instanceof AMOperation) { 22 | lastInStack.setSkip(context.value as number); 23 | } 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/directive-inherit/package0.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/directive-implements", 3 | "version": "0.5.4-alpha.4", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "prepare": "rimraf ./lib && babel ./src --out-dir ./lib" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "graphql-tag": "^2.10.1", 15 | "graphql-tools": "^4.0.5" 16 | }, 17 | "devDependencies": { 18 | "@babel/cli": "^7.1.2", 19 | "@babel/core": "^7.1.2", 20 | "@babel/node": "^7.2.2", 21 | "@babel/plugin-proposal-class-properties": "^7.2.3", 22 | "@babel/plugin-transform-runtime": "^7.2.0", 23 | "@babel/preset-env": "^7.1.0", 24 | "rimraf": "^3.0.0" 25 | }, 26 | "gitHead": "a3ca3faecd58ba20913b072aa09733876c3342bb" 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/gt.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class GTSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | const namedType = getNamedType(field.type); 8 | return ['Int', 'Float', 'Date', 'String'].includes(namedType.toString()); 9 | } 10 | getFieldName(field: AMModelField) { 11 | return `${field.name}_gt`; 12 | } 13 | getFieldType(field: AMModelField) { 14 | const namedType = getNamedType(field.type); 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $gt: value, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/lt.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class LTSelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | const namedType = getNamedType(field.type); 8 | return ['Int', 'Float', 'Date', 'String'].includes(namedType.toString()); 9 | } 10 | getFieldName(field: AMModelField) { 11 | return `${field.name}_lt`; 12 | } 13 | getFieldType(field: AMModelField) { 14 | const namedType = getNamedType(field.type); 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $lt: value, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/ra-data/src/utils/computeAddRemoveUpdate.ts: -------------------------------------------------------------------------------- 1 | import difference from 'lodash/difference'; 2 | 3 | type ID = string; 4 | 5 | const formatId = (id: ID) => ({ id }); 6 | 7 | export const computeFieldsToAdd = (oldIds: ID[], newIds: ID[]) => { 8 | return difference(newIds, oldIds).map(formatId); 9 | }; 10 | 11 | export const computeFieldsToRemove = (oldIds: ID[], newIds: ID[]) => { 12 | return difference(oldIds, newIds).map(formatId); 13 | }; 14 | 15 | export const computeFieldsToUpdate = (oldIds: ID[], newIds: ID[]) => { 16 | return oldIds.filter(oldId => newIds.includes(oldId)).map(formatId); 17 | }; 18 | 19 | export const computeFieldsToAddRemoveUpdate = (oldIds: ID[], newIds: ID[]) => ({ 20 | fieldsToAdd: computeFieldsToAdd(oldIds, newIds), 21 | fieldsToRemove: computeFieldsToRemove(oldIds, newIds), 22 | fieldsToUpdate: computeFieldsToUpdate(oldIds, newIds), 23 | }); 24 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/gte.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class GTESelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | const namedType = getNamedType(field.type); 8 | return ['Int', 'Float', 'Date', 'String'].includes(namedType.toString()); 9 | } 10 | getFieldName(field: AMModelField) { 11 | return `${field.name}_gte`; 12 | } 13 | getFieldType(field: AMModelField) { 14 | const namedType = getNamedType(field.type); 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $gte: value, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/lte.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, isCompositeType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | 5 | export class LTESelector extends AMQuerySelectorFieldFactory { 6 | isApplicable(field: AMModelField) { 7 | const namedType = getNamedType(field.type); 8 | return ['Int', 'Float', 'Date', 'String'].includes(namedType.toString()); 9 | } 10 | getFieldName(field: AMModelField) { 11 | return `${field.name}_lte`; 12 | } 13 | getFieldType(field: AMModelField) { 14 | const namedType = getNamedType(field.type); 15 | if (!isCompositeType(namedType)) { 16 | return namedType; 17 | } 18 | } 19 | transformValue(value: any) { 20 | return { 21 | $lte: value, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/example-federation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/example-federation", 3 | "version": "1.2.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "yarn dev", 8 | "dev": "nodemon", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "private": true, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@apollo/federation": "^0.25.1", 17 | "@apollo/gateway": "^0.28.2", 18 | "@graphex/acl": "0.7.0", 19 | "@graphex/core": "0.7.0", 20 | "@graphex/directive-implements": "0.7.0", 21 | "apollo-server": "^2.2.6", 22 | "graphql": "14.6.0", 23 | "mongodb": "^3.1.10", 24 | "mongodb-memory-server": "^6.9.6" 25 | }, 26 | "devDependencies": { 27 | "nodemon": "^2.0.7", 28 | "ts-node": "^10.0.0", 29 | "typescript": "^4.3.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/objectField.ts: -------------------------------------------------------------------------------- 1 | import { AMContext } from '../context'; 2 | import { AMObjectFieldValueType, AMModelField } from '../../definitions'; 3 | 4 | export class AMObjectFieldContext extends AMContext { 5 | fieldName: string; 6 | field: AMModelField; 7 | value: AMObjectFieldValueType; 8 | nested = false; 9 | 10 | constructor(fieldName?: string, field?: AMModelField) { 11 | super(); 12 | this.fieldName = fieldName; 13 | this.field = field; 14 | } 15 | 16 | setValue(value: AMObjectFieldValueType) { 17 | this.value = value; 18 | } 19 | 20 | addValue(key: string, value: any) { 21 | if (!(this.value instanceof Object)) { 22 | this.value = {}; 23 | } 24 | this.value[key] = value; 25 | } 26 | 27 | setNested(nested: boolean) { 28 | this.nested = nested; 29 | } 30 | 31 | isNested() { 32 | return this.nested; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/execution/operations/deleteOperation.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from '../operation'; 2 | import { AMDBExecutor, AMDBExecutorOperationType } from '../../definitions'; 3 | import { completeAMResultPromise } from '../resultPromise/utils'; 4 | 5 | export class AMDeleteOperation extends AMOperation { 6 | async execute(executor: AMDBExecutor) { 7 | executor({ 8 | type: this.many 9 | ? AMDBExecutorOperationType.DELETE_MANY 10 | : AMDBExecutorOperationType.DELETE_ONE, 11 | collection: this.collectionName, 12 | selector: await completeAMResultPromise( 13 | this.selector ? this.selector.selector : undefined 14 | ), 15 | fields: await completeAMResultPromise( 16 | this.fieldsSelection ? this.fieldsSelection.fields : undefined 17 | ), 18 | }) 19 | .then(this._result.resolve) 20 | .catch(this._result.reject); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/src/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import { Model, Table, Column, DataType } from 'sequelize-typescript'; 2 | 3 | @Table({ 4 | tableName: 'user', 5 | timestamps: false, 6 | }) 7 | export default class User extends Model { 8 | @Column({ 9 | primaryKey: true, 10 | type: DataType.INTEGER, 11 | autoIncrement: true, 12 | }) 13 | id!: number; 14 | 15 | @Column({ 16 | allowNull: true, 17 | type: DataType.STRING, 18 | }) 19 | username?: string; 20 | 21 | @Column({ 22 | field: 'created_at', 23 | allowNull: true, 24 | type: DataType.DATE, 25 | }) 26 | createdAt?: Date; 27 | 28 | @Column({ 29 | field: 'updated_at', 30 | allowNull: true, 31 | type: DataType.DATE, 32 | }) 33 | updatedAt?: Date; 34 | 35 | @Column({ 36 | field: 'deleted_at', 37 | allowNull: true, 38 | type: DataType.DATE, 39 | }) 40 | deletedAt?: Date; 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/output/fieldFactories/aggregateNumericFields.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType } from 'graphql'; 2 | import { 3 | AMFieldFactory, 4 | AMModelField, 5 | AMModelType, 6 | } from '../../../../definitions'; 7 | import { isEmbeddedType } from '../../../../utils'; 8 | import { defaultSelectionVisitorHandler } from '../visitorHandlers'; 9 | 10 | export class AMAggregateNumericFieldsFieldFactory extends AMFieldFactory { 11 | isApplicable(field: AMModelField) { 12 | return isEmbeddedType(getNamedType(field.type)); 13 | } 14 | getFieldName(field: AMModelField): string { 15 | return field.name; 16 | } 17 | getField(field: AMModelField) { 18 | return { 19 | type: this.configResolver.resolveType( 20 | getNamedType(field.type) as AMModelType, 21 | this.links.embedded 22 | ), 23 | ...defaultSelectionVisitorHandler(field.dbName), 24 | } as AMModelField; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/directive-inherit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/directive-inherit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "prepare": "rimraf ./lib && babel ./src --out-dir ./lib" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "graphql-tag": "^2.10.1", 18 | "graphql-tools": "^4.0.5", 19 | "ramda": "^0.26.1" 20 | }, 21 | "devDependencies": { 22 | "@babel/cli": "^7.1.2", 23 | "@babel/core": "^7.1.2", 24 | "@babel/node": "^7.2.2", 25 | "@babel/plugin-proposal-class-properties": "^7.2.3", 26 | "@babel/plugin-transform-runtime": "^7.2.0", 27 | "@babel/preset-env": "^7.1.0", 28 | "rimraf": "^3.0.0" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/execution/contexts/listValue.ts: -------------------------------------------------------------------------------- 1 | import { AMContext } from '../context'; 2 | import { AMObjectFieldValueType } from '../../definitions'; 3 | import { toArray } from '../../utils'; 4 | 5 | export class AMListValueContext extends AMContext { 6 | values: AMObjectFieldValueType[] = []; 7 | proxy = false; 8 | 9 | constructor(values?: AMObjectFieldValueType[]) { 10 | super(); 11 | if (values) { 12 | this.values = values; 13 | } 14 | } 15 | 16 | addValue(value: AMObjectFieldValueType | AMObjectFieldValueType[]) { 17 | if (this.proxy) { 18 | this.values = toArray(value); 19 | } else { 20 | this.values.push(value); 21 | } 22 | } 23 | 24 | setValues(values: AMObjectFieldValueType[]) { 25 | this.values = values; 26 | } 27 | 28 | setProxy(value: boolean) { 29 | this.proxy = value; 30 | } 31 | 32 | toJSON() { 33 | return this.values; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/example-server-sequelize", 3 | "version": "1.2.4", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "private": true, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@graphex/core": "1.2.4", 16 | "apollo-server": "^2.24.0", 17 | "dotenv": "^9.0.0", 18 | "graphql": "14.6.0", 19 | "pg": "^8.6.0", 20 | "reflect-metadata": "^0.1.13", 21 | "sequelize-typescript": "^2.1.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/cli": "^7.2.0", 25 | "@babel/core": "^7.2.0", 26 | "@babel/node": "^7.2.0", 27 | "@babel/preset-env": "^7.2.0", 28 | "babel-plugin-import-graphql": "^2.6.2", 29 | "nodemon": "^2.0.7", 30 | "ts-node": "^9.1.1", 31 | "typescript": "^4.2.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/src/db.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize-typescript'; 2 | 3 | const sequelizePostgres = new Sequelize( 4 | process.env.DATABASE_NAME, // 5 | null, 6 | null, 7 | { 8 | host: process.env.DATABASE_HOST, 9 | port: Number(process.env.DATABASE_PORT), 10 | username: process.env.DATABASE_USER, 11 | password: process.env.DATABASE_PASSWORD, 12 | dialect: 'postgres', 13 | quoteIdentifiers: false, 14 | logging: true, 15 | pool: { max: 1, min: 1, idle: 10000 }, 16 | models: [__dirname + '/models/**/*.model.ts'], 17 | } 18 | ); 19 | 20 | sequelizePostgres 21 | .authenticate() 22 | .then(async () => { 23 | console.log('Connection to PostgreSQL has been established successfully.'); 24 | await sequelizePostgres.sync({ force: false }); 25 | }) 26 | .catch((err) => { 27 | console.log('Unable to connect to the PostgreSQL database:', err); 28 | }); 29 | 30 | export default sequelizePostgres; 31 | -------------------------------------------------------------------------------- /packages/core/src/execution/operations/readOperation.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from '../operation'; 2 | import { AMDBExecutor, AMDBExecutorOperationType } from '../../definitions'; 3 | import { completeAMResultPromise } from '../resultPromise/utils'; 4 | 5 | export class AMReadOperation extends AMOperation { 6 | async execute(executor: AMDBExecutor) { 7 | executor({ 8 | type: this.many 9 | ? AMDBExecutorOperationType.FIND 10 | : AMDBExecutorOperationType.FIND_ONE, 11 | collection: this.collectionName, 12 | selector: await completeAMResultPromise( 13 | this.selector ? this.selector.selector : undefined 14 | ), 15 | fields: await completeAMResultPromise( 16 | this.fieldsSelection ? this.fieldsSelection.fields : undefined 17 | ), 18 | options: { sort: this.orderBy, limit: this.first, skip: this.skip }, 19 | }) 20 | .then(this._result.resolve) 21 | .catch(this._result.reject); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/prepare/utils.ts: -------------------------------------------------------------------------------- 1 | import { find } from 'ramda'; 2 | import { print, ArgumentNode } from 'graphql'; 3 | 4 | /** 5 | * Code copied from here 6 | * https://github.com/graphql/graphql-js/blob/v14.6.0/src/validation/rules/OverlappingFieldsCanBeMerged.js#L643 7 | */ 8 | 9 | export function sameArguments( 10 | arguments1: ReadonlyArray, 11 | arguments2: ReadonlyArray 12 | ): boolean { 13 | if (arguments1.length !== arguments2.length) { 14 | return false; 15 | } 16 | return arguments1.every(argument1 => { 17 | const argument2 = find( 18 | argument => argument.name.value === argument1.name.value, 19 | arguments2 20 | ); 21 | if (!argument2) { 22 | return false; 23 | } 24 | return sameValue(argument1.value, argument2.value); 25 | }); 26 | } 27 | 28 | function sameValue(value1, value2) { 29 | return print(value1) === print(value2); 30 | } 31 | 32 | /** 33 | * End of copied code 34 | */ 35 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/externalRelations/inputTypes/createOneRequiredRelationOutside.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AMInputFieldConfigMap, 3 | AMInputObjectType, 4 | AMModelType, 5 | AMTypeFactory, 6 | } from '../../../definitions'; 7 | 8 | export class AMCreateOneRequiredRelationOutsideTypeFactory extends AMTypeFactory { 9 | getTypeName(modelType: AMModelType): string { 10 | return `${modelType.name}CreateOneRequiredRelationOutsideInput`; 11 | } 12 | getType(modelType: AMModelType) { 13 | return new AMInputObjectType({ 14 | name: this.getTypeName(modelType), 15 | fields: () => { 16 | const fields = { 17 | connect: { 18 | type: this.configResolver.resolveInputType(modelType, [ 19 | 'whereUnique', 20 | 'interfaceWhereUnique', 21 | ]), 22 | }, 23 | } as AMInputFieldConfigMap; 24 | 25 | return fields; 26 | }, 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/directive-implements/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/directive-implements", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "prepare": "rimraf ./lib && babel ./src --out-dir ./lib" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "graphql-tag": "^2.10.1", 18 | "graphql-tools": "^4.0.5" 19 | }, 20 | "devDependencies": { 21 | "@babel/cli": "^7.1.2", 22 | "@babel/core": "^7.1.2", 23 | "@babel/node": "^7.2.2", 24 | "@babel/plugin-proposal-class-properties": "^7.2.3", 25 | "@babel/plugin-transform-runtime": "^7.2.0", 26 | "@babel/preset-env": "^7.1.0", 27 | "rimraf": "^3.0.0" 28 | }, 29 | "publishConfig": { 30 | "access": "public" 31 | }, 32 | "gitHead": "a3ca3faecd58ba20913b072aa09733876c3342bb" 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/resolve.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode, GraphQLResolveInfo } from 'graphql'; 2 | import { AMTransaction } from './execution/transaction'; 3 | import { AMVisitor } from './execution/visitor'; 4 | // import Serializer from './serializer'; 5 | 6 | export const resolve = (parent, args, context, info: GraphQLResolveInfo) => { 7 | context.fieldsRegistry = new Map(); 8 | const transaction = new AMTransaction(context.fieldsRegistry); 9 | 10 | const rq: DocumentNode = { 11 | kind: 'Document', 12 | definitions: [ 13 | { 14 | ...info.operation, 15 | selectionSet: { 16 | kind: 'SelectionSet', 17 | selections: info.fieldNodes, 18 | }, 19 | }, 20 | ], 21 | }; 22 | 23 | AMVisitor.visit( 24 | info.schema, 25 | rq, 26 | info.variableValues, 27 | transaction, 28 | info.fragments 29 | ); 30 | // console.log(Serializer.print(transaction)); 31 | return transaction.execute(context.queryExecutor); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/type-geojson/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/type-geojson", 3 | "version": "1.2.4", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "prepare": "rimraf ./lib && babel --extensions '.ts,.js' ./src --out-dir ./lib", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "dependencies": { 14 | "@graphex/core": "1.2.4" 15 | }, 16 | "devDependencies": { 17 | "@babel/cli": "^7.1.2", 18 | "@babel/core": "^7.1.2", 19 | "@babel/node": "^7.2.2", 20 | "@babel/plugin-proposal-class-properties": "^7.2.3", 21 | "@babel/plugin-proposal-optional-chaining": "^7.7.5", 22 | "@babel/plugin-transform-runtime": "^7.2.0", 23 | "@babel/preset-env": "^7.1.0", 24 | "@babel/preset-typescript": "^7.9.0", 25 | "rimraf": "^2.6.3" 26 | }, 27 | "publishConfig": { 28 | "access": "public" 29 | }, 30 | "keywords": [], 31 | "author": "", 32 | "license": "ISC" 33 | } 34 | -------------------------------------------------------------------------------- /packages/ra-data/src/introspectionOptions.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'lodash/camelCase'; 2 | import pluralize from 'pluralize'; 3 | import { 4 | CREATE, 5 | DELETE, 6 | GET_LIST, 7 | GET_MANY, 8 | GET_MANY_REFERENCE, 9 | GET_ONE, 10 | UPDATE, 11 | } from 'react-admin'; 12 | import { Resource } from './definitions'; 13 | 14 | export default { 15 | operationNames: { 16 | [GET_LIST]: (resource: Resource) => 17 | `${pluralize(camelCase(resource.name))}`, 18 | [GET_ONE]: (resource: Resource) => `${camelCase(resource.name)}`, 19 | [GET_MANY]: (resource: Resource) => 20 | `${pluralize(camelCase(resource.name))}`, 21 | [GET_MANY_REFERENCE]: (resource: Resource) => 22 | `${pluralize(camelCase(resource.name))}`, 23 | [CREATE]: (resource: Resource) => `create${resource.name}`, 24 | [UPDATE]: (resource: Resource) => `update${resource.name}`, 25 | [DELETE]: (resource: Resource) => `delete${resource.name}`, 26 | }, 27 | exclude: undefined, 28 | include: undefined, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/acl/__tests__/matchingTypes.test.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import AMM from '@graphex/core'; 3 | import { matchingTypes, extractAbstractTypes } from '../src/utils'; 4 | 5 | const schema = new AMM({}).makeExecutableSchema({ 6 | typeDefs: gql` 7 | interface Node { 8 | id: ID @id 9 | } 10 | 11 | type Post implements Node @model { 12 | id: ID @id 13 | title: String 14 | } 15 | 16 | type Comment implements Node @model { 17 | id: ID @id 18 | message: String 19 | } 20 | `, 21 | }); 22 | 23 | test('Exact', () => { 24 | expect(matchingTypes(schema, new RegExp('^(?:Post|Comment)$'))) 25 | .toMatchInlineSnapshot(` 26 | Array [ 27 | "Post", 28 | "Comment", 29 | ] 30 | `); 31 | }); 32 | 33 | test('Interface', () => { 34 | expect( 35 | extractAbstractTypes(schema, matchingTypes(schema, new RegExp('^Node$'))) 36 | ).toMatchInlineSnapshot(` 37 | Array [ 38 | "Node", 39 | "Post", 40 | "Comment", 41 | ] 42 | `); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/ra-data/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 | # [1.0.0](https://github.com/apollo-model/apollo-model/compare/v0.8.0...v1.0.0) (2021-06-06) 7 | 8 | 9 | ### Features 10 | 11 | * remove embedded directive ([e60dfca](https://github.com/apollo-model/apollo-model/commit/e60dfca)), closes [#16](https://github.com/apollo-model/apollo-model/issues/16) 12 | 13 | 14 | ### BREAKING CHANGES 15 | 16 | * directive embedded was removed 17 | 18 | 19 | 20 | 21 | 22 | # [0.8.0](https://github.com/apollo-model/apollo-model/compare/v0.7.0...v0.8.0) (2021-06-02) 23 | 24 | **Note:** Version bump only for package @graphex/ra-data-apollo-model 25 | 26 | 27 | 28 | 29 | 30 | # [0.7.0-alpha.3](https://github.com/apollo-model/apollo-model/compare/v0.7.0-alpha.2...v0.7.0-alpha.3) (2021-06-02) 31 | 32 | **Note:** Version bump only for package @graphex/ra-data-apollo-model 33 | -------------------------------------------------------------------------------- /packages/example-now/index.js: -------------------------------------------------------------------------------- 1 | const { ApolloServer } = require('apollo-server-micro'); 2 | import ApolloModelMongo, { QueryExecutor } from '@graphex/core'; 3 | import { MongoClient, ObjectID } from 'mongodb'; 4 | import typeDefs from './model.graphql'; 5 | 6 | let DB = null; 7 | 8 | export const connectToDatabase = () => { 9 | if (DB && DB.serverConfig.isConnected()) { 10 | return Promise.resolve(DB); 11 | } 12 | return MongoClient.connect(process.env.MONGO_URL, { 13 | useNewUrlParser: true, 14 | }).then((client) => { 15 | DB = client.db(process.env.MONGO_DB); 16 | return DB; 17 | }); 18 | }; 19 | 20 | let schema = new ApolloModelMongo({ 21 | queryExecutor: QueryExecutor(connectToDatabase), 22 | }).makeExecutableSchema({ 23 | typeDefs, 24 | }); 25 | 26 | let server = new ApolloServer({ 27 | schema, 28 | introspection: true, 29 | playground: true, 30 | }); 31 | 32 | let Handler = server.createHandler({ path: '/' }); 33 | 34 | module.exports = (req, res) => { 35 | return Handler(req, res); 36 | }; 37 | -------------------------------------------------------------------------------- /packages/core/src/execution/operations/createOperation.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from '../operation'; 2 | import { AMDBExecutor, AMDBExecutorOperationType } from '../../definitions'; 3 | import { completeAMResultPromise } from '../resultPromise/utils'; 4 | 5 | export class AMCreateOperation extends AMOperation { 6 | async execute(executor: AMDBExecutor) { 7 | executor({ 8 | type: this.many 9 | ? AMDBExecutorOperationType.INSERT_MANY 10 | : AMDBExecutorOperationType.INSERT_ONE, 11 | collection: this.collectionName, 12 | ...(this.data 13 | ? { doc: await completeAMResultPromise(this.data.data) } 14 | : undefined), 15 | ...(this.dataList 16 | ? { docs: await completeAMResultPromise(this.dataList.values) } 17 | : undefined), 18 | fields: await completeAMResultPromise( 19 | this.fieldsSelection ? this.fieldsSelection.fields : undefined 20 | ), 21 | }) 22 | .then(this._result.resolve) 23 | .catch(this._result.reject); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/subdocuments/inputTypes/updateOneNested.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInputObjectType } from 'graphql'; 2 | import { 3 | AMInputObjectType, 4 | AMModelType, 5 | AMTypeFactory, 6 | } from '../../../definitions'; 7 | 8 | export class AMUpdateOneNestedTypeFactory extends AMTypeFactory { 9 | getTypeName(modelType: AMModelType): string { 10 | return `${modelType.name}UpdateOneNestedInput`; 11 | } 12 | getType(modelType: AMModelType) { 13 | return new AMInputObjectType({ 14 | name: this.getTypeName(modelType), 15 | fields: () => { 16 | const fields = { 17 | create: { 18 | type: this.configResolver.resolveInputType(modelType, [ 19 | 'create', 20 | 'interfaceCreate', 21 | ]), 22 | }, 23 | update: { 24 | type: this.configResolver.resolveInputType(modelType, ['update']), 25 | }, 26 | }; 27 | 28 | return fields; 29 | }, 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/example-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/example-gateway", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon --exec babel-node src/index.js", 8 | "default": "nodemon --exec babel-node default-server/index.js", 9 | "test": "jest", 10 | "prepare": "rimraf ./lib && babel ./src --out-dir ./lib" 11 | }, 12 | "private": true, 13 | "devDependencies": { 14 | "@babel/cli": "^7.1.2", 15 | "@babel/core": "^7.1.2", 16 | "@babel/node": "^7.2.2", 17 | "@babel/plugin-proposal-class-properties": "^7.2.3", 18 | "@babel/plugin-proposal-pipeline-operator": "^7.5.0", 19 | "@babel/plugin-transform-runtime": "^7.2.0", 20 | "@babel/preset-env": "^7.1.0", 21 | "rimraf": "^2.6.3" 22 | }, 23 | "keywords": [], 24 | "author": "", 25 | "license": "ISC", 26 | "dependencies": { 27 | "apollo-link": "^1.2.12", 28 | "apollo-link-http": "^1.5.15", 29 | "express": "^4.17.1", 30 | "express-http-proxy": "^1.5.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/mongodb-executor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/mongodb-executor", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "prepare": "rimraf ./lib && tsc" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@babel/cli": "^7.1.2", 18 | "@babel/core": "^7.1.2", 19 | "@babel/node": "^7.2.2", 20 | "@babel/plugin-proposal-class-properties": "^7.2.3", 21 | "@babel/plugin-transform-runtime": "^7.2.0", 22 | "@babel/preset-env": "^7.1.0", 23 | "dataloader": "^1.4.0", 24 | "lodash": "^4.17.15", 25 | "object-hash": "^1.3.1", 26 | "pluralize": "^8.0.0", 27 | "rimraf": "^2.6.3" 28 | }, 29 | "publishConfig": { 30 | "access": "public" 31 | }, 32 | "devDependencies": { 33 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", 34 | "typescript": "^4.2.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/example-server/src/index.ts: -------------------------------------------------------------------------------- 1 | import Graphex from '@graphex/core'; 2 | import { applyRole } from '@graphex/acl'; 3 | import QueryExecutor from '@graphex/mongodb-executor'; 4 | import * as DirectiveImplements from '@graphex/directive-implements'; 5 | import { ApolloServer } from 'apollo-server'; 6 | 7 | import { getDb } from './db/connection'; 8 | import { runSeed } from './db/seed'; 9 | import typeDefs from './model'; 10 | import { anonymusRole } from './roles/anonymus'; 11 | 12 | const schema = new Graphex({ 13 | options: { aclWhere: true }, 14 | modules: [DirectiveImplements], 15 | }).makeExecutableSchema({ 16 | typeDefs, 17 | }); 18 | 19 | const anonymusSchema = applyRole(schema, anonymusRole); 20 | 21 | const aclServer = new ApolloServer({ 22 | schema: anonymusSchema, 23 | introspection: true, 24 | playground: true, 25 | context: () => ({ 26 | queryExecutor: QueryExecutor(getDb), 27 | }), 28 | }); 29 | 30 | aclServer.listen({ port: 4000 }).then(({ url }) => { 31 | console.log(`🚀 Server ready at ${url}`); 32 | }); 33 | 34 | runSeed(); 35 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/subdocuments/inputTypes/createManyNested.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLList } from 'graphql'; 2 | import { 3 | AMInputObjectType, 4 | AMModelType, 5 | AMTypeFactory, 6 | } from '../../../definitions'; 7 | 8 | export class AMCreateManyNestedTypeFactory extends AMTypeFactory { 9 | getTypeName(modelType: AMModelType): string { 10 | return `${modelType.name}CreateManyNestedInput`; 11 | } 12 | getType(modelType: AMModelType) { 13 | return new AMInputObjectType({ 14 | name: this.getTypeName(modelType), 15 | fields: () => { 16 | const fields = { 17 | create: { 18 | type: new GraphQLList( 19 | this.configResolver.resolveInputType(modelType, [ 20 | 'create', 21 | 'interfaceCreate', 22 | ]) 23 | ), 24 | // we can keep amEnter and amLeave empty because child object will pass value to parent directly 25 | }, 26 | }; 27 | 28 | return fields; 29 | }, 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "prettier"], 4 | "extends": [ 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier", 7 | "prettier/@typescript-eslint" 8 | ], 9 | "rules": { 10 | "semi": "off", 11 | "max-len": [ 12 | "warn", 13 | { 14 | "tabWidth": 2, 15 | "code": 120, 16 | "ignoreStrings": true, 17 | "ignoreTemplateLiterals": true 18 | } 19 | ], 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/semi": [2, "always"], 23 | "comma-dangle": ["error", "always-multiline"], 24 | "@typescript-eslint/no-use-before-define": [ 25 | "error", 26 | { 27 | "functions": false, 28 | "classes": false, 29 | "variables": false 30 | } 31 | ], 32 | "@typescript-eslint/camelcase": [ 33 | "error", 34 | { 35 | "properties": "never" 36 | } 37 | ], 38 | "prettier/prettier": "error" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest All --watch", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": ["--runInBand", "--watch"], 10 | "console": "integratedTerminal", 11 | "internalConsoleOptions": "neverOpen", 12 | "disableOptimisticBPs": true, 13 | "windows": { 14 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 15 | }, 16 | "timeout": 30000 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Jest Current File", 22 | "program": "${workspaceFolder}/node_modules/.bin/jest", 23 | "args": ["${fileBasenameNoExtension}", "--config", "jest.config.js"], 24 | "console": "integratedTerminal", 25 | "internalConsoleOptions": "neverOpen", 26 | "disableOptimisticBPs": true, 27 | "windows": { 28 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 29 | }, 30 | "timeout": 30000 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/federation/types/entityType.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { GraphQLUnionType } from 'graphql'; 3 | import { AMModelType, IAMTypeFactory } from '../../../definitions'; 4 | import { getDirective } from '../../../utils'; 5 | 6 | export const AMFederationEntityTypeFactory: IAMTypeFactory = { 7 | getTypeName(): string { 8 | return `_Entity`; 9 | }, 10 | getType(modelType, schemaInfo) { 11 | const keyTypes = []; 12 | 13 | Object.values(schemaInfo.schema.getTypeMap()).forEach( 14 | (type: AMModelType) => { 15 | const typeWrap = new TypeWrap(type); 16 | if ( 17 | ((getDirective(type, 'model') || 18 | typeWrap.interfaceWithDirective('model')) && 19 | !(typeWrap.isAbstract() || typeWrap.isInterface())) || 20 | getDirective(type, 'federated') 21 | ) { 22 | keyTypes.push(type); 23 | } 24 | } 25 | ); 26 | 27 | return new GraphQLUnionType({ 28 | name: '_Entity', 29 | types: keyTypes, 30 | }); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/some.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { getNamedType, isCompositeType } from 'graphql'; 3 | import { AMModelField, AMModelType } from '../../../../definitions'; 4 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 5 | 6 | export class SomeSelector extends AMQuerySelectorFieldFactory { 7 | isApplicable(field: AMModelField) { 8 | const typeWrap = new TypeWrap(field.type); 9 | return !field.relation && !field.relationOutside && typeWrap.isMany(); 10 | } 11 | getFieldName(field: AMModelField) { 12 | return `${field.name}_some`; 13 | } 14 | getFieldType(field: AMModelField) { 15 | const namedType = getNamedType(field.type); 16 | if (!isCompositeType(namedType)) { 17 | return namedType; 18 | } else { 19 | return this.configResolver.resolveInputType( 20 | namedType as AMModelType, 21 | this.links.where 22 | ); 23 | } 24 | } 25 | transformValue(value: any) { 26 | return { 27 | $elemMatch: value, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/subdocuments/fieldFactories/createNested.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { 3 | AMInputField, 4 | AMInputFieldFactory, 5 | AMModelField, 6 | AMModelType, 7 | } from '../../../definitions'; 8 | import { isSubdocumentField } from '../../../utils'; 9 | import { defaultObjectFieldVisitorHandler } from '../../model/input/inputTypes/visitorHandlers'; 10 | 11 | export class AMCreateNestedFieldFactory extends AMInputFieldFactory { 12 | isApplicable(field: AMModelField) { 13 | return isSubdocumentField(field); 14 | } 15 | getFieldName(field: AMModelField) { 16 | return field.name; 17 | } 18 | getField(field: AMModelField) { 19 | const typeWrap = new TypeWrap(field.type); 20 | const type = this.configResolver.resolveInputType( 21 | typeWrap.realType() as AMModelType, 22 | typeWrap.isMany() ? this.links.many : this.links.one 23 | ); 24 | 25 | return { 26 | name: this.getFieldName(field), 27 | type, 28 | ...defaultObjectFieldVisitorHandler(field.dbName), 29 | } as AMInputField; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/externalRelations/fieldFactories/updateRelationOutside.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { 3 | AMInputFieldFactory, 4 | AMModelField, 5 | AMModelType, 6 | } from '../../../definitions'; 7 | 8 | export class AMUpdateRelationOutsideFieldFactory extends AMInputFieldFactory { 9 | isApplicable(field: AMModelField) { 10 | return Boolean(field.relationOutside); 11 | } 12 | getFieldName(field) { 13 | return field.name; 14 | } 15 | getField(field: AMModelField) { 16 | const typeWrap = new TypeWrap(field.type); 17 | const isMany = typeWrap.isMany(); 18 | const type = this.configResolver.resolveInputType( 19 | typeWrap.realType() as AMModelType, 20 | isMany 21 | ? 'updateManyRelationOutside' 22 | : // : isRequired 23 | // ? AMUpdateOneRequiredRelationTypeFactory 24 | 'updateOneRelationOutside' 25 | ); 26 | 27 | return { 28 | name: this.getFieldName(field), 29 | extensions: undefined, 30 | type, 31 | dbName: field.relationOutside.storeField, 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/ra-data/__tests__/isList.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind } from 'graphql'; 2 | import isList from '../src/utils/isList'; 3 | 4 | describe('isList', () => { 5 | it('returns the correct type for SCALAR types', () => { 6 | expect(isList({ name: 'foo', kind: TypeKind.SCALAR })).toEqual(false); 7 | }); 8 | it('returns the correct type for NON_NULL types', () => { 9 | expect( 10 | isList({ 11 | kind: TypeKind.NON_NULL, 12 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 13 | }) 14 | ).toEqual(false); 15 | }); 16 | it('returns the correct type for LIST types', () => { 17 | expect( 18 | isList({ 19 | kind: TypeKind.LIST, 20 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 21 | }) 22 | ).toEqual(true); 23 | }); 24 | it('returns the correct type for NON_NULL LIST types', () => { 25 | expect( 26 | isList({ 27 | kind: TypeKind.NON_NULL, 28 | ofType: { 29 | kind: TypeKind.LIST, 30 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 31 | }, 32 | }) 33 | ).toEqual(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/core/src/config/typeFactories.ts: -------------------------------------------------------------------------------- 1 | import { AMConnectionTypeFactory } from '../schemaGeneration/model/output/types/connection'; 2 | import { AMAggregateTypeFactory } from '../schemaGeneration/model/output/types/aggregate'; 3 | import { AMAggregateNumericFieldsTypeFactory } from '../schemaGeneration/model/output/types/aggregateNumericFields'; 4 | 5 | export const typeFactories = { 6 | aggregate: { 7 | factory: AMAggregateTypeFactory, 8 | links: { 9 | sum: 'aggregateNumericFields', 10 | min: 'aggregateNumericFields', 11 | max: 'aggregateNumericFields', 12 | }, 13 | }, 14 | aggregateNumericFields: { 15 | factory: AMAggregateNumericFieldsTypeFactory, 16 | links: { 17 | embedded: 'aggregateNumericFields', 18 | }, 19 | dynamicLinks: { 20 | _default: { 21 | fieldFactories: ['aggregateNumericFields'], 22 | }, 23 | Int: { 24 | fieldFactories: ['namedType'], 25 | }, 26 | Float: { 27 | fieldFactories: ['namedType'], 28 | }, 29 | }, 30 | }, 31 | connection: { 32 | factory: AMConnectionTypeFactory, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/externalRelations/fieldFactories/createRelationOutside.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { 3 | AMInputField, 4 | AMInputFieldFactory, 5 | AMModelField, 6 | AMModelType, 7 | } from '../../../definitions'; 8 | 9 | export class AMCreateRelationOutsideFieldFactory extends AMInputFieldFactory { 10 | isApplicable(field: AMModelField) { 11 | return Boolean(field.relationOutside); 12 | } 13 | getFieldName(field: AMModelField) { 14 | return field.name; 15 | } 16 | getField(field: AMModelField) { 17 | const typeWrap = new TypeWrap(field.type); 18 | const isMany = typeWrap.isMany(); 19 | const isRequired = typeWrap.isRequired(); 20 | const type = this.configResolver.resolveInputType( 21 | typeWrap.realType() as AMModelType, 22 | isMany 23 | ? this.links.many 24 | : isRequired 25 | ? this.links.oneRequired 26 | : this.links.one 27 | ); 28 | 29 | return { 30 | name: this.getFieldName(field), 31 | type, 32 | dbName: field.relationOutside.storeField, 33 | } as AMInputField; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/ra-data/__tests__/isRequired.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind } from 'graphql'; 2 | import isRequired from '../src/utils/isRequired'; 3 | 4 | describe('isRequired', () => { 5 | it('returns the correct type for SCALAR types', () => { 6 | expect(isRequired({ name: 'foo', kind: TypeKind.SCALAR })).toEqual(false); 7 | }); 8 | it('returns the correct type for NON_NULL types', () => { 9 | expect( 10 | isRequired({ 11 | kind: TypeKind.NON_NULL, 12 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 13 | }) 14 | ).toEqual(true); 15 | }); 16 | it('returns the correct type for LIST types', () => { 17 | expect( 18 | isRequired({ 19 | kind: TypeKind.LIST, 20 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 21 | }) 22 | ).toEqual(false); 23 | }); 24 | it('returns the correct type for NON_NULL LIST types', () => { 25 | expect( 26 | isRequired({ 27 | kind: TypeKind.NON_NULL, 28 | ofType: { 29 | kind: TypeKind.LIST, 30 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 31 | }, 32 | }) 33 | ).toEqual(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/acl/__tests__/prepare.ts: -------------------------------------------------------------------------------- 1 | import QueryExecutor from '@graphex/mongodb-executor'; 2 | import { MongoClient } from 'mongodb'; 3 | import { MongoMemoryServer } from 'mongodb-memory-server'; 4 | 5 | const util = require('util'); 6 | 7 | export default () => { 8 | let mongod; 9 | let client: MongoClient; 10 | 11 | return { 12 | async start() { 13 | mongod = new MongoMemoryServer(); 14 | const MONGO_URL = await mongod.getConnectionString(); 15 | const MONGO_DB = await mongod.getDbName(); 16 | 17 | let DB = null; 18 | 19 | const connectToDatabase = async () => { 20 | if (DB && DB.serverConfig.isConnected()) { 21 | return DB; 22 | } 23 | client = await MongoClient.connect(MONGO_URL, { 24 | useNewUrlParser: true, 25 | useUnifiedTopology: true, 26 | }); 27 | DB = await client.db(MONGO_DB); 28 | return DB; 29 | }; 30 | 31 | const QE = QueryExecutor(connectToDatabase); 32 | return { QE, connectToDatabase }; 33 | }, 34 | async stop() { 35 | await client.close(); 36 | await mongod.stop(); 37 | }, 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/directives/discriminator.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | import { getDirective } from '../../../utils'; 4 | 5 | export const typeDef = gql` 6 | directive @discriminator(value: String) on OBJECT | INTERFACE 7 | `; 8 | 9 | class Discriminator extends SchemaDirectiveVisitor { 10 | visitInterface(iface) { 11 | const { value } = this.args; 12 | iface.mmDiscriminatorField = value; 13 | } 14 | 15 | visitObject(object) { 16 | const { value } = this.args; 17 | 18 | //if interface has been initialized already we shuld update it's mmDiscriminatorMap 19 | object.getInterfaces().forEach(iface => { 20 | if ( 21 | getDirective(iface, 'model') && 22 | iface.mmDiscriminatorMap && 23 | object.mmDiscriminator 24 | ) { 25 | delete iface.mmDiscriminatorMap[object.mmDiscriminator]; 26 | iface.mmDiscriminatorMap[value] = object.name; 27 | } 28 | }); 29 | object.mmDiscriminator = value; 30 | } 31 | } 32 | 33 | export const schemaDirectives = { 34 | discriminator: Discriminator, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/core/src/schemaInfo.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, GraphQLNamedType } from 'graphql'; 2 | import { 3 | AMSchemaInfo, 4 | AMResolveFactoryType, 5 | AMModelType, 6 | IAMTypeFactory, 7 | AMOptions, 8 | } from './definitions'; 9 | 10 | export const makeSchemaInfo = ( 11 | schema: GraphQLSchema, 12 | options?: AMOptions 13 | ): AMSchemaInfo => { 14 | const resolveType = (typeName: string) => { 15 | return schema.getType(typeName); 16 | }; 17 | 18 | const resolveFactoryType: AMResolveFactoryType = ( 19 | inputType: AMModelType, 20 | typeFactory: IAMTypeFactory 21 | ) => { 22 | const typeName = typeFactory.getTypeName(inputType); 23 | let type = schema.getType(typeName) as T; 24 | if (!type) { 25 | type = typeFactory.getType(inputType, { 26 | schema, 27 | resolveType, 28 | resolveFactoryType, 29 | options: options ? options : {}, 30 | }); 31 | schema.getTypeMap()[typeName] = type; 32 | } 33 | return type; 34 | }; 35 | 36 | return { 37 | schema, 38 | resolveType, 39 | resolveFactoryType, 40 | options: options ? options : {}, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/subdocuments/inputTypes/createOneNested.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInputObjectType } from 'graphql'; 2 | import { 3 | AMInputObjectType, 4 | AMModelType, 5 | AMTypeFactory, 6 | } from '../../../definitions'; 7 | 8 | export class AMCreateOneNestedTypeFactory extends AMTypeFactory { 9 | getTypeName(modelType: AMModelType): string { 10 | if ( 11 | `${modelType.name}CreateOneNestedInput` === 12 | 'AggregateNumericFieldsInCommentCreateOneNestedInput' 13 | ) { 14 | throw new Error(); 15 | } 16 | return `${modelType.name}CreateOneNestedInput`; 17 | } 18 | getType(modelType: AMModelType) { 19 | return new AMInputObjectType({ 20 | name: this.getTypeName(modelType), 21 | fields: () => { 22 | const fields = { 23 | create: { 24 | type: this.configResolver.resolveInputType(modelType, [ 25 | 'create', 26 | 'interfaceCreate', 27 | ]), 28 | }, 29 | }; 30 | 31 | return fields; 32 | }, 33 | // we can keep this empty because child object will pass value to parent directly 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/all.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { getNamedType, GraphQLList, isCompositeType } from 'graphql'; 3 | import { AMModelField, AMModelType } from '../../../../definitions'; 4 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 5 | import { makeArray } from '../fieldFactories/utils'; 6 | 7 | export class AllSelector extends AMQuerySelectorFieldFactory { 8 | isApplicable(field: AMModelField) { 9 | const typeWrap = new TypeWrap(field.type); 10 | return typeWrap.isMany(); 11 | } 12 | getFieldName(field: AMModelField) { 13 | return `${field.name}_all`; 14 | } 15 | getFieldType(field: AMModelField) { 16 | const namedType = getNamedType(field.type); 17 | 18 | if (!isCompositeType(namedType)) { 19 | return new GraphQLList(namedType); 20 | } else { 21 | return new GraphQLList( 22 | this.configResolver.resolveInputType( 23 | namedType as AMModelType, 24 | this.links.whereClean 25 | ) 26 | ); 27 | } 28 | } 29 | transformValue(value: any) { 30 | return { 31 | $all: makeArray(value), 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/execution/operations/aggregateOperation.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from '../operation'; 2 | import { AMDBExecutor, AMDBExecutorOperationType } from '../../definitions'; 3 | import { completeAMResultPromise } from '../resultPromise/utils'; 4 | import { compact } from '../../utils'; 5 | 6 | export class AMAggregateOperation extends AMOperation { 7 | public groupBy: string; 8 | 9 | async execute(executor: AMDBExecutor) { 10 | executor({ 11 | type: AMDBExecutorOperationType.AGGREGATE, 12 | collection: this.collectionName, 13 | selector: await completeAMResultPromise( 14 | this.selector ? this.selector.selector : undefined 15 | ), 16 | fields: await completeAMResultPromise( 17 | this.fieldsSelection ? this.fieldsSelection.fields : undefined 18 | ), 19 | options: { 20 | sort: this.orderBy, 21 | limit: this.first, 22 | skip: this.skip, 23 | groupBy: this.groupBy, 24 | }, 25 | }) 26 | .then(this._result.resolve) 27 | .catch(this._result.reject); 28 | } 29 | 30 | toJSON() { 31 | return compact({ 32 | ...super.toJSON(), 33 | groupBy: this.groupBy, 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/exact.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { getNamedType, GraphQLList, isCompositeType } from 'graphql'; 3 | import { AMModelField, AMModelType } from '../../../../definitions'; 4 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 5 | import { makeArray } from '../fieldFactories/utils'; 6 | 7 | export class ExactSelector extends AMQuerySelectorFieldFactory { 8 | isApplicable(field: AMModelField) { 9 | const typeWrap = new TypeWrap(field.type); 10 | return typeWrap.isMany(); 11 | } 12 | getFieldName(field: AMModelField) { 13 | return `${field.name}_exact`; 14 | } 15 | getFieldType(field: AMModelField) { 16 | const namedType = getNamedType(field.type); 17 | 18 | if (!isCompositeType(namedType)) { 19 | return new GraphQLList(namedType); 20 | } else { 21 | return new GraphQLList( 22 | this.configResolver.resolveInputType( 23 | namedType as AMModelType, 24 | this.links.whereClean 25 | ) 26 | ); 27 | } 28 | } 29 | transformValue(value: any) { 30 | return { 31 | $eq: makeArray(value), 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/example-now/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/example-now", 3 | "version": "1.2.4", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "babel ./ --out-dir ../example-lib --ignore node_modules/ --ignore .git/ --copy-files", 8 | "start": "MONGO_URL='mongodb+srv://public:public@cluster0-c6p6b.mongodb.net/admin' MONGO_DB='db1' micro", 9 | "now": "now --regions sfo1 -e MONGO_URL='mongodb+srv://public:public@cluster0-c6p6b.mongodb.net/admin' -e MONGO_DB='db1'", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "private": true, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@babel/runtime": "^7.2.0", 18 | "@graphex/core": "1.2.4", 19 | "apollo-server": "^2.2.6", 20 | "apollo-server-micro": "^2.3.1", 21 | "graphql": "^14.4.2", 22 | "mongodb": "3.1.10" 23 | }, 24 | "devDependencies": { 25 | "@babel/cli": "^7.2.0", 26 | "@babel/core": "^7.2.0", 27 | "@babel/node": "^7.2.0", 28 | "@babel/plugin-transform-runtime": "^7.2.0", 29 | "@babel/preset-env": "^7.2.0", 30 | "babel-plugin-import-graphql": "^2.6.2", 31 | "micro": "^9.3.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/directive-implements/src/index.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 3 | 4 | export const typeDefs = gql` 5 | directive @implements(name: String) on INTERFACE 6 | `; 7 | 8 | class Implements extends SchemaDirectiveVisitor { 9 | visitInterface(iface) { 10 | const { name } = this.args; 11 | let names = name.replace(/\s/g, '').split('&'); 12 | 13 | const typeMap = this.schema.getTypeMap(); 14 | let implementIFaces = names.map(name => typeMap[name]); 15 | implementIFaces.forEach(newIface => { 16 | iface._fields = { ...iface._fields, ...newIface._fields }; 17 | }); 18 | 19 | Object.values(typeMap) 20 | .filter(type => type._interfaces && type._interfaces.includes(iface)) 21 | .forEach(type => { 22 | type._interfaces.push(...implementIFaces); 23 | 24 | names.forEach(ifaceName => { 25 | if (!this.schema._implementations[ifaceName]) 26 | this.schema._implementations[ifaceName] = []; 27 | 28 | this.schema._implementations[ifaceName].push(type); 29 | }); 30 | }); 31 | } 32 | } 33 | 34 | export const schemaDirectives = { 35 | implements: Implements, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/core/__tests__/types/aggregation.test.ts: -------------------------------------------------------------------------------- 1 | import { printType } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | import { generateSchema } from './generateSchema'; 4 | 5 | describe('aggregation', () => { 6 | const schema = generateSchema( 7 | gql` 8 | type Dish @model { 9 | id: ID @id @unique @db(name: "_id") 10 | title: String 11 | price: Float! 12 | details: DishDetails 13 | } 14 | 15 | type DishDetails { 16 | weight: Float 17 | } 18 | ` 19 | ); 20 | 21 | test('AggregateDish', () => { 22 | expect(printType(schema.getType('AggregateDish'))).toMatchInlineSnapshot(` 23 | "type AggregateDish { 24 | count: Int! 25 | sum: AggregateNumericFieldsInDish 26 | min: AggregateNumericFieldsInDish 27 | max: AggregateNumericFieldsInDish 28 | }" 29 | `); 30 | }); 31 | 32 | test('AggregateNumericFieldsInDish', () => { 33 | expect(printType(schema.getType('AggregateNumericFieldsInDish'))) 34 | .toMatchInlineSnapshot(` 35 | "type AggregateNumericFieldsInDish { 36 | price: Float 37 | details: AggregateNumericFieldsInDishDetails 38 | }" 39 | `); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/relations/fieldFactories/updateRelation.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { 3 | AMInputFieldFactory, 4 | AMModelField, 5 | AMModelType, 6 | } from '../../../definitions'; 7 | 8 | export class AMUpdateRelationFieldFactory extends AMInputFieldFactory { 9 | isApplicable(field: AMModelField) { 10 | return Boolean(field.relation) && !field.relation.external; 11 | } 12 | getFieldName(field) { 13 | return field.name; 14 | } 15 | getField(field) { 16 | const typeWrap = new TypeWrap(field.type); 17 | const isMany = typeWrap.isMany(); 18 | const type = this.configResolver.resolveInputType( 19 | typeWrap.realType() as AMModelType, 20 | isMany 21 | ? 'updateManyRelation' 22 | : // : isRequired 23 | // ? AMUpdateOneRequiredRelationTypeFactory 24 | 'updateOneRelation' 25 | ); 26 | 27 | return { 28 | name: this.getFieldName(field), 29 | extensions: undefined, 30 | type, 31 | dbName: field.relation.storeField, // TODO: replace with field.dbName 32 | relation: field.relation, 33 | // No handler because update types will set data directly in operation. 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/inputTypes/orderBy.ts: -------------------------------------------------------------------------------- 1 | import { EnumValueNode, getNamedType, isCompositeType } from 'graphql'; 2 | import { 3 | AMEnumType, 4 | AMModelType, 5 | AMTypeFactory, 6 | } from '../../../../definitions'; 7 | import { AMOperation } from '../../../../execution/operation'; 8 | 9 | export class AMOrderByTypeFactory extends AMTypeFactory { 10 | getTypeName(modelType: AMModelType): string { 11 | return `${modelType.name}OrderByInput`; 12 | } 13 | getType(modelType: AMModelType) { 14 | const values = {}; 15 | Object.values(modelType.getFields()).forEach(field => { 16 | if (!isCompositeType(getNamedType(field.type))) { 17 | values[`${field.name}_ASC`] = { value: { [field.dbName]: 1 } }; 18 | values[`${field.name}_DESC`] = { value: { [field.dbName]: -1 } }; 19 | } 20 | }); 21 | 22 | return new AMEnumType({ 23 | name: this.getTypeName(modelType), 24 | values, 25 | amLeave(node: EnumValueNode, transaction, stack) { 26 | const lastInStack = stack.last(); 27 | 28 | if (lastInStack instanceof AMOperation) { 29 | lastInStack.setOrderBy(values[node.value].value); 30 | } 31 | }, 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/type-geojson/src/update.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AMInputField, 3 | AMInputFieldFactory, 4 | AMModelField, 5 | AMObjectFieldContext, 6 | } from '@graphex/core'; 7 | import { GraphQLNamedType } from 'graphql'; 8 | 9 | export class AMGeoJSONUpdateFieldFactory extends AMInputFieldFactory { 10 | isApplicable() { 11 | return true; 12 | } 13 | getFieldName(field: AMModelField) { 14 | return `${field.name}`; 15 | } 16 | getField(field) { 17 | return { 18 | name: this.getFieldName(field), 19 | type: this.schemaInfo.schema.getType( 20 | `${(field.type as GraphQLNamedType).name}Input` 21 | ), 22 | amEnter(node, transaction, stack) { 23 | const action = new AMObjectFieldContext(field.dbName); 24 | stack.push(action); 25 | }, 26 | amLeave(node, transaction, stack) { 27 | const operation = stack.lastOperation(); 28 | const path = stack.getFieldPath(operation); 29 | const context = stack.pop() as AMObjectFieldContext; 30 | 31 | const data = stack.getOperationData(operation); 32 | const set = (data.data && data.data['$set']) || {}; 33 | data.addValue('$set', set); 34 | set[path] = context.value; 35 | }, 36 | } as AMInputField; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/ra-data/__tests__/buildQuery.test.ts: -------------------------------------------------------------------------------- 1 | describe('buildQuery', () => { 2 | // const queryType = 'query_type'; 3 | 4 | // const resource = { 5 | // type: { name: 'Post' }, 6 | // GET_LIST: queryType, 7 | // }; 8 | // const introspectionResults = { 9 | // resources: [resource], 10 | // }; 11 | 12 | // it('throws an error if resource is unknown', () => { 13 | // expect(() => 14 | // buildQueryFactory()(introspectionResults as any)( 15 | // 'GET_LIST', 16 | // 'Comment', 17 | // {} as any, 18 | // {} as any 19 | // ) 20 | // ).toThrow( 21 | // 'Unknown resource Comment. Make sure it has been declared on your server side schema. Known resources are Post' 22 | // ); 23 | // }); 24 | 25 | // it('throws an error if resource does not have a query or mutation for specified AOR fetch type', () => { 26 | // expect(() => 27 | // buildQueryFactory()(introspectionResults as any)( 28 | // 'CREATE', 29 | // 'Post', 30 | // {} as any, 31 | // {} as any 32 | // ) 33 | // ).toThrow( 34 | // 'No query or mutation matching aor fetch type CREATE could be found for resource Post' 35 | // ); 36 | // }); 37 | 38 | it('', () => { 39 | return; 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /database/seeders/01-user.ts: -------------------------------------------------------------------------------- 1 | import { QueryInterface } from 'sequelize'; 2 | 3 | module.exports = { 4 | up: async (queryInterface: QueryInterface) => { 5 | // test if seed was already ran 6 | await queryInterface.sequelize 7 | .query('SELECT count(*) FROM user') 8 | .then(res => { 9 | if (res.length > 0) { 10 | throw new Error('Seed for user was already ran'); 11 | } 12 | }); 13 | 14 | const transaction = await queryInterface.sequelize.transaction(); 15 | try { 16 | await queryInterface.bulkInsert( 17 | 'user', 18 | [ 19 | { 20 | id: 100, 21 | username: 'johndoe', 22 | }, 23 | { 24 | id: 101, 25 | username: 'janedoe', 26 | }, 27 | ], 28 | { transaction } 29 | ); 30 | 31 | if (process.env.MIGRATIONS_DRYRUN === 'true') { 32 | throw new Error(`Seed file did not execute, running in DRY RUN mode`); 33 | } else { 34 | await transaction.commit(); 35 | } 36 | } catch (err) { 37 | transaction.rollback(); 38 | throw err; 39 | } 40 | }, 41 | 42 | down: (queryInterface: QueryInterface) => { 43 | return queryInterface.bulkDelete('user', null, {}); 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /packages/core/__tests__/integration-acl.test.ts: -------------------------------------------------------------------------------- 1 | jest.setTimeout(20000); 2 | 3 | import prepare from './integration-prepare'; 4 | const testInstance = prepare(); 5 | let query, mutate, connectToDatabase; 6 | 7 | const _ = require('lodash'); 8 | import gql from 'graphql-tag'; 9 | import { getIntrospectionQuery } from 'graphql'; 10 | 11 | beforeAll(async () => { 12 | const instance = await testInstance.start({ 13 | aclWhere: true, 14 | }); 15 | query = instance.query; 16 | mutate = instance.mutate; 17 | connectToDatabase = instance.connectToDatabase; 18 | 19 | const DB = await connectToDatabase(); 20 | await DB.collection('posts').createIndex({ place: '2dsphere' }); 21 | }); 22 | 23 | afterAll(async () => { 24 | testInstance.stop(); 25 | }); 26 | 27 | let admin1, admin2; 28 | test('Create admins', async () => { 29 | const { errors, data } = await query({ 30 | query: gql` 31 | mutation { 32 | admin1: createAdmin(data: { username: "admin1" }) { 33 | id 34 | username 35 | } 36 | admin2: createAdmin(data: { username: "admin2" }) { 37 | id 38 | username 39 | } 40 | } 41 | `, 42 | variables: {}, 43 | }); 44 | expect(errors).toBeUndefined(); 45 | admin1 = data.admin1; 46 | admin2 = data.admin2; 47 | }); 48 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/relations/fieldFactories/createRelation.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { 3 | AMInputField, 4 | AMInputFieldFactory, 5 | AMModelField, 6 | AMModelType, 7 | } from '../../../definitions'; 8 | import { defaultObjectFieldVisitorHandler } from '../../common/visitorHandlers'; 9 | 10 | export class AMCreateRelationFieldFactory extends AMInputFieldFactory { 11 | isApplicable(field: AMModelField) { 12 | return Boolean(field.relation) && !field.relation.external; 13 | } 14 | getFieldName(field) { 15 | return field.name; 16 | } 17 | getField(field: AMModelField) { 18 | const typeWrap = new TypeWrap(field.type); 19 | const isMany = typeWrap.isMany(); 20 | const isRequired = typeWrap.isRequired(); 21 | const type = this.configResolver.resolveInputType( 22 | typeWrap.realType() as AMModelType, 23 | isMany 24 | ? this.links.many 25 | : isRequired 26 | ? this.links.oneRequired 27 | : this.links.one 28 | ); 29 | 30 | return { 31 | name: this.getFieldName(field), 32 | type, 33 | dbName: field.relation.storeField, 34 | relation: field.relation, 35 | ...defaultObjectFieldVisitorHandler(field.relation.storeField, field), 36 | } as AMInputField; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Graphex 3 | 4 | Graphex is an open source no-code framework to build and deploy GraphQL backends quickly without much effort from the engineering teams. You can create your GraphQL models and get a fully functional GraphQL server that supports "CRUD" like operations against your data source. 5 | 6 | ## Features 7 | - CRUD like operations on GraphQL types 8 | - Automatic Filters for Queries 9 | - Relations between types with OOTB operations 10 | - Work with sub-documents (MongoDB only) 11 | - Support GraphQL Federation and external relations 12 | 13 | See full [feature set](./features.md) 14 | 15 | ## Quick Start 16 | [![Edit graphex-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/graphexio/graphex/tree/master/packages/example-server) 17 | 18 | 19 | ## Data Sources 20 | Out of the box Graphex supports working with MongoDB, but is easily extendable to other data sources through adapters. 21 | 22 | 23 | ## Packages within this Monorepo 24 | - `@graphex/core` - The engine that generates the graphex server. See [here](packages/core/README.md) for more details 25 | - `@graphex/acl` - Adds access control to types, fields and filters 26 | 27 | ## Licence 28 | Graphex © 2021, Released under the [ISC Open Source Initiative License](https://opensource.org/licenses/ISC) 29 | -------------------------------------------------------------------------------- /packages/example-server-sequelize/src/models/post.model.ts: -------------------------------------------------------------------------------- 1 | import { Model, Table, Column, DataType } from 'sequelize-typescript'; 2 | 3 | @Table({ 4 | tableName: 'post', 5 | timestamps: false, 6 | }) 7 | export default class Post extends Model { 8 | @Column({ 9 | type: DataType.INTEGER, 10 | primaryKey: true, 11 | autoIncrement: true, 12 | }) 13 | id: number; 14 | 15 | @Column({ 16 | type: DataType.STRING(255), 17 | }) 18 | title: string; 19 | 20 | @Column({ 21 | type: DataType.TEXT, 22 | }) 23 | body: string; 24 | 25 | @Column({ 26 | type: DataType.INTEGER, 27 | references: { 28 | model: 'user', 29 | key: 'id', 30 | }, 31 | onDelete: 'CASCADE', 32 | }) 33 | owner_id: number; 34 | 35 | @Column({ 36 | allowNull: true, 37 | type: DataType.ARRAY(DataType.INTEGER), 38 | }) 39 | like_ids?: number[]; 40 | 41 | @Column({ 42 | field: 'created_at', 43 | allowNull: true, 44 | type: DataType.DATE, 45 | }) 46 | createdAt?: Date; 47 | 48 | @Column({ 49 | field: 'updated_at', 50 | allowNull: true, 51 | type: DataType.DATE, 52 | }) 53 | updatedAt?: Date; 54 | 55 | // @Column({ 56 | // field: 'deleted_at', 57 | // allowNull: true, 58 | // type: DataType.DATE, 59 | // }) 60 | // deletedAt?: Date; 61 | } 62 | -------------------------------------------------------------------------------- /packages/schema-filter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/schema-filter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "dev": "nodemon --exec babel-node dev-server/index.js", 11 | "test": "jest", 12 | "prepare": "rimraf ./lib && tsc", 13 | "watch": "babel ./src --out-dir ./lib --watch" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "@apollo-model/graphql-tools": "4.0.7", 19 | "@apollo/federation": "^0.7.0", 20 | "@graphex/ast-from-value": "1.0.0", 21 | "@graphex/type-wrap": "1.0.0", 22 | "ramda": "^0.27.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/cli": "^7.1.2", 26 | "@babel/core": "^7.1.2", 27 | "@babel/node": "^7.2.2", 28 | "@babel/plugin-proposal-class-properties": "^7.5.5", 29 | "@babel/plugin-proposal-pipeline-operator": "^7.5.0", 30 | "@babel/preset-env": "^7.5.5", 31 | "@types/ramda": "^0.27.4", 32 | "apollo-server": "^2.8.1", 33 | "apollo-server-testing": "^2.8.1", 34 | "jest": "^25.1.0", 35 | "nodemon": "^1.19.1", 36 | "rimraf": "^2.6.3", 37 | "typescript": "^4.2.4" 38 | }, 39 | "peerDependencies": { 40 | "graphql": "^14.6.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/not_in.ts: -------------------------------------------------------------------------------- 1 | import { getNamedType, GraphQLList, isCompositeType } from 'graphql'; 2 | import { AMModelField, AMModelType } from '../../../../definitions'; 3 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 4 | import { makeArray } from '../fieldFactories/utils'; 5 | 6 | export class NotInSelector extends AMQuerySelectorFieldFactory { 7 | isApplicable(field: AMModelField) { 8 | const namedType = getNamedType(field.type); 9 | return ( 10 | (isCompositeType(namedType) || 11 | ['ID', 'ObjectID', 'Int', 'Float', 'String'].includes( 12 | namedType.toString() 13 | )) && 14 | !field.relation 15 | ); 16 | } 17 | getFieldName(field: AMModelField) { 18 | return `${field.name}_not_in`; 19 | } 20 | getFieldType(field: AMModelField) { 21 | const namedType = getNamedType(field.type); 22 | if (!isCompositeType(namedType)) { 23 | return new GraphQLList(namedType); 24 | } else { 25 | return new GraphQLList( 26 | this.configResolver.resolveInputType( 27 | namedType as AMModelType, 28 | this.links.whereClean 29 | ) 30 | ); 31 | } 32 | } 33 | transformValue(value: any) { 34 | return { 35 | $not: { $in: makeArray(value) }, 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/serializer/utils.ts: -------------------------------------------------------------------------------- 1 | const getKeysOfEnumerableProperties = object => { 2 | const keys = Object.keys(object).sort(); 3 | 4 | if (Object.getOwnPropertySymbols) { 5 | Object.getOwnPropertySymbols(object).forEach(symbol => { 6 | if (Object.getOwnPropertyDescriptor(object, symbol).enumerable) { 7 | keys.push(symbol.toString()); 8 | } 9 | }); 10 | } 11 | 12 | return keys; 13 | }; 14 | 15 | export function printObjectProperties( 16 | val, 17 | config, 18 | indentation, 19 | depth, 20 | refs, 21 | printer 22 | ) { 23 | let result = ''; 24 | const keys = getKeysOfEnumerableProperties(val); 25 | 26 | if (keys.length) { 27 | result += config.spacingOuter; 28 | const indentationNext = indentation + config.indent; 29 | 30 | for (let i = 0; i < keys.length; i++) { 31 | const key = keys[i]; 32 | const name = printer(key, config, indentationNext, depth, refs); 33 | const value = printer(val[key], config, indentationNext, depth, refs); 34 | result += indentationNext + name + ': ' + value; 35 | 36 | if (i < keys.length - 1) { 37 | result += ',' + config.spacingInner; 38 | } else if (!config.min) { 39 | result += ','; 40 | } 41 | } 42 | 43 | result += config.spacingOuter + indentation; 44 | } 45 | 46 | return result; 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/output/types/connection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLInt, 3 | GraphQLList, 4 | GraphQLNonNull, 5 | GraphQLObjectType, 6 | } from 'graphql'; 7 | import { 8 | AMModelType, 9 | AMObjectType, 10 | AMTypeFactory, 11 | } from '../../../../definitions'; 12 | 13 | export class AMConnectionTypeFactory extends AMTypeFactory { 14 | getTypeName(modelType): string { 15 | return `${modelType.name}Connection`; 16 | } 17 | getType(modelType) { 18 | const type = new AMObjectType({ 19 | name: this.getTypeName(modelType), 20 | fields: () => { 21 | const fields = { 22 | nodes: { 23 | type: new GraphQLNonNull( 24 | new GraphQLList(new GraphQLNonNull(modelType)) 25 | ), 26 | nodesRelation: true, 27 | }, 28 | totalCount: { 29 | type: GraphQLInt, 30 | aggregateRelation: true, 31 | }, 32 | aggregate: { 33 | type: this.configResolver.resolveType( 34 | modelType, 35 | 'aggregate' 36 | ) as GraphQLObjectType, 37 | aggregateRelation: true, 38 | }, 39 | }; 40 | 41 | return fields; 42 | }, 43 | }); 44 | (type as AMModelType).mmConnection = true; 45 | 46 | return type; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/ra-data/__tests__/getFinalType.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind } from 'graphql'; 2 | import getFinalType from '../src/utils/getFinalType'; 3 | 4 | describe('getFinalType', () => { 5 | it('returns the correct type for SCALAR types', () => { 6 | expect(getFinalType({ name: 'foo', kind: TypeKind.SCALAR })).toEqual({ 7 | name: 'foo', 8 | kind: TypeKind.SCALAR, 9 | }); 10 | }); 11 | it('returns the correct type for NON_NULL types', () => { 12 | expect( 13 | getFinalType({ 14 | kind: TypeKind.NON_NULL, 15 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 16 | }) 17 | ).toEqual({ 18 | name: 'foo', 19 | kind: TypeKind.SCALAR, 20 | }); 21 | }); 22 | it('returns the correct type for LIST types', () => { 23 | expect( 24 | getFinalType({ 25 | kind: TypeKind.LIST, 26 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 27 | }) 28 | ).toEqual({ 29 | name: 'foo', 30 | kind: TypeKind.SCALAR, 31 | }); 32 | }); 33 | it('returns the correct type for NON_NULL LIST types', () => { 34 | expect( 35 | getFinalType({ 36 | kind: TypeKind.NON_NULL, 37 | ofType: { 38 | kind: TypeKind.LIST, 39 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 40 | }, 41 | }) 42 | ).toEqual({ name: 'foo', kind: TypeKind.SCALAR }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/externalRelations/directives/relationOutside.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import gql from 'graphql-tag'; 3 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 4 | import { AMModelField, AMModelType } from '../../../definitions'; 5 | import { getRelationFieldName } from '../../../utils'; 6 | 7 | export const typeDef = gql` 8 | directive @relationOutside(storeField: String = null) on FIELD_DEFINITION 9 | `; 10 | 11 | export class RelationOutsideDirective extends SchemaDirectiveVisitor { 12 | visitFieldDefinition(field: AMModelField) { 13 | const typeWrap = new TypeWrap(field.type); 14 | const isMany = typeWrap.isMany(); 15 | const relationType = typeWrap.realType() as AMModelType; 16 | const storeField = 17 | this?.args?.storeField ?? 18 | getRelationFieldName(relationType.name, 'id', isMany); 19 | 20 | field.relationOutside = { 21 | storeField, 22 | }; 23 | 24 | if (isMany) { 25 | field.resolve = parent => 26 | parent[storeField]?.map(item => ({ 27 | [relationType.mmUniqueFields?.[0]?.name]: item, 28 | })); 29 | } else { 30 | field.resolve = parent => ({ 31 | [relationType.mmUniqueFields?.[0]?.name]: parent[storeField], 32 | }); 33 | } 34 | } 35 | } 36 | 37 | export const schemaDirectives = { 38 | relationOutside: RelationOutsideDirective, 39 | }; 40 | -------------------------------------------------------------------------------- /database/migrations/20210507120700-user.ts: -------------------------------------------------------------------------------- 1 | import { default as sequelize, QueryInterface } from 'sequelize'; 2 | import { isDryRun } from '../helpers'; 3 | 4 | export default { 5 | up: async (queryInterface: QueryInterface, Sequelize: typeof sequelize) => { 6 | const transaction = await queryInterface.sequelize.transaction(); 7 | 8 | await queryInterface.createTable( 9 | 'user', 10 | { 11 | id: { 12 | type: Sequelize.INTEGER, 13 | primaryKey: true, 14 | autoIncrement: true, 15 | }, 16 | username: { 17 | type: Sequelize.STRING(255), 18 | allowNull: false, 19 | }, 20 | created_at: { 21 | type: Sequelize.DATE, 22 | allowNull: false, 23 | defaultValue: Sequelize.fn('NOW'), 24 | }, 25 | updated_at: { 26 | type: Sequelize.DATE, 27 | allowNull: false, 28 | defaultValue: Sequelize.fn('NOW'), 29 | }, 30 | deleted_at: { 31 | type: Sequelize.DATE, 32 | }, 33 | }, 34 | { transaction, logging: console.log } 35 | ); 36 | 37 | if (isDryRun()) { 38 | throw new Error(`Migration did not execute, running in DRY RUN mode`); 39 | } else { 40 | await transaction.commit(); 41 | } 42 | }, 43 | down: async queryInterface => { 44 | await queryInterface.dropTable('user'); 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /packages/core/src/prepare/fillDiscriminators.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, isInterfaceType, isObjectType } from 'graphql'; 2 | import { AMModelType } from '../definitions'; 3 | import { lowercaseFirstLetter, isAbstractType } from '../utils'; 4 | 5 | export const fillDiscriminators = (schema: GraphQLSchema) => { 6 | Object.values(schema.getTypeMap()).forEach((iface: AMModelType) => { 7 | if ( 8 | isInterfaceType(iface) && 9 | !isAbstractType(iface) // TODO: move this logic into abstract interface module 10 | ) { 11 | if (!iface.mmDiscriminatorField) { 12 | iface.mmDiscriminatorField = '_type'; 13 | } 14 | 15 | iface.mmDiscriminatorMap = iface.mmDiscriminatorMap || {}; 16 | Object.values(schema.getTypeMap()) 17 | .filter( 18 | type => isObjectType(type) && type.getInterfaces().includes(iface) 19 | ) 20 | .forEach((type: AMModelType) => { 21 | type.mmModelInherited = true; 22 | if (!type.mmDiscriminator) { 23 | type.mmDiscriminator = lowercaseFirstLetter(type.name); 24 | } 25 | 26 | type.mmDiscriminatorField = iface.mmDiscriminatorField; 27 | iface.mmDiscriminatorMap[type.mmDiscriminator] = type.name; 28 | }); 29 | 30 | iface.resolveType = doc => { 31 | return iface.mmDiscriminatorMap[doc[iface.mmDiscriminatorField]]; 32 | }; 33 | } 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/fieldFactories/querySelectorComplex.ts: -------------------------------------------------------------------------------- 1 | import { ASTNode, GraphQLInputType } from 'graphql'; 2 | import { 3 | AMInputField, 4 | AMInputFieldFactory, 5 | AMModelField, 6 | } from '../../../../definitions'; 7 | import { AMObjectFieldContext } from '../../../../execution/contexts/objectField'; 8 | import { AMTransaction } from '../../../../execution/transaction'; 9 | import { AMVisitorStack } from '../../../../execution/visitorStack'; 10 | 11 | export abstract class AMQuerySelectorComplexFieldFactory extends AMInputFieldFactory { 12 | abstract getFieldType(field: AMModelField): GraphQLInputType; 13 | abstract applyValue( 14 | node: ASTNode, 15 | transaction: AMTransaction, 16 | stack: AMVisitorStack, 17 | context: AMObjectFieldContext, 18 | field: AMModelField 19 | ): void; 20 | 21 | getField(field: AMModelField) { 22 | const type = this.getFieldType(field); 23 | 24 | return { 25 | name: this.getFieldName(field), 26 | type, 27 | amEnter: (node, transaction, stack) => { 28 | const context = new AMObjectFieldContext(field.dbName); 29 | stack.push(context); 30 | }, 31 | amLeave: (node, transaction, stack) => { 32 | const context = stack.pop() as AMObjectFieldContext; 33 | this.applyValue(node, transaction, stack, context, field); 34 | }, 35 | } as AMInputField; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/querySelectors/in.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getNamedType, 3 | GraphQLList, 4 | isCompositeType, 5 | isEnumType, 6 | } from 'graphql'; 7 | import { AMModelField, AMModelType } from '../../../../definitions'; 8 | import { AMQuerySelectorFieldFactory } from '../fieldFactories/querySelector'; 9 | import { makeArray } from '../fieldFactories/utils'; 10 | 11 | export class InSelector extends AMQuerySelectorFieldFactory { 12 | isApplicable(field: AMModelField) { 13 | const namedType = getNamedType(field.type); 14 | return ( 15 | (isCompositeType(namedType) || 16 | isEnumType(namedType) || 17 | ['ID', 'ObjectID', 'Int', 'Float', 'String'].includes( 18 | namedType.toString() 19 | )) && 20 | !field.relation 21 | ); 22 | } 23 | getFieldName(field: AMModelField) { 24 | return `${field.name}_in`; 25 | } 26 | getFieldType(field: AMModelField) { 27 | const namedType = getNamedType(field.type); 28 | if (!isCompositeType(namedType)) { 29 | return new GraphQLList(namedType); 30 | } else { 31 | return new GraphQLList( 32 | this.configResolver.resolveInputType( 33 | namedType as AMModelType, 34 | this.links.whereClean 35 | ) 36 | ); 37 | } 38 | } 39 | transformValue(value: any) { 40 | return { 41 | $in: makeArray(value), 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/example-server/src/model.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | export default gql` 3 | interface Node @inherit { 4 | id: ObjectID! @id @unique @db(name: "_id") 5 | } 6 | 7 | interface Timestamp @inherit { 8 | createdAt: Date @createdAt @db(name: "created_at") 9 | updatedAt: Date @updatedAt 10 | } 11 | 12 | type Category implements Node & Timestamp @model { 13 | title: String @unique 14 | parentCategory: Category @relation(storeField: "parentCategoryId") 15 | subcategories: [Category!] @extRelation(storeField: "parentCategoryId") 16 | posts: [Post!] @extRelation 17 | } 18 | 19 | type Post implements Node & Timestamp @model { 20 | title: String! 21 | body: String! 22 | category: Category @relation 23 | keywords: [String!] 24 | owner: User! @relation 25 | } 26 | 27 | interface User @implements(name: "Node & Timestamp") @inherit @model { 28 | username: String! @unique 29 | } 30 | 31 | enum AdminAccess { 32 | owner 33 | moderator 34 | } 35 | 36 | type Admin implements User { 37 | access: AdminAccess 38 | } 39 | 40 | enum SubscriberPlan { 41 | free 42 | standard 43 | premium 44 | } 45 | 46 | type SubscriberProfile { 47 | firstName: String 48 | lastName: String 49 | } 50 | 51 | type Subscriber implements User { 52 | plan: SubscriberPlan! 53 | profile: SubscriberProfile @subdocument 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /packages/core/src/prepare/fieldVisitorEvents.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | isInputObjectType, 4 | isInterfaceType, 5 | isObjectType, 6 | } from 'graphql'; 7 | import { AMModelField, AMModelType } from '../definitions'; 8 | import { AMFieldsSelectionContext } from '../execution'; 9 | 10 | export const fieldVisitorEvents = ( 11 | schema: GraphQLSchema, 12 | fieldVisitorEventsMap: {} 13 | ) => { 14 | Object.values(schema.getTypeMap()).forEach((type: AMModelType) => { 15 | if ( 16 | isObjectType(type) || 17 | isInterfaceType(type) || 18 | isInputObjectType(type) 19 | ) { 20 | Object.values(type.getFields()).forEach((field: AMModelField) => { 21 | if ( 22 | fieldVisitorEventsMap[type.name] && 23 | fieldVisitorEventsMap[type.name][field.name] 24 | ) { 25 | const events = fieldVisitorEventsMap[type.name][field.name]; 26 | 27 | field.amEnter = events.amEnter; 28 | field.amLeave = events.amLeave; 29 | } else { 30 | /** 31 | * Add default visitor handler 32 | */ 33 | field.amEnter = (node, transaction, stack) => { 34 | const lastStackItem = stack.last(); 35 | if (lastStackItem instanceof AMFieldsSelectionContext) { 36 | lastStackItem.addField(field.dbName); 37 | } 38 | }; 39 | } 40 | }); 41 | } 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /packages/acl/__tests__/modelField.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType } from 'graphql'; 2 | import AMM from '@graphex/core'; 3 | import gql from 'graphql-tag'; 4 | 5 | import { modelField } from '../src'; 6 | 7 | const schema = new AMM({}).makeExecutableSchema({ 8 | typeDefs: gql` 9 | type Brand @model { 10 | id: ID @id 11 | title: String 12 | } 13 | `, 14 | }); 15 | 16 | test('fieldAccessRule', () => { 17 | const modelName = 'Brand'; 18 | const fieldName = 'title'; 19 | const type = schema.getType(modelName) as GraphQLObjectType; 20 | const field = type.getFields()[fieldName]; 21 | 22 | const rule = modelField(modelName, fieldName, 'R')(schema); 23 | 24 | expect(rule({ type, field })).toBe(true); 25 | }); 26 | 27 | test('fieldAccessRule Wildcard', () => { 28 | const modelName = 'Brand'; 29 | const fieldName = 'title'; 30 | const type = schema.getType('Brand') as GraphQLObjectType; 31 | const field = type.getFields()['title']; 32 | 33 | const Query = schema.getType('Query'); 34 | const Mutation = schema.getType('Mutation'); 35 | // const Subscription = schema.getType('Subscription'); 36 | 37 | const rule = modelField('.*', '.*', 'R')(schema); 38 | 39 | expect(rule({ type, field })).toBe(true); 40 | expect(rule({ type: Query, field })).toBe(false); 41 | expect(rule({ type: Mutation, field })).toBe(false); 42 | // expect(rule({ type: Subscription, field, schema })).toBe(false); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/ra-data/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import AMM, { defaultConfig } from '@graphex/core'; 2 | import * as DirectiveImplements from '@graphex/directive-implements'; 3 | import * as TypeGeoJSON from '@graphex/type-geojson'; 4 | import { ApolloServer } from 'apollo-server'; 5 | import { createTestClient } from 'apollo-server-testing'; 6 | import { DocumentNode } from 'graphql'; 7 | import makeIntrospection from 'ra-data-graphql/lib/introspection'; 8 | import * as R from 'ramda'; 9 | import introspectionOptions from '../src/introspectionOptions'; 10 | import { IntrospectionResult } from '../src/introspectionResult'; 11 | 12 | export const prepareIntrospection = async ( 13 | typeDefs: DocumentNode 14 | ): Promise => { 15 | const schema = new AMM({ 16 | modules: [DirectiveImplements, TypeGeoJSON], 17 | options: { 18 | config: R.mergeDeepRight(defaultConfig, TypeGeoJSON.config), 19 | }, 20 | }).makeExecutableSchema({ 21 | resolverValidationOptions: { 22 | requireResolversForResolveType: false, 23 | }, 24 | typeDefs: [typeDefs], 25 | }); 26 | // console.log(printSchema(schema)); 27 | 28 | const server = new ApolloServer({ 29 | schema, 30 | }); 31 | 32 | const testClient = createTestClient(server as any); 33 | 34 | const introspection = await makeIntrospection( 35 | testClient, 36 | introspectionOptions 37 | ); 38 | return new IntrospectionResult(introspection); 39 | }; 40 | -------------------------------------------------------------------------------- /packages/acl/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLField, 3 | GraphQLInputField, 4 | GraphQLSchema, 5 | GraphQLType, 6 | isAbstractType, 7 | isInputObjectType, 8 | isObjectType, 9 | isInterfaceType, 10 | } from 'graphql'; 11 | import R from 'ramda'; 12 | 13 | export const matchingTypes = R.curry((schema: GraphQLSchema, pattern: RegExp) => 14 | Object.values(schema.getTypeMap()).filter(type => type.name.match(pattern)) 15 | ); 16 | 17 | export const extractAbstractTypes = R.curry( 18 | (schema: GraphQLSchema, types: GraphQLType[]) => 19 | types.flatMap(type => { 20 | if (isAbstractType(type)) { 21 | return [type, ...schema.getPossibleTypes(type)] as GraphQLType[]; 22 | } else { 23 | return type; 24 | } 25 | }) 26 | ); 27 | 28 | export const matchingFields = R.curry( 29 | (schema: GraphQLSchema, typeName, pattern: RegExp) => { 30 | const type = schema.getType(typeName); 31 | if ( 32 | isObjectType(type) || 33 | isInputObjectType(type) || 34 | isInterfaceType(type) 35 | ) { 36 | const fields = type.getFields(); 37 | return Object.values( 38 | fields 39 | ).filter((field: GraphQLField | GraphQLInputField) => 40 | field.name.match(pattern) 41 | ); 42 | } else { 43 | return []; 44 | } 45 | } 46 | ); 47 | 48 | export const toEntries = str => [str, true]; 49 | export const toMap = entries => new Map(entries); 50 | -------------------------------------------------------------------------------- /packages/acl/src/rules.ts: -------------------------------------------------------------------------------- 1 | import pluralize from 'pluralize'; 2 | import R from 'ramda'; 3 | import { GraphQLNamedType } from 'graphql'; 4 | 5 | export const operationAccessRule = (regex) => () => undefined; 6 | 7 | export const regexFields = (regex) => (schema) => ({ type, field }) => { 8 | return regex.test(`${type.name}.${field.name}`); 9 | }; 10 | 11 | export const anyField = (schema) => ({ type, field }) => { 12 | return !['Query', 'Mutation', 'Subscription'].includes(type.name); 13 | }; 14 | 15 | export const allQueries = (schema) => ({ type, field }) => { 16 | return type.name === 'Query'; 17 | }; 18 | 19 | export const allMutations = (schema) => ({ type, field }) => { 20 | return type.name === 'Mutation'; 21 | }; 22 | 23 | export const allACLTypes = (schema) => ({ 24 | type, 25 | field, 26 | }: { 27 | type: GraphQLNamedType; 28 | field; 29 | }) => { 30 | return type.name.endsWith('WhereACLInput'); 31 | }; 32 | 33 | export const modelCustomActions = (modelName, actions: string[]) => ( 34 | schema 35 | ) => { 36 | const modelNameToRegExp = (model) => 37 | actions.map((action) => new RegExp(`^Mutation\\.${action}${model}$`)); 38 | 39 | const modelNames = [modelName, pluralize(modelName)]; 40 | const enableFields = R.pipe( 41 | R.chain(modelNameToRegExp), 42 | R.map(R.test) 43 | )(modelNames); 44 | 45 | return ({ type, field }) => { 46 | return R.anyPass(enableFields)(`${type.name}.${field.name}`); 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/fieldFactories/querySelector.ts: -------------------------------------------------------------------------------- 1 | import { ASTNode, GraphQLInputType } from 'graphql'; 2 | import { AMModelField } from '../../../../definitions'; 3 | import { AMObjectFieldContext } from '../../../../execution/contexts/objectField'; 4 | import { AMSelectorContext } from '../../../../execution/contexts/selector'; 5 | import { AMTransaction } from '../../../../execution/transaction'; 6 | import { AMVisitorStack } from '../../../../execution/visitorStack'; 7 | import { AMQuerySelectorComplexFieldFactory } from './querySelectorComplex'; 8 | 9 | export abstract class AMQuerySelectorFieldFactory extends AMQuerySelectorComplexFieldFactory { 10 | abstract getFieldType(field: AMModelField): GraphQLInputType; 11 | abstract transformValue(value: any): any; 12 | applyValue( 13 | node: ASTNode, 14 | transaction: AMTransaction, 15 | stack: AMVisitorStack, 16 | context: AMObjectFieldContext, 17 | field: AMModelField 18 | ): void; 19 | applyValue( 20 | node: ASTNode, 21 | transaction: AMTransaction, 22 | stack: AMVisitorStack, 23 | context: AMObjectFieldContext 24 | ) { 25 | const lastInStack = stack.last(); 26 | if ( 27 | lastInStack instanceof AMSelectorContext || 28 | lastInStack instanceof AMObjectFieldContext 29 | ) { 30 | lastInStack.addValue( 31 | context.fieldName, 32 | this.transformValue(context.value) 33 | ); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/execution/operations/deleteDbRefOperation.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from '../operation'; 2 | import { AMDBExecutor, AMDBExecutorOperationType } from '../../definitions'; 3 | import { completeAMResultPromise } from '../resultPromise/utils'; 4 | 5 | import R from 'ramda'; 6 | import { DBRef } from 'mongodb'; 7 | 8 | export class AMDeleteDBRefOperation extends AMOperation { 9 | async execute(executor: AMDBExecutor) { 10 | try { 11 | const refList = (await completeAMResultPromise( 12 | this.dbRefList || [this.dbRef] 13 | )) as DBRef[]; 14 | const groupedRefs = R.groupBy(R.prop('namespace'), refList); 15 | 16 | const result = Object.fromEntries( 17 | await Promise.all( 18 | Object.entries(groupedRefs).map(async ([collectionName, refs]) => { 19 | const data = await executor({ 20 | type: AMDBExecutorOperationType.DELETE_MANY, 21 | collection: collectionName, 22 | selector: { _id: { $in: refs.map(ref => ref.oid) } }, 23 | fields: await completeAMResultPromise( 24 | this.fieldsSelection ? this.fieldsSelection.fields : undefined 25 | ), 26 | options: { sort: this.orderBy }, 27 | }); 28 | 29 | return [collectionName, data]; 30 | }) 31 | ) 32 | ); 33 | this._result.resolve(result); 34 | } catch (err) { 35 | this._result.reject(err); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/rootMethods/createMutation.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLNonNull } from 'graphql'; 2 | import R from 'ramda'; 3 | import { 4 | AMField, 5 | AMMethodFieldFactory, 6 | AMModelType, 7 | GraphQLOperationType, 8 | } from '../../definitions'; 9 | import { AMCreateOperation } from '../../execution/operations/createOperation'; 10 | import { resolve } from '../../resolve'; 11 | 12 | export class AMModelCreateMutationFieldFactory extends AMMethodFieldFactory { 13 | getOperationType() { 14 | return GraphQLOperationType.Mutation; 15 | } 16 | getFieldName(modelType: AMModelType): string { 17 | return R.concat('create')(modelType.name); 18 | } 19 | getField(modelType: AMModelType) { 20 | return { 21 | name: this.getFieldName(modelType), 22 | description: '', 23 | isDeprecated: false, 24 | type: modelType, 25 | args: [ 26 | { 27 | name: 'data', 28 | type: new GraphQLNonNull( 29 | this.configResolver.resolveInputType(modelType, this.links.data) 30 | ), 31 | }, 32 | ], 33 | amEnter(node, transaction, stack) { 34 | const operation = new AMCreateOperation(transaction, { 35 | many: false, 36 | collectionName: modelType.mmCollectionName, 37 | }); 38 | stack.push(operation); 39 | }, 40 | amLeave(node, transaction, stack) { 41 | stack.pop(); 42 | }, 43 | resolve: resolve, 44 | } as AMField; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/relations/visitorHandlers/abstract/index.ts: -------------------------------------------------------------------------------- 1 | import { AMModelType } from '../../../../definitions'; 2 | import { AMDataContext } from '../../../../execution/contexts/data'; 3 | import { AMListValueContext } from '../../../../execution/contexts/listValue'; 4 | 5 | export const abstractReadHandlerFactory = (modelType: AMModelType) => ( 6 | methodName: string 7 | ) => { 8 | return { 9 | amEnter(node, transaction, stack) { 10 | const listContext = new AMListValueContext(); 11 | stack.push(listContext); 12 | }, 13 | amLeave(node, transaction, stack) { 14 | const listContext = stack.pop() as AMListValueContext; 15 | const lastInStack = stack.last(); 16 | if (lastInStack instanceof AMDataContext) { 17 | lastInStack.addValue(methodName, listContext.values); 18 | } 19 | }, 20 | }; 21 | }; 22 | 23 | export const abstractCreateHandlerFactory = (modelType: AMModelType) => ( 24 | methodName: string 25 | ) => { 26 | return { 27 | amEnter(node, transaction, stack) { 28 | const listContext = new AMListValueContext(); 29 | listContext.setProxy(true); 30 | stack.push(listContext); 31 | }, 32 | amLeave(node, transaction, stack) { 33 | const listContext = stack.pop() as AMListValueContext; 34 | const lastInStack = stack.last(); 35 | if (lastInStack instanceof AMDataContext) { 36 | lastInStack.addValue(methodName, listContext.values); 37 | } 38 | }, 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /packages/schema-filter/src/utils.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import * as R from 'ramda'; 3 | 4 | export const capitalizeFirstLetter = (string) => { 5 | return string.charAt(0).toUpperCase() + string.slice(1); 6 | }; 7 | 8 | const reduceArgs = (map, arg) => { 9 | map[arg.name] = arg; 10 | return map; 11 | }; 12 | 13 | export const getFields = (stackItem) => stackItem.type.getFields(); 14 | export const getArgs = (stackItem) => stackItem.args; 15 | 16 | export const getNameValue = (node) => node.name.value; 17 | export const getFragmentTypeName = (node) => node.typeCondition.name.value; 18 | 19 | export const mapTypeForTypeStack = (type) => ({ type }); 20 | 21 | export const mapFieldForTypeStack = (field) => ({ 22 | type: new TypeWrap(field.type).realType(), 23 | args: field.args.reduce(reduceArgs, {}), 24 | }); 25 | 26 | export const mapNodeForTypeStack = (node) => ({ 27 | type: new TypeWrap(node.type).realType(), 28 | }); 29 | 30 | export const groupFields = (predicate, object) => { 31 | const result = {}; 32 | for (const key in object) { 33 | const predicateValue = predicate(object[key]); 34 | if (!result[predicateValue]) result[predicateValue] = {}; 35 | result[predicateValue][key] = object[key]; 36 | } 37 | return result; 38 | }; 39 | 40 | export const reduceValues = (values) => { 41 | return values.reduce((state, item) => { 42 | state[item.name] = R.omit(['deprecationReason', 'isDeprecated'], item); 43 | return state; 44 | }, {}); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/relations/visitorHandlers/index.ts: -------------------------------------------------------------------------------- 1 | import { AMModelType } from '../../../definitions'; 2 | import { defaultObjectFieldVisitorHandler } from '../../common/visitorHandlers'; 3 | import { 4 | abstractCreateHandlerFactory, 5 | abstractReadHandlerFactory, 6 | } from './abstract'; 7 | import { 8 | modelCreateManyHandlerFactory, 9 | modelCreateOneHandlerFactory, 10 | modelReadManyHandlerFactory, 11 | modelReadOneHandlerFactory, 12 | } from './model'; 13 | 14 | export const readManyHandlerFactory = (modelType: AMModelType) => { 15 | return modelType.mmAbstract 16 | ? abstractReadHandlerFactory(modelType) 17 | : modelReadManyHandlerFactory(modelType); 18 | }; 19 | 20 | export const createManyHandlerFactory = (modelType: AMModelType) => { 21 | return modelType.mmAbstract 22 | ? abstractCreateHandlerFactory(modelType) 23 | : modelCreateManyHandlerFactory(modelType); 24 | }; 25 | 26 | export const createOneHandlerFactory = (modelType: AMModelType) => { 27 | return modelType.mmAbstract 28 | ? defaultObjectFieldVisitorHandler /* For abstract interface we create operations inside AMInterfaceCreateTypeFactory */ 29 | : modelCreateOneHandlerFactory(modelType); 30 | }; 31 | 32 | export const readOneHandlerFactory = (modelType: AMModelType) => { 33 | return modelType.mmAbstract 34 | ? defaultObjectFieldVisitorHandler /* For abstract interface we create operations inside AMInterfaceCreateTypeFactory */ 35 | : modelReadOneHandlerFactory(modelType); 36 | }; 37 | -------------------------------------------------------------------------------- /packages/type-geojson/src/whereWithin.ts: -------------------------------------------------------------------------------- 1 | import { AMInputFieldFactory, AMModelField } from '@graphex/core'; 2 | import { 3 | AMDataContext, 4 | AMObjectFieldContext, 5 | AMSelectorContext, 6 | AMInputField, 7 | } from '@graphex/core'; 8 | 9 | import { ObjectFieldNode } from 'graphql'; 10 | 11 | export class AMGeoJSONWithinFieldFactory extends AMInputFieldFactory { 12 | isApplicable() { 13 | return true; 14 | } 15 | getFieldName(field: AMModelField) { 16 | return `${field.name}_within`; 17 | } 18 | getField(field: AMModelField) { 19 | return { 20 | name: this.getFieldName(field), 21 | type: this.schemaInfo.schema.getTypeMap().GeoJSONPointWithinInput, 22 | 23 | amEnter(node: ObjectFieldNode, transaction, stack) { 24 | const action = new AMObjectFieldContext(field.dbName); 25 | stack.push(action); 26 | }, 27 | amLeave(node, transaction, stack) { 28 | const context = stack.pop() as AMObjectFieldContext; 29 | 30 | const lastInStack = stack.last(); 31 | if ( 32 | lastInStack instanceof AMDataContext || 33 | lastInStack instanceof AMSelectorContext 34 | ) { 35 | const value = context.value as { 36 | geometry: number[][][]; 37 | }; 38 | 39 | lastInStack.addValue(context.fieldName, { 40 | $geoWithin: { 41 | $geometry: value.geometry, 42 | }, 43 | }); 44 | } 45 | }, 46 | } as AMInputField; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/model/input/fieldFactories/update.ts: -------------------------------------------------------------------------------- 1 | import TypeWrap from '@graphex/type-wrap'; 2 | import { getNamedType, isCompositeType } from 'graphql'; 3 | import { 4 | AMInputFieldFactory, 5 | AMInputObjectType, 6 | } from '../../../../definitions'; 7 | import { AMObjectFieldContext } from '../../../../execution/contexts/objectField'; 8 | 9 | export class AMUpdateFieldFactory extends AMInputFieldFactory { 10 | isApplicable(field) { 11 | return ( 12 | !isCompositeType(getNamedType(field.type)) && 13 | !field.isID && 14 | !field.isReadOnly 15 | ); 16 | } 17 | getFieldName(field) { 18 | return field.name; 19 | } 20 | getField(field) { 21 | return { 22 | name: this.getFieldName(field), 23 | extensions: undefined, 24 | type: new TypeWrap(field.type) 25 | .setRequired(false) 26 | .type() as AMInputObjectType, 27 | amEnter(node, transaction, stack) { 28 | const action = new AMObjectFieldContext(field.dbName); 29 | stack.push(action); 30 | }, 31 | amLeave(node, transaction, stack) { 32 | const operation = stack.lastOperation(); 33 | const path = stack.getFieldPath(operation); 34 | const context = stack.pop() as AMObjectFieldContext; 35 | 36 | const data = stack.getOperationData(operation); 37 | const set = (data.data && data.data['$set']) || {}; 38 | data.addValue('$set', set); 39 | set[path] = context.value; 40 | }, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/acl/src/applyRules.ts: -------------------------------------------------------------------------------- 1 | import R from 'ramda'; 2 | import { SchemaFilter, removeUnusedTypes } from '@graphex/schema-filter'; 3 | import { transformSchema } from '@apollo-model/graphql-tools'; 4 | 5 | export const applyRules = ( 6 | schema, 7 | { 8 | allow: allowRules = [], 9 | deny: denyRules = [], 10 | defaults = [], 11 | argsDefaults = [], 12 | } 13 | ) => { 14 | const prepareRules = (rules) => rules.map((rule) => rule(schema)); 15 | 16 | allowRules = prepareRules(allowRules); 17 | denyRules = prepareRules(denyRules); 18 | defaults = prepareRules(defaults); 19 | argsDefaults = prepareRules(argsDefaults); 20 | 21 | const filterFields = SchemaFilter({ 22 | filterFields: (type, field) => { 23 | const allow = R.anyPass(allowRules)({ type, field, schema }); 24 | const deny = R.anyPass(denyRules)({ type, field, schema }); 25 | 26 | return allow && !deny; 27 | }, 28 | defaultFields: (type, field) => { 29 | const defaultFn = defaults.find((item) => 30 | item.cond({ type, field, schema }) 31 | ); 32 | if (!defaultFn) { 33 | return undefined; 34 | } 35 | return defaultFn.fn; 36 | }, 37 | defaultArgs: (type, field) => { 38 | const defaultFn = argsDefaults.find((item) => 39 | item.cond({ type, field, schema }) 40 | ); 41 | if (!defaultFn) { 42 | return undefined; 43 | } 44 | return defaultFn.fn; 45 | }, 46 | }); 47 | 48 | return removeUnusedTypes(transformSchema(schema, [filterFields])); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/schema-filter/src/removeUnusedTypes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLNamedType, 4 | isUnionType, 5 | isInterfaceType, 6 | isObjectType, 7 | isInputObjectType, 8 | getNamedType, 9 | } from 'graphql'; 10 | 11 | export const removeUnusedTypes = (schema: GraphQLSchema) => { 12 | const visitedTypes = new Set(); 13 | 14 | const visitType = (type: GraphQLNamedType) => { 15 | if (visitedTypes.has(type)) return; 16 | visitedTypes.add(type); 17 | 18 | if (isUnionType(type)) { 19 | type.getTypes().forEach(visitType); 20 | } else if (isInterfaceType(type)) { 21 | schema.getPossibleTypes(type).forEach(visitType); 22 | } else if (isObjectType(type)) { 23 | Object.values(type.getFields()).forEach(field => { 24 | visitType(getNamedType(field.type)); 25 | field.args.forEach(arg => { 26 | visitType(getNamedType(arg.type)); 27 | }); 28 | }); 29 | } else if (isInputObjectType(type)) { 30 | Object.values(type.getFields()).forEach(field => 31 | visitType(getNamedType(field.type)) 32 | ); 33 | } 34 | }; 35 | 36 | [ 37 | schema.getQueryType(), 38 | schema.getMutationType(), 39 | schema.getSubscriptionType(), 40 | ] 41 | .filter(type => Boolean(type)) 42 | .forEach(visitType); 43 | 44 | return new GraphQLSchema({ 45 | query: schema.getQueryType(), 46 | mutation: schema.getMutationType(), 47 | subscription: schema.getSubscriptionType(), 48 | types: Array.from(visitedTypes.values()), 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /packages/acl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/acl", 3 | "version": "1.2.4", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/*" 8 | ], 9 | "scripts": { 10 | "dev": "MONGO_URL='mongodb+srv://public:public@cluster0-c6p6b.mongodb.net/admin' MONGO_DB='db1' nodemon --exec babel-node dev-server/index.js", 11 | "test": "jest", 12 | "prepare": "rimraf ./lib && babel --extensions '.ts,.js' ./src --out-dir ./lib" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "@apollo-model/graphql-tools": "4.0.7", 19 | "@apollo/federation": "^0.7.0", 20 | "@graphex/ast-from-value": "1.0.0", 21 | "@graphex/core": "1.2.4", 22 | "@graphex/mongodb-executor": "1.0.0", 23 | "@graphex/schema-filter": "1.0.0", 24 | "@graphex/type-wrap": "1.0.0", 25 | "apollo-server": "^2.7.0", 26 | "ramda": "^0.26.1" 27 | }, 28 | "devDependencies": { 29 | "@apollo/gateway": "^0.8.1", 30 | "@babel/cli": "^7.1.2", 31 | "@babel/core": "^7.1.2", 32 | "@babel/node": "^7.2.2", 33 | "@babel/plugin-proposal-class-properties": "^7.2.3", 34 | "@babel/plugin-proposal-pipeline-operator": "^7.5.0", 35 | "@babel/plugin-transform-runtime": "^7.2.0", 36 | "@babel/preset-env": "^7.1.0", 37 | "@babel/preset-typescript": "^7.9.0", 38 | "rimraf": "^2.6.3" 39 | }, 40 | "publishConfig": { 41 | "access": "public" 42 | }, 43 | "peerDependencies": { 44 | "graphql": "14.6.0" 45 | }, 46 | "gitHead": "659f8df0bee46a717d9939878771136ee17e5f53" 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/src/schemaGeneration/rootMethods/deleteOneMutation.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLNonNull } from 'graphql'; 2 | import R from 'ramda'; 3 | import { 4 | AMField, 5 | AMMethodFieldFactory, 6 | AMModelType, 7 | GraphQLOperationType, 8 | } from '../../definitions'; 9 | import { AMDeleteOperation } from '../../execution/operations/deleteOperation'; 10 | import { resolve } from '../../resolve'; 11 | import { attachDiscriminatorToOperationHandler } from './visitorHandlers/attachDiscriminatorToOperationHandler'; 12 | 13 | export class AMModelDeleteOneMutationFieldFactory extends AMMethodFieldFactory { 14 | getOperationType() { 15 | return GraphQLOperationType.Mutation; 16 | } 17 | getFieldName(modelType: AMModelType): string { 18 | return R.concat('delete')(modelType.name); 19 | } 20 | getField(modelType: AMModelType) { 21 | return { 22 | name: this.getFieldName(modelType), 23 | description: '', 24 | isDeprecated: false, 25 | type: modelType, 26 | args: [ 27 | { 28 | name: 'where', 29 | type: new GraphQLNonNull( 30 | this.configResolver.resolveInputType(modelType, this.links.where) 31 | ), 32 | }, 33 | ], 34 | amEnter(node, transaction, stack) { 35 | const operation = new AMDeleteOperation(transaction, { 36 | many: false, 37 | collectionName: modelType.mmCollectionName, 38 | }); 39 | stack.push(operation); 40 | }, 41 | ...attachDiscriminatorToOperationHandler(modelType), 42 | resolve: resolve, 43 | } as AMField; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/example-gateway/src/index.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { execute, makePromise } from 'apollo-link'; 3 | import { HttpLink } from 'apollo-link-http'; 4 | import gql from 'graphql-tag'; 5 | 6 | var proxy = require('express-http-proxy'); 7 | var app = require('express')(); 8 | 9 | const ammServer = 'http://localhost:4000'; /* example-server */ 10 | const defaultServer = 11 | 'http://localhost:4002'; /* example-gateway/default-server */ 12 | const authorizedServer = 'http://localhost:4001'; /* acl/dev-server */ 13 | 14 | const AMMLink = new HttpLink({ uri: ammServer, fetch }); 15 | 16 | const roles = { 17 | default: proxy(defaultServer), 18 | admin: proxy(authorizedServer), 19 | }; 20 | 21 | const selectProxy = async (req, res, next) => { 22 | let { token } = req.headers; 23 | 24 | //Do not store token in DB as is in production 25 | const operation = { 26 | query: gql` 27 | query getSession($token: ID) { 28 | sessions(where: { token: $token }) { 29 | user { 30 | ... on Node { 31 | id 32 | __typename 33 | } 34 | } 35 | } 36 | } 37 | `, 38 | variables: { token }, 39 | }; 40 | 41 | const { data } = await makePromise(execute(AMMLink, operation)); 42 | const { sessions } = data; 43 | let role = 'default'; 44 | if (sessions.length > 0) { 45 | let session = sessions[0]; 46 | if (session.user.__typename == 'Admin') role = 'admin'; 47 | } 48 | 49 | roles[role](req, res, next); 50 | }; 51 | 52 | app.use('/', selectProxy); 53 | 54 | app.listen(3000); 55 | -------------------------------------------------------------------------------- /packages/core/__tests__/types/where.test.ts: -------------------------------------------------------------------------------- 1 | import { printType } from 'graphql'; 2 | import gql from 'graphql-tag'; 3 | import { generateSchema } from './generateSchema'; 4 | 5 | describe('where', () => { 6 | const schema = generateSchema(gql` 7 | type Post @model { 8 | id: ID @id @unique @db(name: "_id") 9 | title: String 10 | status: String @readonly 11 | } 12 | `); 13 | 14 | const postWhereInputType = schema.getType('PostWhereInput'); 15 | 16 | test('schema', () => { 17 | expect(printType(postWhereInputType)).toMatchInlineSnapshot(` 18 | "input PostWhereInput { 19 | AND: [PostWhereInput] 20 | OR: [PostWhereInput] 21 | id_exists: Boolean 22 | id_in: [ID] 23 | id_not_in: [ID] 24 | id: ID 25 | id_not: ID 26 | title_exists: Boolean 27 | title_in: [String] 28 | title_not_in: [String] 29 | title: String 30 | title_lt: String 31 | title_lte: String 32 | title_gt: String 33 | title_gte: String 34 | title_not: String 35 | title_contains: String 36 | title_starts_with: String 37 | title_ends_with: String 38 | status_exists: Boolean 39 | status_in: [String] 40 | status_not_in: [String] 41 | status: String 42 | status_lt: String 43 | status_lte: String 44 | status_gt: String 45 | status_gte: String 46 | status_not: String 47 | status_contains: String 48 | status_starts_with: String 49 | status_ends_with: String 50 | }" 51 | `); 52 | }); 53 | }); -------------------------------------------------------------------------------- /packages/core/src/execution/operations/readDbRefOperation.ts: -------------------------------------------------------------------------------- 1 | import { AMOperation } from '../operation'; 2 | import { AMDBExecutor, AMDBExecutorOperationType } from '../../definitions'; 3 | import { completeAMResultPromise } from '../resultPromise/utils'; 4 | 5 | import R from 'ramda'; 6 | import { DBRef } from 'mongodb'; 7 | 8 | export class AMReadDBRefOperation extends AMOperation { 9 | async execute(executor: AMDBExecutor) { 10 | try { 11 | const refList = (await completeAMResultPromise( 12 | this.dbRefList || [this.dbRef] 13 | )) as DBRef[]; 14 | const groupedRefs = R.groupBy(R.prop('namespace'), refList); 15 | 16 | const result = Object.fromEntries( 17 | await Promise.all( 18 | Object.entries(groupedRefs).map(async ([collectionName, refs]) => { 19 | const data = await executor({ 20 | type: AMDBExecutorOperationType.FIND, 21 | collection: collectionName, 22 | selector: { _id: { $in: refs.map(ref => ref.oid) } }, 23 | fields: await completeAMResultPromise( 24 | this.fieldsSelection ? this.fieldsSelection.fields : undefined 25 | ), 26 | options: { 27 | sort: this.orderBy, 28 | limit: this.first, 29 | skip: this.skip, 30 | }, 31 | }); 32 | 33 | return [collectionName, R.indexBy(R.prop('_id'), data)]; 34 | }) 35 | ) 36 | ); 37 | this._result.resolve(result); 38 | } catch (err) { 39 | this._result.reject(err); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/ra-data/__tests__/introspectionResult.test.ts: -------------------------------------------------------------------------------- 1 | import { prepareIntrospection } from './utils'; 2 | import { IntrospectionResult } from '../src/introspectionResult'; 3 | import gql from 'graphql-tag'; 4 | 5 | let introspection: IntrospectionResult; 6 | beforeAll(async () => { 7 | introspection = await prepareIntrospection(gql` 8 | interface User @model @inherit { 9 | id: ID! @id @unique 10 | username: String 11 | } 12 | type Admin implements User { 13 | nick: String 14 | } 15 | type Visitor implements User { 16 | session: String 17 | } 18 | 19 | type Post @model { 20 | id: ID! @id @unique 21 | title: String 22 | owner: Visitor! @relation 23 | likes: [Visitor!]! @relation 24 | moderator: User! @relation 25 | approves: [User!]! @relation 26 | keywords: [String!]! 27 | } 28 | 29 | type Meta { 30 | tags: [String!]! 31 | slug: String 32 | } 33 | `); 34 | }); 35 | 36 | test('getUpdateType', () => { 37 | // const resource = {}; 38 | expect(introspection.getUpdateType('Admin', 'data').name).toMatch( 39 | 'AdminUpdateInput' 40 | ); 41 | expect(introspection.getUpdateType('User', 'where').name).toMatch( 42 | 'UserInterfaceWhereUniqueInput' 43 | ); 44 | }); 45 | 46 | test('getOneWhereType', () => { 47 | expect(introspection.getGetOneWhereType('User').name).toMatch( 48 | 'UserInterfaceWhereUniqueInput' 49 | ); 50 | }); 51 | 52 | test('getManyWhereType', () => { 53 | expect(introspection.getGetManyWhereType('User').name).toMatch( 54 | 'UserInterfaceWhereInput' 55 | ); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/ra-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphex/ra-data-apollo-model", 3 | "version": "1.0.0", 4 | "description": "An apollo-model data provider for react-admin", 5 | "main": "lib/index.js", 6 | "sideEffects": false, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/apollo-model/apollo-model" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/apollo-model/apollo-model/issues" 13 | }, 14 | "homepage": "https://github.com/apollo-model/apollo-model", 15 | "keywords": [ 16 | "reactjs", 17 | "react", 18 | "react-admin", 19 | "apoll-model" 20 | ], 21 | "files": [ 22 | "lib/*" 23 | ], 24 | "license": "MIT", 25 | "scripts": { 26 | "prepare": "tsc", 27 | "watch": "tsc --watch" 28 | }, 29 | "dependencies": { 30 | "graphql-tag": "^2.6.1", 31 | "lodash": "~4.17.5", 32 | "pluralize": "^7.0.0", 33 | "ra-data-graphql": "^2.3.0", 34 | "ramda": "^0.27.0", 35 | "react": "^16.12.0", 36 | "react-dom": "^16.12.0" 37 | }, 38 | "peerDependencies": { 39 | "graphql": "^14.6.0", 40 | "react-admin": "^2.3.0" 41 | }, 42 | "devDependencies": { 43 | "@types/jest": "^25.1.1", 44 | "@types/lodash": "^4.14.116", 45 | "@types/pluralize": "^0.0.29", 46 | "apollo-client": "^2.4.2", 47 | "babel-cli": "~6.26.0", 48 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 49 | "babel-preset-env": "^1.7.0", 50 | "cross-env": "^5.2.0", 51 | "react-admin": "^2.3.0", 52 | "rimraf": "^2.6.2", 53 | "typescript": "^4.2.4" 54 | }, 55 | "publishConfig": { 56 | "access": "public" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - prepare 3 | - test 4 | - release 5 | 6 | workflow: 7 | rules: 8 | - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\)/ 9 | when: never 10 | - when: always 11 | 12 | prepare: 13 | stage: prepare 14 | image: node:12 15 | cache: 16 | paths: 17 | - .yarn 18 | only: 19 | - branches 20 | artifacts: 21 | paths: 22 | - node_modules 23 | - packages/*/node_modules 24 | - packages/*/lib 25 | script: 26 | - yarn install --frozen-lockfile --cache-folder .yarn 27 | 28 | test: 29 | stage: test 30 | image: node:12 31 | only: 32 | - branches 33 | dependencies: 34 | - prepare 35 | artifacts: 36 | when: always 37 | reports: 38 | cobertura: coverage/clover.xml 39 | junit: 40 | - junit.xml 41 | coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/' 42 | script: 43 | - yarn test --ci --reporters=jest-junit --reporters=default --collectCoverage 44 | 45 | release: 46 | stage: release 47 | image: node:12 48 | only: 49 | - master 50 | dependencies: 51 | - prepare 52 | script: 53 | - git config --global user.email "bot@graphex.io" 54 | - git config --global user.name "Graphex Bot" 55 | - git checkout ${CI_COMMIT_REF_NAME} 56 | - git remote set-url origin https://private-token:$GIT_PUSH_TOKEN@${CI_PROJECT_URL:8}.git 57 | - yarn global add lerna 58 | - npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN 59 | - 'lerna version --conventional-commits --create-release gitlab --message "chore(release): publish" --exact --yes' 60 | - lerna publish from-git --no-verify-access --yes 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphex", 3 | "version": "0.4.0", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://gitlab.com/graphexio/graphex.git" 8 | }, 9 | "devDependencies": { 10 | "@types/jest": "^24.0.18", 11 | "@types/mongodb": "^3.5.0", 12 | "@typescript-eslint/eslint-plugin": "^2.21.0", 13 | "@typescript-eslint/parser": "^2.21.0", 14 | "eslint": "^6.8.0", 15 | "eslint-config-prettier": "^6.10.1", 16 | "eslint-plugin-prettier": "^3.1.2", 17 | "graphql": "14.6.0", 18 | "jest": "^25.1.0", 19 | "jest-junit": "^12.2.0", 20 | "lerna": "^3.13.1", 21 | "pg": "^8.6.0", 22 | "prettier": "^2.2.1", 23 | "sequelize": "^6.6.2", 24 | "sequelize-cli": "^6.2.0" 25 | }, 26 | "private": true, 27 | "workspaces": { 28 | "packages": [ 29 | "packages/*" 30 | ] 31 | }, 32 | "scripts": { 33 | "bootstrap": "lerna bootstrap", 34 | "prepare": "lerna run --stream --sort prepare", 35 | "test": "jest", 36 | "lint": "eslint 'packages/*/{src,__tests__}/**/*.ts'", 37 | "build:migrations": "rimraf distdb && tsc -p tsconfig.migrations.json", 38 | "db:migrate": "yarn build:migrations && sequelize-cli db:migrate", 39 | "db:migrate:dryrun": "yarn db:migrate && MIGRATIONS_DRYRUN=true sequelize-cli db:migrate", 40 | "db:seed:all": "yarn build:migrations && sequelize-cli db:seed:all" 41 | }, 42 | "keywords": [], 43 | "author": "", 44 | "license": "ISC", 45 | "bugs": { 46 | "url": "https://gitlab.com/graphexio/graphex/-/issues" 47 | }, 48 | "homepage": "https://gitlab.com/graphexio/graphex" 49 | } 50 | --------------------------------------------------------------------------------