diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index c22d087b08..507f08c6e7 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -1,4 +1,4 @@ -name: PostgreSQL tests +name: LaunchQL tests on: push: workflow_dispatch: @@ -37,29 +37,15 @@ jobs: --health-retries 5 steps: - # - name: Install Git - # run: apk add --no-cache git - + # TODO remove deps on git config - name: Configure Git (for tests) run: | git config --global user.name "CI Test User" git config --global user.email "ci@example.com" - # - name: Install Sqitch on Alpine - # run: | - # apk add --no-cache curl make perl perl-utils perl-dev build-base \ - # libpq libpq-dev postgresql-dev \ - # perl-dbd-pg perl-dbi perl-dbd-sqlite \ - # cpanminus - - # cpanm --notest --quiet App::Sqitch - - name: checkout uses: actions/checkout@v4 - # - name: deps - # run: apk update && apk add bash git python3-dev make g++ - - name: deps run: yarn @@ -92,14 +78,14 @@ jobs: - name: launchql/uuid-stream run: cd ./packages/uuid-stream && yarn test - - name: launchql/query-builder - run: cd ./packages/query-builder && yarn test - - name: launchql/introspectron run: cd ./packages/introspectron && yarn test - - name: launchql/react-client - run: cd ./packages/react-client && yarn test + - name: launchql/query-builder + run: cd ./packages/query-builder && yarn test + + - name: launchql/query + run: cd ./packages/query && yarn test - name: launchql/launchql-gen run: cd ./packages/launchql-gen && yarn test diff --git a/packages/introspectron/package.json b/packages/introspectron/package.json index 0edf7f075f..80c6f5e865 100644 --- a/packages/introspectron/package.json +++ b/packages/introspectron/package.json @@ -30,7 +30,6 @@ "test:watch": "jest --watch" }, "dependencies": { - "graphql-tag": "2.12.5", - "lodash": "4.17.20" + "graphql-tag": "2.12.5" } } \ No newline at end of file diff --git a/packages/introspectron/src/introspect.ts b/packages/introspectron/src/introspect.ts index 347043d794..24ab9ea576 100644 --- a/packages/introspectron/src/introspect.ts +++ b/packages/introspectron/src/introspect.ts @@ -1,7 +1,6 @@ // @ts-nocheck import { makeIntrospectionQuery } from './query'; import { parseTags } from './utils'; -import flatMap from 'lodash/flatMap'; export const introspect = async ( pgClient, @@ -66,8 +65,7 @@ export const introspect = async ( }); }); - const extensionConfigurationClassIds = flatMap( - result.extension, + const extensionConfigurationClassIds = result.extension.flatMap( (e) => e.configurationClassIds ); result.class.forEach((klass) => { diff --git a/packages/react-client/README.md b/packages/query/README.md similarity index 55% rename from packages/react-client/README.md rename to packages/query/README.md index 9bdda8a033..dd3e06754c 100644 --- a/packages/react-client/README.md +++ b/packages/query/README.md @@ -1,4 +1,4 @@ -# @launchql/react-client +# `@launchql/query`

@@ -9,57 +9,68 @@ - +

