From 6aace6c051219cf3225a0c8d06eedc518de198b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Fri, 24 May 2024 09:25:17 +0200 Subject: [PATCH 1/6] Update `BaseForm` types (#1325) --- packages/uniforms/src/BaseForm.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/uniforms/src/BaseForm.tsx b/packages/uniforms/src/BaseForm.tsx index 79df4f87b..b62c1ab81 100644 --- a/packages/uniforms/src/BaseForm.tsx +++ b/packages/uniforms/src/BaseForm.tsx @@ -26,7 +26,6 @@ export type BaseFormProps = { noValidate: boolean; onChange?: (key: string, value: unknown) => void; onSubmit: (model: Model) => void | Promise; - placeholder?: boolean; readOnly?: boolean; schema: Bridge; showInlineError?: boolean; @@ -175,8 +174,11 @@ export class BaseForm< getNativeFormProps(): { [key: string]: unknown; - onSubmit: BaseForm['onSubmit']; + children?: React.ReactNode; + id?: BaseFormProps['id']; key: string; + noValidate: BaseFormProps['noValidate']; + onSubmit: BaseForm['onSubmit']; } { const props = omit(this.props, [ 'autosave', From e7d77854b81233dde34ddc926b680390b645e41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Fri, 24 May 2024 09:26:30 +0200 Subject: [PATCH 2/6] Compatibility with StrictMode (#1324) --- packages/uniforms/src/BaseForm.tsx | 45 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/uniforms/src/BaseForm.tsx b/packages/uniforms/src/BaseForm.tsx index b62c1ab81..f61533a90 100644 --- a/packages/uniforms/src/BaseForm.tsx +++ b/packages/uniforms/src/BaseForm.tsx @@ -93,14 +93,6 @@ export class BaseForm< if (this.delayId) { clearTimeout(this.delayId); } - - // There are at least 4 places where we'd need to check, whether or not we - // actually perform `setState` after the component gets unmounted. Instead, - // we override it to hide the React warning. Also because React no longer - // will raise it in the newer versions. - // https://github.com/facebook/react/pull/22114 - // https://github.com/vazco/uniforms/issues/1152 - this.setState = () => {}; } delayId?: ReturnType | undefined; @@ -237,12 +229,14 @@ export class BaseForm< this.delayId = setTimeout(() => { // ...and wait for all scheduled `setState`s to commit. This is required // for AutoForm to validate correct model, waiting in `onChange`. - this.setState( - () => null, - () => { - this.onSubmit(); - }, - ); + if (this.mounted) { + this.setState( + () => null, + () => { + this.onSubmit(); + }, + ); + } }, this.props.autosaveDelay); } } @@ -258,10 +252,12 @@ export class BaseForm< } onReset() { - // @ts-expect-error - // It's bound in constructor. - // eslint-disable-next-line @typescript-eslint/unbound-method - this.setState(this.__reset); + if (this.mounted) { + // @ts-expect-error + // It's bound in constructor. + // eslint-disable-next-line @typescript-eslint/unbound-method + this.setState(this.__reset); + } } onSubmit(event?: SyntheticEvent) { @@ -270,16 +266,23 @@ export class BaseForm< event.stopPropagation(); } - this.setState(state => (state.submitted ? null : { submitted: true })); + if (this.mounted) { + this.setState(state => (state.submitted ? null : { submitted: true })); + } const result = this.props.onSubmit(this.getModel('submit')); if (!(result instanceof Promise)) { return Promise.resolve(); } - this.setState({ submitting: true }); + if (this.mounted) { + this.setState({ submitting: true }); + } + return result.finally(() => { - this.setState({ submitting: false }); + if (this.mounted) { + this.setState({ submitting: false }); + } }); } From 75dbb94d8f6018528ae1bfa47ab8277634f26530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Fri, 24 May 2024 09:46:22 +0200 Subject: [PATCH 3/6] Improve Zod bridge - passing props (#1321) --- .../__tests__/ZodBridge.ts | 24 +++++++++++++++++++ packages/uniforms-bridge-zod/src/ZodBridge.ts | 8 +++++++ packages/uniforms-bridge-zod/src/register.ts | 15 +++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index 9dd1dc797..561525e0d 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -1,3 +1,4 @@ +import { connectField } from 'uniforms'; import { ZodBridge } from 'uniforms-bridge-zod'; import { any, @@ -447,6 +448,29 @@ describe('ZodBridge', () => { const bridge = new ZodBridge({ schema }); expect(bridge.getProps('a')).toEqual({ label: 'A', required: true }); }); + + it('works with uniforms props', () => { + const schema = object({ a: string().uniforms({ type: 'password' }) }); + const bridge = new ZodBridge({ schema }); + expect(bridge.getProps('a')).toEqual({ + label: 'A', + required: true, + type: 'password', + }); + }); + + it('works with uniforms props (component)', () => { + const field = jest.fn(() => null); + const Field = connectField(field); + + const schema = object({ a: string().uniforms(Field) }); + const bridge = new ZodBridge({ schema }); + expect(bridge.getProps('a')).toEqual({ + component: Field, + label: 'A', + required: true, + }); + }); }); describe('#getSubfields', () => { diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index 6c7a241c2..c6a36667a 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -155,6 +155,14 @@ export default class ZodBridge extends Bridge { }; let field = this.getField(name); + + const uniforms = field._uniforms; + if (typeof uniforms === 'function') { + props.component = uniforms; + } else { + Object.assign(props, uniforms); + } + if (field instanceof ZodDefault) { field = field.removeDefault(); props.required = false; diff --git a/packages/uniforms-bridge-zod/src/register.ts b/packages/uniforms-bridge-zod/src/register.ts index fe2af507a..fae893e9c 100644 --- a/packages/uniforms-bridge-zod/src/register.ts +++ b/packages/uniforms-bridge-zod/src/register.ts @@ -1,4 +1,5 @@ -import { filterDOMProps } from 'uniforms'; +import { ConnectedField, filterDOMProps, UnknownObject } from 'uniforms'; +import { z, ZodTypeAny } from 'zod'; // There's no possibility to retrieve them at runtime. declare module 'uniforms' { @@ -9,3 +10,15 @@ declare module 'uniforms' { } filterDOMProps.register('minCount', 'maxCount'); + +declare module 'zod' { + interface ZodType { + uniforms(uniforms: UnknownObject | ConnectedField): ZodTypeAny; + _uniforms: UnknownObject | ConnectedField; + } +} + +z.ZodType.prototype.uniforms = function extend(uniforms) { + this._uniforms = uniforms; + return this; +}; From a77b8e288d87ca112159a19556e9c50b00d17320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Fri, 24 May 2024 09:46:40 +0200 Subject: [PATCH 4/6] Improve Zod error messages (#1320) --- .../__tests__/ZodBridge.ts | 9 ++++--- packages/uniforms-bridge-zod/src/ZodBridge.ts | 27 ++++++++++++++++--- packages/uniforms/__suites__/ErrorsField.tsx | 4 ++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index 561525e0d..5c530f95a 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -132,7 +132,7 @@ describe('ZodBridge', () => { const schema = object({ a: string(), b: number() }); const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); - const messages = error?.issues?.map(issue => issue.message); + const messages = ['A: Required', 'B: Required']; expect(bridge.getErrorMessages(error)).toEqual(messages); }); @@ -140,7 +140,10 @@ describe('ZodBridge', () => { const schema = object({ a: array(array(string())) }); const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); - const messages = error?.issues?.map(issue => issue.message); + const messages = [ + 'A (0, 2): Expected string, received number', + 'A (1, 0): Expected string, received number', + ]; expect(bridge.getErrorMessages(error)).toEqual(messages); }); @@ -148,7 +151,7 @@ describe('ZodBridge', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); - const messages = error?.issues?.map(issue => issue.message); + const messages = ['C: Expected string, received number']; expect(bridge.getErrorMessages(error)).toEqual(messages); }); }); diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index c6a36667a..84bb972b4 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -10,6 +10,7 @@ import { ZodDefault, ZodEnum, ZodError, + ZodIssue, ZodNativeEnum, ZodNumber, ZodNumberDef, @@ -28,6 +29,23 @@ function isNativeEnumValue(value: unknown) { return typeof value !== 'string'; } +function getLabel(value: unknown) { + return upperFirst(lowerCase(joinName(null, value).slice(-1)[0])); +} + +function getFullLabel(path: ZodIssue['path'], indexes: number[] = []): string { + const lastElement = path[path.length - 1]; + + if (typeof lastElement === 'number') { + const slicedPath = path.slice(0, path.length - 1); + return getFullLabel(slicedPath, [lastElement, ...indexes]); + } + + return indexes.length > 0 + ? `${getLabel(path)} (${indexes.join(', ')})` + : getLabel(path); +} + /** Option type used in SelectField or RadioField */ type Option = { disabled?: boolean; @@ -73,9 +91,10 @@ export default class ZodBridge extends Bridge { getErrorMessages(error: unknown) { if (error instanceof ZodError) { - // TODO: There's no information which field caused which error. We could - // do some generic prefixing, e.g., `{name}: {message}`. - return error.issues.map(issue => issue.message); + return error.issues.map(issue => { + const name = getFullLabel(issue.path); + return `${name}: ${issue.message}`; + }); } if (error instanceof Error) { @@ -149,7 +168,7 @@ export default class ZodBridge extends Bridge { getProps(name: string) { const props: UnknownObject & { options?: Option[] } = { ...(this.provideDefaultLabelFromFieldName && { - label: upperFirst(lowerCase(joinName(null, name).slice(-1)[0])), + label: getLabel(name), }), required: true, }; diff --git a/packages/uniforms/__suites__/ErrorsField.tsx b/packages/uniforms/__suites__/ErrorsField.tsx index ef333fc4e..5aa31e884 100644 --- a/packages/uniforms/__suites__/ErrorsField.tsx +++ b/packages/uniforms/__suites__/ErrorsField.tsx @@ -15,7 +15,9 @@ export function testErrorsField(ErrorsField: ComponentType) { ]), schema: z.object({ x: z.boolean(), y: z.number(), z: z.string() }), }); - expect(screen.getAllByText(errorMessage)).toHaveLength(3); + expect(screen.getByText(`X: ${errorMessage}`)).toBeInTheDocument(); + expect(screen.getByText(`Y: ${errorMessage}`)).toBeInTheDocument(); + expect(screen.getByText(`Z: ${errorMessage}`)).toBeInTheDocument(); }); test(' - renders error from props', () => { From 7d6db69ddc43b31c74490af33903af813017d9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Fri, 24 May 2024 10:40:48 +0200 Subject: [PATCH 5/6] Add support for Zod `.refine` and `.superRefine` (#1319) --- .../__tests__/ZodBridge.ts | 43 +++++++++++++++++++ packages/uniforms-bridge-zod/src/ZodBridge.ts | 10 ++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index 5c530f95a..b874f679b 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -29,6 +29,7 @@ import { undefined as undefined_, union, unknown, + ZodIssueCode, } from 'zod'; describe('ZodBridge', () => { @@ -71,6 +72,42 @@ describe('ZodBridge', () => { expect(bridge.getError('a.b', error)).toBe(null); expect(bridge.getError('a.b.c', error)).toBe(issues?.[0]); }); + + it('works with refined schema', () => { + const errorMessage = 'Different values'; + + const schema = object({ + a: string(), + b: string(), + }).refine(({ a, b }) => a === b, { + message: errorMessage, + path: ['b'], + }); + + const bridge = new ZodBridge({ schema }); + const error = bridge.getValidator()({ a: 'a', b: 'b' }); + expect(error?.issues?.[0]?.message).toBe(errorMessage); + }); + + it('works with super refined schema', () => { + const errorMessage = 'Different values'; + + const schema = object({ + a: string(), + b: string(), + }).superRefine((val, ctx) => { + if (val.a !== val.b) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: errorMessage, + }); + } + }); + + const bridge = new ZodBridge({ schema }); + const error = bridge.getValidator()({ a: 'a', b: 'b' }); + expect(error?.issues?.[0]?.message).toBe(errorMessage); + }); }); describe('#getErrorMessage', () => { @@ -201,6 +238,12 @@ describe('ZodBridge', () => { expect(bridge.getField('a')).toBe(schema.shape.a); expect(bridge.getField('a.b')).toBe(schema.shape.a.unwrap().shape.b); }); + + it('works with ZodEffects', () => { + const schema = object({}).refine(data => data); + const bridge = new ZodBridge({ schema }); + expect(bridge.getField('')).toBe(schema._def.schema); + }); }); describe('#getInitialValue', () => { diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index 84bb972b4..967aa8a8f 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -8,6 +8,7 @@ import { ZodBoolean, ZodDate, ZodDefault, + ZodEffects, ZodEnum, ZodError, ZodIssue, @@ -55,14 +56,14 @@ type Option = { }; export default class ZodBridge extends Bridge { - schema: ZodObject; + schema: ZodObject | ZodEffects>; provideDefaultLabelFromFieldName: boolean; constructor({ schema, provideDefaultLabelFromFieldName = true, }: { - schema: ZodObject; + schema: ZodObject | ZodEffects>; provideDefaultLabelFromFieldName?: boolean; }) { super(); @@ -106,6 +107,11 @@ export default class ZodBridge extends Bridge { getField(name: string) { let field: ZodType = this.schema; + + if (this.schema instanceof ZodEffects) { + field = this.schema._def.schema; + } + for (const key of joinName(null, name)) { if (field instanceof ZodDefault) { field = field.removeDefault(); From 1436d4c645fc77bf4120163bf8a7b4cce5a2f302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Fri, 24 May 2024 11:32:33 +0200 Subject: [PATCH 6/6] Deprecate `uniforms-bridge-graphql` package (#1322) --- .github/labeler.yml | 5 - README.md | 1 - docs/api-bridges.md | 185 ++++---- package.json | 1 - packages/uniforms-bridge-graphql/README.md | 11 - .../__tests__/GraphQLBridge.ts | 440 ------------------ .../__tests__/index.ts | 8 - packages/uniforms-bridge-graphql/package.json | 35 -- .../src/GraphQLBridge.ts | 219 --------- packages/uniforms-bridge-graphql/src/index.ts | 1 - .../uniforms-bridge-graphql/tsconfig.cjs.json | 12 - .../uniforms-bridge-graphql/tsconfig.esm.json | 12 - reproductions/App.tsx | 1 - reproductions/package.json | 1 - reproductions/schema/graphql-schema.tsx | 31 -- tsconfig.build.json | 2 - tsconfig.global.json | 2 - website/lib/presets.ts | 36 -- website/lib/schema.ts | 5 - website/package.json | 2 - 20 files changed, 97 insertions(+), 913 deletions(-) delete mode 100644 packages/uniforms-bridge-graphql/README.md delete mode 100644 packages/uniforms-bridge-graphql/__tests__/GraphQLBridge.ts delete mode 100644 packages/uniforms-bridge-graphql/__tests__/index.ts delete mode 100644 packages/uniforms-bridge-graphql/package.json delete mode 100644 packages/uniforms-bridge-graphql/src/GraphQLBridge.ts delete mode 100644 packages/uniforms-bridge-graphql/src/index.ts delete mode 100644 packages/uniforms-bridge-graphql/tsconfig.cjs.json delete mode 100644 packages/uniforms-bridge-graphql/tsconfig.esm.json delete mode 100644 reproductions/schema/graphql-schema.tsx diff --git a/.github/labeler.yml b/.github/labeler.yml index e8da31ba5..a643b0c42 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,6 @@ 'Area: Bridge': - changed-files: - any-glob-to-any-file: - - packages/uniforms-bridge-graphql/**/* - packages/uniforms-bridge-json-schema/**/* - packages/uniforms-bridge-simple-schema-2/**/* - packages/uniforms-bridge-simple-schema/**/* @@ -50,10 +49,6 @@ - packages/uniforms-mui/**/* - packages/uniforms-semantic/**/* - packages/uniforms-unstyled/**/* -'Bridge: GraphQL': - - changed-files: - - any-glob-to-any-file: - - packages/uniforms-bridge-graphql/**/* 'Bridge: JSON Schema': - changed-files: - any-glob-to-any-file: diff --git a/README.md b/README.md index 19d734c9a..a49b7ccb1 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,6 @@ - **Inline and asynchronous form validation** - **Integrations with various schemas:** - **[JSON Schema](http://json-schema.org/)** - - **[GraphQL](https://github.com/graphql/graphql-js)** - **[SimpleSchema](https://github.com/aldeed/meteor-simple-schema)** - **[SimpleSchema@2](https://github.com/aldeed/node-simple-schema)** - **[Zod](https://github.com/colinhacks/zod)** diff --git a/docs/api-bridges.md b/docs/api-bridges.md index 7bd648b96..e4044e7eb 100644 --- a/docs/api-bridges.md +++ b/docs/api-bridges.md @@ -11,101 +11,16 @@ To make use of any schema, uniforms have to create a _bridge_ of it - a unified Currently available bridges: -- `GraphQLBridge` in `uniforms-bridge-graphql` ([schema documentation](https://graphql.org/)) - `JSONSchemaBridge` in `uniforms-bridge-json-schema` ([schema documentation](https://json-schema.org/)) - `SimpleSchema2Bridge` in `uniforms-bridge-simple-schema-2` ([schema documentation](https://github.com/longshotlabs/simpl-schema#readme)) - `SimpleSchemaBridge` in `uniforms-bridge-simple-schema` ([schema documentation](https://github.com/Meteor-Community-Packages/meteor-simple-schema/blob/master/DOCS.md)) - `ZodBridge` in `uniforms-bridge-zod` ([schema documentation](https://zod.dev/)) -If you see a lot of [`Warning: Unknown props...`](https://fb.me/react-unknown-prop) logs, check if your schema or theme doesn't provide extra props. If so, consider [registering it with `filterDOMProps`](/docs/api-helpers#filterdomprops). - -## `GraphQLBridge` - -This bridge enables using GraphQL schema types as uniforms forms. -This saves you from not having to rewrite the form schema in your code. -As a trade-off, you have to write the validator from scratch. In some cases, it might be easier to rewrite the schema and use, for example, [`JSONSchemaBridge` with `ajv`](/docs/api-bridges#jsonschemabridge). -If only a simple or no validation is needed, this bridge is perfectly suited to work with GraphQL schemas. - -The constructor accepts these arguments: - -- `schema: GraphQLType` can be any type parsed and extracted from a GraphQL schema. -- `validator: (model: Record) => any` a custom validator function that should return a falsy value if no errors are present or information about errors in the model as described in the [custom bridge section](/docs/examples-custom-bridge#validator-definition). -- `extras: Record = {}` used to extend the schema generated from GraphQL type with extra field configuration. -- `provideDefaultLabelFromFieldName = true` if set to `true`, the bridge will use the field name as a label if no label is provided in the schema. - -### Code example - -```tsx -import { GraphQLBridge } from 'uniforms-bridge-graphql'; -import { buildASTSchema, parse } from 'graphql'; - -const schema = ` - type Author { - id: String! - firstName: String - lastName: String - } +Deprecated packages: - type Post { - id: Int! - author: Author! - title: String - votes: Int - } - - # This is required by buildASTSchema - type Query { anything: ID } -`; - -const schemaType = buildASTSchema(parse(schema)).getType('Post'); -const schemaExtras = { - id: { - options: [ - { label: 1, value: 1 }, - { label: 2, value: 2 }, - { label: 3, value: 3 }, - ], - }, - title: { - options: [ - { label: 1, value: 'a' }, - { label: 2, value: 'b' }, - ], - }, - 'author.firstName': { - placeholder: 'John', - }, -}; - -const schemaValidator = (model: object) => { - const details = []; - - if (!model.id) { - details.push({ name: 'id', message: 'ID is required!' }); - } - - if (!model.author.id) { - details.push({ name: 'author.id', message: 'Author ID is required!' }); - } - - if (model.votes < 0) { - details.push({ - name: 'votes', - message: 'Votes must be a non-negative number!', - }); - } - - // ... - - return details.length ? { details } : null; -}; +- `GraphQLBridge` in `uniforms-bridge-graphql` ([schema documentation](https://graphql.org/)) -const bridge = new GraphQLBridge({ - schema: schemaType, - validator: schemaValidator, - extras: schemaExtras, -}); -``` +If you see a lot of [`Warning: Unknown props...`](https://fb.me/react-unknown-prop) logs, check if your schema or theme doesn't provide extra props. If so, consider [registering it with `filterDOMProps`](/docs/api-helpers#filterdomprops). ## `JSONSchemaBridge` @@ -259,3 +174,97 @@ const schema = z.object({ aboutMe: z.string() }); const bridge = new ZodBridge({ schema }); ``` + +## `GraphQLBridge` + +:::caution + +GraphQLBridge is deprecated since uniforms v4. + +::: + +This bridge enables using GraphQL schema types as uniforms forms. +This saves you from not having to rewrite the form schema in your code. +As a trade-off, you have to write the validator from scratch. In some cases, it might be easier to rewrite the schema and use, for example, [`JSONSchemaBridge` with `ajv`](/docs/api-bridges#jsonschemabridge). +If only a simple or no validation is needed, this bridge is perfectly suited to work with GraphQL schemas. + +The constructor accepts these arguments: + +- `schema: GraphQLType` can be any type parsed and extracted from a GraphQL schema. +- `validator: (model: Record) => any` a custom validator function that should return a falsy value if no errors are present or information about errors in the model as described in the [custom bridge section](/docs/examples-custom-bridge#validator-definition). +- `extras: Record = {}` used to extend the schema generated from GraphQL type with extra field configuration. +- `provideDefaultLabelFromFieldName = true` if set to `true`, the bridge will use the field name as a label if no label is provided in the schema. + +### Code example + +```tsx +import { GraphQLBridge } from 'uniforms-bridge-graphql'; +import { buildASTSchema, parse } from 'graphql'; + +const schema = ` + type Author { + id: String! + firstName: String + lastName: String + } + + type Post { + id: Int! + author: Author! + title: String + votes: Int + } + + # This is required by buildASTSchema + type Query { anything: ID } +`; + +const schemaType = buildASTSchema(parse(schema)).getType('Post'); +const schemaExtras = { + id: { + options: [ + { label: 1, value: 1 }, + { label: 2, value: 2 }, + { label: 3, value: 3 }, + ], + }, + title: { + options: [ + { label: 1, value: 'a' }, + { label: 2, value: 'b' }, + ], + }, + 'author.firstName': { + placeholder: 'John', + }, +}; + +const schemaValidator = (model: object) => { + const details = []; + + if (!model.id) { + details.push({ name: 'id', message: 'ID is required!' }); + } + + if (!model.author.id) { + details.push({ name: 'author.id', message: 'Author ID is required!' }); + } + + if (model.votes < 0) { + details.push({ + name: 'votes', + message: 'Votes must be a non-negative number!', + }); + } + + // ... + + return details.length ? { details } : null; +}; + +const bridge = new GraphQLBridge({ + schema: schemaType, + validator: schemaValidator, + extras: schemaExtras, +}); +``` diff --git a/package.json b/package.json index b957156df..3e3088231 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "eslint-config-vazco": "6.2.0", "eslint-import-resolver-alias": "1.1.2", "eslint-import-resolver-typescript": "2.3.0", - "graphql": "^15.0.0", "husky": "8.0.1", "invariant": "^2.0.0", "jest": "27.0.6", diff --git a/packages/uniforms-bridge-graphql/README.md b/packages/uniforms-bridge-graphql/README.md deleted file mode 100644 index 2ed2e49a6..000000000 --- a/packages/uniforms-bridge-graphql/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# uniforms-bridge-graphql - -> GraphQL bridge for `uniforms`. - -## Install - -```sh -$ npm install uniforms-bridge-graphql -``` - -For more in depth documentation see [uniforms.tools](https://uniforms.tools). diff --git a/packages/uniforms-bridge-graphql/__tests__/GraphQLBridge.ts b/packages/uniforms-bridge-graphql/__tests__/GraphQLBridge.ts deleted file mode 100644 index 12e4d7f6e..000000000 --- a/packages/uniforms-bridge-graphql/__tests__/GraphQLBridge.ts +++ /dev/null @@ -1,440 +0,0 @@ -import { GraphQLString, buildASTSchema, parse } from 'graphql'; -import { GraphQLBridge } from 'uniforms-bridge-graphql'; - -describe('GraphQLBridge', () => { - const schemaI = ` - scalar Scalar - - enum AccessLevel { - Admin - User - } - - input Author { - id: ID! - confirmed: Boolean - decimal1: Float - decimal2: Float! - firstName: String = "John" - lastName: String = "Doe" - level: AccessLevel - tags: [String]! - } - - input Category { - owners: [Author!] - } - - input Post { - id: Int! - author: Author! - custom: Scalar - title: String - votes: Int - example: ID - category: [Category!]! - } - - # This is required by buildASTSchema - type Query { anything: ID } - `; - - const schemaT = schemaI.replace(/input/g, 'type').replace(/\s*=.+/g, ''); - - const schemaData = { - author: { component: 'div' }, - 'author.tags.$': { initialValue: 'x' }, - id: { - allowedValues: [1, 2, 3], - label: 'Post ID', - placeholder: 'Post ID', - }, - title: { - initialValue: 'Some Title', - options: [ - { label: 1, value: 'a' }, - { label: 2, value: 'b' }, - { label: 3, value: 'Some Title' }, - ], - }, - votes: { - initialValue: 44, - options: { - a: 1, - b: 2, - c: 44, - }, - }, - }; - - const schemaValidator = jest.fn(); - - const astI = buildASTSchema(parse(schemaI)); - const astT = buildASTSchema(parse(schemaT)); - - const bridgeI = new GraphQLBridge({ - schema: astI.getType('Post')!, - validator: schemaValidator, - extras: schemaData, - }); - const bridgeT = new GraphQLBridge({ - schema: astT.getType('Post')!, - validator: schemaValidator, - extras: schemaData, - }); - const bridgeTNoDefaultLabel = new GraphQLBridge({ - schema: astT.getType('Post')!, - validator: schemaValidator, - extras: schemaData, - provideDefaultLabelFromFieldName: false, - }); - - describe('#constructor()', () => { - it('always ensures `extras`', () => { - const bridge = new GraphQLBridge({ - schema: astI.getType('Post')!, - validator: schemaValidator, - }); - expect(bridge.extras).toEqual({}); - }); - }); - - describe('#getError', () => { - it('works without error', () => { - expect(bridgeI.getError('title', undefined)).toBe(null); - }); - - it('works with invalid error', () => { - expect(bridgeI.getError('title', {})).toBe(null); - expect(bridgeI.getError('title', { invalid: true })).toBe(null); - }); - - it('works with correct error', () => { - expect( - bridgeI.getError('title', { details: [{ name: 'title' }] }), - ).toEqual({ name: 'title' }); - expect(bridgeI.getError('title', { details: [{ name: 'field' }] })).toBe( - null, - ); - }); - }); - - describe('#getErrorMessage', () => { - it('works without error', () => { - expect(bridgeI.getErrorMessage('title', undefined)).toBe(''); - }); - - it('works with invalid error', () => { - expect(bridgeI.getErrorMessage('title', {})).toBe(''); - expect(bridgeI.getErrorMessage('title', { invalid: true })).toBe(''); - }); - - it('works with correct error', () => { - expect( - bridgeI.getErrorMessage('title', { - details: [{ name: 'title', message: '!' }], - }), - ).toBe('!'); - expect( - bridgeI.getErrorMessage('title', { - details: [{ name: 'field', message: '$' }], - }), - ).toBe(''); - }); - }); - - describe('#getErrorMessages', () => { - it('works without error', () => { - expect(bridgeI.getErrorMessages(null)).toEqual([]); - expect(bridgeI.getErrorMessages(undefined)).toEqual([]); - }); - - it('works with other errors', () => { - expect(bridgeI.getErrorMessages('correct')).toEqual(['correct']); - expect(bridgeI.getErrorMessages(999999999)).toEqual([999999999]); - }); - - it('works with Error', () => { - expect(bridgeI.getErrorMessages(new Error('correct'))).toEqual([ - 'correct', - ]); - }); - - it('works with ValidationError', () => { - expect( - bridgeI.getErrorMessages({ - details: [{ name: 'title', message: '!' }], - }), - ).toEqual(['!']); - expect( - bridgeI.getErrorMessages({ - details: [{ name: 'field', message: '$' }], - }), - ).toEqual(['$']); - }); - }); - - describe('#getField', () => { - it('return correct definition (input)', () => { - expect(bridgeI.getField('author.firstName')).toEqual({ - astNode: expect.objectContaining({}), - defaultValue: 'John', - description: undefined, - name: 'firstName', - type: GraphQLString, - }); - }); - - it('return correct definition (type)', () => { - expect(bridgeT.getField('author.firstName')).toEqual({ - args: [], - astNode: expect.objectContaining({}), - deprecationReason: undefined, - description: undefined, - isDeprecated: false, - name: 'firstName', - type: GraphQLString, - }); - }); - - it('throws on not found field', () => { - const error = /Field not found in schema/; - expect(() => bridgeI.getField('x')).toThrow(error); - expect(() => bridgeI.getField('author.x')).toThrow(error); - expect(() => bridgeI.getField('author.tags.x')).toThrow(error); - }); - }); - - describe('#getInitialValue', () => { - it('works with arrays', () => { - expect(bridgeI.getInitialValue('author.tags')).toEqual([]); - }); - - it('works with objects', () => { - expect(bridgeI.getInitialValue('author')).toEqual({ - firstName: 'John', - lastName: 'Doe', - tags: [], - }); - }); - - it('works with undefined primitives', () => { - expect(bridgeI.getInitialValue('id')).toBe(undefined); - }); - - it('works with defined primitives', () => { - expect(bridgeI.getInitialValue('votes')).toBe(44); - }); - - it('works with default values', () => { - expect(bridgeI.getInitialValue('author.firstName')).toBe('John'); - }); - }); - - describe('#getProps', () => { - describe('labels are derived properly', () => { - describe('and no extra data is passed', () => { - it('should use AST field name', () => { - expect(bridgeT.getProps('title')).toEqual({ - label: 'Title', - required: false, - options: [ - { label: 1, value: 'a' }, - { label: 2, value: 'b' }, - { label: 3, value: 'Some Title' }, - ], - initialValue: 'Some Title', - }); - expect(bridgeT.getProps('author.decimal1')).toEqual({ - decimal: true, - label: 'Decimal 1', - required: false, - }); - expect(bridgeTNoDefaultLabel.getProps('author.decimal1')).toEqual({ - decimal: true, - required: false, - }); - expect(bridgeT.getProps('author.firstName')).toEqual({ - label: 'First name', - required: false, - }); - expect(bridgeTNoDefaultLabel.getProps('author.firstName')).toEqual({ - required: false, - }); - }); - }); - - describe('and extra data is present', () => { - it('should use extra data', () => { - expect(bridgeT.getProps('id')).toEqual({ - allowedValues: [1, 2, 3], - label: 'Post ID', - placeholder: 'Post ID', - required: true, - }); - }); - }); - }); - }); - - it('works with allowedValues', () => { - expect(bridgeI.getProps('id')).toEqual({ - label: 'Post ID', - placeholder: 'Post ID', - required: true, - allowedValues: [1, 2, 3], - }); - }); - - it('works with custom component', () => { - expect(bridgeI.getProps('author')).toEqual({ - label: 'Author', - required: true, - component: 'div', - }); - }); - - it('works with Number type', () => { - expect(bridgeI.getProps('author.decimal1')).toEqual({ - label: 'Decimal 1', - required: false, - decimal: true, - }); - - expect(bridgeI.getProps('author.decimal2')).toEqual({ - label: 'Decimal 2', - required: true, - decimal: true, - }); - }); - - it('works with options (array)', () => { - expect(bridgeI.getProps('title')).toMatchObject({ - options: [ - { label: 1, value: 'a' }, - { label: 2, value: 'b' }, - { label: 3, value: 'Some Title' }, - ], - }); - }); - - it('works with options (object)', () => { - expect(bridgeI.getProps('title')).toMatchObject({ - options: [ - { label: 1, value: 'a' }, - { label: 2, value: 'b' }, - { label: 3, value: 'Some Title' }, - ], - }); - }); - - describe('when enum', () => { - it('should return possibleValues', () => { - expect(bridgeI.getProps('author.level').options).toEqual([ - { label: 'Admin', value: 'Admin' }, - { label: 'User', value: 'User' }, - ]); - }); - - it('should prefer options over enum', () => { - const bridge = new GraphQLBridge({ - schema: astI.getType('Post')!, - validator: schemaValidator, - extras: { - ...schemaData, - 'author.level': { - options: [ - { label: 'A', value: 'a' }, - { label: 'B', value: 'b' }, - ], - }, - }, - }); - expect(bridge.getProps('author.level').options).toEqual([ - { label: 'A', value: 'a' }, - { label: 'B', value: 'b' }, - ]); - }); - }); - - describe('#getSubfields', () => { - it('works on top level', () => { - expect(bridgeI.getSubfields()).toEqual([ - 'id', - 'author', - 'custom', - 'title', - 'votes', - 'example', - 'category', - ]); - }); - - it('works with nested types', () => { - expect(bridgeI.getSubfields('author')).toEqual([ - 'id', - 'confirmed', - 'decimal1', - 'decimal2', - 'firstName', - 'lastName', - 'level', - 'tags', - ]); - }); - - it('works with primitives', () => { - expect(bridgeI.getSubfields('id')).toEqual([]); - expect(bridgeI.getSubfields('author.id')).toEqual([]); - }); - }); - - describe('#getType', () => { - [ - [astI, bridgeI, 'input'] as const, - [astT, bridgeT, 'type'] as const, - ].forEach(([ast, bridge, mode]) => { - it(`works with any type (${mode})`, () => { - expect(bridge.getType('author')).toBe(Object); - expect(bridge.getType('author.confirmed')).toBe(Boolean); - expect(bridge.getType('author.decimal1')).toBe(Number); - expect(bridge.getType('author.decimal2')).toBe(Number); - expect(bridge.getType('author.firstName')).toBe(String); - expect(bridge.getType('author.id')).toBe(String); - expect(bridge.getType('author.lastName')).toBe(String); - expect(bridge.getType('author.level')).toBe(ast.getType('AccessLevel')); - expect(bridge.getType('author.tags')).toBe(Array); - expect(bridge.getType('author.tags.$')).toBe(String); - expect(bridge.getType('category')).toBe(Array); - expect(bridge.getType('category.$')).toBe(Object); - expect(bridge.getType('category.$.owners')).toBe(Array); - expect(bridge.getType('category.$.owners.$')).toBe(Object); - expect(bridge.getType('category.$.owners.$.decimal1')).toBe(Number); - expect(bridge.getType('category.$.owners.$.decimal2')).toBe(Number); - expect(bridge.getType('category.$.owners.$.firstName')).toBe(String); - expect(bridge.getType('category.$.owners.$.id')).toBe(String); - expect(bridge.getType('category.$.owners.$.lastName')).toBe(String); - expect(bridge.getType('category.$.owners.$.tags')).toBe(Array); - expect(bridge.getType('category.$.owners.$.tags.$')).toBe(String); - expect(bridge.getType('category.1.owners.$.tags.$')).toBe(String); - expect(bridge.getType('category.$.owners.2.tags.$')).toBe(String); - expect(bridge.getType('category.$.owners.$.tags.3')).toBe(String); - expect(bridge.getType('category.4.owners.5.tags.$')).toBe(String); - expect(bridge.getType('category.6.owners.$.tags.7')).toBe(String); - expect(bridge.getType('category.$.owners.8.tags.9')).toBe(String); - expect(bridge.getType('category.0.owners.0.tags.0')).toBe(String); - expect(bridge.getType('custom')).toBe(ast.getType('Scalar')); - expect(bridge.getType('example')).toBe(String); - expect(bridge.getType('id')).toBe(Number); - expect(bridge.getType('title')).toBe(String); - expect(bridge.getType('votes')).toBe(Number); - }); - }); - }); - - describe('#getValidator', () => { - it('calls correct validator', () => { - expect(bridgeI.getValidator()).toBe(schemaValidator); - }); - }); -}); diff --git a/packages/uniforms-bridge-graphql/__tests__/index.ts b/packages/uniforms-bridge-graphql/__tests__/index.ts deleted file mode 100644 index ae697b630..000000000 --- a/packages/uniforms-bridge-graphql/__tests__/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as uniformsGraphQL from 'uniforms-bridge-graphql'; - -it('exports everything', () => { - expect(uniformsGraphQL).toEqual({ - default: expect.any(Function), - GraphQLBridge: expect.any(Function), - }); -}); diff --git a/packages/uniforms-bridge-graphql/package.json b/packages/uniforms-bridge-graphql/package.json deleted file mode 100644 index 3beef451c..000000000 --- a/packages/uniforms-bridge-graphql/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "uniforms-bridge-graphql", - "version": "4.0.0-alpha.5", - "license": "MIT", - "main": "./cjs/index.js", - "module": "./esm/index.js", - "sideEffects": false, - "description": "GraphQL schema bridge for uniforms.", - "repository": "https://github.com/vazco/uniforms/tree/master/packages/uniforms-bridge-graphql", - "bugs": "https://github.com/vazco/uniforms/issues", - "funding": "https://github.com/vazco/uniforms?sponsor=1", - "keywords": [ - "form", - "forms", - "graphql", - "react", - "schema", - "validation" - ], - "files": [ - "cjs/*.d.ts", - "cjs/*.js", - "esm/*.d.ts", - "esm/*.js", - "src/*.ts", - "src/*.tsx" - ], - "dependencies": { - "graphql": "^15.0.0", - "invariant": "^2.0.0", - "lodash": "^4.0.0", - "tslib": "^2.2.0", - "uniforms": "^4.0.0-alpha.5" - } -} diff --git a/packages/uniforms-bridge-graphql/src/GraphQLBridge.ts b/packages/uniforms-bridge-graphql/src/GraphQLBridge.ts deleted file mode 100644 index 904a58ef8..000000000 --- a/packages/uniforms-bridge-graphql/src/GraphQLBridge.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { - GraphQLInputField, - GraphQLType, - getNullableType, - isEnumType, - isInputObjectType, - isListType, - isNonNullType, - isObjectType, - isScalarType, -} from 'graphql/type/definition'; -import invariant from 'invariant'; -import lowerCase from 'lodash/lowerCase'; -import memoize from 'lodash/memoize'; -import upperFirst from 'lodash/upperFirst'; -import { Bridge, UnknownObject, joinName } from 'uniforms'; - -function fieldInvariant(name: string, condition: boolean): asserts condition { - invariant(condition, 'Field not found in schema: "%s"', name); -} - -/** Option type used in SelectField or RadioField */ -type Option = { - disabled?: boolean; - label?: string; - key?: string; - value: Value; -}; - -export default class GraphQLBridge extends Bridge { - extras: UnknownObject; - provideDefaultLabelFromFieldName: boolean; - schema: GraphQLType; - validator: (model: UnknownObject) => unknown; - - constructor({ - extras = {}, - provideDefaultLabelFromFieldName = true, - schema, - validator, - }: { - extras?: UnknownObject; - provideDefaultLabelFromFieldName?: boolean; - schema: GraphQLType; - validator: (model: UnknownObject) => unknown; - }) { - super(); - - this.extras = extras; - this.provideDefaultLabelFromFieldName = provideDefaultLabelFromFieldName; - this.schema = schema; - this.validator = validator; - - // Memoize for performance and referential equality. - this.getField = memoize(this.getField.bind(this)); - this.getInitialValue = memoize(this.getInitialValue.bind(this)); - this.getProps = memoize(this.getProps.bind(this)); - this.getSubfields = memoize(this.getSubfields.bind(this)); - this.getType = memoize(this.getType.bind(this)); - } - - // TODO: Get rid of this `any`. - getError(name: string, error: any) { - const details = error?.details; - if (!Array.isArray(details)) { - return null; - } - - return details.find(error => error.name === name) || null; - } - - // TODO: Get rid of this `any`. - getErrorMessage(name: string, error: any) { - const scopedError = this.getError(name, error); - return scopedError?.message || ''; - } - - // TODO: Get rid of this `any`. - getErrorMessages(error: any) { - if (!error) { - return []; - } - - const { details } = error; - return Array.isArray(details) - ? details.map(error => error.message) - : [error.message || error]; - } - - getField(name: string) { - return joinName(null, name).reduce( - (field, namePart) => { - const fieldType = getNullableType(field.type); - - if (namePart === '$' || namePart === '' + parseInt(namePart, 10)) { - fieldInvariant(name, isListType(fieldType)); - return { ...field, type: fieldType.ofType }; - } - - if (isInputObjectType(fieldType) || isObjectType(fieldType)) { - const fields = fieldType.getFields(); - fieldInvariant(name, namePart in fields); - return fields[namePart] as GraphQLInputField; - } - - fieldInvariant(name, false); - }, - { name: '', type: this.schema } as GraphQLInputField, - ); - } - - getInitialValue(name: string): unknown { - const type = this.getType(name); - - if (type === Array) { - return []; - } - - if (type === Object) { - const value: UnknownObject = {}; - this.getSubfields(name).forEach(key => { - const initialValue = this.getInitialValue(joinName(name, key)); - if (initialValue !== undefined) { - value[key] = initialValue; - } - }); - return value; - } - - const { defaultValue } = this.getField(name); - // @ts-expect-error The `extras` should be typed more precisely. - return defaultValue ?? this.extras[name]?.initialValue; - } - - getProps(nameNormal: string) { - const nameGeneric = nameNormal.replace(/\.\d+/g, '.$'); - - const field = this.getField(nameGeneric); - const props = { - required: isNonNullType(field.type), - // @ts-expect-error The `extras` should be typed more precisely. - ...this.extras[nameGeneric], - // @ts-expect-error The `extras` should be typed more precisely. - ...this.extras[nameNormal], - }; - - const fieldType = getNullableType(field.type); - if (isScalarType(fieldType) && fieldType.name === 'Float') { - props.decimal = true; - } - - if (this.provideDefaultLabelFromFieldName && props.label === undefined) { - props.label = upperFirst(lowerCase(field.name)); - } - - type OptionDict = Record; - type OptionList = Option[]; - type Options = OptionDict | OptionList; - let options: Options = props.options; - if (options) { - if (!Array.isArray(options)) { - options = Object.entries(options).map(([key, value]) => ({ - key, - label: key, - value, - })); - } - } else if (isEnumType(fieldType)) { - const values = fieldType.getValues(); - options = values.map(option => ({ - label: option.name, - value: option.value, - })); - } - - return Object.assign(props, { options }); - } - - getSubfields(name = '') { - const type = getNullableType(this.getField(name).type); - return isInputObjectType(type) || isObjectType(type) - ? Object.keys(type.getFields()) - : []; - } - - getType(name: string) { - const type = getNullableType(this.getField(name).type); - - if (isInputObjectType(type) || isObjectType(type)) { - return Object; - } - if (isListType(type)) { - return Array; - } - if (isScalarType(type)) { - if (type.name === 'Boolean') { - return Boolean; - } - if (type.name === 'Float') { - return Number; - } - if (type.name === 'ID') { - return String; - } - if (type.name === 'Int') { - return Number; - } - if (type.name === 'String') { - return String; - } - } - - return type; - } - - getValidator(/* options */) { - return this.validator; - } -} diff --git a/packages/uniforms-bridge-graphql/src/index.ts b/packages/uniforms-bridge-graphql/src/index.ts deleted file mode 100644 index 320b35c37..000000000 --- a/packages/uniforms-bridge-graphql/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default, default as GraphQLBridge } from './GraphQLBridge'; diff --git a/packages/uniforms-bridge-graphql/tsconfig.cjs.json b/packages/uniforms-bridge-graphql/tsconfig.cjs.json deleted file mode 100644 index a31da6582..000000000 --- a/packages/uniforms-bridge-graphql/tsconfig.cjs.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": "src", - "outDir": "cjs", - "rootDir": "src", - "module": "CommonJS", - "tsBuildInfoFile": "../../node_modules/.cache/uniforms-bridge-graphql.cjs.tsbuildinfo" - }, - "include": ["src"], - "references": [{ "path": "../uniforms/tsconfig.cjs.json" }] -} diff --git a/packages/uniforms-bridge-graphql/tsconfig.esm.json b/packages/uniforms-bridge-graphql/tsconfig.esm.json deleted file mode 100644 index 618ac3b30..000000000 --- a/packages/uniforms-bridge-graphql/tsconfig.esm.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": "src", - "outDir": "esm", - "rootDir": "src", - "module": "ES6", - "tsBuildInfoFile": "../../node_modules/.cache/uniforms-bridge-graphql.esm.tsbuildinfo" - }, - "include": ["src"], - "references": [{ "path": "../uniforms/tsconfig.esm.json" }] -} diff --git a/reproductions/App.tsx b/reproductions/App.tsx index ef31c6177..d24343f71 100644 --- a/reproductions/App.tsx +++ b/reproductions/App.tsx @@ -9,7 +9,6 @@ import { AutoForm } from 'uniforms-mui'; // import { AutoForm } from 'uniforms-semantic'; // import { bridge as schema } from './schema/json-schema'; -// import { bridge as schema } from './schema/graphql-schema'; // import { bridge as schema } from './schema/simple-schema-2'; import { bridge as schema } from './schema/all-fields-schema'; diff --git a/reproductions/package.json b/reproductions/package.json index 9bf44958e..9f7a497e0 100644 --- a/reproductions/package.json +++ b/reproductions/package.json @@ -26,7 +26,6 @@ "uniforms-bootstrap3": "^4.0.0-alpha.0", "uniforms-bootstrap4": "^4.0.0-alpha.0", "uniforms-bootstrap5": "^4.0.0-alpha.0", - "uniforms-bridge-graphql": "^4.0.0-alpha.0", "uniforms-bridge-json-schema": "^4.0.0-alpha.0", "uniforms-bridge-simple-schema": "^4.0.0-alpha.0", "uniforms-bridge-simple-schema-2": "^4.0.0-alpha.0", diff --git a/reproductions/schema/graphql-schema.tsx b/reproductions/schema/graphql-schema.tsx deleted file mode 100644 index 6d0adb13c..000000000 --- a/reproductions/schema/graphql-schema.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { parse } from 'graphql/language/parser'; -import { buildASTSchema } from 'graphql/utilities'; -import { GraphQLBridge } from 'uniforms-bridge-graphql'; - -const schema = ` - type Address { - a: Float - b: String! - titleA: String! - d: String! - title: String! - } - - # This is required by buildASTSchema - type Query { anything: ID } -`; - -const validator = () => { - /* Empty object for no errors */ -}; - -const extras = { - a: { label: 'Horse' }, - b: { placeholder: 'Horse', required: false }, - titleA: { label: 'Horse' }, - title: { label: 'Horse A', placeholder: 'Horse B' }, -}; - -const type = buildASTSchema(parse(schema)).getType('Address')!; - -export const bridge = new GraphQLBridge({ schema: type, validator, extras }); diff --git a/tsconfig.build.json b/tsconfig.build.json index d1138ed85..aac9aa6bf 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -12,8 +12,6 @@ { "path": "packages/uniforms-bootstrap4/tsconfig.esm.json" }, { "path": "packages/uniforms-bootstrap5/tsconfig.cjs.json" }, { "path": "packages/uniforms-bootstrap5/tsconfig.esm.json" }, - { "path": "packages/uniforms-bridge-graphql/tsconfig.cjs.json" }, - { "path": "packages/uniforms-bridge-graphql/tsconfig.esm.json" }, { "path": "packages/uniforms-bridge-json-schema/tsconfig.cjs.json" }, { "path": "packages/uniforms-bridge-json-schema/tsconfig.esm.json" }, { "path": "packages/uniforms-bridge-simple-schema/tsconfig.cjs.json" }, diff --git a/tsconfig.global.json b/tsconfig.global.json index 178b15ccf..5e6861fee 100644 --- a/tsconfig.global.json +++ b/tsconfig.global.json @@ -23,8 +23,6 @@ { "path": "packages/uniforms-bootstrap4/tsconfig.cjs.json" }, { "path": "packages/uniforms-bootstrap5/tsconfig.esm.json" }, { "path": "packages/uniforms-bootstrap5/tsconfig.cjs.json" }, - { "path": "packages/uniforms-bridge-graphql/tsconfig.esm.json" }, - { "path": "packages/uniforms-bridge-graphql/tsconfig.cjs.json" }, { "path": "packages/uniforms-bridge-json-schema/tsconfig.esm.json" }, { "path": "packages/uniforms-bridge-json-schema/tsconfig.cjs.json" }, { "path": "packages/uniforms-bridge-simple-schema/tsconfig.esm.json" }, diff --git a/website/lib/presets.ts b/website/lib/presets.ts index bc090a1b1..642591fad 100644 --- a/website/lib/presets.ts +++ b/website/lib/presets.ts @@ -25,42 +25,6 @@ const presets = { }) `, - 'Address (GraphQL)': preset` - new GraphQLBridge({ - schema: buildASTSchema( - parse(\` - type Address { - city: String - state: String! - street: String! - zip: String! - } - - # This is required by buildASTSchema - type Query { anything: ID } - \`) - ).getType('Address'), - function (model) { - const details = []; - if (!model.state) - details.push({ name: 'state', message: 'State is required!' }); - if (!model.street) - details.push({ name: 'street', message: 'Street is required!' }); - if (!model.zip) - details.push({ name: 'zip', message: 'Zip is required!' }); - if (model.city && model.city.length > 50) - details.push({ name: 'city', message: 'City can be at least 50 characters long!' }); - if (model.street && model.street.length > 100) - details.push({ name: 'street', message: 'Street can be at least 100 characters long!' }); - if (model.zip && !/^[0-9]{5}$/.test(model.zip)) - details.push({ name: 'zip', message: 'Zip does not match the regular expression!' }); - if (details.length) - return { details }; - }, - extras: { zip: { label: 'Zip code' } } - }) - `, - 'Address (JSONSchema)': preset` (() => { const ajv = new Ajv({ allErrors: true, useDefaults: true, keywords: ["uniforms"] }); diff --git a/website/lib/schema.ts b/website/lib/schema.ts index 63f2d7346..8f0b292da 100644 --- a/website/lib/schema.ts +++ b/website/lib/schema.ts @@ -1,9 +1,7 @@ import Ajv from 'ajv'; -import { buildASTSchema, parse } from 'graphql'; import MessageBox from 'message-box'; import SimpleSchema from 'simpl-schema'; import { filterDOMProps } from 'uniforms'; -import { GraphQLBridge } from 'uniforms-bridge-graphql'; import { JSONSchemaBridge } from 'uniforms-bridge-json-schema'; import { SimpleSchema2Bridge } from 'uniforms-bridge-simple-schema-2'; import { ZodBridge } from 'uniforms-bridge-zod'; @@ -30,13 +28,10 @@ SimpleSchema.extendOptions(['uniforms']); // This is required for the eval. scope.Ajv = Ajv; -scope.GraphQLBridge = GraphQLBridge; scope.JSONSchemaBridge = JSONSchemaBridge; scope.SimpleSchema = SimpleSchema; scope.SimpleSchema2Bridge = SimpleSchema2Bridge; scope.ZodBridge = ZodBridge; -scope.buildASTSchema = buildASTSchema; -scope.parse = parse; scope.z = z; // Dynamic field error. diff --git a/website/package.json b/website/package.json index 80335ac58..08696f8f2 100644 --- a/website/package.json +++ b/website/package.json @@ -17,7 +17,6 @@ "ajv": "8.0.5", "antd": "4.10.3", "core-js": "3.8.3", - "graphql": "^15.0.0", "lz-string": "1.4.4", "raw-loader": "4.0.2", "react": "17.0.2", @@ -30,7 +29,6 @@ "uniforms-bootstrap3": "^4.0.0-alpha.0", "uniforms-bootstrap4": "^4.0.0-alpha.0", "uniforms-bootstrap5": "^4.0.0-alpha.0", - "uniforms-bridge-graphql": "^4.0.0-alpha.0", "uniforms-bridge-json-schema": "^4.0.0-alpha.0", "uniforms-bridge-simple-schema": "^4.0.0-alpha.0", "uniforms-bridge-simple-schema-2": "^4.0.0-alpha.0",