-Generate GraphQL mutations/queries +> Fluent GraphQL query and mutation builder for PostGraphile-based schemas. + +## Installation ```sh -npm install @launchql/react-client +npm install @launchql/query ``` +## Why Use `@launchql/query`? + +* ⚡ Build complex, nested GraphQL queries fluently +* ✅ Schema-aware via introspection (PostGraphile optimized) +* 🧠 Prevents common query syntax issues +* 🧩 Designed for composability and clean syntax + ## Usage -```js -import { Client } from '@launchql/react-client'; +```ts +import { Client } from '@launchql/query'; const client = new Client({ - introspection: { ...queries, ...mutations } + introspection: { ...queries, ...mutations } // provide your GraphQL schema metadata }); - const result = client - .query('Action') - .edges(true) - .getMany({ - select: { - id: true, - name: true, - photo: true, - title: true, - actionResults: { - select: { - id: true, - actionId: true - }, - variables: { - first: 10, - before: null, - filter: { - name: { - in: ['abc', 'def'] - }, - actionId: { equalTo: 'dc310161-7a42-4b93-6a56-9fa48adcad7e' } +const result = client + .query('Action') + .edges(true) + .getMany({ + select: { + id: true, + name: true, + photo: true, + title: true, + actionResults: { + select: { + id: true, + actionId: true + }, + variables: { + first: 10, + before: null, + filter: { + name: { + in: ['abc', 'def'] + }, + actionId: { + equalTo: 'dc310161-7a42-4b93-6a56-9fa48adcad7e' } } } } - }) - .print(); + } + }) + .print(); ``` -# output +## Output -```gql +```graphql query getActionsQuery( $first: Int $last: Int @@ -111,4 +122,4 @@ query getActionsQuery( } } } -``` +``` \ No newline at end of file diff --git a/packages/react-client/__fixtures__/api/introspection.json b/packages/query/__fixtures__/api/introspection.json similarity index 100% rename from packages/react-client/__fixtures__/api/introspection.json rename to packages/query/__fixtures__/api/introspection.json diff --git a/packages/react-client/__fixtures__/api/meta-obj.json b/packages/query/__fixtures__/api/meta-obj.json similarity index 100% rename from packages/react-client/__fixtures__/api/meta-obj.json rename to packages/query/__fixtures__/api/meta-obj.json diff --git a/packages/react-client/__fixtures__/api/meta-schema.json b/packages/query/__fixtures__/api/meta-schema.json similarity index 100% rename from packages/react-client/__fixtures__/api/meta-schema.json rename to packages/query/__fixtures__/api/meta-schema.json diff --git a/packages/react-client/__fixtures__/generate-fixtures.js b/packages/query/__fixtures__/generate-fixtures.js similarity index 90% rename from packages/react-client/__fixtures__/generate-fixtures.js rename to packages/query/__fixtures__/generate-fixtures.js index 136632fb64..8cd92a9c76 100644 --- a/packages/react-client/__fixtures__/generate-fixtures.js +++ b/packages/query/__fixtures__/generate-fixtures.js @@ -1,7 +1,7 @@ const path = require('path'); const fs = require('fs'); const intro = require('introspectron'); -const client = require('@launchql/react-client'); +const builder = require('@launchql/query'); function generateIntrospectionFixture() { const inDir = path.resolve( @@ -35,7 +35,7 @@ function generateMetaObjectFixture() { const outDir = path.resolve(__dirname, './api/meta-obj.json'); fs.readFile(inDir, { encoding: 'utf8' }, (err, data) => { if (err) return console.log(err); - const converted = client.MetaObject.convertFromMetaSchema(JSON.parse(data)); + const converted = builder.MetaObject.convertFromMetaSchema(JSON.parse(data)); fs.writeFile(outDir, JSON.stringify(converted), (err) => { if (err) return console.log(err); console.log('DONE'); diff --git a/packages/react-client/__tests__/__snapshots__/client.test.ts.snap b/packages/query/__tests__/__snapshots__/builder.test.ts.snap similarity index 100% rename from packages/react-client/__tests__/__snapshots__/client.test.ts.snap rename to packages/query/__tests__/__snapshots__/builder.test.ts.snap diff --git a/packages/react-client/__tests__/__snapshots__/meta-object.test.ts.snap b/packages/query/__tests__/__snapshots__/meta-object.test.ts.snap similarity index 100% rename from packages/react-client/__tests__/__snapshots__/meta-object.test.ts.snap rename to packages/query/__tests__/__snapshots__/meta-object.test.ts.snap diff --git a/packages/react-client/__tests__/client.test.ts b/packages/query/__tests__/builder.test.ts similarity index 86% rename from packages/react-client/__tests__/client.test.ts rename to packages/query/__tests__/builder.test.ts index 492c215f46..0e30aae528 100644 --- a/packages/react-client/__tests__/client.test.ts +++ b/packages/query/__tests__/builder.test.ts @@ -1,16 +1,16 @@ // @ts-nocheck import introspection from '../__fixtures__/api/introspection.json'; import metaObject from '../__fixtures__/api/meta-obj.json'; -import { Client } from '../src'; +import { QueryBuilder } from '../src'; describe('getMany', () => { it('should select only scalar fields by default', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client.query('Action').getMany().print(); + const result = builder.query('Action').getMany().print(); expect(result._hash).toMatchSnapshot(); expect(result._queryName).toMatchSnapshot(); @@ -22,12 +22,12 @@ describe('getMany', () => { }); it('should whitelist selected fields', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .getMany({ select: { @@ -60,12 +60,12 @@ describe('getMany', () => { }); it('should select totalCount in subfields by default', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .getMany({ select: { @@ -98,12 +98,12 @@ it('should select totalCount in subfields by default', () => { }); it('selects relation field', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .getMany({ select: { @@ -140,24 +140,24 @@ it('selects relation field', () => { }); it('selects all scalar fields of junction table by default', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client.query('ActionGoal').getMany().print(); + const result = builder.query('ActionGoal').getMany().print(); expect(/(actionId)|(goalId)|(ownerId)/.test(result._hash)).toBe(true); expect(result._hash).toMatchSnapshot(); }); it('selects belongsTo relation field', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .getMany({ select: { @@ -177,12 +177,12 @@ it('selects belongsTo relation field', () => { }); it('selects non-scalar custom types', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .getMany({ select: { @@ -199,11 +199,11 @@ it('selects non-scalar custom types', () => { }); it('getMany edges', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .edges(true) .getMany({ @@ -236,11 +236,11 @@ it('getMany edges', () => { }); it('getOne', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .getOne({ select: { @@ -272,12 +272,12 @@ it('getOne', () => { }); it('getAll', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .all({ select: { @@ -309,22 +309,22 @@ it('getAll', () => { }); it('create with default scalar selection', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client.query('Action').create().print(); + const result = builder.query('Action').create().print(); expect(result._hash).toMatchSnapshot(); expect(result._queryName).toMatchSnapshot(); }); it('create with custom selection', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .create({ select: { @@ -342,21 +342,21 @@ it('create with custom selection', () => { }); it('update with default scalar selection', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client.query('Action').update().print(); + const result = builder.query('Action').update().print(); expect(result._hash).toMatchSnapshot(); expect(result._queryName).toMatchSnapshot(); }); it('update with custom selection', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('Action') .update({ select: { @@ -373,22 +373,22 @@ it('update with custom selection', () => { }); it('delete', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client.query('Action').delete().print(); + const result = builder.query('Action').delete().print(); expect(result._hash).toMatchSnapshot(); expect(result._queryName).toMatchSnapshot(); }); it('expands further selections of custom ast fields in nested selection', () => { - const client = new Client({ + const builder = new QueryBuilder({ meta: metaObject, introspection }); - const result = client + const result = builder .query('ActionGoal') .getMany({ select: { diff --git a/packages/react-client/__tests__/meta-object.test.ts b/packages/query/__tests__/meta-object.test.ts similarity index 100% rename from packages/react-client/__tests__/meta-object.test.ts rename to packages/query/__tests__/meta-object.test.ts diff --git a/packages/react-client/jest.config.js b/packages/query/jest.config.js similarity index 100% rename from packages/react-client/jest.config.js rename to packages/query/jest.config.js diff --git a/packages/react-client/package.json b/packages/query/package.json similarity index 90% rename from packages/react-client/package.json rename to packages/query/package.json index f685085ee8..e6fd4e2e7d 100644 --- a/packages/react-client/package.json +++ b/packages/query/package.json @@ -1,7 +1,7 @@ { - "name": "@launchql/react-client", + "name": "@launchql/query", "version": "2.0.0", - "description": "LaunchQL React Client", + "description": "LaunchQL Query", "author": "Dan Lynch ", "main": "index.js", "module": "esm/index.js", @@ -34,7 +34,6 @@ "gql-ast": "^2.0.0", "graphql": "15.5.2", "inflection": "1.12.0", - "lodash": "^4.17.20", "pluralize": "8.0.0" } } \ No newline at end of file diff --git a/packages/react-client/src/ast.ts b/packages/query/src/ast.ts similarity index 98% rename from packages/react-client/src/ast.ts rename to packages/query/src/ast.ts index 59120c2363..12b4cb8432 100644 --- a/packages/react-client/src/ast.ts +++ b/packages/query/src/ast.ts @@ -3,10 +3,10 @@ import * as t from 'gql-ast'; import plz from 'pluralize'; import inflection from 'inflection'; -import isArray from 'lodash/isArray'; -import isObject from 'lodash/isObject'; import { getCustomAst, isIntervalType } from './custom-ast'; +const isObject = val => val !== null && typeof val === 'object'; + const NON_MUTABLE_PROPS = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy']; const objectToArray = (obj) => Object.keys(obj).map((k) => ({ name: k, ...obj[k] })); @@ -91,7 +91,7 @@ export const getAll = ({ queryName, operationName, query, selection }) => { }; export const getMany = ({ - client, // we can use props here to enable pagination, etc + builder, // we can use props here to enable pagination, etc queryName, operationName, query, @@ -245,7 +245,7 @@ export const getMany = ({ ] }) }), - client._edges + builder._edges ? t.field({ name: 'edges', selectionSet: t.selectionSet({ @@ -277,7 +277,7 @@ export const getMany = ({ }; export const getOne = ({ - client, // we can use props here to enable pagination, etc + builder, // we can use props here to enable pagination, etc queryName, operationName, query, @@ -621,7 +621,7 @@ function getValueAst(value) { return t.booleanValue({ value }); } - if (isArray(value)) { + if (Array.isArray(value)) { return t.listValue({ values: value.map((v) => getValueAst(v)) }); } diff --git a/packages/react-client/src/custom-ast.ts b/packages/query/src/custom-ast.ts similarity index 100% rename from packages/react-client/src/custom-ast.ts rename to packages/query/src/custom-ast.ts diff --git a/packages/react-client/src/index.ts b/packages/query/src/index.ts similarity index 97% rename from packages/react-client/src/index.ts rename to packages/query/src/index.ts index cbc8992cf0..ec655f7bb6 100644 --- a/packages/react-client/src/index.ts +++ b/packages/query/src/index.ts @@ -3,11 +3,11 @@ import { print as gqlPrint } from 'graphql'; import { getMany, getOne, getAll, createOne, patchOne, deleteOne } from './ast'; import inflection from 'inflection'; import { validateMetaObject } from './meta-object'; -import { isObject } from 'lodash'; - export * as MetaObject from './meta-object'; -export class Client { +const isObject = val => val !== null && typeof val === 'object'; + +export class QueryBuilder { constructor({ meta = {}, introspection }) { this._introspection = introspection; this._meta = meta; @@ -19,7 +19,7 @@ export class Client { const validate = validateMetaObject(this._meta); if (typeof validate === 'object' && validate.errors) { throw new Error( - `Client: meta object is not in correct format ${validate.errors}` + `QueryBuilder: meta object is not in correct format ${validate.errors}` ); } } @@ -163,7 +163,7 @@ export class Client { this.select(select); this._ast = getMany({ - client: this, + builder: this, queryName: this._queryName, operationName: this._key, query: defn, @@ -188,7 +188,7 @@ export class Client { this.select(select); this._ast = getAll({ - client: this, + builder: this, queryName: this._queryName, operationName: this._key, query: defn, @@ -212,7 +212,7 @@ export class Client { const defn = this._introspection[this._key]; this.select(select); this._ast = getOne({ - client: this, + builder: this, queryName: this._queryName, operationName: this._key, query: defn, @@ -237,7 +237,7 @@ export class Client { const defn = this._introspection[this._key]; this.select(select); this._ast = createOne({ - client: this, + builder: this, operationName: this._key, mutationName: this._queryName, mutation: defn, @@ -263,7 +263,7 @@ export class Client { this.select(select); this._ast = deleteOne({ - client: this, + builder: this, operationName: this._key, mutationName: this._queryName, mutation: defn, @@ -289,7 +289,7 @@ export class Client { this.select(select); this._ast = patchOne({ - client: this, + builder: this, operationName: this._key, mutationName: this._queryName, mutation: defn, diff --git a/packages/react-client/src/meta-object/convert.ts b/packages/query/src/meta-object/convert.ts similarity index 100% rename from packages/react-client/src/meta-object/convert.ts rename to packages/query/src/meta-object/convert.ts diff --git a/packages/react-client/src/meta-object/format.json b/packages/query/src/meta-object/format.json similarity index 100% rename from packages/react-client/src/meta-object/format.json rename to packages/query/src/meta-object/format.json diff --git a/packages/react-client/src/meta-object/index.ts b/packages/query/src/meta-object/index.ts similarity index 100% rename from packages/react-client/src/meta-object/index.ts rename to packages/query/src/meta-object/index.ts diff --git a/packages/react-client/src/meta-object/validate.ts b/packages/query/src/meta-object/validate.ts similarity index 100% rename from packages/react-client/src/meta-object/validate.ts rename to packages/query/src/meta-object/validate.ts diff --git a/packages/react-client/tsconfig.esm.json b/packages/query/tsconfig.esm.json similarity index 100% rename from packages/react-client/tsconfig.esm.json rename to packages/query/tsconfig.esm.json diff --git a/packages/react-client/tsconfig.json b/packages/query/tsconfig.json similarity index 100% rename from packages/react-client/tsconfig.json rename to packages/query/tsconfig.json diff --git a/packages/react/README.md b/packages/react/README.md index 56631158d1..58f446064a 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -1,4 +1,4 @@ -# launchql-react +# @launchql/react

@@ -9,5 +9,5 @@ - +

\ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json index 66db1a224d..8ad6787754 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -28,17 +28,15 @@ "test:watch": "jest --watch" }, "dependencies": { - "@launchql/react-client": "^2.0.0", + "@launchql/query": "^2.0.0", "graphql-request": "^7.1.2", "introspectron": "^2.0.0", - "lodash": "4.17.20", "react-dom": "^19.1.0", "react-query": "^3.39.3", "react": "^19.1.0" }, "devDependencies": { "@types/react": "^19.1.3", - "@types/lodash": "^4.17.16", "@testing-library/jest-dom": "5.11.10", "@testing-library/react": "11.2.5" } diff --git a/packages/react/src/use-introspection.ts b/packages/react/src/use-introspection.ts index 7eed5d96c2..97df5d7d5f 100644 --- a/packages/react/src/use-introspection.ts +++ b/packages/react/src/use-introspection.ts @@ -2,9 +2,10 @@ import { useEffect } from 'react'; import { useQuery } from 'react-query'; // @ts-ignore import { IntrospectionQuery, parseGraphQuery } from 'introspectron'; -import noop from 'lodash/noop'; import { useGraphqlClient } from './use-graphql-client'; +const noop = () => {}; + export function useIntrospection(options = {}) { // @ts-ignore const { headers, onSuccess = noop, onError = noop, ...restOptions } = options; diff --git a/packages/react/src/use-launchql-client.ts b/packages/react/src/use-launchql-client.ts index 929b499189..90c2d8eeb5 100644 --- a/packages/react/src/use-launchql-client.ts +++ b/packages/react/src/use-launchql-client.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { Client as LqlClient, MetaObject } from '@launchql/react-client'; +import { Client as LqlClient, MetaObject } from '@launchql/query'; import { useIntrospection } from './use-introspection'; import { useSchemaMeta } from './use-schema-meta'; diff --git a/yarn.lock b/yarn.lock index 8dedd9901a..3ed1042d8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1677,11 +1677,6 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/lodash@^4.17.16": - version "4.17.16" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.16.tgz#94ae78fab4a38d73086e962d0b65c30d816bfb0a" - integrity sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g== - "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -6014,7 +6009,7 @@ lodash@4.17.20: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -"lodash@>=4 <5", lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: +"lodash@>=4 <5", lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==